diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 6b4a9f87..e2b0d915 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -10,7 +10,7 @@ on: - main env: - MAIN_PYTHON_VERSION: '3.11' + MAIN_PYTHON_VERSION: '3.12' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/ci_cd_nightly.yml b/.github/workflows/ci_cd_nightly.yml index 5cd8167c..9b7f9acf 100644 --- a/.github/workflows/ci_cd_nightly.yml +++ b/.github/workflows/ci_cd_nightly.yml @@ -6,7 +6,7 @@ on: - cron: "0 3 * * *" env: - MAIN_PYTHON_VERSION: '3.11' + MAIN_PYTHON_VERSION: '3.12' DEV_MAJOR_REV: '25' DEV_MINOR_REV: '2' diff --git a/examples/00_tips/tips_01.py b/examples/00_tips/tips_01.py index d2dd8049..81bbc014 100644 --- a/examples/00_tips/tips_01.py +++ b/examples/00_tips/tips_01.py @@ -25,41 +25,41 @@ 3D visualization ---------------- -Visualize 3D imported geometry +The following example demonstrates how to visualize imported geometry in 3D. """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ - +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) - # %% -# Download and import geometry -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download geometry +# Download and import the geometry file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# %% +# Download the geometry file geometry_path = download_file("Valve.pmdb", "pymechanical", "embedding") # %% -# Import geometry +# Define the model and import the geometry file +model = app.Model -geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import = model.GeometryImportGroup.AddGeometryImport() geometry_import.Import(geometry_path) # %% -# Visualize in 3D -# ~~~~~~~~~~~~~~~ +# Visualize the model in 3D +# ~~~~~~~~~~~~~~~~~~~~~~~~~ app.plot() @@ -68,8 +68,11 @@ # This visualization is currently available only for geometry and on version 24R2 or later # %% -# Cleanup -# ~~~~~~~ +# Clean up the files and app +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete the downloaded files delete_downloads() -app.new() + +# Close the app +app.close() diff --git a/examples/00_tips/tips_02.py b/examples/00_tips/tips_02.py index 9552ea58..7d1ffc51 100644 --- a/examples/00_tips/tips_02.py +++ b/examples/00_tips/tips_02.py @@ -25,35 +25,42 @@ Export image ------------ -Export image and display +The following example demonstrates how to export an image of the imported geometry +and display it using matplotlib. """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file # %% -# Embed Mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create an instance of the App class app = App() + +# Update the global variables app.update_globals(globals()) -print(app) +# Print the app to ensure it is working +print(app) # %% -# Download and import geometry -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download geometry +# Download and import the geometry file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# %% +# Download the geometry file geometry_path = download_file("Valve.pmdb", "pymechanical", "embedding") # %% -# Import geometry +# Import the geometry file geometry_import = Model.GeometryImportGroup.AddGeometryImport() geometry_import.Import(geometry_path) @@ -62,13 +69,13 @@ # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Orientation -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +# Set the orientation of the camera +ExtAPI.Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) -# Export format +# Set the image export format image_export_format = GraphicsImageExportFormat.PNG -# Resolution and background +# Configure the export settings for the image settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution settings_720p.Background = GraphicsBackgroundType.White @@ -76,24 +83,34 @@ settings_720p.Height = 720 settings_720p.CurrentGraphicsDisplay = False -# Rotate the geometry if needed -ExtAPI.Graphics.Camera.Rotate(180, CameraAxisType.ScreenY) - +# Rotate the geometry on the Y-axis +Graphics.Camera.Rotate(180, CameraAxisType.ScreenY) # %% -# Custom function for displaying the image -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a function to display the image +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ from matplotlib import image as mpimg from matplotlib import pyplot as plt -# Temporary directory to save the image -cwd = os.path.join(os.getcwd(), "out") +# Directory to save the image +output_path = Path.cwd() / "out" + +def display_image(image_name) -> None: + """Display the image using matplotlib. -def display_image(image_name): + Parameters + ---------- + image_name : str + The name of the image file to display. + """ + # Create the full path to the image + image_path = output_path / image_name + + # Plot the figure and display the image plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) + plt.imshow(mpimg.imread(str(image_path))) plt.xticks([]) plt.yticks([]) plt.axis("off") @@ -104,19 +121,22 @@ def display_image(image_name): # Export and display the image # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Fits the geometry in the viewing area +# Fit the geometry in the viewing area Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "geometry.png"), image_export_format, settings_720p -) +# Export the image +geometry_image = output_path / "geometry.png" +Graphics.ExportImage(str(geometry_image), image_export_format, settings_720p) -# Display the image using matplotlib -display_image("geometry.png") +# Display the image +display_image(geometry_image.name) # %% -# Cleanup -# ~~~~~~~ +# Clean up the downloaded files and app +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete the downloaded files delete_downloads() -app.new() + +# Close the app +app.close() diff --git a/examples/00_tips/tips_03.py b/examples/00_tips/tips_03.py index 06fe34a5..6dea8ed4 100644 --- a/examples/00_tips/tips_03.py +++ b/examples/00_tips/tips_03.py @@ -23,27 +23,25 @@ """.. _ref_tips_03: Project tree --------------------- +------------ -Display the heirarchial Mechanical project structure. +The following example demonstrates how to print the heirarchial Mechanical project structure. """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ - +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file # %% -# Embed Mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) - # %% # Download the mechdb file # ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -65,12 +63,16 @@ # Display the tree only under the first analysis # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app.print_tree(Model.Analyses[0]) +first_analysis = app.Model.Analyses[0] +app.print_tree(first_analysis) # %% -# Cleanup -# ~~~~~~~ +# Clean up the downloaded files and app +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete the downloaded files delete_downloads() -app.new() + +# Close the app +app.close() diff --git a/examples/01_basic/bolt_pretension.py b/examples/01_basic/bolt_pretension.py index fa70200b..56ae53ec 100644 --- a/examples/01_basic/bolt_pretension.py +++ b/examples/01_basic/bolt_pretension.py @@ -22,21 +22,22 @@ """.. _ref_bolt_pretension: -Bolt Pretension +Bolt pretension --------------- This example demonstrates how to insert a Static Structural analysis into a new Mechanical session and execute a sequence of Python scripting commands that define and solve a bolt-pretension analysis. Scripts then evaluate the following results: deformation, -equivalent stresses, contact, and bolt +equivalent stresses, contact, and bolt. """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +import typing from PIL import Image from ansys.mechanical.core import App @@ -46,179 +47,169 @@ from matplotlib.animation import FuncAnimation # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ app = App() -app.update_globals(globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") - plt.show() - +# Import the enums and global variables instead of using app.update_globals(globals()) +# or App(globals=globals()) +from ansys.mechanical.core.embedding.enum_importer import * +from ansys.mechanical.core.embedding.global_importer import Quantity +from ansys.mechanical.core.embedding.transaction import Transaction # %% # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) -Graphics.Camera.SetFit() -image_export_format = GraphicsImageExportFormat.PNG -settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() -settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False -Graphics.Camera.Rotate(180, CameraAxisType.ScreenY) +# Set camera orientation +graphics = app.Graphics +camera = graphics.Camera +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +camera.SetFit() +camera.Rotate(180, CameraAxisType.ScreenY) + +# Set camera settings for 720p resolution +graphics_image_export_settings = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() +graphics_image_export_settings.Resolution = GraphicsResolutionType.EnhancedResolution +graphics_image_export_settings.Background = GraphicsBackgroundType.White +graphics_image_export_settings.CurrentGraphicsDisplay = False +graphics_image_export_settings.Width = 1280 +graphics_image_export_settings.Height = 720 # %% -# Download and import geometry -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download the geometry file - -geometry_path = download_file( - "example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic" -) +# Set the geometry import group for the model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# %% -# Import geometry +# Set the model +model = app.Model -geometry_import_group = Model.GeometryImportGroup +# Create a geometry import group for the model +geometry_import_group = model.GeometryImportGroup +# Add the geometry import to the group geometry_import = geometry_import_group.AddGeometryImport() +# Set the geometry import format geometry_import_format = ( Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic ) +# Set the geometry import preferences geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() geometry_import_preferences.ProcessNamedSelections = True + +# %% +# Download and import the geometry +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Download the geometry file from the ansys/example-data repository +geometry_path = download_file( + "example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic" +) + +# Import/reload the geometry from the CAD (.agdb) file using the provided preferences geometry_import.Import( geometry_path, geometry_import_format, geometry_import_preferences ) # sphinx_gallery_start_ignore -assert str(geometry_import.ObjectState) == "Solved", "Geometry Import unsuccessful" +# Assert the geometry import was successful +assert geometry_import.ObjectState == ObjectState.Solved, "Geometry Import unsuccessful" # sphinx_gallery_end_ignore +# Visualize the model in 3D app.plot() - # %% -# Download and import material -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download materials +# Download and import the materials +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -mat_Copper_file_path = download_file( +# %% +# Download the material files from the ansys/example-data repository +copper_material_file_path = download_file( "example_06_Mat_Copper.xml", "pymechanical", "00_basic" ) -mat_Steel_file_path = download_file( +steel_material_file_path = download_file( "example_06_Mat_Steel.xml", "pymechanical", "00_basic" ) # %% -# Import materials - -MAT = Model.Materials -MAT.Import(mat_Copper_file_path) -MAT.Import(mat_Steel_file_path) +# Add materials to the model and import the material files +model_materials = model.Materials +model_materials.Import(copper_material_file_path) +model_materials.Import(steel_material_file_path) # sphinx_gallery_start_ignore -assert str(MAT.ObjectState) == "FullyDefined", "Materials are not defined" +# Assert the materials are defined +assert ( + model_materials.ObjectState == ObjectState.FullyDefined +), "Materials are not defined" # sphinx_gallery_end_ignore # %% -# Define Analysis and unit system +# Define analysis and unit system # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Add Structural analysis - -Model.AddStaticStructuralAnalysis() -STAT_STRUC = Model.Analyses[0] -STAT_STRUC_SOLN = STAT_STRUC.Solution -STAT_STRUC_ANA_SETTING = STAT_STRUC.Children[0] # %% -# Set up the unit system. - -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM +# Add static structural analysis to the model +model.AddStaticStructuralAnalysis() +static_structural = model.Analyses[0] +static_structural_solution = static_structural.Solution +static_structural_analysis_setting = static_structural.Children[0] # %% -# Store all main tree nodes as variables - -MODEL = Model -GEOM = Model.Geometry -CONN_GRP = Model.Connections -CS_GRP = Model.CoordinateSystems -MSH = Model.Mesh -NS_GRP = Model.NamedSelections - -# %% -# Store named selections - -block3_block2_cont_NS = [x for x in Tree.AllObjects if x.Name == "block3_block2_cont"][ - 0 +# Store the named selections +named_selections_dictionary = {} +named_selections_list = [ + "block3_block2_cont", + "block3_block2_targ", + "shank_block3_cont", + "shank_block3_targ", + "block1_washer_cont", + "block1_washer_targ", + "washer_bolt_cont", + "washer_bolt_targ", + "shank_bolt_targ", + "shank_bolt_cont", + "block2_block1_cont", + "block2_block1_targ", ] -block3_block2_targ_NS = [x for x in Tree.AllObjects if x.Name == "block3_block2_targ"][ - 0 -] -shank_block3_targ_NS = [x for x in Tree.AllObjects if x.Name == "shank_block3_targ"][0] -shank_block3_cont_NS = [x for x in Tree.AllObjects if x.Name == "shank_block3_cont"][0] -block1_washer_cont_NS = [x for x in Tree.AllObjects if x.Name == "block1_washer_cont"][ - 0 -] -block1_washer_targ_NS = [x for x in Tree.AllObjects if x.Name == "block1_washer_targ"][ - 0 -] -washer_bolt_cont_NS = [x for x in Tree.AllObjects if x.Name == "washer_bolt_cont"][0] -washer_bolt_targ_NS = [x for x in Tree.AllObjects if x.Name == "washer_bolt_targ"][0] -shank_bolt_targ_NS = [x for x in Tree.AllObjects if x.Name == "shank_bolt_targ"][0] -shank_bolt_cont_NS = [x for x in Tree.AllObjects if x.Name == "shank_bolt_cont"][0] -block2_block1_cont_NS = [x for x in Tree.AllObjects if x.Name == "block2_block1_cont"][ - 0 -] -block2_block1_targ_NS = [x for x in Tree.AllObjects if x.Name == "block2_block1_targ"][ - 0 -] -all_bodies = [x for x in Tree.AllObjects if x.Name == "all_bodies"][0] -bodies_5 = [x for x in Tree.AllObjects if x.Name == "bodies_5"][0] -shank = [x for x in Tree.AllObjects if x.Name == "shank"][0] -shank_face = [x for x in Tree.AllObjects if x.Name == "shank_face"][0] -shank_face2 = [x for x in Tree.AllObjects if x.Name == "shank_face2"][0] -bottom_surface = [x for x in Tree.AllObjects if x.Name == "bottom_surface"][0] -block2_surface = [x for x in Tree.AllObjects if x.Name == "block2_surface"][0] -shank_surface = [x for x in Tree.AllObjects if x.Name == "shank_surface"][0] # %% -# Assign material to bodies - -SURFACE1 = GEOM.Children[0].Children[0] -SURFACE1.Material = "Steel" +# Set the unit system to Standard NMM -SURFACE2 = GEOM.Children[1].Children[0] -SURFACE2.Material = "Copper" +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM -SURFACE3 = GEOM.Children[2].Children[0] -SURFACE3.Material = "Copper" +# %% +# Get tree objects for each named selection +for named_selection in named_selections_list: + named_selections_dictionary[named_selection] = app.DataModel.GetObjectsByName( + named_selection + )[0] -SURFACE4 = GEOM.Children[3].Children[0] -SURFACE4.Material = "Steel" +# %% +# Create a list with material assignment for each ``model.Geometry.Children`` index +children_materials = ["Steel", "Copper", "Copper", "Steel", "Steel", "Steel"] -SURFACE5 = GEOM.Children[4].Children[0] -SURFACE5.Material = "Steel" +# %% +# Assign surface materials to the ``model.Geometry`` bodies +geometry = model.Geometry +for children_index, material_name in enumerate(children_materials): + # Get the surface of the body + surface = geometry.Children[children_index].Children[0] + # Assign the material to the surface + surface.Material = material_name -SURFACE6 = GEOM.Children[5].Children[0] -SURFACE6.Material = "Steel" +# %% +# Add and define a coordinate system +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # %% -# Define coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add a coordinate system to the model +coordinate_systems = model.CoordinateSystems +coordinate_system = coordinate_systems.AddCoordinateSystem() -coordinate_system = CS_GRP.AddCoordinateSystem() +# %% +# Define the coordinate system and set its axis properties coordinate_system.OriginDefineBy = CoordinateSystemAlignmentType.Fixed coordinate_system.OriginX = Quantity(-195, "mm") coordinate_system.OriginY = Quantity(100, "mm") @@ -226,312 +217,650 @@ def display_image(image_name): coordinate_system.PrimaryAxis = CoordinateSystemAxisType.PositiveZAxis # %% -# Define Contacts -# ~~~~~~~~~~~~~~~ -# Change contact settings +# Create functions for contact region set up +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # %% -# Delete existing contacts +# Add a contact region to the body with the specified source location, target location, +# and contact type +def set_contact_region_locations_and_types( + body: typing.Union[ + Ansys.ACT.Automation.Mechanical.Connections, + Ansys.ACT.Automation.Mechanical.Connections.ConnectionGroup, + ], + source_location: Ansys.ACT.Automation.Mechanical.NamedSelection, + target_location: Ansys.ACT.Automation.Mechanical.NamedSelection, + contact_type: ContactType, +) -> Ansys.ACT.Automation.Mechanical.Connections.ContactRegion: + """Add a contact region to the body with the specified source location, target location, + and contact type. + + Parameters + ---------- + body : Ansys.ACT.Automation.Mechanical.Connections or + Ansys.ACT.Automation.Mechanical.Connections.ConnectionGroup + The body to which the contact region will be added. + source_location : Ansys.ACT.Automation.Mechanical.NamedSelection + The source location for the contact region. + target_location : Ansys.ACT.Automation.Mechanical.NamedSelection + The target location for the contact region. + contact_type : ContactType + The type of contact for the contact region. + + Returns + ------- + Ansys.ACT.Automation.Mechanical.Connections.ContactRegion + The created contact region. + """ + contact_region = body.AddContactRegion() + contact_region.SourceLocation = source_location + contact_region.TargetLocation = target_location + contact_region.ContactType = contact_type + return contact_region -for connection in CONN_GRP.Children: - if connection.DataModelObjectCategory == DataModelObjectCategory.ConnectionGroup: - connection.Delete() -CONT_REG1 = CONN_GRP.AddContactRegion() -CONT_REG1.SourceLocation = NS_GRP.Children[0] -CONT_REG1.TargetLocation = NS_GRP.Children[1] -CONT_REG1.ContactType = ContactType.Frictional -CONT_REG1.FrictionCoefficient = 0.2 -CONT_REG1.SmallSliding = ContactSmallSlidingType.Off -CONT_REG1.UpdateStiffness = UpdateContactStiffness.Never -CMD1 = CONT_REG1.AddCommandSnippet() +# %% +# Set the friction coefficient, small sliding, and update stiffness settings for the contact region +def advanced_contact_settings( + contact_region: Ansys.ACT.Automation.Mechanical.Connections.ContactRegion, + friction_coefficient: int, + small_sliding: ContactSmallSlidingType, + update_stiffness: UpdateContactStiffness, +) -> None: + """Set the friction coefficient, small sliding, and update stiffness settings for the + contact region. + + Parameters + ---------- + contact_region : Ansys.ACT.Automation.Mechanical.Connections.ContactRegion + The contact region to set the settings for. + friction_coefficient : int + The friction coefficient for the contact region. + small_sliding : ContactSmallSlidingType + The small sliding setting for the contact region. + update_stiffness : UpdateContactStiffness + The update stiffness setting for the contact region. + """ + contact_region.FrictionCoefficient = friction_coefficient + contact_region.SmallSliding = small_sliding + contact_region.UpdateStiffness = update_stiffness + # %% -# Add missing contact keyopt and Archard Wear Model using a command snippet +# Add a command snippet to the contact region with the specified Archard Wear Model +def add_command_snippet( + contact_region: Ansys.ACT.Automation.Mechanical.Connections.ContactRegion, + archard_wear_model: str, +) -> None: + """Add a command snippet to the contact region with the specified Archard Wear Model. + + Parameters + ---------- + contact_region : Ansys.ACT.Automation.Mechanical.Connections.ContactRegion + The contact region to add the command snippet to. + archard_wear_model : str + The Archard Wear Model command snippet to add to the contact region. + """ + contact_region_cmd = contact_region.AddCommandSnippet() + contact_region_cmd.AppendText(archard_wear_model) -AWM = """keyopt,cid,9,5 -rmodif,cid,10,0.00 -rmodif,cid,23,0.001""" -CMD1.AppendText(AWM) - -CONTS = CONN_GRP.Children[0] -CONT_REG2 = CONTS.AddContactRegion() -CONT_REG2.SourceLocation = NS_GRP.Children[3] -CONT_REG2.TargetLocation = NS_GRP.Children[2] -CONT_REG2.ContactType = ContactType.Bonded -CONT_REG2.ContactFormulation = ContactFormulation.MPC - -CONT_REG3 = CONTS.AddContactRegion() -CONT_REG3.SourceLocation = NS_GRP.Children[4] -CONT_REG3.TargetLocation = NS_GRP.Children[5] -CONT_REG3.ContactType = ContactType.Frictional -CONT_REG3.FrictionCoefficient = 0.2 -CONT_REG3.SmallSliding = ContactSmallSlidingType.Off -CONT_REG3.UpdateStiffness = UpdateContactStiffness.Never -CMD3 = CONT_REG3.AddCommandSnippet() - -# Add missing contact keyopt and Archard Wear Model using a command snippet - -AWM3 = """keyopt,cid,9,5 -rmodif,cid,10,0.00 -rmodif,cid,23,0.001""" -CMD3.AppendText(AWM3) - -CONT_REG4 = CONTS.AddContactRegion() -CONT_REG4.SourceLocation = NS_GRP.Children[6] -CONT_REG4.TargetLocation = NS_GRP.Children[7] -CONT_REG4.ContactType = ContactType.Bonded -CONT_REG4.ContactFormulation = ContactFormulation.MPC - -CONT_REG5 = CONTS.AddContactRegion() -CONT_REG5.SourceLocation = NS_GRP.Children[9] -CONT_REG5.TargetLocation = NS_GRP.Children[8] -CONT_REG5.ContactType = ContactType.Bonded -CONT_REG5.ContactFormulation = ContactFormulation.MPC - -CONT_REG6 = CONTS.AddContactRegion() -CONT_REG6.SourceLocation = NS_GRP.Children[10] -CONT_REG6.TargetLocation = NS_GRP.Children[11] -CONT_REG6.ContactType = ContactType.Frictional -CONT_REG6.FrictionCoefficient = 0.2 -CONT_REG6.SmallSliding = ContactSmallSlidingType.Off -CONT_REG6.UpdateStiffness = UpdateContactStiffness.Never -CMD6 = CONT_REG6.AddCommandSnippet() - -# Add missing contact keyopt and Archard Wear Model using a command snippet - -AWM6 = """keyopt,cid,9,5 + +# %% +# Add and define contact regions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Set up the model connections and delete the existing connections for ConnectionGroups +connections = model.Connections +for connection in connections.Children: + if connection.DataModelObjectCategory == DataModelObjectCategory.ConnectionGroup: + app.DataModel.Remove(connection) + +# %% +# Set the archard wear model and get the named selections from the model + +# Set the archard wear model +archard_wear_model = """keyopt,cid,9,5 rmodif,cid,10,0.00 rmodif,cid,23,0.001""" -CMD6.AppendText(AWM6) + +# Get named selections from the model for contact regions +named_selections = model.NamedSelections + +# %% +# Add a contact region for the model's named selections Children 0 and 1 with the specified +# contact type +contact_region = set_contact_region_locations_and_types( + body=connections, + source_location=named_selections.Children[0], + target_location=named_selections.Children[1], + contact_type=ContactType.Frictional, +) +# Set the friction coefficient, small sliding, and update stiffness settings for the contact region +advanced_contact_settings( + contact_region=contact_region, + friction_coefficient=0.2, + small_sliding=ContactSmallSlidingType.Off, + update_stiffness=UpdateContactStiffness.Never, +) +# Add a command snippet to the contact region with the specified Archard Wear Model +add_command_snippet(contact_region, archard_wear_model) + +# %% +# Set the connection group for the contact regions +connection_group = connections.Children[0] + +# %% +# Add a contact region for the model's named selections Children 2 and 3 with the specified +# contact type +contact_region_2 = set_contact_region_locations_and_types( + body=connection_group, + source_location=named_selections.Children[3], + target_location=named_selections.Children[2], + contact_type=ContactType.Bonded, +) +contact_region_2.ContactFormulation = ContactFormulation.MPC + +# %% +# Add a contact region for the model's named selections Children 4 and 5 with the specified +# contact type +contact_region_3 = set_contact_region_locations_and_types( + body=connection_group, + source_location=named_selections.Children[4], + target_location=named_selections.Children[5], + contact_type=ContactType.Frictional, +) +# Set the friction coefficient, small sliding, and update stiffness settings for the contact region +advanced_contact_settings( + contact_region=contact_region_3, + friction_coefficient=0.2, + small_sliding=ContactSmallSlidingType.Off, + update_stiffness=UpdateContactStiffness.Never, +) +# Add a command snippet to the contact region with the specified Archard Wear Model +add_command_snippet(contact_region_3, archard_wear_model) + +# %% +# Add a contact region for the model's named selections Children 6 and 7 with the specified +# contact type +contact_region_4 = set_contact_region_locations_and_types( + body=connection_group, + source_location=named_selections.Children[6], + target_location=named_selections.Children[7], + contact_type=ContactType.Bonded, +) +contact_region_4.ContactFormulation = ContactFormulation.MPC + +# %% +# Add a contact region for the model's named selections Children 8 and 9 with the specified +# contact type +contact_region_5 = set_contact_region_locations_and_types( + body=connection_group, + source_location=named_selections.Children[9], + target_location=named_selections.Children[8], + contact_type=ContactType.Bonded, +) +contact_region_5.ContactFormulation = ContactFormulation.MPC + +# %% +# Add a contact region for the model's named selections Children 10 and 11 with the specified +# contact type +contact_region_6 = set_contact_region_locations_and_types( + body=connection_group, + source_location=named_selections.Children[10], + target_location=named_selections.Children[11], + contact_type=ContactType.Frictional, +) +# Set the friction coefficient, small sliding, and update stiffness settings for the contact region +advanced_contact_settings( + contact_region=contact_region_6, + friction_coefficient=0.2, + small_sliding=ContactSmallSlidingType.Off, + update_stiffness=UpdateContactStiffness.Never, +) +# Add a command snippet to the contact region with the specified Archard Wear Model +add_command_snippet(contact_region_6, archard_wear_model) # %% -# Mesh -# ~~~~ +# Create functions to set up the mesh +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Hex_Method = MSH.AddAutomaticMethod() -Hex_Method.Location = all_bodies -Hex_Method.Method = MethodType.Automatic -BODY_SIZING1 = MSH.AddSizing() -BODY_SIZING1.Location = bodies_5 -BODY_SIZING1.ElementSize = Quantity(15, "mm") +# %% +# Set the mesh method location for the specified method and object name +def set_mesh_method_location(method, object_name: str, location_type: str = "") -> None: + """Set the location of the method based on the specified name and location type. + + Parameters + ---------- + method : Ansys.ACT.Automation.Mechanical.MeshMethod + The method to set the location for. + object_name : str + The name of the object to set the location for. + location_type : str, optional + The type of location to set for the method. Can be "source", "target", or empty string. + Default is an empty string. + """ + # Get the tree object for the specified name + tree_obj = app.DataModel.GetObjectsByName(object_name)[0] + + # Set the method location based on the specified location type + if location_type == "source": + method.SourceLocation = tree_obj + elif location_type == "target": + method.TargetLocation = tree_obj + else: + method.Location = tree_obj -BODY_SIZING2 = MSH.AddSizing() -BODY_SIZING2.Location = shank -BODY_SIZING2.ElementSize = Quantity(7, "mm") -Face_Meshing = MSH.AddFaceMeshing() -Face_Meshing.Location = shank_face -Face_Meshing.MappedMesh = False +# %% +# Add a mesh sizing to the mesh with the specified name, quantity value, and measurement +def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: + """Add a mesh sizing to the mesh with the specified name, quantity value, and measurement. + + Parameters + ---------- + mesh : Ansys.ACT.Automation.Mechanical.Mesh + The mesh to add the sizing to. + object_name : str + The name of the object to set the sizing for. + element_size : Quantity + The element size for the mesh sizing. + """ + # Add sizing to the mesh + body_sizing = mesh.AddSizing() + # Get the tree object for the specified name + body_sizing.Location = app.DataModel.GetObjectsByName(object_name)[0] + + # Set the element size to the mesh + body_sizing.ElementSize = element_size -Sweep_Method = MSH.AddAutomaticMethod() -Sweep_Method.Location = shank -Sweep_Method.Method = MethodType.Sweep -Sweep_Method.SourceTargetSelection = 2 -Sweep_Method.SourceLocation = shank_face -Sweep_Method.TargetLocation = shank_face2 -MSH.Activate() -MSH.GenerateMesh() +# %% +# Add mesh methods, sizing, and face meshing +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") +# %% +# Add the mesh sizing to the ``bodies_5`` and ``shank`` objects + +mesh = model.Mesh +add_mesh_sizing(mesh=mesh, object_name="bodies_5", element_size=Quantity(15, "mm")) +add_mesh_sizing(mesh=mesh, object_name="shank", element_size=Quantity(7, "mm")) + +# %% +# Add an automatic method to the mesh and set the method type +hex_method = mesh.AddAutomaticMethod() +hex_method.Method = MethodType.Automatic +# Set the method location for the all_bodies object +set_mesh_method_location(method=hex_method, object_name="all_bodies") + +# %% +# Add face meshing to the mesh and set the MappedMesh property to False +face_meshing = mesh.AddFaceMeshing() +face_meshing.MappedMesh = False +# Set the method location for the face meshing +set_mesh_method_location(method=face_meshing, object_name="shank_face") + +# %% +# Add an automatic method to the mesh, set the method type, and set the source target selection +sweep_method = mesh.AddAutomaticMethod() +sweep_method.Method = MethodType.Sweep +sweep_method.SourceTargetSelection = 2 +# Set the method locations for the shank, shank_face, and shank_face2 objects +set_mesh_method_location(method=sweep_method, object_name="shank") +set_mesh_method_location( + method=sweep_method, object_name="shank_face", location_type="source" +) +set_mesh_method_location( + method=sweep_method, object_name="shank_face2", location_type="target" +) + +# %% +# Activate and generate the mesh +mesh.Activate() +mesh.GenerateMesh() + +# Fit the view to the entire model +camera.SetFit() +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" +mesh_image_path = str(output_path / "mesh.png") +# Set the image export format and export the image +image_export_format = GraphicsImageExportFormat.PNG +graphics.ExportImage( + mesh_image_path, image_export_format, graphics_image_export_settings +) + + +# %% +# Create a function to display the image using matplotlib +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +): + """Display the image with the specified parameters.""" + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure + plt.show() + + +# %% +# Display the mesh image +display_image(mesh_image_path) # %% # Analysis settings # ~~~~~~~~~~~~~~~~~ -STAT_STRUC_ANA_SETTING.NumberOfSteps = 4 +# Set the number of steps for the static structural analysis +static_structural_analysis_setting.NumberOfSteps = 4 + +# Set the step index list step_index_list = [1] +# Set the automatic time stepping method for the static structural analysis +# based on the step index with Transaction(): for step_index in step_index_list: - STAT_STRUC_ANA_SETTING.SetAutomaticTimeStepping( + static_structural_analysis_setting.SetAutomaticTimeStepping( step_index, AutomaticTimeStepping.Off ) -STAT_STRUC_ANA_SETTING.Activate() -step_index_list = [1] - +# Set the number of substeps for the static structural analysis +# based on the step index with Transaction(): for step_index in step_index_list: - STAT_STRUC_ANA_SETTING.SetNumberOfSubSteps(step_index, 2) + static_structural_analysis_setting.SetNumberOfSubSteps(step_index, 2) + +# Activate the static structural analysis settings +static_structural_analysis_setting.Activate() -STAT_STRUC_ANA_SETTING.Activate() -STAT_STRUC_ANA_SETTING.SolverType = SolverType.Direct -STAT_STRUC_ANA_SETTING.SolverPivotChecking = SolverPivotChecking.Off +# Set the solver type and solver pivoting check for the static structural analysis +static_structural_analysis_setting.SolverType = SolverType.Direct +static_structural_analysis_setting.SolverPivotChecking = SolverPivotChecking.Off # %% # Define loads and boundary conditions # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -FIX_SUP = STAT_STRUC.AddFixedSupport() -FIX_SUP.Location = block2_surface - -Tabular_Force = STAT_STRUC.AddForce() -Tabular_Force.Location = bottom_surface -Tabular_Force.DefineBy = LoadDefineBy.Components -Tabular_Force.XComponent.Inputs[0].DiscreteValues = [ - Quantity("0[s]"), - Quantity("1[s]"), - Quantity("2[s]"), - Quantity("3[s]"), - Quantity("4[s]"), +# Add fixed support to the static structural analysis +fixed_support = static_structural.AddFixedSupport() +# Set the fixed support location for the block2_surface object +set_mesh_method_location(method=fixed_support, object_name="block2_surface") + +# Create a new force on the static structural analysis +tabular_force = static_structural.AddForce() +# Set the force location for the bottom_surface object +set_mesh_method_location(method=tabular_force, object_name="bottom_surface") + +# Define the tabular force input and output components +tabular_force.DefineBy = LoadDefineBy.Components +tabular_force.XComponent.Inputs[0].DiscreteValues = [ + Quantity(0, "s"), + Quantity(1, "s"), + Quantity(2, "s"), + Quantity(3, "s"), + Quantity(4, "s"), ] -Tabular_Force.XComponent.Output.DiscreteValues = [ - Quantity("0[N]"), - Quantity("0[N]"), - Quantity("5.e+005[N]"), - Quantity("0[N]"), - Quantity("-5.e+005[N]"), +tabular_force.XComponent.Output.DiscreteValues = [ + Quantity(0, "N"), + Quantity(0, "N"), + Quantity(5.0e005, "N"), + Quantity(0, "N"), + Quantity(-5.0e005, "N"), ] -Bolt_Pretension = STAT_STRUC.AddBoltPretension() -Bolt_Pretension.Location = shank_surface -Bolt_Pretension.Preload.Inputs[0].DiscreteValues = [ - Quantity("1[s]"), - Quantity("2[s]"), - Quantity("3[s]"), - Quantity("4[s]"), +# Add a bolt presentation to the static structural analysis +bolt_presentation = static_structural.AddBoltPretension() +# Set the bolt presentation location for the shank_surface object +set_mesh_method_location(bolt_presentation, "shank_surface") + +# Define the bolt presentation input and output components +bolt_presentation.Preload.Inputs[0].DiscreteValues = [ + Quantity(1, "s"), + Quantity(2, "s"), + Quantity(3, "s"), + Quantity(4, "s"), ] -Bolt_Pretension.Preload.Output.DiscreteValues = [ - Quantity("6.1363e+005[N]"), - Quantity("0 [N]"), - Quantity("0 [N]"), - Quantity("0[N]"), +bolt_presentation.Preload.Output.DiscreteValues = [ + Quantity(6.1363e005, "N"), + Quantity(0, "N"), + Quantity(0, "N"), + Quantity(0, "N"), ] -Bolt_Pretension.SetDefineBy(2, BoltLoadDefineBy.Lock) -Bolt_Pretension.SetDefineBy(3, BoltLoadDefineBy.Lock) -Bolt_Pretension.SetDefineBy(4, BoltLoadDefineBy.Lock) +bolt_presentation.SetDefineBy(2, BoltLoadDefineBy.Lock) +bolt_presentation.SetDefineBy(3, BoltLoadDefineBy.Lock) +bolt_presentation.SetDefineBy(4, BoltLoadDefineBy.Lock) -Tree.Activate([Bolt_Pretension]) -Graphics.ExportImage( - os.path.join(cwd, "loads_and_boundaryconditions.png"), +# Activate the bolt presentation +app.Tree.Activate([bolt_presentation]) + +# Set the image path for the loads and boundary conditions +loads_boundary_conditions_image_path = str( + output_path / "loads_boundary_conditions.png" +) +# Export the image of the loads and boundary conditions +graphics.ExportImage( + loads_boundary_conditions_image_path, image_export_format, - settings_720p, + graphics_image_export_settings, ) -display_image("loads_and_boundaryconditions.png") + +# Display the image of the loads and boundary conditions +display_image(loads_boundary_conditions_image_path) # %% # Insert results # ~~~~~~~~~~~~~~ -Post_Contact_Tool = STAT_STRUC_SOLN.AddContactTool() -Post_Contact_Tool.ScopingMethod = GeometryDefineByType.Worksheet -Bolt_Tool = STAT_STRUC_SOLN.AddBoltTool() -Bolt_Working_Load = Bolt_Tool.AddWorkingLoad() -Total_Deformation = STAT_STRUC_SOLN.AddTotalDeformation() -Equivalent_stress_1 = STAT_STRUC_SOLN.AddEquivalentStress() -Equivalent_stress_2 = STAT_STRUC_SOLN.AddEquivalentStress() -Equivalent_stress_2.Location = shank -Force_Reaction_1 = STAT_STRUC_SOLN.AddForceReaction() -Force_Reaction_1.BoundaryConditionSelection = FIX_SUP -Moment_Reaction_2 = STAT_STRUC_SOLN.AddMomentReaction() -Moment_Reaction_2.BoundaryConditionSelection = FIX_SUP - -# %% -# Solve -# ~~~~~ - -STAT_STRUC_SOLN.Solve(True) -STAT_STRUC_SS = STAT_STRUC_SOLN.Status +# Add a contact tool to the static structural solution and set the scoping method for it +post_contact_tool = static_structural_solution.AddContactTool() +post_contact_tool.ScopingMethod = GeometryDefineByType.Worksheet + +# Add a bolt tool to the static structural solution and add a working load to it +bolt_tool = static_structural_solution.AddBoltTool() +bolt_tool.AddWorkingLoad() + +# Add the total deformation to the static structural solution +total_deformation = static_structural_solution.AddTotalDeformation() + +# Add equivalent stress to the static structural solution +equivalent_stress_1 = static_structural_solution.AddEquivalentStress() + +# Add equivalent stress to the static structural solution and set the location for the shank object +equivalent_stress_2 = static_structural_solution.AddEquivalentStress() +set_mesh_method_location(method=equivalent_stress_2, object_name="shank") + +# Add a force reaction to the static structural solution and set the boundary condition selection +# to the fixed support +force_reaction_1 = static_structural_solution.AddForceReaction() +force_reaction_1.BoundaryConditionSelection = fixed_support + +# Add a moment reaction to the static structural solution and set the boundary condition selection +# to the fixed support +moment_reaction_2 = static_structural_solution.AddMomentReaction() +moment_reaction_2.BoundaryConditionSelection = fixed_support + +# %% +# Solve the static structural solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Solve the static structural solution and wait for it to finish +static_structural_solution.Solve(True) + # sphinx_gallery_start_ignore -assert str(STAT_STRUC_SS) == "Done", "Solution status is not 'Done'" +# Assert the solution status is "Done" +assert ( + static_structural_solution.Status == SolutionStatusType.Done +), "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ +# Show messages +# ~~~~~~~~~~~~~ -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Print all messages from Mechanical +app.messages.show() + +# %% +# Display the results +# ~~~~~~~~~~~~~~~~~~~ # %% -# Results -# ~~~~~~~ # Total deformation -Tree.Activate([Total_Deformation]) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "total_deformation.png"), image_export_format, settings_720p +# Activate the object +app.Tree.Activate([total_deformation]) +# Set the camera to fit the model +camera.SetFit() +# Set the image name and path for the object +image_path = str(output_path / f"total_deformation.png") +# Export the image of the object +app.Graphics.ExportImage( + image_path, image_export_format, graphics_image_export_settings ) -display_image("total_deformation.png") +# Display the image of the object +display_image(image_path) # %% # Equivalent stress on all bodies -Tree.Activate([Equivalent_stress_1]) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "equivalent_stress_total.png"), image_export_format, settings_720p +# Activate the object +app.Tree.Activate([equivalent_stress_1]) +# Set the camera to fit the model +camera.SetFit() +# Set the image name and path for the object +image_path = str(output_path / f"equivalent_stress_all_bodies.png") +# Export the image of the object +app.Graphics.ExportImage( + image_path, image_export_format, graphics_image_export_settings ) -display_image("equivalent_stress_total.png") +# Display the image of the object +display_image(image_path) # %% -# Equivalent stress on shank - -Tree.Activate([Equivalent_stress_2]) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "equivalent_stress_shank.png"), image_export_format, settings_720p +# Equivalent stress on the shank + +# Activate the object +app.Tree.Activate([equivalent_stress_2]) +# Set the camera to fit the model +camera.SetFit() +# Set the image name and path for the object +image_path = str(output_path / f"equivalent_stress_shank.png") +# Export the image of the object +app.Graphics.ExportImage( + image_path, image_export_format, graphics_image_export_settings ) -display_image("equivalent_stress_shank.png") +# Display the image of the object +display_image(image_path) # %% -# Export contact status animation +# Export and display the contact status animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Post_Contact_Tool_status = Post_Contact_Tool.Children[0] -Tree.Activate([Post_Contact_Tool_status]) -Graphics.Camera.SetFit() -animation_export_format = ( - Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF -) -settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() -settings_720p.Width = 1280 -settings_720p.Height = 720 -Post_Contact_Tool_status.ExportAnimation( - os.path.join(cwd, "contact_status.gif"), animation_export_format, settings_720p -) -gif = Image.open(os.path.join(cwd, "contact_status.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) +# %% +# Create a function to update the animation frames +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. + + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] +# %% +# Export and display the contact status animation +# Get the post contact tool status +post_contact_tool_status = post_contact_tool.Children[0] -ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=200, repeat=True, blit=True +# Activate the post contact tool status in the tree +app.Tree.Activate([post_contact_tool_status]) + +# Set the camera to fit the model +camera.SetFit() + +# Set the animation export format and settings +animation_export_format = GraphicsAnimationExportFormat.GIF +animation_export_settings = Ansys.Mechanical.Graphics.AnimationExportSettings() +animation_export_settings.Width = 1280 +animation_export_settings.Height = 720 + +# Set the path for the contact status GIF +contact_status_gif_path = str(output_path / "contact_status.gif") + +# Export the contact status animation to a GIF file +post_contact_tool_status.ExportAnimation( + contact_status_gif_path, animation_export_format, animation_export_settings ) + +# Open the GIF file and create an animation +gif = Image.open(contact_status_gif_path) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(8, 4)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) + +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation +FuncAnimation( + figure, + update_animation, + frames=range(gif.n_frames), + interval=200, + repeat=True, + blit=True, +) + +# Show the animation plt.show() # %% -# Project tree -# ~~~~~~~~~~~~ +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "bolt_pretension.mechdat")) -app.new() +# Save the project +bolt_presentation_mechdat_path = str(output_path / "bolt_pretension.mechdat") +app.save(bolt_presentation_mechdat_path) -# %% -# Delete the example file +# Close the app +app.close() +# Delete the example files delete_downloads() diff --git a/examples/01_basic/fracture_analysis_contact_debonding.py b/examples/01_basic/fracture_analysis_contact_debonding.py index 44737126..7b682a8f 100644 --- a/examples/01_basic/fracture_analysis_contact_debonding.py +++ b/examples/01_basic/fracture_analysis_contact_debonding.py @@ -22,7 +22,7 @@ """.. _ref_contact: -Fracture Analysis - Contact debonding +Fracture analysis - contact debonding ------------------------------------- The following example demonstrates the use of the Contact Debonding @@ -32,10 +32,11 @@ """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +from typing import TYPE_CHECKING from PIL import Image from ansys.mechanical.core import App @@ -44,45 +45,148 @@ from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation +if TYPE_CHECKING: + import Ansys + # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - +# %% +# Configure camera and graphics for image export +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") - plt.show() +# Set camera orientation +graphics = app.Graphics +camera = graphics.Camera +camera.SetSpecificViewOrientation(ViewOrientationType.Front) +# Set camera settings for 720p resolution +image_export_format = GraphicsImageExportFormat.PNG +graphics_image_export_settings = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() +graphics_image_export_settings.Resolution = GraphicsResolutionType.EnhancedResolution +graphics_image_export_settings.Background = GraphicsBackgroundType.White +graphics_image_export_settings.CurrentGraphicsDisplay = False +graphics_image_export_settings.Width = 1280 +graphics_image_export_settings.Height = 720 # %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure + plt.show() -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Front) -image_export_format = GraphicsImageExportFormat.PNG -settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() -settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False # %% -# Download geometry and materials files +# Download and import the geometry file # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set the model +model = app.Model +# Create a geometry import group for the model +geometry_import_group = model.GeometryImportGroup +# Add the geometry import to the group +geometry_import = geometry_import_group.AddGeometryImport() +# Set the geometry import format +geometry_import_format = ( + Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +) +# Set the geometry import preferences +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.AnalysisType = ( + Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.AnalysisType.Type2D +) + +# Download the geometry file from the ansys/example-data repository geometry_path = download_file( "Contact_Debonding_Example.agdb", "pymechanical", "embedding" ) + +# Import/reload the geometry from the CAD (.agdb) file using the provided preferences +geometry_import.Import( + geometry_path, geometry_import_format, geometry_import_preferences +) + +# Visualize the model in 3D +app.plot() + +# %% +# Download and import the material files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Download the material files from the ansys/example-data repository mat1_path = download_file( "Contact_Debonding_Example_Mat1.xml", "pymechanical", "embedding" ) @@ -90,368 +194,458 @@ def display_image(image_name): "Contact_Debonding_Example_Mat2.xml", "pymechanical", "embedding" ) +# Add materials to the model and import the material files +model_materials = model.Materials +model_materials.Import(mat1_path) +model_materials.Import(mat2_path) # %% -# Import the geometry -# ~~~~~~~~~~~~~~~~~~~ +# Add connections to the model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -geometry_import = Model.GeometryImportGroup.AddGeometryImport() -geometry_import_format = ( - Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic -) -geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() -geometry_import_preferences.ProcessNamedSelections = True -geometry_import_preferences.AnalysisType = ( - Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.AnalysisType.Type2D -) -geometry_import.Import( - geometry_path, geometry_import_format, geometry_import_preferences -) +# Add connections to the model +add_connections = model.AddConnections() +# Add a connection group to the connections +add_connections.AddConnectionGroup() -app.plot() +# Define and create automatic connections for the model +connections = model.Connections +connections.CreateAutomaticConnections() # %% -# Material import, named selections, and connections -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import materials - -MODEL = Model -GEOMETRY = Model.Geometry -MAT_GRP = MODEL.Materials -MAT_GRP.Import(mat1_path) -MAT_GRP.Import(mat2_path) - -PART = [x for x in Tree.AllObjects if x.Name == "Part 2"][0] -MAT_BODY = [ - i - for i in MAT_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.Material](True) - if i.Name == "Interface Body Material" -][0] -MAT_CZM = [ - i - for i in MAT_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.Material](True) - if i.Name == "CZM Crack Material" -][0] - -# %% -# Connections - -connections = MODEL.AddConnections() -CONNECTIONS_GRP = connections.AddConnectionGroup() -MODEL.Connections.CreateAutomaticConnections() -CONNECTIONS_GRP = Model.Connections -CONTACTS = [ - i - for i in CONNECTIONS_GRP.GetChildren[ - Ansys.ACT.Automation.Mechanical.Connections.ConnectionGroup - ](True) - if i.Name == "Contacts" -][0] -CONTACT_REGION = [ - i - for i in CONTACTS.GetChildren[ - Ansys.ACT.Automation.Mechanical.Connections.ContactRegion - ](True) - if i.Name == "Contact Region" -][0] - -# %% -# Named selections - -NAMED_SELECTIONS = Model.NamedSelections -NS_EDGE_HIGH = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "High_Edge" -][0] -NS_EDGE_LOW = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Low_Edge" -][0] -NS_EDGES_SHORT = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Short_Edges" -][0] -NS_EDGES_LONG = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Long_Edges" -][0] -NS_EDGES_FIXED = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Fixed_Edges" -][0] -NS_VERTEX_DISP1 = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Disp1_Vertex" -][0] -NS_VERTEX_DISP2 = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Disp2_Vertex" -][0] -NS_FACES_BOTH = [ - i - for i in NAMED_SELECTIONS.GetChildren[ - Ansys.ACT.Automation.Mechanical.NamedSelection - ](True) - if i.Name == "Both_Faces" -][0] - -# %% -# Define static structural analysis and settings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add a static structural analysis to the model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Add a static structural analysis to the model +model.AddStaticStructuralAnalysis() +static_structural_analysis = app.DataModel.AnalysisByName("Static Structural") +static_structural_analysis_solution = static_structural_analysis.Solution + +# Set the unit system +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM + +# %% +# Activate the geometry and set the 2D behavior +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Define the geometry for the model +geometry = model.Geometry +# Activate the geometry +geometry.Activate() +# Set the 2D behavior for the geometry +geometry.Model2DBehavior = Model2DBehavior.PlaneStrain -MODEL.AddStaticStructuralAnalysis() -STATIC_STRUCTURAL = DataModel.AnalysisByName("Static Structural") -ANALYSIS_SETTINGS = STATIC_STRUCTURAL.AnalysisSettings -SOLUTION = STATIC_STRUCTURAL.Solution -MESH = Model.Mesh -# Set unit system +# %% +# Create a function to get the child object by name +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM -# Set 2D behavior +def get_child_object(body, child_type, name: str): + """Get the named selection child by name.""" + return [ + child for child in body.GetChildren[child_type](True) if child.Name == name + ][0] -GEOMETRY.Activate() -GEOMETRY.Model2DBehavior = Model2DBehavior.PlaneStrain -# Assign material +# %% +# Activate the ``Part 2`` object and set its material +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -PART.Activate() -PART.Material = MAT_BODY.Name +# Get the ``Part 2`` object from the tree +part2_object = app.DataModel.GetObjectsByName("Part 2")[0] -# Define contact Region +# Activate the ``Part 2`` object +part2_object.Activate() -CONTACT_REGION.Activate() -CONTACT_REGION.SourceLocation = NS_EDGE_HIGH -CONTACT_REGION.TargetLocation = NS_EDGE_LOW -CONTACT_REGION.ContactType = ContactType.Bonded -CONTACT_REGION.ContactFormulation = ContactFormulation.PurePenalty +# Set the material for the ``Part 2`` object +part2_object.Material = get_child_object( + model_materials, Ansys.ACT.Automation.Mechanical.Material, "Interface Body Material" +).Name # %% -# Define mesh controls and generate mesh +# Define the contact and contact regions # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -MESH.Activate() -MESH.ElementOrder = ElementOrder.Quadratic -MESH.UseAdaptiveSizing = False -MESH.ElementSize = Quantity("0.75 [mm]") +# %% +# Activate the contact region -SIZING_MESH = MESH.AddSizing() -SIZING_MESH.Location = NS_EDGES_SHORT -SIZING_MESH.ElementSize = Quantity("0.75 [mm]") -SIZING_MESH.Behavior = SizingBehavior.Hard +# Get the contact from the connection group +contact = get_child_object( + connections, Ansys.ACT.Automation.Mechanical.Connections.ConnectionGroup, "Contacts" +) -SIZING_MESH2 = MESH.AddSizing() -SIZING_MESH2.Location = NS_EDGES_LONG -SIZING_MESH2.ElementSize = Quantity("0.5 [mm]") -SIZING_MESH2.Behavior = SizingBehavior.Hard +# Get the contact region from the contact +contact_region = get_child_object( + contact, Ansys.ACT.Automation.Mechanical.Connections.ContactRegion, "Contact Region" +) +# Activate the contact region +contact_region.Activate() -FACE_MESHING = MESH.AddFaceMeshing() -FACE_MESHING.Location = NS_FACES_BOTH -FACE_MESHING.Method = FaceMeshingMethod.Quadrilaterals +# %% +# Set properties for the contact region -MESH.Activate() -MESH.GenerateMesh() +# Define the model named selections +named_selections = model.NamedSelections +# Set the source location to the high edge named selection +contact_region.SourceLocation = get_child_object( + named_selections, Ansys.ACT.Automation.Mechanical.NamedSelection, "High_Edge" +) +# Set the target location to the low edge named selection` +contact_region.TargetLocation = get_child_object( + named_selections, Ansys.ACT.Automation.Mechanical.NamedSelection, "Low_Edge" +) +# Set the contact type to bonded +contact_region.ContactType = ContactType.Bonded +# Set the contact formulation to pure penalty +contact_region.ContactFormulation = ContactFormulation.PurePenalty + +# %% +# Generate the mesh +# ~~~~~~~~~~~~~~~~~ + +# Define the mesh for the model +mesh = model.Mesh + +# Set the mesh element order to quadratic +mesh.ElementOrder = ElementOrder.Quadratic +# Turn off adaptive sizing +mesh.UseAdaptiveSizing = False +# Set the mesh element size to 0.75 mm +mesh.ElementSize = Quantity("0.75 [mm]") -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") # %% -# Add contact debonding object -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a function to add sizing to the mesh + + +def add_sizing( + mesh: Ansys.ACT.Automation.Mechanical.MeshControls.Mesh, + name: str, + element_size: Ansys.Core.Units.Quantity, + behavior: Ansys.Mechanical.DataModel.Enums.SizingBehavior, +) -> None: + """Add sizing to the mesh and set its location, element size, and behavior. + + Parameters + ---------- + mesh : Ansys.ACT.Automation.Mechanical.MeshControls.Mesh + The mesh object to add sizing to. + name : str + The name of the named selection to use for sizing. + element_size : Ansys.Core.Units.Quantity + The element size to set for the sizing. + behavior : Ansys.Mechanical.DataModel.Enums.SizingBehavior + The behavior of the sizing (e.g., hard or soft). + """ + sizing = mesh.AddSizing() + sizing.Location = get_child_object( + named_selections, Ansys.ACT.Automation.Mechanical.NamedSelection, name + ) + sizing.ElementSize = element_size + sizing.Behavior = behavior + + +# %% +# Add sizing to the mesh for the short and long edges +add_sizing(mesh, "Short_Edges", Quantity("0.75 [mm]"), SizingBehavior.Hard) +add_sizing(mesh, "Long_Edges", Quantity("0.5 [mm]"), SizingBehavior.Hard) + +# %% +# Add sizing to the mesh for both faces +sizing_mesh_both_faces = mesh.AddFaceMeshing() +sizing_mesh_both_faces.Location = get_child_object( + named_selections, Ansys.ACT.Automation.Mechanical.NamedSelection, "Both_Faces" +) +# Set the face meshing method to quadrilaterals +sizing_mesh_both_faces.Method = FaceMeshingMethod.Quadrilaterals + +# Activate and generate the mesh +mesh.Activate() +mesh.GenerateMesh() + +# Display the mesh image +set_camera_and_display_image( + camera, graphics, graphics_image_export_settings, output_path, "mesh.png" +) + +# %% +# Add a contact debonding object +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -MODEL.Activate() -FRACTURE = MODEL.AddFracture() +# Activate the model +model.Activate() -CONTACT_DEBONDING = FRACTURE.AddContactDebonding() -CONTACT_DEBONDING.Material = MAT_CZM.Name -CONTACT_DEBONDING.ContactRegion = CONTACT_REGION +# Add a fracture to the model +fracture = model.AddFracture() + +# Add contact debonding to the fracture +contact_debonding = fracture.AddContactDebonding() +# Set the material for the contact debonding +contact_debonding.Material = get_child_object( + model_materials, Ansys.ACT.Automation.Mechanical.Material, "CZM Crack Material" +).Name +# Set the contact region for the contact debonding +contact_debonding.ContactRegion = contact_region # %% -# Define analysis settings -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Define the static structural analysis settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -ANALYSIS_SETTINGS.Activate() -ANALYSIS_SETTINGS.AutomaticTimeStepping = AutomaticTimeStepping.On -ANALYSIS_SETTINGS.DefineBy = TimeStepDefineByType.Substeps -ANALYSIS_SETTINGS.MaximumSubsteps = 100 -ANALYSIS_SETTINGS.InitialSubsteps = 100 -ANALYSIS_SETTINGS.MinimumSubsteps = 100 -ANALYSIS_SETTINGS.LargeDeflection = True +# Define the static structural analysis settings +analysis_settings = static_structural_analysis.AnalysisSettings +# Activate the analysis settings +analysis_settings.Activate() +# Turn on automatic time stepping +analysis_settings.AutomaticTimeStepping = AutomaticTimeStepping.On +# Define the time step settings with substeps +analysis_settings.DefineBy = TimeStepDefineByType.Substeps +# Set the initial, minimum, and maximum time step sizes +analysis_settings.InitialSubsteps = 100 +analysis_settings.MinimumSubsteps = 100 +analysis_settings.MaximumSubsteps = 100 +# Turn on large deflection +analysis_settings.LargeDeflection = True # %% # Define boundary conditions # ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Add fixed support -STATIC_STRUCTURAL.Activate() -FIXED_SUPPORT = STATIC_STRUCTURAL.AddFixedSupport() -FIXED_SUPPORT.Location = NS_EDGES_FIXED +# Add fixed support to the static structural analysis +fixed_support = static_structural_analysis.AddFixedSupport() +# Set the fixed support location to the fixed edges named selection +fixed_support.Location = get_child_object( + named_selections, Ansys.ACT.Automation.Mechanical.NamedSelection, "Fixed_Edges" +) + +# %% +# Add displacements to the static structural analysis +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # %% -# Add displacement +# Create a function to add displacement to the static structural analysis + + +def add_displacement( + static_structural_analysis: Ansys.ACT.Automation.Mechanical.Analysis, + named_selections: Ansys.ACT.Automation.Mechanical.NamedSelections, + name: str, + y_component_value: Ansys.Core.Units.Quantity, +) -> None: + """Add a displacement to the static structural analysis. + + Parameters + ---------- + static_structural_analysis : Ansys.ACT.Automation.Mechanical.Analysis + The static structural analysis object. + named_selections : Ansys.ACT.Automation.Mechanical.NamedSelections + The named selections object. + name : str + The name of the named selection to use for displacement. + y_component_value : str + The value of the Y component for the displacement. + """ + # Add a displacement to the static structural analysis + displacement = static_structural_analysis.AddDisplacement() + # Set the location for the displacement to the named selection with the given name + displacement.Location = get_child_object( + named_selections, Ansys.ACT.Automation.Mechanical.NamedSelection, name + ) + # Set the displacement type to components + displacement.DefineBy = LoadDefineBy.Components + # Set the value of the Y component for the displacement + displacement.YComponent.Output.DiscreteValues = [y_component_value] + + return displacement -STATIC_STRUCTURAL.Activate() -DISPLACEMENT = STATIC_STRUCTURAL.AddDisplacement() -DISPLACEMENT.Location = NS_VERTEX_DISP1 -DISPLACEMENT.DefineBy = LoadDefineBy.Components -DISPLACEMENT.YComponent.Output.DiscreteValues = [Quantity("10 [mm]")] -STATIC_STRUCTURAL.Activate() -DISPLACEMENT2 = STATIC_STRUCTURAL.AddDisplacement() -DISPLACEMENT2.Location = NS_VERTEX_DISP2 -DISPLACEMENT2.DefineBy = LoadDefineBy.Components -DISPLACEMENT2.YComponent.Output.DiscreteValues = [Quantity("-10 [mm]")] +# %% +# Add displacements to the static structural analysis + +displacement1_vertex = add_displacement( + static_structural_analysis, named_selections, "Disp1_Vertex", Quantity("10 [mm]") +) +displacement2_vertex = add_displacement( + static_structural_analysis, named_selections, "Disp2_Vertex", Quantity("-10 [mm]") +) + +# %% +# Set the camera to fit the model and display the image of the boundary conditions -STATIC_STRUCTURAL.Activate() +static_structural_analysis.Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "boundary_conditions.png"), image_export_format, settings_720p +set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + output_path, + "boundary_conditions.png", ) -display_image("boundary_conditions.png") # %% -# Add results -# ~~~~~~~~~~~ +# Add results to the solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Activate the static structural analysis solution +static_structural_analysis_solution.Activate() -SOLUTION.Activate() -DIRECTIONAL_DEFORMATION = SOLUTION.AddDirectionalDeformation() -DIRECTIONAL_DEFORMATION.NormalOrientation = NormalOrientationType.YAxis +# %% +# Add directional deformation to the static structural analysis solution -FORCE_REACTION = SOLUTION.AddForceReaction() -FORCE_REACTION.BoundaryConditionSelection = DISPLACEMENT +directional_deformation = ( + static_structural_analysis_solution.AddDirectionalDeformation() +) +# Set the orientation of the directional deformation to Y-axis +directional_deformation.NormalOrientation = NormalOrientationType.YAxis # %% -# Solve -# ~~~~~ +# Add the force reaction to the static structural analysis solution +force_reaction = static_structural_analysis_solution.AddForceReaction() +# Set the boundary condition selection to the vertex named selection +force_reaction.BoundaryConditionSelection = displacement1_vertex -STATIC_STRUCTURAL.Activate() -SOLUTION.Solve(True) +# %% +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ + +static_structural_analysis_solution.Solve(True) # sphinx_gallery_start_ignore -assert str(SOLUTION.Status) == "Done", "Solution status is not 'Done'" +assert ( + static_structural_analysis_solution.Status == SolutionStatusType.Done +), "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ +# Show messages +# ~~~~~~~~~~~~~ -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Print all messages from Mechanical +app.messages.show() # %% -# Display results -# ~~~~~~~~~~~~~~~ -# Directional deformation +# Activate the reactions and display the images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -DIRECTIONAL_DEFORMATION.Activate() +# %% +# Directional deformation -Graphics.ExportImage( - os.path.join(cwd, "directional_deformation.png"), image_export_format, settings_720p +directional_deformation.Activate() +set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + output_path, + "directional_deformation.png", ) -display_image("directional_deformation.png") # %% # Force reaction -FORCE_REACTION.Activate() - -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "force_reaction.png"), image_export_format, settings_720p +force_reaction.Activate() +set_camera_and_display_image( + camera, graphics, graphics_image_export_settings, output_path, "force_reaction.png" ) -display_image("force_reaction.png") # %% -# Export animation -# ~~~~~~~~~~~~~~~~ +# Export the animation +# ~~~~~~~~~~~~~~~~~~~~ -animation_export_format = ( - Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF -) -settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() -settings_720p.Width = 1280 -settings_720p.Height = 720 +# %% +# Create a function to update the animation frame -FORCE_REACTION.ExportAnimation( - os.path.join(cwd, "force_reaction.gif"), animation_export_format, settings_720p -) -gif = Image.open(os.path.join(cwd, "force_reaction.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. -def update(frame): + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] -ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=100, repeat=True, blit=True +# %% +# Display the animation of the force reaction + +# Set the animation export format and settings +animation_export_format = GraphicsAnimationExportFormat.GIF +animation_export_settings = Ansys.Mechanical.Graphics.AnimationExportSettings() +animation_export_settings.Width = 1280 +animation_export_settings.Height = 720 + +# Set the path for the contact status GIF +force_reaction_gif_path = output_path / "force_reaction.gif" + +# Export the force reaction animation to a GIF file +force_reaction.ExportAnimation( + str(force_reaction_gif_path), animation_export_format, animation_export_settings ) + +# Open the GIF file and create an animation +gif = Image.open(force_reaction_gif_path) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) + +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation +FuncAnimation( + figure, + update_animation, + frames=range(gif.n_frames), + interval=100, + repeat=True, + blit=True, +) + +# Show the animation plt.show() # %% -# Display output file from solve -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Display the output file from the solve +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get the working directory for the static structural analysis +solve_path = Path(static_structural_analysis.WorkingDir) +# Get the solve output path +solve_out_path = solve_path / "solve.out" -def write_file_contents_to_console(path): - """Write file contents to console.""" - with open(path, "rt") as file: +# Print the content of the solve output file if it exists +if solve_out_path: + with solve_out_path.open("rt") as file: for line in file: print(line, end="") - -solve_path = STATIC_STRUCTURAL.WorkingDir -solve_out_path = os.path.join(solve_path, "solve.out") -if solve_out_path: - write_file_contents_to_console(solve_out_path) - # %% -# Project tree -# ~~~~~~~~~~~~ +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "contact_debonding.mechdat")) -app.new() +# Save the project +mechdat_path = output_path / "contact_debonding.mechdat" +app.save(str(mechdat_path)) -# %% -# Delete example files +# Close the app +app.close() +# Delete the example files delete_downloads() diff --git a/examples/01_basic/harmonic_acoustics.py b/examples/01_basic/harmonic_acoustics.py index 1b183abd..346fa652 100644 --- a/examples/01_basic/harmonic_acoustics.py +++ b/examples/01_basic/harmonic_acoustics.py @@ -32,10 +32,11 @@ """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +from typing import TYPE_CHECKING from PIL import Image from ansys.mechanical.core import App @@ -44,22 +45,93 @@ from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation +if TYPE_CHECKING: + import Ansys + # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ app = App() app.update_globals(globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, + set_fit: bool = False, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + if set_fit: + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure plt.show() @@ -67,7 +139,13 @@ def display_image(image_name): # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to isometric view +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) + +# Set the image export format to PNG and configure the export settings image_export_format = GraphicsImageExportFormat.PNG settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution @@ -75,454 +153,550 @@ def display_image(image_name): settings_720p.Width = 1280 settings_720p.Height = 720 settings_720p.CurrentGraphicsDisplay = False -Graphics.Camera.Rotate(180, CameraAxisType.ScreenY) +camera.Rotate(180, CameraAxisType.ScreenY) # %% # Download geometry and materials files # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the geometry file from the ansys/example-data repository geometry_path = download_file("C_GEOMETRY.agdb", "pymechanical", "embedding") +# Download the material file from the ansys/example-data repository mat_path = download_file("Air-material.xml", "pymechanical", "embedding") # %% # Import the geometry # ~~~~~~~~~~~~~~~~~~~ -geometry_import = Model.GeometryImportGroup.AddGeometryImport() +# Define the model +model = app.Model + +# Add the geometry import group and set its preferences +geometry_import = model.GeometryImportGroup.AddGeometryImport() geometry_import_format = ( Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic ) geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() geometry_import_preferences.ProcessNamedSelections = True + +# Import the geometry file with the specified format and preferences geometry_import.Import( geometry_path, geometry_import_format, geometry_import_preferences ) +# Define the geometry in the model +geometry = model.Geometry -GEOM = Model.Geometry +# Suppress the bodies at the specified geometry.Children indices +suppressed_indices = [0, 1, 2, 3, 4, 6, 9, 10] +for index, child in enumerate(geometry.Children): + if index in suppressed_indices: + child.Suppressed = True -solid1 = GEOM.Children[0] -solid2 = GEOM.Children[1] -solid3 = GEOM.Children[2] -solid4 = GEOM.Children[3] -solid5 = GEOM.Children[4] -solid6 = GEOM.Children[5] -solid7 = GEOM.Children[6] -solid8 = GEOM.Children[7] -solid9 = GEOM.Children[8] -solid10 = GEOM.Children[9] -solid11 = GEOM.Children[10] +# Visualize the model in 3D +app.plot() -solid1.Suppressed = True -solid2.Suppressed = True -solid3.Suppressed = True -solid4.Suppressed = True -solid5.Suppressed = True -solid7.Suppressed = True -solid10.Suppressed = True -solid11.Suppressed = True +# %% +# Store all variables necessary for analysis +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +mesh = model.Mesh +named_selection = model.NamedSelections +connection = model.Connections +coordinate_systems = model.CoordinateSystems +mat = model.Materials -app.plot() +# %% +# Set up the analysis +# ~~~~~~~~~~~~~~~~~~~ + +# Add the harmonic acoustics analysis and unit system +model.AddHarmonicAcousticAnalysis() +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS # %% -# Store all Variables necessary for analysis -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import and assign the materials -MESH = Model.Mesh -NS = Model.NamedSelections -CONN = Model.Connections -CS = Model.CoordinateSystems -MAT = Model.Materials +mat.Import(mat_path) + +# Assign the material to the ``geometry.Children`` bodies that are not suppressed +for child in range(geometry.Children.Count): + if child not in suppressed_indices: + geometry.Children[child].Material = "Air" # %% -# Setup the Analysis -# ~~~~~~~~~~~~~~~~~~ -# Add harmonic acoustics and unit system +# Create a coordinate system -Model.AddHarmonicAcousticAnalysis() -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS +lcs1 = coordinate_systems.AddCoordinateSystem() +lcs1.OriginX = Quantity("0 [mm]") +lcs1.OriginY = Quantity("0 [mm]") +lcs1.OriginZ = Quantity("0 [mm]") +lcs1.PrimaryAxisDefineBy = CoordinateSystemAlignmentType.GlobalZ # %% -# Import and assign materials +# Generate the mesh -MAT.Import(mat_path) -solid6.Material = "Air" -solid8.Material = "Air" -solid9.Material = "Air" +mesh.ElementSize = Quantity("200 [mm]") +mesh.GenerateMesh() # %% -# Create coordinate system -LCS1 = CS.AddCoordinateSystem() -LCS1.OriginX = Quantity("0 [mm]") -LCS1.OriginY = Quantity("0 [mm]") -LCS1.OriginZ = Quantity("0 [mm]") -LCS1.PrimaryAxisDefineBy = CoordinateSystemAlignmentType.GlobalZ +# Create named selections +# ~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Create a function to set up named selections + + +def setup_named_selection(scoping_method, name): + """Create a named selection with the specified scoping method and name. + + Parameters + ---------- + scoping_method : GeometryDefineByType + The scoping method for the named selection. + name : str + The name of the named selection. + + Returns + ------- + Ansys.ACT.Automation.Mechanical.NamedSelection + The created named selection. + """ + ns = model.AddNamedSelection() + ns.ScopingMethod = scoping_method + ns.Name = name + return ns + + +# %% +# Create a function to add generation criteria to the named selection + + +def add_generation_criteria( + named_selection, + value, + active=True, + action=SelectionActionType.Add, + entity_type=SelectionType.GeoFace, + criterion=SelectionCriterionType.Size, + operator=SelectionOperatorType.Equal, +): + """Add generation criteria to the named selection. + + Parameters + ---------- + named_selection : Ansys.ACT.Automation.Mechanical.NamedSelection + The named selection to which the criteria will be added. + value : Quantity + The value for the criteria. + active : bool + Whether the criteria is active. + action : SelectionActionType + The action type for the criteria. + entity_type : SelectionType + The entity type for the criteria. + criterion : SelectionCriterionType + The criterion type for the criteria. + operator : SelectionOperatorType + The operator for the criteria. + """ + generation_criteria = named_selection.GenerationCriteria + criteria = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() + criteria.Active = active + criteria.Action = action + criteria.EntityType = entity_type + criteria.Criterion = criterion + criteria.Operator = operator + criteria.Value = value + generation_criteria.Add(criteria) + + +# %% +# Add a named selection for the surface velocity and define its generation criteria + +sf_velo = setup_named_selection(GeometryDefineByType.Worksheet, "sf_velo") +add_generation_criteria(sf_velo, Quantity("3e6 [mm^2]")) +add_generation_criteria( + sf_velo, + Quantity("15000 [mm]"), + action=SelectionActionType.Filter, + criterion=SelectionCriterionType.LocationZ, +) +# Activate and generate the named selection +sf_velo.Activate() +sf_velo.Generate() # %% -# Generate mesh +# Add named selections for the absorption faces and define its generation criteria -MESH.ElementSize = Quantity("200 [mm]") -MESH.GenerateMesh() +abs_face = setup_named_selection(GeometryDefineByType.Worksheet, "abs_face") +add_generation_criteria(abs_face, Quantity("1.5e6 [mm^2]")) +add_generation_criteria( + abs_face, + Quantity("500 [mm]"), + action=SelectionActionType.Filter, + criterion=SelectionCriterionType.LocationY, +) +# Activate and generate the named selection +abs_face.Activate() +abs_face.Generate() +# %% +# Add named selections for the pressure faces and define its generation criteria + +pres_face = setup_named_selection(GeometryDefineByType.Worksheet, "pres_face") +add_generation_criteria(pres_face, Quantity("1.5e6 [mm^2]")) +add_generation_criteria( + pres_face, + Quantity("4500 [mm]"), + action=SelectionActionType.Filter, + criterion=SelectionCriterionType.LocationY, +) +# Activate and generate the named selection +pres_face.Activate() +pres_face.Generate() # %% -# Create named selections -# ~~~~~~~~~~~~~~~~~~~~~~~~ - -SF_Velo = Model.AddNamedSelection() -SF_Velo.ScopingMethod = GeometryDefineByType.Worksheet -SF_Velo.Name = "SF_Velo" -GEN_CRT1 = SF_Velo.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoFace -CRT1.Criterion = SelectionCriterionType.Size -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = Quantity("3e6 [mm^2]") -GEN_CRT1.Add(CRT1) -CRT2 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT2.Active = True -CRT2.Action = SelectionActionType.Filter -CRT2.EntityType = SelectionType.GeoFace -CRT2.Criterion = SelectionCriterionType.LocationZ -CRT2.Operator = SelectionOperatorType.Equal -CRT2.Value = Quantity("15000 [mm]") -GEN_CRT1.Add(CRT2) -SF_Velo.Activate() -SF_Velo.Generate() - -ABS_Face = Model.AddNamedSelection() -ABS_Face.ScopingMethod = GeometryDefineByType.Worksheet -ABS_Face.Name = "ABS_Face" -GEN_CRT2 = ABS_Face.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoFace -CRT1.Criterion = SelectionCriterionType.Size -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = Quantity("1.5e6 [mm^2]") -GEN_CRT2.Add(CRT1) -CRT2 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT2.Active = True -CRT2.Action = SelectionActionType.Filter -CRT2.EntityType = SelectionType.GeoFace -CRT2.Criterion = SelectionCriterionType.LocationY -CRT2.Operator = SelectionOperatorType.Equal -CRT2.Value = Quantity("500 [mm]") -GEN_CRT2.Add(CRT2) -ABS_Face.Activate() -ABS_Face.Generate() - -PRES_Face = Model.AddNamedSelection() -PRES_Face.ScopingMethod = GeometryDefineByType.Worksheet -PRES_Face.Name = "PRES_Face" -GEN_CRT3 = PRES_Face.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoFace -CRT1.Criterion = SelectionCriterionType.Size -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = Quantity("1.5e6 [mm^2]") -GEN_CRT3.Add(CRT1) -CRT2 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT2.Active = True -CRT2.Action = SelectionActionType.Filter -CRT2.EntityType = SelectionType.GeoFace -CRT2.Criterion = SelectionCriterionType.LocationY -CRT2.Operator = SelectionOperatorType.Equal -CRT2.Value = Quantity("4500 [mm]") -GEN_CRT3.Add(CRT2) -PRES_Face.Activate() -PRES_Face.Generate() - -ACOUSTIC_Region = Model.AddNamedSelection() -ACOUSTIC_Region.ScopingMethod = GeometryDefineByType.Worksheet -ACOUSTIC_Region.Name = "ACOUSTIC_Region" -GEN_CRT4 = ACOUSTIC_Region.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoBody -CRT1.Criterion = SelectionCriterionType.Type -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = 8 -GEN_CRT4.Add(CRT1) -ACOUSTIC_Region.Activate() -ACOUSTIC_Region.Generate() - -# %% -# Analysis settings -# ~~~~~~~~~~~~~~~~~ - -ANALYSIS_SETTINGS = Model.Analyses[0].AnalysisSettings -ANALYSIS_SETTINGS.RangeMaximum = Quantity("100 [Hz]") -ANALYSIS_SETTINGS.SolutionIntervals = 50 -ANALYSIS_SETTINGS.CalculateVelocity = True -ANALYSIS_SETTINGS.CalculateEnergy = True -ANALYSIS_SETTINGS.CalculateVolumeEnergy = True - -# %% -# Boundary conditions and load +# Add named selections for the acoustic region and define its generation criteria + +acoustic_region = setup_named_selection( + GeometryDefineByType.Worksheet, "acoustic_region" +) +add_generation_criteria( + acoustic_region, + 8, + entity_type=SelectionType.GeoBody, + criterion=SelectionCriterionType.Type, +) +# Activate and generate the named selection +acoustic_region.Activate() +acoustic_region.Generate() + +# %% +# Set up the analysis settings # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -HARM_ACOUST = Model.Analyses[0] +analysis_settings = model.Analyses[0].AnalysisSettings +analysis_settings.RangeMaximum = Quantity("100 [Hz]") +analysis_settings.SolutionIntervals = 50 +analysis_settings.CalculateVelocity = True +analysis_settings.CalculateEnergy = True +analysis_settings.CalculateVolumeEnergy = True # %% -# Acoustic region +# Set the boundary conditions and load +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Acoustic_region = [x for x in HARM_ACOUST.Children if x.Name == "Acoustics Region"][0] -Acoustic_region.Location = ACOUSTIC_Region +# Get the harmonic acoustics analysis +harmonic_acoustics = model.Analyses[0] # %% -# Surface velocity +# Set the location for the acoustics region from the harmonic acoustics analysis -SURF_VEL = HARM_ACOUST.AddAcousticSurfaceVelocity() -SURF_VEL.Location = SF_Velo -SURF_VEL.Magnitude.Output.DiscreteValues = [Quantity("5000 [mm s-1]")] +acoustics_region = [ + child for child in harmonic_acoustics.Children if child.Name == "Acoustics Region" +][0] +acoustics_region.Location = acoustic_region # %% -# Acoustic pressure +# Add a surface velocity boundary condition to the harmonic acoustics analysis -ACOUST_PRES = HARM_ACOUST.AddAcousticPressure() -ACOUST_PRES.Location = PRES_Face -ACOUST_PRES.Magnitude = Quantity("1.5e-7 [MPa]") +surface_velocity = harmonic_acoustics.AddAcousticSurfaceVelocity() +surface_velocity.Location = sf_velo +surface_velocity.Magnitude.Output.DiscreteValues = [Quantity("5000 [mm s-1]")] # %% -# Acoustic absoption surface +# Add an acoustic pressure boundary condition to the harmonic acoustics analysis -ABSORP_SURF = HARM_ACOUST.AddAcousticAbsorptionSurface() -ABSORP_SURF.Location = ABS_Face -ABSORP_SURF.AbsorptionCoefficient.Output.DiscreteValues = [Quantity("0.02")] +acoustic_pressure = harmonic_acoustics.AddAcousticPressure() +acoustic_pressure.Location = pres_face +acoustic_pressure.Magnitude = Quantity("1.5e-7 [MPa]") -HARM_ACOUST.Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "bounday_conditions.png"), image_export_format, settings_720p +# %% +# Add an acoustic absorption surface to the harmonic acoustics analysis + +absorption_surface = harmonic_acoustics.AddAcousticAbsorptionSurface() +absorption_surface.Location = abs_face +absorption_surface.AbsorptionCoefficient.Output.DiscreteValues = [Quantity("0.02")] + +# Activate the harmonic acoustics analysis +harmonic_acoustics.Activate() +# Set the camera to fit the mesh and export the image +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "bounday_conditions.png", set_fit=True ) -display_image("bounday_conditions.png") # %% -# Add results -# ~~~~~~~~~~~ +# Add results to the harmonic acoustics solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -SOLN = Model.Analyses[0].Solution +# Get the harmonic acoustics solution +solution = model.Analyses[0].Solution # %% -# Acoustic pressure +# Add the acoustic pressure result -ACOUST_PRES_RES1 = SOLN.AddAcousticPressureResult() -ACOUST_PRES_RES1.By = SetDriverStyle.ResultSet -ACOUST_PRES_RES1.SetNumber = 25 +acoustic_pressure_result_1 = solution.AddAcousticPressureResult() +acoustic_pressure_result_1.By = SetDriverStyle.ResultSet +acoustic_pressure_result_1.SetNumber = 25 # %% -# Acoustic velocity - total and directional +# Add the acoustic total and directional velocity results -ACOUST_TOT_VEL1 = SOLN.AddAcousticTotalVelocityResult() -ACOUST_TOT_VEL1.Frequency = Quantity("50 [Hz]") +# Add the acoustic total velocity result and set its frequency +acoustic_total_velocity_1 = solution.AddAcousticTotalVelocityResult() +acoustic_total_velocity_1.Frequency = Quantity("50 [Hz]") -ACOUST_DIR_VEL1 = SOLN.AddAcousticDirectionalVelocityResult() -ACOUST_DIR_VEL1.Frequency = Quantity("50 [Hz]") -ACOUST_DIR_VEL1.CoordinateSystem = LCS1 +# Add the acoustic directional velocity result and set its frequency and coordinate system +acoustic_directional_velocity_1 = solution.AddAcousticDirectionalVelocityResult() +acoustic_directional_velocity_1.Frequency = Quantity("50 [Hz]") +acoustic_directional_velocity_1.CoordinateSystem = lcs1 -ACOUST_DIR_VEL2 = SOLN.AddAcousticDirectionalVelocityResult() -ACOUST_DIR_VEL2.NormalOrientation = NormalOrientationType.ZAxis -ACOUST_DIR_VEL2.By = SetDriverStyle.ResultSet -ACOUST_DIR_VEL2.SetNumber = 25 +# Add the acoustic total velocity result and set its orientation +acoustic_directional_velocity_2 = solution.AddAcousticDirectionalVelocityResult() +acoustic_directional_velocity_2.NormalOrientation = NormalOrientationType.ZAxis +acoustic_directional_velocity_2.By = SetDriverStyle.ResultSet +acoustic_directional_velocity_2.SetNumber = 25 # %% -# Acoustic sound pressure and frequency bands +# Add the acoustic sound pressure levels and frequency band responses + +# Add the acoustic sound pressure level and set its frequency +acoustic_spl = solution.AddAcousticSoundPressureLevel() +acoustic_spl.Frequency = Quantity("50 [Hz]") -ACOUST_SPL = SOLN.AddAcousticSoundPressureLevel() -ACOUST_SPL.Frequency = Quantity("50 [Hz]") +# Add the acoustic A-weighted sound pressure level and set its frequency +acoustic_a_spl = solution.AddAcousticAWeightedSoundPressureLevel() +acoustic_a_spl.Frequency = Quantity("50 [Hz]") -ACOUST_A_SPL = SOLN.AddAcousticAWeightedSoundPressureLevel() -ACOUST_A_SPL.Frequency = Quantity("50 [Hz]") +# Add the acoustic frequency band sound pressure level +acoustic_frq_band_spl = solution.AddAcousticFrequencyBandSPL() -ACOUST_FRQ_BAND_SPL = SOLN.AddAcousticFrequencyBandSPL() +# Add the acoustic A-weighted frequency band sound pressure level +a_freq_band_spl = solution.AddAcousticFrequencyBandAWeightedSPL() -A_FREQ_BAND_SPL = SOLN.AddAcousticFrequencyBandAWeightedSPL() +# Add the acoustic velocity frequency response and set its orientation and location +z_velocity_response = solution.AddAcousticVelocityFrequencyResponse() +z_velocity_response.NormalOrientation = NormalOrientationType.ZAxis +# Set the location to the pressure face named selection +z_velocity_response.Location = pres_face -Z_VELO_RESP = SOLN.AddAcousticVelocityFrequencyResponse() -Z_VELO_RESP.NormalOrientation = NormalOrientationType.ZAxis -Z_VELO_RESP.Location = PRES_Face -Z_VELO_RESP.NormalOrientation = NormalOrientationType.ZAxis +# %% +# Add the acoustic kinetic and potentional energy frequency responses + +# Add the acoustic kinetic energy frequency response and set its location +# to the absorption face named selection +ke_response = solution.AddAcousticKineticEnergyFrequencyResponse() +ke_response.Location = abs_face +ke_display = ke_response.TimeHistoryDisplay + +# Add the acoustic potential energy frequency response and set its location +# to the absorption face named selection +pe_response = solution.AddAcousticPotentialEnergyFrequencyResponse() +pe_response.Location = abs_face +pe_display = pe_response.TimeHistoryDisplay # %% -# Acoustic kinetic and potentional energy frequency response +# Create a function to set the properties of the acoustic velocity result + -KE_RESP = SOLN.AddAcousticKineticEnergyFrequencyResponse() -KE_RESP.Location = ABS_Face -KE_display = KE_RESP.TimeHistoryDisplay +def set_properties( + element, + frequency, + location, + amplitude=True, + normal_orientation=None, +): + """Set the properties of the acoustic velocity result.""" + element.Frequency = frequency + element.Amplitude = amplitude + if normal_orientation: + element.NormalOrientation = normal_orientation + # Set the location to the specified named selection + element.Location = location + return element -PE_RESP = SOLN.AddAcousticPotentialEnergyFrequencyResponse() -PE_RESP.Location = ABS_Face -PE_display = PE_RESP.TimeHistoryDisplay # %% -# Acoustic total and directional velocity +# Add the acoustic total and directional velocity results -ACOUST_TOT_VEL2 = SOLN.AddAcousticTotalVelocityResult() -ACOUST_TOT_VEL2.Location = PRES_Face -ACOUST_TOT_VEL2.Frequency = Quantity("30 [Hz]") -ACOUST_TOT_VEL2.Amplitude = True +acoustic_total_velocity_2 = solution.AddAcousticTotalVelocityResult() +set_properties(acoustic_total_velocity_2, Quantity("30 [Hz]"), pres_face) -ACOUST_DIR_VEL3 = SOLN.AddAcousticDirectionalVelocityResult() -ACOUST_DIR_VEL3.NormalOrientation = NormalOrientationType.ZAxis -ACOUST_DIR_VEL3.Location = PRES_Face -ACOUST_DIR_VEL3.Frequency = Quantity("10 [Hz]") -ACOUST_DIR_VEL3.Amplitude = True +acoustic_directional_velocity_3 = solution.AddAcousticDirectionalVelocityResult() +set_properties( + acoustic_directional_velocity_3, + Quantity("10 [Hz]"), + pres_face, + normal_orientation=NormalOrientationType.ZAxis, +) + +# %% +# Add the acoustic kinetic and potential energy results -ACOUST_KE = SOLN.AddAcousticKineticEnergy() -ACOUST_KE.Location = ABS_Face -ACOUST_KE.Frequency = Quantity("68 [Hz]") -ACOUST_KE.Amplitude = True +acoustic_ke = solution.AddAcousticKineticEnergy() +set_properties(acoustic_ke, Quantity("68 [Hz]"), abs_face) -ACOUST_PE = SOLN.AddAcousticPotentialEnergy() -ACOUST_PE.Location = ABS_Face -ACOUST_PE.Frequency = Quantity("10 [Hz]") -ACOUST_PE.Amplitude = True +acoustic_pe = solution.AddAcousticPotentialEnergy() +set_properties(acoustic_pe, Quantity("10 [Hz]"), abs_face) # %% -# Solve -# ~~~~~ +# Solve the harmonic acoustics analysis solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -SOLN.Solve(True) +solution.Solve(True) # sphinx_gallery_start_ignore -assert str(SOLN.Status) == "Done", "Solution status is not 'Done'" +assert solution.Status == SolutionStatusType.Done, "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ - -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Show messages +# ~~~~~~~~~~~~~ +# Print all messages from Mechanical +app.messages.show() # %% # Postprocessing # ~~~~~~~~~~~~~~ # %% -# Total acoustic pressure -# ^^^^^^^^^^^^^^^^^^^^^^^ +# Display the total acoustic pressure result -Tree.Activate([ACOUST_PRES_RES1]) -Graphics.ExportImage( - os.path.join(cwd, "acou_pressure.png"), image_export_format, settings_720p +app.Tree.Activate([acoustic_pressure_result_1]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "pressure.png" ) -display_image("acou_pressure.png") # %% -# Total acoustic velocity -# ^^^^^^^^^^^^^^^^^^^^^^^ +# Display the total acoustic velocity -Tree.Activate([ACOUST_PRES_RES1]) -Graphics.ExportImage( - os.path.join(cwd, "totalvelocity.png"), image_export_format, settings_720p +app.Tree.Activate([acoustic_pressure_result_1]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_velocity.png" ) -display_image("totalvelocity.png") # %% -# Sound pressure level -# ^^^^^^^^^^^^^^^^^^^^ +# Display the acoustic sound pressure level -Tree.Activate([ACOUST_SPL]) -Graphics.ExportImage( - os.path.join(cwd, "sound_pressure.png"), image_export_format, settings_720p +app.Tree.Activate([acoustic_spl]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "sound_pressure_level.png" ) -display_image("sound_pressure.png") # %% -# Total velocity on pressure surface -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Display the acoustic directional velocity -Tree.Activate([ACOUST_TOT_VEL2]) -Graphics.ExportImage( - os.path.join(cwd, "totalvelocity_pressure.png"), image_export_format, settings_720p +app.Tree.Activate([acoustic_directional_velocity_3]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "directional_velocity.png" ) -display_image("totalvelocity_pressure.png") # %% -# Kinetic energy on absorption face -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Display the acoustic kinetic energy -Tree.Activate([ACOUST_KE]) -Graphics.ExportImage( - os.path.join(cwd, "kineticenergy.png"), image_export_format, settings_720p +app.Tree.Activate([acoustic_ke]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "kinetic_energy.png" ) -display_image("kineticenergy.png") # %% -# Total acoustic pressure animation -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Create a function to update the animation frames + +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. + + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] + + +# %% +# Display the total acoustic pressure animation + +# Set the animation export format to GIF animation_export_format = ( Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF ) + +# Configure the export settings for the animation settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() settings_720p.Width = 1280 settings_720p.Height = 720 -ACOUST_PRES_RES1.ExportAnimation( - os.path.join(cwd, "press.gif"), animation_export_format, settings_720p +# Export the animation of the acoustic pressure result +press_gif = output_path / "press.gif" +acoustic_pressure_result_1.ExportAnimation( + str(press_gif), animation_export_format, settings_720p ) -gif = Image.open(os.path.join(cwd, "press.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) - - -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] +# Open the GIF file and create an animation +gif = Image.open(press_gif) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=200, repeat=True, blit=True + figure, + update_animation, + frames=range(gif.n_frames), + interval=200, + repeat=True, + blit=True, ) + +# Show the animation plt.show() # %% -# Display output file from solve -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Display the output file from the solve +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get the working directory of the solve +solve_path = harmonic_acoustics.WorkingDir +solve_out_path = Path(solve_path) / "solve.out" -def write_file_contents_to_console(path): - """Write file contents to console.""" - with open(path, "rt") as file: +# Check if the solve output file exists and write its contents to the console +if solve_out_path: + with solve_out_path.open("rt") as file: for line in file: print(line, end="") - -solve_path = HARM_ACOUST.WorkingDir -solve_out_path = os.path.join(solve_path, "solve.out") -if solve_out_path: - write_file_contents_to_console(solve_out_path) - # %% -# Project tree -# ~~~~~~~~~~~~ +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ + +# Save the project +mechdat_file = output_path / "harmonic_acoustics.mechdat" +app.save(str(mechdat_file)) -app.save(os.path.join(cwd, "harmnonic_acoustics.mechdat")) -app.new() +# Close the app +app.close() -# delete example file +# Delete the example file delete_downloads() diff --git a/examples/01_basic/modal_acoustics_analysis.py b/examples/01_basic/modal_acoustics_analysis.py index b84354ba..c29ddee9 100644 --- a/examples/01_basic/modal_acoustics_analysis.py +++ b/examples/01_basic/modal_acoustics_analysis.py @@ -32,16 +32,17 @@ noise reduction in various settings, audio speaker design, and geophysical exploration. Mechanical enables you to model pure acoustic problems and fluid-structure -interaction (FSI) problems.A coupled acoustic analysis accounts for FSI. +interaction (FSI) problems. A coupled acoustic analysis accounts for FSI. An uncoupled acoustic analysis simulates the fluid only and ignores any fluid-structure interaction. """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +from typing import TYPE_CHECKING from PIL import Image from ansys.mechanical.core import App @@ -50,30 +51,109 @@ from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation +if TYPE_CHECKING: + import Ansys + # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, + set_fit: bool = False, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + set_fit: bool, Optional + If True, set the camera to fit the mesh. + If False, do not set the camera to fit the mesh. + """ + if set_fit: + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure plt.show() # %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Configure the graphics for image export +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +graphics = app.Graphics +camera = graphics.Camera -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +# Set the camera orientation to isometric view +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) + +# Set the image export format and settings image_export_format = GraphicsImageExportFormat.PNG settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution @@ -83,478 +163,556 @@ def display_image(image_name): settings_720p.CurrentGraphicsDisplay = False # %% -# Download geometry and materials files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the geometry and material files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the geometry file from the ansys/example-data repository geometry_path = download_file("sloshing_geometry.agdb", "pymechanical", "embedding") +# Download the water material file from the ansys/example-data repository mat_path = download_file("Water_material_explicit.xml", "pymechanical", "embedding") - # %% -# Import the geometry -# ~~~~~~~~~~~~~~~~~~~ +# Import and display the geometry +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Define the model +model = app.Model -geometry_import_group = Model.GeometryImportGroup +# Add the geometry import group and set its preferences +geometry_import_group = model.GeometryImportGroup geometry_import = geometry_import_group.AddGeometryImport() geometry_import_format = ( Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic ) geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() geometry_import_preferences.ProcessNamedSelections = True + +# Import the geometry file with the specified format and preferences geometry_import.Import( geometry_path, geometry_import_format, geometry_import_preferences ) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "geometry.png"), image_export_format, settings_720p +# Set the camera to fit the model and display the image +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "geometry.png", set_fit=True ) -display_image("geometry.png") # %% # Store all variables necessary for analysis # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -GEOM = Model.Geometry -MESH = Model.Mesh -NS = Model.NamedSelections -CONN = Model.Connections -MAT = Model.Materials +geometry = model.Geometry +mesh = model.Mesh +named_selections = model.NamedSelections +connections = model.Connections +materials = model.Materials # %% -# Import material setup analysis -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add modal acoustic analysis and import the material +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Model.AddModalAcousticAnalysis() -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS -MAT.Import(mat_path) -print("Material Import Done !") - -# %% -# Get all required named selections and assign materials -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -acst_bodies = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Acoustic_bodies" -][0] -struct_bodies = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Structural_bodies" -][0] -top_bodies = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "top_bodies" -][0] -cont_bodies = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "container_bodies" -][0] -cont_V1 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_V1" -][0] -cont_V2 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_V2" -][0] -cont_V3 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_V3" -][0] -cont_face1 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_face1" -][0] -cont_face2 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_face2" -][0] -cont_face3 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_face3" -][0] -cont_face4 = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cont_face4" -][0] -free_faces = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Free_faces" -][0] -fsi_faces = [ - i - for i in NS.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "FSI_faces" -][0] - -solid1 = [ - i - for i in GEOM.GetChildren[Ansys.ACT.Automation.Mechanical.Body](True) - if i.Name == "Solid1" -][0] -solid2 = [ - i - for i in GEOM.GetChildren[Ansys.ACT.Automation.Mechanical.Body](True) - if i.Name == "Solid2" -][0] -solid3 = [ - i - for i in GEOM.GetChildren[Ansys.ACT.Automation.Mechanical.Body](True) - if i.Name == "Solid3" -][0] -solid4 = [ - i - for i in GEOM.GetChildren[Ansys.ACT.Automation.Mechanical.Body](True) - if i.Name == "Solid4" -][0] - - -# %% -# Assign material water to acoustic parts - -solid1.Material = "WATER" -solid2.Material = "WATER" -solid3.Material = "WATER" -solid4.Material = "WATER" - -# %% -# Mesh -# ~~~~ - -MESH.ElementOrder = ElementOrder.Quadratic - -method1 = MESH.AddAutomaticMethod() -method1.Location = acst_bodies -method1.Method = MethodType.AllTriAllTet - -method2 = MESH.AddAutomaticMethod() -method2.Location = top_bodies -method2.Method = MethodType.Automatic +# Add a modal acoustic analysis to the model +model.AddModalAcousticAnalysis() +# Set the unit system to Standard MKS +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS +# Import the water material from the specified XML file +materials.Import(mat_path) -# Add mesh sizing +# %% +# Assign material to solid bodies +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -sizing1 = MESH.AddSizing() -sizing1.Location = top_bodies -sizing1.ElementSize = Quantity("0.2 [m]") -sizing1.Behavior = SizingBehavior.Hard -# Add mesh sizing +def get_solid_set_material(name: str) -> None: + """Get the solid body by name and assign the material. -sizing2 = MESH.AddSizing() -sizing2.Location = acst_bodies -sizing2.ElementSize = Quantity("0.2 [m]") -sizing2.Behavior = SizingBehavior.Hard + Parameters + ---------- + name : str + The name of the solid body to get. + """ + # Get the solid body by name + solid = [ + i + for i in geometry.GetChildren[Ansys.ACT.Automation.Mechanical.Body](True) + if i.Name == name + ][0] -# Add mesh method + # Assign material water to acoustic parts + solid.Material = "WATER" -method3 = MESH.AddAutomaticMethod() -method3.Location = cont_bodies -method3.Method = MethodType.Sweep -method3.SourceTargetSelection = 4 -MESH.GenerateMesh() +# Assign material water to acoustic parts for solids 1 to 4 +for i in range(1, 5): + solid_name = f"Solid{i}" + get_solid_set_material(solid_name) -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") # %% -# Insert contacts -# ~~~~~~~~~~~~~~~ -# Contact 1 -CONN_GROUP = CONN.AddConnectionGroup() -CONT1 = CONN_GROUP.AddContactRegion() -CONT1.SourceLocation = cont_V1 -CONT1.TargetLocation = cont_face1 -CONT1.ContactFormulation = ContactFormulation.MPC -CONT1.Behavior = ContactBehavior.Asymmetric -CONT1.PinballRegion = ContactPinballType.Radius -CONT1.PinballRadius = Quantity("0.25 [m]") +# Add mesh methods and sizings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # %% -# Contact 2 +# Create a function to get the named selection by name -CONT2 = CONN_GROUP.AddContactRegion() -CONT2.SourceLocation = cont_V2 -CONT2.TargetLocation = cont_face2 -CONT2.ContactFormulation = ContactFormulation.MPC -CONT2.Behavior = ContactBehavior.Asymmetric -CONT2.PinballRegion = ContactPinballType.Radius -CONT2.PinballRadius = Quantity("0.25 [m]") -# %% -# Contact 3 +def get_named_selection(name: str) -> Ansys.ACT.Automation.Mechanical.NamedSelection: + """Get the named selection by name. + + Parameters + ---------- + name : str + The name of the named selection to get. + + Returns + ------- + Ansys.ACT.Automation.Mechanical.NamedSelection + The named selection object. + """ + return [ + child + for child in named_selections.GetChildren[ + Ansys.ACT.Automation.Mechanical.NamedSelection + ](True) + if child.Name == name + ][0] -CONT3 = CONN_GROUP.AddContactRegion() -CONT3.SourceLocation = cont_V3 -CONT3.TargetLocation = cont_face3 -CONT3.ContactFormulation = ContactFormulation.MPC -CONT3.Behavior = ContactBehavior.Asymmetric -CONT3.PinballRegion = ContactPinballType.Radius -CONT3.PinballRadius = Quantity("0.25 [m]") # %% -# Contact 3 +# Create a function to set the mesh properties + + +def set_mesh_properties( + mesh_element, + named_selection, + method_type=None, + element_size=None, + behavior=None, +): + """Set the properties for mesh automatic methods and sizings. + + Parameters + ---------- + mesh_element + The mesh element to set properties for. + named_selection : Ansys.ACT.Automation.Mechanical.NamedSelection + The named selection to set the location for the mesh element. + method_type : MethodType, optional + The method type for the mesh (default is None). + element_size : Quantity, optional + The element size for the mesh (default is None). + behavior : SizingBehavior, optional + The sizing behavior for the mesh (default is None). + """ + mesh_element.Location = named_selection + if method_type: + mesh_element.Method = method_type + if element_size: + mesh_element.ElementSize = element_size + if behavior: + mesh_element.Behavior = behavior -sel_manager = ExtAPI.SelectionManager -cnv4 = DataModel.GeoData.Assemblies[0].Parts[1].Bodies[0].Vertices[3] -cont_V4 = sel_manager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) -cont_V4.Entities = [cnv4] # %% -# Contact 4 +# Add automatic mesh methods + +mesh.ElementOrder = ElementOrder.Quadratic -CONT4 = CONN_GROUP.AddContactRegion() -CONT4.TargetLocation = cont_face4 -CONT4.SourceLocation = cont_V4 -CONT4.ContactFormulation = ContactFormulation.MPC -CONT4.Behavior = ContactBehavior.Asymmetric -CONT4.PinballRegion = ContactPinballType.Radius -CONT4.PinballRadius = Quantity("0.25 [m]") +method1 = mesh.AddAutomaticMethod() +acst_bodies = get_named_selection("Acoustic_bodies") +# Set the automatic method location to the acoustic bodies and the method type to AllTriAllTet +set_mesh_properties(method1, acst_bodies, MethodType.AllTriAllTet) + +# Add an automatic mesh method +method2 = mesh.AddAutomaticMethod() +top_bodies = get_named_selection("top_bodies") +# Set the automatic method location to the top bodies and the method type to Automatic +set_mesh_properties(method2, top_bodies, MethodType.Automatic) # %% -# Fully define Modal Multiphysics region with two physics regions +# Add mesh sizing + +sizing1 = mesh.AddSizing() +# Set the mesh sizing location to the top bodies, the element size to 0.2m, and +# the sizing behavior to hard +set_mesh_properties( + sizing1, top_bodies, element_size=Quantity("0.2 [m]"), behavior=SizingBehavior.Hard +) -MODAL_ACST = DataModel.Project.Model.Analyses[0] -ACOUST_REG = MODAL_ACST.Children[2] -ACOUST_REG.Location = acst_bodies +sizing2 = mesh.AddSizing() +# Set the mesh sizing location to the acoustic bodies, the element size to 0.2m, and +# the sizing behavior to hard +set_mesh_properties( + sizing2, acst_bodies, element_size=Quantity("0.2 [m]"), behavior=SizingBehavior.Hard +) +# %% +# Add a mesh method for the container bodies -STRUCT_REG = MODAL_ACST.AddPhysicsRegion() -STRUCT_REG.Structural = True -STRUCT_REG.RenameBasedOnDefinition() -STRUCT_REG.Location = struct_bodies +# Add an automatic mesh method +method3 = mesh.AddAutomaticMethod() +container_bodies = get_named_selection("container_bodies") +# Set the automatic method location to the container bodies and the method type to Sweep +set_mesh_properties(method3, container_bodies, MethodType.Sweep) +# Set the source target selection to 4 +method3.SourceTargetSelection = 4 +# %% +# Generate the mesh and display the image + +mesh.GenerateMesh() +set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") + +# %% +# Set up the contact regions in the connection group +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Create a function to set the contact region properties + + +def set_contact_region_properties( + contact_region, + source_location, + target_location, + contact_formulation=ContactFormulation.MPC, + contact_behavior=ContactBehavior.Asymmetric, + pinball_region=ContactPinballType.Radius, + pinball_radius=Quantity("0.25 [m]"), + set_target_before_src=False, +): + """Set the properties for the contact region. + + Parameters + ---------- + contact_region : Ansys.ACT.Automation.Mechanical.ContactRegion + The contact region to set properties for. + source_location : Ansys.ACT.Automation.Mechanical.NamedSelection + The source location for the contact region. + target_location : Ansys.ACT.Automation.Mechanical.NamedSelection + The target location for the contact region. + contact_formulation : ContactFormulation, optional + The contact formulation for the contact region (default is MPC). + contact_behavior : ContactBehavior, optional + The contact behavior for the contact region (default is Asymmetric). + pinball_region : ContactPinballType, optional + The pinball region type for the contact region (default is Radius). + pinball_radius : Quantity, optional + The pinball radius for the contact region (default is 0.25 m). + set_target_before_src : bool, optional + Whether to set the target location before the source location (default is False). + """ + # If the target location is set before the source location + if set_target_before_src: + contact_region.TargetLocation = get_named_selection(target_location) + contact_region.SourceLocation = source_location + else: + contact_region.SourceLocation = get_named_selection(source_location) + contact_region.TargetLocation = get_named_selection(target_location) + contact_region.ContactFormulation = contact_formulation + contact_region.Behavior = contact_behavior + contact_region.PinballRegion = pinball_region + contact_region.PinballRadius = pinball_radius + + +# %% +# Add contact regions and set their properties + +# Add a connection group to the model +connection_group = connections.AddConnectionGroup() + +# Add the first contact region to the connection group +contact_region_1 = connection_group.AddContactRegion() +# Set the source location to the Cont_V1 named selection and the target location to the Cont_face1 +# named selection +set_contact_region_properties(contact_region_1, "Cont_V1", "Cont_face1") + +# Add the second contact region to the connection group +contact_region_2 = connection_group.AddContactRegion() +# Set the source location to the Cont_V2 named selection and the target location to the Cont_face2 +# named selection +set_contact_region_properties(contact_region_2, "Cont_V2", "Cont_face2") + +# Add the third contact region to the connection group +contact_region_3 = connection_group.AddContactRegion() +# Set the source location to the Cont_V3 named selection and the target location to the Cont_face3 +# named selection +set_contact_region_properties(contact_region_3, "Cont_V3", "Cont_face3") + +# Set the selection manager +sel_manager = app.ExtAPI.SelectionManager +# Get the contact vertex from the geometry +contact_vertex = DataModel.GeoData.Assemblies[0].Parts[1].Bodies[0].Vertices[3] +# Create a selection info object for the contact vertex +contact_vertex_4 = sel_manager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +# Set the contact vertex as the selected entity +contact_vertex_4.Entities = [contact_vertex] + +# Add the fourth contact region to the connection group +contact_region_4 = connection_group.AddContactRegion() +# Set the target location to the contact vertex and the source location to the Cont_face4 +# named selection +set_contact_region_properties( + contact_region_4, contact_vertex_4, "Cont_face4", set_target_before_src=True +) # %% -# Analysis settings -# ~~~~~~~~~~~~~~~~~ +# Fully define the modal multiphysics region with two physics regions + +# Get the modal acoustic analysis object +modal_acst = DataModel.Project.Model.Analyses[0] +# Get the acoustic region from the modal acoustic analysis +acoustic_region = modal_acst.Children[2] +# Set the acoustic region to the acoustic bodies named selection +acoustic_region.Location = acst_bodies -ANALYSIS_SETTINGS = MODAL_ACST.Children[1] -ANALYSIS_SETTINGS.MaximumModesToFind = 12 -ANALYSIS_SETTINGS.SearchRangeMinimum = Quantity("0.1 [Hz]") -ANALYSIS_SETTINGS.SolverType = SolverType.Unsymmetric -ANALYSIS_SETTINGS.GeneralMiscellaneous = True -ANALYSIS_SETTINGS.CalculateReactions = True +# Add a physics region to the modal acoustic analysis +structural_region = modal_acst.AddPhysicsRegion() +# Set the physics region to structural and rename it based on the definition +structural_region.Structural = True +structural_region.RenameBasedOnDefinition() +# Set the structural region to the structural bodies named selection +structural_region.Location = get_named_selection("Structural_bodies") # %% -# Boundary conditions and load +# Set up the analysis settings # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Free surface -FREE_SF = MODAL_ACST.AddAcousticFreeSurface() -FREE_SF.Location = free_faces +# Get the analysis settings from the modal acoustic analysis +analysis_settings = modal_acst.Children[1] +# Set the analysis settings properties +analysis_settings.MaximumModesToFind = 12 +analysis_settings.SearchRangeMinimum = Quantity("0.1 [Hz]") +analysis_settings.SolverType = SolverType.Unsymmetric +analysis_settings.GeneralMiscellaneous = True +analysis_settings.CalculateReactions = True # %% -# Solid fluid interface +# Set the boundary conditions and load +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -FSI_OBJ = MODAL_ACST.AddFluidSolidInterface() -FSI_OBJ.Location = fsi_faces +# Add an acoustic free surface to the modal acoustic analysis +free_surface = modal_acst.AddAcousticFreeSurface() +# Set the free surface location to the free faces named selection +free_surface.Location = get_named_selection("Free_faces") # %% -# Gravity +# Add the solid fluid interface -ACCELERATION = MODAL_ACST.AddAcceleration() -ACCELERATION.DefineBy = LoadDefineBy.Components -ACCELERATION.YComponent.Output.DiscreteValues = [Quantity("9.81 [m sec^-1 sec^-1]")] +# Add a fluid solid interface to the modal acoustic analysis +fsi_object = modal_acst.AddFluidSolidInterface() +# Set the fluid solid interface location to the FSI faces named selection +fsi_object.Location = get_named_selection("FSI_faces") # %% -# Fixed Support +# Add the gravity load -fv1 = DataModel.GeoData.Assemblies[0].Parts[1].Bodies[0].Vertices[0] -fv2 = DataModel.GeoData.Assemblies[0].Parts[1].Bodies[1].Vertices[0] -fv3 = DataModel.GeoData.Assemblies[0].Parts[1].Bodies[2].Vertices[0] -fv4 = DataModel.GeoData.Assemblies[0].Parts[1].Bodies[3].Vertices[0] +# Add gravity to the modal acoustic analysis +acceleration = modal_acst.AddAcceleration() +# Set the components and the Y-component of the acceleration to +# 9.81 m/s^2 (gravitational acceleration) +acceleration.DefineBy = LoadDefineBy.Components +acceleration.YComponent.Output.DiscreteValues = [Quantity("9.81 [m sec^-1 sec^-1]")] -fvert = sel_manager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) -fvert.Entities = [fv1, fv2, fv3, fv4] -FIXED_SUPPORT = MODAL_ACST.AddFixedSupport() -FIXED_SUPPORT.Location = fvert +# %% +# Add fixed support -MODAL_ACST.Activate() -Graphics.ExportImage( - os.path.join(cwd, "geometry.png"), image_export_format, settings_720p +# Get vertices from the geometry +vertices = [] +for body in range(0, 4): + vertices.append(DataModel.GeoData.Assemblies[0].Parts[1].Bodies[body].Vertices[0]) + +# Create a selection info object for the geometry entities +fvert = sel_manager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +# Set the list of vertices as the geometry entities +fvert.Entities = vertices + +# Add a fixed support to the modal acoustic analysis +fixed_support = modal_acst.AddFixedSupport() +# Set the location of the fixed support to the geometry entities +fixed_support.Location = fvert + +# Activate the modal acoustic analysis and display the image +modal_acst.Activate() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "geometry.png" ) -display_image("geometry.png") # %% -# Add results -# ~~~~~~~~~~~ -# Add 10 modes +# Add results to the solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -soln = Model.Analyses[0].Solution -TOT_DEF1 = soln.AddTotalDeformation() -TOT_DEF2 = soln.AddTotalDeformation() -TOT_DEF2.Mode = 2 -TOT_DEF3 = soln.AddTotalDeformation() -TOT_DEF3.Mode = 3 -TOT_DEF4 = soln.AddTotalDeformation() -TOT_DEF4.Mode = 4 -TOT_DEF5 = soln.AddTotalDeformation() -TOT_DEF5.Mode = 5 -TOT_DEF6 = soln.AddTotalDeformation() -TOT_DEF6.Mode = 6 -TOT_DEF7 = soln.AddTotalDeformation() -TOT_DEF7.Mode = 7 -TOT_DEF8 = soln.AddTotalDeformation() -TOT_DEF8.Mode = 8 -TOT_DEF9 = soln.AddTotalDeformation() -TOT_DEF9.Mode = 9 -TOT_DEF10 = soln.AddTotalDeformation() -TOT_DEF10.Mode = 10 +# Get the solution object from the modal acoustic analysis +solution = model.Analyses[0].Solution + +# Add 10 total deformation results, setting the mode for every result except the first +total_deformation_results = [] +for mode in range(1, 11): + total_deformation = solution.AddTotalDeformation() + if mode > 1: + total_deformation.Mode = mode + total_deformation_results.append(total_deformation) # %% -# Add acoustic pressure +# Add the acoustic pressure result to the solution -ACOUST_PRES_RES = soln.AddAcousticPressureResult() +acoustic_pressure_result = solution.AddAcousticPressureResult() # %% -# Add force reaction scoped to fixed Support +# Scope the force reaction to the fixed support -FORCE_REACT1 = soln.AddForceReaction() -FORCE_REACT1.BoundaryConditionSelection = FIXED_SUPPORT +# Add a force reaction to the solution +force_reaction_1 = solution.AddForceReaction() +# Set the boundary condition selection to the fixed support +force_reaction_1.BoundaryConditionSelection = fixed_support # %% -# Solve -# ~~~~~ +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ -soln.Solve(True) +solution.Solve(True) # sphinx_gallery_start_ignore -assert str(soln.Status) == "Done", "Solution status is not 'Done'" +assert solution.Status == SolutionStatusType.Done, "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ +# Show messages +# ~~~~~~~~~~~~~ -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Print all messages from Mechanical +app.messages.show() +# %% +# Display the results +# ~~~~~~~~~~~~~~~~~~~ # %% -# Results -# ~~~~~~~ -# Total deformation - mode 1 +# Activate the first total deformation result and display the image -Tree.Activate([TOT_DEF1]) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "totaldeformation1.png"), image_export_format, settings_720p +app.Tree.Activate([total_deformation_results[0]]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_deformation.png", set_fit=True ) -display_image("totaldeformation1.png") - # %% -# Acoustic pressure +# Activate the acoustic pressure result and display the image -Tree.Activate([ACOUST_PRES_RES]) -Graphics.ExportImage( - os.path.join(cwd, "acoustic_pressure.png"), image_export_format, settings_720p +app.Tree.Activate([acoustic_pressure_result]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "acoustic_pressure.png" ) -display_image("acoustic_pressure.png") +# %% +# Display all modal frequency, force reaction, and acoustic pressure values + +print("Modal Acoustic Results") +print("----------------------") + +# Print the frequency values for each mode +for index, result in enumerate(total_deformation_results, start=1): + frequency_value = result.ReportedFrequency.Value + print(f"Frequency for mode {index}: ", frequency_value) + +# Get the maximum and minimum values of the acoustic pressure result +pressure_result_max = acoustic_pressure_result.Maximum.Value +pressure_result_min = acoustic_pressure_result.Minimum.Value + +# Get the force reaction values for the x and z axes +force_reaction_1_x = force_reaction_1.XAxis.Value +force_reaction_1_z = force_reaction_1.ZAxis.Value + +# Print the results +print("Acoustic pressure minimum : ", pressure_result_min) +print("Acoustic pressure Maximum : ", pressure_result_max) +print("Force reaction x-axis : ", force_reaction_1_x) +print("Force reaction z-axis : ", force_reaction_1_z) # %% -# Display all modal frequency, force reaction -# and acoustic pressure values +# Create a function to update the animation frames -FREQ1 = TOT_DEF1.ReportedFrequency.Value -FREQ2 = TOT_DEF2.ReportedFrequency.Value -FREQ3 = TOT_DEF3.ReportedFrequency.Value -FREQ4 = TOT_DEF4.ReportedFrequency.Value -FREQ5 = TOT_DEF5.ReportedFrequency.Value -FREQ6 = TOT_DEF6.ReportedFrequency.Value -FREQ7 = TOT_DEF7.ReportedFrequency.Value -FREQ8 = TOT_DEF8.ReportedFrequency.Value -FREQ9 = TOT_DEF9.ReportedFrequency.Value -FREQ10 = TOT_DEF10.ReportedFrequency.Value -PRMAX = ACOUST_PRES_RES.Maximum.Value -PRMIN = ACOUST_PRES_RES.Minimum.Value +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. -FRC1_X = FORCE_REACT1.XAxis.Value -FRC1_Z = FORCE_REACT1.ZAxis.Value + Parameters + ---------- + frame : int + The frame number to update the animation. -print("Modal Acoustic Results") -print("----------------------") -print("Frequency for mode 1 : ", FREQ1) -print("Frequency for mode 2 : ", FREQ2) -print("Frequency for mode 3 : ", FREQ3) -print("Frequency for mode 4 : ", FREQ4) -print("Frequency for mode 5 : ", FREQ5) -print("Frequency for mode 6 : ", FREQ6) -print("Frequency for mode 7 : ", FREQ7) -print("Frequency for mode 8 : ", FREQ8) -print("Frequency for mode 9 : ", FREQ9) -print("Frequency for mode 10 : ", FREQ10) -print("Acoustic pressure minimum : ", PRMIN) -print("Acoustic pressure Maximum : ", PRMAX) -print("Force reaction x-axis : ", FRC1_X) -print("Force reaction z-axis : ", FRC1_Z) - -# %% -# Total deformation animation for mode 10 + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] + + +# %% +# Play the total deformation animation +# Set the animation export format to GIF animation_export_format = ( Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF ) + +# Set the export settings for the animation settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() settings_720p.Width = 1280 settings_720p.Height = 720 -TOT_DEF10.ExportAnimation( - os.path.join(cwd, "deformation_10.gif"), animation_export_format, settings_720p +# Export the total deformation animation for the last result +deformation_gif = ( + output_path / f"total_deformation_{len(total_deformation_results)}.gif" +) +total_deformation_results[-1].ExportAnimation( + str(deformation_gif), animation_export_format, settings_720p ) -gif = Image.open(os.path.join(cwd, "deformation_10.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) - - -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] - -ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=100, repeat=True, blit=True +# Open the GIF file and create an animation +gif = Image.open(deformation_gif) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) + +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation +FuncAnimation( + figure, + update_animation, + frames=range(gif.n_frames), + interval=100, + repeat=True, + blit=True, ) + +# Show the animation plt.show() # %% -# Project tree -# ~~~~~~~~~~~~ +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "modal_acoustics.mechdat")) -app.new() +# Save the project file +mechdat_file = output_path / "modal_acoustics.mechdat" +app.save(str(mechdat_file)) -# %% -# Delete example file +# Close the app +app.close() +# Delete the example files delete_downloads() diff --git a/examples/01_basic/steady_state_thermal_analysis.py b/examples/01_basic/steady_state_thermal_analysis.py index 4e782394..3d292a8d 100644 --- a/examples/01_basic/steady_state_thermal_analysis.py +++ b/examples/01_basic/steady_state_thermal_analysis.py @@ -34,11 +34,11 @@ """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ - +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +from typing import TYPE_CHECKING from PIL import Image from ansys.mechanical.core import App @@ -47,22 +47,90 @@ from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation +if TYPE_CHECKING: + import Ansys + # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure plt.show() @@ -70,8 +138,14 @@ def display_image(image_name): # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) -Graphics.Camera.SetFit() +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to isometric view +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +camera.SetFit() + +# Set the image export format and settings image_export_format = GraphicsImageExportFormat.PNG settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution @@ -80,351 +154,536 @@ def display_image(image_name): settings_720p.Height = 720 settings_720p.CurrentGraphicsDisplay = False - # %% -# Download and import geometry -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download the geometry file. +# Download the geometry file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the geometry file from the ansys/example-data repository geometry_path = download_file("LONGBAR.x_t", "pymechanical", "embedding") # %% # Import the geometry +# ~~~~~~~~~~~~~~~~~~~ + +# Define the model +model = app.Model -geometry_import_group = Model.GeometryImportGroup +# Add the geometry import group and set its preferences +geometry_import_group = model.GeometryImportGroup geometry_import = geometry_import_group.AddGeometryImport() geometry_import_format = ( Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic ) geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() geometry_import_preferences.ProcessNamedSelections = True + +# Import the geometry file with the specified format and preferences geometry_import.Import( geometry_path, geometry_import_format, geometry_import_preferences ) +# Visualize the model in 3D app.plot() - # %% # Add steady state thermal analysis # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Model.AddSteadyStateThermalAnalysis() -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS -STAT_THERM = Model.Analyses[0] -MODEL = Model -CS = MODEL.CoordinateSystems -LCS1 = CS.AddCoordinateSystem() -LCS1.OriginX = Quantity("0 [m]") +# Add a steady state thermal analysis to the model +model.AddSteadyStateThermalAnalysis() +# Set the Mechanical unit system to Standard MKS +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS + +# Get the steady state thermal analysis +stat_therm = model.Analyses[0] + +# Add a coordinate system to the model +coordinate_systems = model.CoordinateSystems -LCS2 = CS.AddCoordinateSystem() -LCS2.OriginX = Quantity("0 [m]") -LCS2.PrimaryAxisDefineBy = CoordinateSystemAlignmentType.GlobalY +# Add two coordinate systems +lcs1 = coordinate_systems.AddCoordinateSystem() +lcs1.OriginX = Quantity("0 [m]") + +lcs2 = coordinate_systems.AddCoordinateSystem() +lcs2.OriginX = Quantity("0 [m]") +lcs2.PrimaryAxisDefineBy = CoordinateSystemAlignmentType.GlobalY # %% # Create named selections and construction geometry # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create named selections - -FACE1 = Model.AddNamedSelection() -FACE1.ScopingMethod = GeometryDefineByType.Worksheet -FACE1.Name = "Face1" -GEN_CRT1 = FACE1.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoFace -CRT1.Criterion = SelectionCriterionType.LocationZ -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = Quantity("20 [m]") -GEN_CRT1.Add(CRT1) -FACE1.Activate() -FACE1.Generate() - -FACE2 = Model.AddNamedSelection() -FACE2.ScopingMethod = GeometryDefineByType.Worksheet -FACE2.Name = "Face2" -GEN_CRT2 = FACE2.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoFace -CRT1.Criterion = SelectionCriterionType.LocationZ -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = Quantity("0 [m]") -GEN_CRT2.Add(CRT1) -FACE2.Activate() -FACE2.Generate() - -FACE3 = Model.AddNamedSelection() -FACE3.ScopingMethod = GeometryDefineByType.Worksheet -FACE3.Name = "Face3" -GEN_CRT3 = FACE3.GenerationCriteria -CRT1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT1.Active = True -CRT1.Action = SelectionActionType.Add -CRT1.EntityType = SelectionType.GeoFace -CRT1.Criterion = SelectionCriterionType.LocationX -CRT1.Operator = SelectionOperatorType.Equal -CRT1.Value = Quantity("1 [m]") -GEN_CRT3.Add(CRT1) -CRT2 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT2.Active = True -CRT2.Action = SelectionActionType.Filter -CRT2.EntityType = SelectionType.GeoFace -CRT2.Criterion = SelectionCriterionType.LocationY -CRT2.Operator = SelectionOperatorType.Equal -CRT2.Value = Quantity("2 [m]") -GEN_CRT3.Add(CRT2) -CRT3 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT3.Active = True -CRT3.Action = SelectionActionType.Filter -CRT3.EntityType = SelectionType.GeoFace -CRT3.Criterion = SelectionCriterionType.LocationZ -CRT3.Operator = SelectionOperatorType.Equal -CRT3.Value = Quantity("12 [m]") -GEN_CRT3.Add(CRT3) -CRT4 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT4.Active = True -CRT4.Action = SelectionActionType.Add -CRT4.EntityType = SelectionType.GeoFace -CRT4.Criterion = SelectionCriterionType.LocationZ -CRT4.Operator = SelectionOperatorType.Equal -CRT4.Value = Quantity("4.5 [m]") -GEN_CRT3.Add(CRT4) -CRT5 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() -CRT5.Active = True -CRT5.Action = SelectionActionType.Filter -CRT5.EntityType = SelectionType.GeoFace -CRT5.Criterion = SelectionCriterionType.LocationY -CRT5.Operator = SelectionOperatorType.Equal -CRT5.Value = Quantity("2 [m]") -GEN_CRT3.Add(CRT5) -FACE3.Activate() -FACE3.Generate() - -BODY1 = Model.AddNamedSelection() -BODY1.ScopingMethod = GeometryDefineByType.Worksheet -BODY1.Name = "Body1" -BODY1.GenerationCriteria.Add(None) -BODY1.GenerationCriteria[0].EntityType = SelectionType.GeoFace -BODY1.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationZ -BODY1.GenerationCriteria[0].Operator = SelectionOperatorType.Equal -BODY1.GenerationCriteria[0].Value = Quantity("1 [m]") -BODY1.GenerationCriteria.Add(None) -BODY1.GenerationCriteria[1].EntityType = SelectionType.GeoFace -BODY1.GenerationCriteria[1].Criterion = SelectionCriterionType.LocationZ -BODY1.GenerationCriteria[1].Operator = SelectionOperatorType.Equal -BODY1.GenerationCriteria[1].Value = Quantity("1 [m]") -BODY1.Generate() + +# %% +# Create a function to add a named selection + + +def setup_named_selection(name, scoping_method=GeometryDefineByType.Worksheet): + """Create a named selection with the specified scoping method and name. + + Parameters + ---------- + name : str + The name of the named selection. + scoping_method : GeometryDefineByType + The scoping method for the named selection. + + Returns + ------- + Ansys.ACT.Automation.Mechanical.NamedSelection + The created named selection. + """ + ns = model.AddNamedSelection() + ns.ScopingMethod = scoping_method + ns.Name = name + return ns + + +# %% +# Create a function to add generation criteria to the named selection + + +def add_generation_criteria( + named_selection, + value, + set_active_action_criteria=True, + active=True, + action=SelectionActionType.Add, + entity_type=SelectionType.GeoFace, + criterion=SelectionCriterionType.Size, + operator=SelectionOperatorType.Equal, +): + """Add generation criteria to the named selection. + + Parameters + ---------- + named_selection : Ansys.ACT.Automation.Mechanical.NamedSelection + The named selection to which the criteria will be added. + value : Quantity + The value for the criteria. + active : bool + Whether the criteria is active. + action : SelectionActionType + The action type for the criteria. + entity_type : SelectionType + The entity type for the criteria. + criterion : SelectionCriterionType + The criterion type for the criteria. + operator : SelectionOperatorType + The operator for the criteria. + """ + generation_criteria = named_selection.GenerationCriteria + criteria = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() + + set_criteria_properties( + criteria, + value, + set_active_action_criteria, + active, + action, + entity_type, + criterion, + operator, + ) + + if set_active_action_criteria: + generation_criteria.Add(criteria) + + +# %% +# Create a function to set the properties of the generation criteria + + +def set_criteria_properties( + criteria, + value, + set_active_action_criteria=True, + active=True, + action=SelectionActionType.Add, + entity_type=SelectionType.GeoFace, + criterion=SelectionCriterionType.Size, + operator=SelectionOperatorType.Equal, +): + """Set the properties of the generation criteria. + + Parameters + ---------- + criteria : Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion + The generation criteria to set properties for. + active : bool + Whether the criteria is active. + action : SelectionActionType + The action type for the criteria. + entity_type : SelectionType + The entity type for the criteria. + criterion : SelectionCriterionType + The criterion type for the criteria. + operator : SelectionOperatorType + The operator for the criteria. + """ + if set_active_action_criteria: + criteria.Active = active + criteria.Action = action + + criteria.EntityType = entity_type + criteria.Criterion = criterion + criteria.Operator = operator + criteria.Value = value + + return criteria + + +# %% +# Add named selections to the model + +face1 = setup_named_selection("Face1") +add_generation_criteria( + face1, Quantity("20 [m]"), criterion=SelectionCriterionType.LocationZ +) +face1.Activate() +face1.Generate() + +face2 = setup_named_selection("Face2") +add_generation_criteria( + face2, Quantity("0 [m]"), criterion=SelectionCriterionType.LocationZ +) +face2.Activate() +face2.Generate() + +face3 = setup_named_selection("Face3") +add_generation_criteria( + face3, Quantity("1 [m]"), criterion=SelectionCriterionType.LocationX +) +add_generation_criteria( + face3, + Quantity("2 [m]"), + criterion=SelectionCriterionType.LocationY, + action=SelectionActionType.Filter, +) +add_generation_criteria( + face3, + Quantity("12 [m]"), + criterion=SelectionCriterionType.LocationZ, + action=SelectionActionType.Filter, +) +add_generation_criteria( + face3, Quantity("4.5 [m]"), criterion=SelectionCriterionType.LocationZ +) +add_generation_criteria( + face3, + Quantity("2 [m]"), + criterion=SelectionCriterionType.LocationY, + action=SelectionActionType.Filter, +) +face3.Activate() +face3.Generate() + +body1 = setup_named_selection("Body1") +body1.GenerationCriteria.Add(None) +set_criteria_properties( + body1.GenerationCriteria[0], + Quantity("1 [m]"), + set_active_action_criteria=False, + criterion=SelectionCriterionType.LocationZ, +) +body1.GenerationCriteria.Add(None) +set_criteria_properties( + body1.GenerationCriteria[1], + Quantity("1 [m]"), + set_active_action_criteria=False, + criterion=SelectionCriterionType.LocationZ, +) +body1.Generate() # %% # Create construction geometry -CONST_GEOM = MODEL.AddConstructionGeometry() -Path = CONST_GEOM.AddPath() -Path.StartYCoordinate = Quantity(2, "m") -Path.StartZCoordinate = Quantity(20, "m") -Path.StartZCoordinate = Quantity(20, "m") -Path.EndXCoordinate = Quantity(2, "m") -SURF = CONST_GEOM.AddSurface() -SURF.CoordinateSystem = LCS2 -CONST_GEOM.UpdateAllSolids() - -# %% -# Define boundary condition and add results -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Add temperature boundary conditions - -TEMP = STAT_THERM.AddTemperature() -TEMP.Location = FACE1 -TEMP.Magnitude.Output.DiscreteValues = [Quantity("22[C]"), Quantity("30[C]")] - -TEMP2 = STAT_THERM.AddTemperature() -TEMP2.Location = FACE2 -TEMP2.Magnitude.Output.DiscreteValues = [Quantity("22[C]"), Quantity("60[C]")] - -TEMP.Magnitude.Inputs[0].DiscreteValues = [ - Quantity("0 [sec]"), - Quantity("1 [sec]"), - Quantity("2 [sec]"), -] -TEMP.Magnitude.Output.DiscreteValues = [ - Quantity("22[C]"), - Quantity("30[C]"), - Quantity("40[C]"), -] - -TEMP2.Magnitude.Inputs[0].DiscreteValues = [ - Quantity("0 [sec]"), - Quantity("1 [sec]"), - Quantity("2 [sec]"), -] -TEMP2.Magnitude.Output.DiscreteValues = [ - Quantity("22[C]"), - Quantity("50[C]"), - Quantity("80[C]"), -] +# Add construction geometry to the model +construction_geometry = model.AddConstructionGeometry() +# Add a path to the construction geometry +construction_geom_path = construction_geometry.AddPath() + +# Set the coordinate system for the construction geometry path +construction_geom_path.StartYCoordinate = Quantity(2, "m") +construction_geom_path.StartZCoordinate = Quantity(20, "m") +construction_geom_path.StartZCoordinate = Quantity(20, "m") +construction_geom_path.EndXCoordinate = Quantity(2, "m") + +# Add a surface to the construction geometry +surface = construction_geometry.AddSurface() +# Set the coordinate system for the surface +surface.CoordinateSystem = lcs2 +# Update the solids in the construction geometry +construction_geometry.UpdateAllSolids() + +# %% +# Define the boundary condition and add results +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Create a function to set the location and output for the temperature boundary condition + + +def set_loc_and_output(temp, location, values): + """Add a temperature set output to the boundary condition. + + Parameters + ---------- + temp : Ansys.Mechanical.DataModel.SteadyStateThermal.Temperature + The temperature boundary condition. + location : Ansys.Mechanical.DataModel.Geometry.GeometryObject + The location of the temperature boundary condition. + values : list[Quantity] + The list of values for the temperature. + """ + temp.Location = location + temp.Magnitude.Output.DiscreteValues = [Quantity(value) for value in values] + + +# %% +# Create a function to set the inputs and outputs for the temperature boundary condition + + +def set_inputs_and_outputs( + condition, + input_quantities: list = ["0 [sec]", "1 [sec]", "2 [sec]"], + output_quantities: list = ["22[C]", "30[C]", "40[C]"], +): + """Set the temperature inputs for the boundary condition. + + Parameters + ---------- + condition : Ansys.Mechanical.DataModel.SteadyStateThermal.Temperature + The temperature boundary condition. + inputs : list[Quantity] + The list of input values for the temperature. + """ + # Set the magnitude for temperature or the ambient temperature for radiation + if "Temperature" in str(type(condition)): + prop = condition.Magnitude + elif "Radiation" in str(type(condition)): + prop = condition.AmbientTemperature + + # Set the inputs and outputs for the temperature or radiation + prop.Inputs[0].DiscreteValues = [Quantity(value) for value in input_quantities] + prop.Output.DiscreteValues = [Quantity(value) for value in output_quantities] + + +# %% +# Add temperature boundary conditions to the steady state thermal analysis + +temp = stat_therm.AddTemperature() +set_loc_and_output(temp, face1, ["22[C]", "30[C]"]) +temp2 = stat_therm.AddTemperature() +set_loc_and_output(temp2, face2, ["22[C]", "60[C]"]) + +set_inputs_and_outputs(temp) +set_inputs_and_outputs(temp2, output_quantities=["22[C]", "50[C]", "80[C]"]) # %% # Add radiation -RAD = STAT_THERM.AddRadiation() -RAD.Location = FACE3 -RAD.AmbientTemperature.Inputs[0].DiscreteValues = [ - Quantity("0 [sec]"), - Quantity("1 [sec]"), - Quantity("2 [sec]"), -] -RAD.AmbientTemperature.Output.DiscreteValues = [ - Quantity("22[C]"), - Quantity("30[C]"), - Quantity("40[C]"), -] -RAD.Correlation = RadiationType.SurfaceToSurface - -# %% -# Analysis settings - -ANLYS_SET = STAT_THERM.AnalysisSettings -ANLYS_SET.NumberOfSteps = 2 -ANLYS_SET.CalculateVolumeEnergy = True - -STAT_THERM.Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "BC_steadystate.png"), image_export_format, settings_720p +# Add a radiation boundary condition to the steady state thermal analysis +radiation = stat_therm.AddRadiation() +radiation.Location = face3 +set_inputs_and_outputs(radiation) +radiation.Correlation = RadiationType.SurfaceToSurface + +# %% +# Set up the analysis settings + +analysis_settings = stat_therm.AnalysisSettings +analysis_settings.NumberOfSteps = 2 +analysis_settings.CalculateVolumeEnergy = True + +# Activate the static thermal analysis and display the image +stat_therm.Activate() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "bc_steady_state.png" ) -display_image("BC_steadystate.png") # %% # Add results # ~~~~~~~~~~~ -# Temperature -STAT_THERM_SOLN = Model.Analyses[0].Solution -TEMP_RST = STAT_THERM_SOLN.AddTemperature() -TEMP_RST.By = SetDriverStyle.MaximumOverTime +# %% +# Add temperature results to the solution + +# Get the solution object for the steady state thermal analysis +stat_therm_soln = model.Analyses[0].Solution +# Add four temperature results to the solution +temp_rst = stat_therm_soln.AddTemperature() +temp_rst.By = SetDriverStyle.MaximumOverTime -TEMP_RST2 = STAT_THERM_SOLN.AddTemperature() -TEMP_RST2.Location = BODY1 +# Set the temperature location to the body1 named selection +temp_rst2 = stat_therm_soln.AddTemperature() +temp_rst2.Location = body1 -TEMP_RST3 = STAT_THERM_SOLN.AddTemperature() -TEMP_RST3.Location = Path +# Set the temperature location to the construction geometry path +temp_rst3 = stat_therm_soln.AddTemperature() +temp_rst3.Location = construction_geom_path -TEMP_RST4 = STAT_THERM_SOLN.AddTemperature() -TEMP_RST4.Location = SURF +# Set the temperaature location to the construction geometry surface +temp_rst4 = stat_therm_soln.AddTemperature() +temp_rst4.Location = surface # %% -# Total and directional heat flux +# Add the total and directional heat flux to the solution -TOT_HFLUX = STAT_THERM_SOLN.AddTotalHeatFlux() -DIR_HFLUX = STAT_THERM_SOLN.AddTotalHeatFlux() -DIR_HFLUX.ThermalResultType = TotalOrDirectional.Directional -DIR_HFLUX.NormalOrientation = NormalOrientationType.ZAxis +total_heat_flux = stat_therm_soln.AddTotalHeatFlux() +directional_heat_flux = stat_therm_soln.AddTotalHeatFlux() -LCS2.PrimaryAxisDefineBy = CoordinateSystemAlignmentType.GlobalZ -DIR_HFLUX.CoordinateSystem = LCS2 -DIR_HFLUX.DisplayOption = ResultAveragingType.Averaged +# Set the thermal result type and normal orientation for the directional heat flux +directional_heat_flux.ThermalResultType = TotalOrDirectional.Directional +directional_heat_flux.NormalOrientation = NormalOrientationType.ZAxis -# %% -# Thermal error +# Set the coordinate system's primary axis for the directional heat flux +lcs2.PrimaryAxisDefineBy = CoordinateSystemAlignmentType.GlobalZ +directional_heat_flux.CoordinateSystem = lcs2 -THERM_ERROR = STAT_THERM_SOLN.AddThermalError() +# Set the display option for the directional heat flux +directional_heat_flux.DisplayOption = ResultAveragingType.Averaged # %% -# Temperature probe +# Add thermal error and temperature probes -TEMP_PROBE = STAT_THERM_SOLN.AddTemperatureProbe() -TEMP_PROBE.GeometryLocation = FACE1 -TEMP_PROBE.LocationMethod = LocationDefinitionMethod.CoordinateSystem -TEMP_PROBE.CoordinateSystemSelection = LCS2 +# Add a thermal error to the solution +thermal_error = stat_therm_soln.AddThermalError() -# %% -# Heat flux probe +# Add a temperature probe to the solution +temp_probe = stat_therm_soln.AddTemperatureProbe() -HFLUX_PROBE = STAT_THERM_SOLN.AddHeatFluxProbe() -HFLUX_PROBE.LocationMethod = LocationDefinitionMethod.CoordinateSystem -HFLUX_PROBE.CoordinateSystemSelection = LCS2 -HFLUX_PROBE.ResultSelection = ProbeDisplayFilter.ZAxis +# Set the temperature probe location to the face1 named selection +temp_probe.GeometryLocation = face1 + +# Set the temperature probe location method to the coordinate system +temp_probe.LocationMethod = LocationDefinitionMethod.CoordinateSystem +temp_probe.CoordinateSystemSelection = lcs2 # %% -# Reaction probe +# Add a heat flux probe + +hflux_probe = stat_therm_soln.AddHeatFluxProbe() -ANLYS_SET.NodalForces = OutputControlsNodalForcesType.Yes -REAC_PROBE = STAT_THERM_SOLN.AddReactionProbe() -REAC_PROBE.LocationMethod = LocationDefinitionMethod.GeometrySelection -REAC_PROBE.GeometryLocation = FACE1 +# Set the location method for the heat flux probe +hflux_probe.LocationMethod = LocationDefinitionMethod.CoordinateSystem +# Set the coordinate system for the heat flux probe +hflux_probe.CoordinateSystemSelection = lcs2 +# Set the result selection to the z-axis for the heat flux probe +hflux_probe.ResultSelection = ProbeDisplayFilter.ZAxis # %% -# Radiation probe +# Add a reaction probe + +# Update the analysis settings to allow output control nodal forces +analysis_settings.NodalForces = OutputControlsNodalForcesType.Yes -Rad_Probe = STAT_THERM_SOLN.AddRadiationProbe() -Rad_Probe.BoundaryConditionSelection = RAD -Rad_Probe.ResultSelection = ProbeDisplayFilter.All +# Add a reaction probe to the solution +reaction_probe = stat_therm_soln.AddReactionProbe() +# Set the reaction probe geometry location to the face1 named selection +reaction_probe.LocationMethod = LocationDefinitionMethod.GeometrySelection +reaction_probe.GeometryLocation = face1 +# %% +# Add a radiation probe + +radiation_probe = stat_therm_soln.AddRadiationProbe() +# Set the radiation probe boundary condition to the radiation boundary condition +radiation_probe.BoundaryConditionSelection = radiation +# Display all results for the radiation probe +radiation_probe.ResultSelection = ProbeDisplayFilter.All # %% -# Solve -# ~~~~~ +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ -STAT_THERM_SOLN.Solve(True) +# Solve the steady state thermal analysis solution +stat_therm_soln.Solve(True) # sphinx_gallery_start_ignore -assert str(STAT_THERM_SOLN.Status) == "Done", "Solution status is not 'Done'" +assert ( + stat_therm_soln.Status == SolutionStatusType.Done +), "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ +# Show messages +# ~~~~~~~~~~~~~ -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Print all messages from Mechanical +app.messages.show() -# Display results -# ~~~~~~~~~~~~~~~ -# Total body temperature +# %% +# Display the results +# ~~~~~~~~~~~~~~~~~~~ -Tree.Activate([TEMP_RST]) -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "temp.png"), image_export_format, settings_720p) -display_image("temp.png") +# Activate the total body temperature and display the image +app.Tree.Activate([temp_rst]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_body_temp.png" +) # %% # Temperature on part of the body -Tree.Activate([TEMP_RST2]) -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "temp2.png"), image_export_format, settings_720p) -display_image("temp2.png") +# Activate the temperature on part of the body and display the image +app.Tree.Activate([temp_rst2]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "part_temp_body.png" +) # %% # Temperature distribution along the specific path -Tree.Activate([TEMP_RST3]) -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "temp3.png"), image_export_format, settings_720p) -display_image("temp3.png") +# Activate the temperature distribution along the specific path and display the image +app.Tree.Activate([temp_rst3]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "path_temp_distribution.png" +) # %% # Temperature of bottom surface -Tree.Activate([TEMP_RST4]) -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "temp4.png"), image_export_format, settings_720p) -display_image("temp4.png") +# Activate the temperature of the bottom surface and display the image +app.Tree.Activate([temp_rst4]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "bottom_surface_temp.png" +) # %% -# Export directional heat flux animation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Directional heat flux +# Export the directional heat flux animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +# %% +# Create a function to update the animation frames +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. + + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] + + +# %% +# Show the directional heat flux animation + +# Activate the directional heat flux +app.Tree.Activate([directional_heat_flux]) -Tree.Activate([DIR_HFLUX]) +# Set the animation export format and settings animation_export_format = ( Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF ) @@ -432,58 +691,64 @@ def display_image(image_name): settings_720p.Width = 1280 settings_720p.Height = 720 -DIR_HFLUX.ExportAnimation( - os.path.join(cwd, "DirectionalHeatFlux.gif"), animation_export_format, settings_720p +# Export the directional heat flux animation as a GIF +directional_heat_flux_gif = output_path / "directional_heat_flux.gif" +directional_heat_flux.ExportAnimation( + str(directional_heat_flux_gif), animation_export_format, settings_720p ) -gif = Image.open(os.path.join(cwd, "DirectionalHeatFlux.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) - - -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] +# Open the GIF file and create an animation +gif = Image.open(directional_heat_flux_gif) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=100, repeat=True, blit=True + figure, + update_animation, + frames=range(gif.n_frames), + interval=100, + repeat=True, + blit=True, ) + +# Show the animation plt.show() # %% -# Display output file from solve -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +# Display the output file from the solve +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def write_file_contents_to_console(path): - """Write file contents to console.""" - with open(path, "rt") as file: +# Get the working directory for the steady state thermal analysis +solve_path = stat_therm.WorkingDir +# Get the path to the solve.out file +solve_out_path = solve_path + "solve.out" +# Print the output of the solve.out file if applicable +if solve_out_path: + with open(solve_out_path, "rt") as file: for line in file: print(line, end="") - -solve_path = STAT_THERM.WorkingDir -solve_out_path = os.path.join(solve_path, "solve.out") -if solve_out_path: - write_file_contents_to_console(solve_out_path) - # %% -# Project tree -# ~~~~~~~~~~~~ +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the app and downloaded files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "steady_state_thermal.mechdat")) -app.new() +# Save the project file +mechdat_path = output_path / "steady_state_thermal.mechdat" +app.save(str(mechdat_path)) -# %% -# Delete example files +# Close the app +app.close() +# Delete the example files delete_downloads() diff --git a/examples/01_basic/topology_optimization_cantilever_beam.py b/examples/01_basic/topology_optimization_cantilever_beam.py index 27fcca0b..d3434a25 100644 --- a/examples/01_basic/topology_optimization_cantilever_beam.py +++ b/examples/01_basic/topology_optimization_cantilever_beam.py @@ -31,39 +31,114 @@ """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +from typing import TYPE_CHECKING from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file from matplotlib import image as mpimg from matplotlib import pyplot as plt +if TYPE_CHECKING: + import Ansys + # %% -# Embed Mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure plt.show() -cwd = os.path.join(os.getcwd(), "out") - # %% # Configure graphics for image export -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Front) +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to the front view +camera.SetSpecificViewOrientation(ViewOrientationType.Front) + +# Set the image export format and settings image_export_format = GraphicsImageExportFormat.PNG settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution @@ -73,135 +148,150 @@ def display_image(image_name): settings_720p.CurrentGraphicsDisplay = False # %% -# Import structural analsys -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download ``.mechdat`` file +# Import the structural analysis model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download ``.mechdat`` file structural_mechdat_file = download_file( "cantilever.mechdat", "pymechanical", "embedding" ) + +# Open the project file app.open(structural_mechdat_file) -STRUCT = Model.Analyses[0] + +# Define the model +model = app.Model + +# Get the structural analysis object +struct = model.Analyses[0] # sphinx_gallery_start_ignore -assert str(STRUCT.ObjectState) == "Solved" +assert struct.ObjectState == ObjectState.Solved # sphinx_gallery_end_ignore -STRUCT_SLN = STRUCT.Solution -STRUCT_SLN.Solve(True) + +# Get the structural analysis object's solution and solve it +struct_sln = struct.Solution +struct_sln.Solve(True) + # sphinx_gallery_start_ignore -assert str(STRUCT_SLN.Status) == "Done", "Solution status is not 'Done'" +assert struct_sln.Status == SolutionStatusType.Done, "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Display structural analsys results -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Total deformation +# Display the structural analysis results +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Activate the total deformation result and display the image -STRUCT_SLN.Children[1].Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "total_deformation.png"), image_export_format, settings_720p +struct_sln.Children[1].Activate() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_deformation.png" ) -display_image("total_deformation.png") # %% -# Equivalent stress +# Activate the equivalent stress result and display the image -STRUCT_SLN.Children[2].Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "equivalent_stress.png"), image_export_format, settings_720p +struct_sln.Children[2].Activate() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "equivalent_stress.png" ) -display_image("equivalent_stress.png") # %% # Topology optimization # ~~~~~~~~~~~~~~~~~~~~~ -# Set MKS unit system - -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS +# Set the MKS unit system +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS -# Get structural analysis and link to topology optimization +# Add the topology optimization analysis to the model and transfer data from the +# structural analysis +topology_optimization = model.AddTopologyOptimizationAnalysis() +topology_optimization.TransferDataFrom(struct) -TOPO_OPT = Model.AddTopologyOptimizationAnalysis() -TOPO_OPT.TransferDataFrom(STRUCT) - -OPT_REG = DataModel.GetObjectsByType(DataModelObjectCategory.OptimizationRegion)[0] -OPT_REG.BoundaryCondition = BoundaryConditionType.AllLoadsAndSupports -OPT_REG.OptimizationType = OptimizationType.TopologyDensity +# Get the optimization region from the data model +optimization_region = DataModel.GetObjectsByType( + DataModelObjectCategory.OptimizationRegion +)[0] +# Set the optimization region's boundary condition to all loads and supports +optimization_region.BoundaryCondition = BoundaryConditionType.AllLoadsAndSupports +# Set the optimization region's optimization type to topology density +optimization_region.OptimizationType = OptimizationType.TopologyDensity # sphinx_gallery_start_ignore -assert str(TOPO_OPT.ObjectState) == "NotSolved" +assert topology_optimization.ObjectState == ObjectState.NotSolved # sphinx_gallery_end_ignore -# Insert volume response constraint object for topology optimization -# Delete mass response constraint - -MASS_CONSTRN = TOPO_OPT.Children[3] -MASS_CONSTRN.Delete() - -# Add volume response constraint - -VOL_CONSTRN = TOPO_OPT.AddVolumeConstraint() +# Delete the mass response constraint from the topology optimization +mass_constraint = topology_optimization.Children[3] +app.DataModel.Remove(mass_constraint) -# Insert member size manufacturing constraint +# Add a volume response constraint to the topology optimization +volume_constraint = topology_optimization.AddVolumeConstraint() -MEM_SIZE_MFG_CONSTRN = TOPO_OPT.AddMemberSizeManufacturingConstraint() -MEM_SIZE_MFG_CONSTRN.Minimum = ManuMemberSizeControlledType.Manual -MEM_SIZE_MFG_CONSTRN.MinSize = Quantity("2.4 [m]") - - -TOPO_OPT.Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "boundary_conditions.png"), image_export_format, settings_720p +# Add a member size manufacturing constraint to the topology optimization +mem_size_manufacturing_constraint = ( + topology_optimization.AddMemberSizeManufacturingConstraint() +) +# Set the constraint's minimum to manual and its minimum size to 2.4m +mem_size_manufacturing_constraint.Minimum = ManuMemberSizeControlledType.Manual +mem_size_manufacturing_constraint.MinSize = Quantity("2.4 [m]") + +# Activate the topology optimization analysis and display the image +topology_optimization.Activate() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "boundary_conditions.png" ) -display_image("boundary_conditions.png") # %% -# Solve -# ~~~~~ +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ + +# Get the topology optimization analysis solution +top_opt_sln = topology_optimization.Solution +# Solve the solution +top_opt_sln.Solve(True) -TOPO_OPT_SLN = TOPO_OPT.Solution -TOPO_OPT_SLN.Solve(True) # sphinx_gallery_start_ignore -assert str(TOPO_OPT_SLN.Status) == "Done", "Solution status is not 'Done'" +assert top_opt_sln.Status == SolutionStatusType.Done, "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ +# Show messages +# ~~~~~~~~~~~~~ -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Print all messages from Mechanical +app.messages.show() # %% -# Display results -# ~~~~~~~~~~~~~~~ +# Display the results +# ~~~~~~~~~~~~~~~~~~~ -TOPO_OPT_SLN.Children[1].Activate() -TOPO_DENS = TOPO_OPT_SLN.Children[1] +# Get the topology density result and activate it +top_opt_sln.Children[1].Activate() +topology_density = top_opt_sln.Children[1] # %% -# Add smoothing to the STL +# Add smoothing to the stereolithography (STL) -TOPO_DENS.AddSmoothing() -TOPO_OPT.Solution.EvaluateAllResults() -TOPO_DENS.Children[0].Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "topo_opitimized_smooth.png"), image_export_format, settings_720p +# Add smoothing to the topology density result +topology_density.AddSmoothing() + +# Evaluate all results for the topology optimization solution +topology_optimization.Solution.EvaluateAllResults() + +# Activate the topology density result after smoothing and display the image +topology_density.Children[0].Activate() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "topo_opitimized_smooth.png" ) -display_image("topo_opitimized_smooth.png") # %% -# Export animation +# Export the animation +app.Tree.Activate([topology_density]) + +# Set the animation export format and settings animation_export_format = ( Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF ) @@ -209,8 +299,10 @@ def display_image(image_name): settings_720p.Width = 1280 settings_720p.Height = 720 -TOPO_DENS.ExportAnimation( - os.path.join(cwd, "Topo_opitimized.gif"), animation_export_format, settings_720p +# Export the animation of the topology density result +topology_optimized_gif = output_path / "topology_opitimized.gif" +topology_density.ExportAnimation( + str(topology_optimized_gif), animation_export_format, settings_720p ) # %% @@ -219,34 +311,34 @@ def display_image(image_name): # %% # Review the results -# Print topology density results +# Print the topology density results print("Topology Density Results") -print("Minimum Density: ", TOPO_DENS.Minimum) -print("Maximum Density: ", TOPO_DENS.Maximum) -print("Iteration Number: ", TOPO_DENS.IterationNumber) -print("Original Volume: ", TOPO_DENS.OriginalVolume.Value) -print("Final Volume: ", TOPO_DENS.FinalVolume.Value) -print("Percent Volume of Original: ", TOPO_DENS.PercentVolumeOfOriginal) -print("Original Mass: ", TOPO_DENS.OriginalMass.Value) -print("Final Mass: ", TOPO_DENS.FinalMass.Value) -print("Percent Mass of Original: ", TOPO_DENS.PercentMassOfOriginal) - +print("Minimum Density: ", topology_density.Minimum) +print("Maximum Density: ", topology_density.Maximum) +print("Iteration Number: ", topology_density.IterationNumber) +print("Original Volume: ", topology_density.OriginalVolume.Value) +print("Final Volume: ", topology_density.FinalVolume.Value) +print("Percent Volume of Original: ", topology_density.PercentVolumeOfOriginal) +print("Original Mass: ", topology_density.OriginalMass.Value) +print("Final Mass: ", topology_density.FinalMass.Value) +print("Percent Mass of Original: ", topology_density.PercentMassOfOriginal) # %% -# Project tree -# ~~~~~~~~~~~~ +# Display the project tree +# ~~~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "cantilever_beam_topology_optimization.mechdat")) -app.new() +# Save the project file +mechdat_file = output_path / "cantilever_beam_topology_optimization.mechdat" +app.save(str(mechdat_file)) -# %% -# Delete the example files +# Close the app +app.close() +# Delete the example files delete_downloads() diff --git a/examples/01_basic/valve.py b/examples/01_basic/valve.py index f85722dc..8051f8e6 100644 --- a/examples/01_basic/valve.py +++ b/examples/01_basic/valve.py @@ -22,17 +22,18 @@ """.. _ref_basic_valve: -Basic Valve Implementation +Basic valve implementation -------------------------- This example demonstrates a basic implementation of a valve in Python. """ # %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -import os +from pathlib import Path +from typing import TYPE_CHECKING from PIL import Image from ansys.mechanical.core import App @@ -41,22 +42,90 @@ from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation +if TYPE_CHECKING: + import Ansys + # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure plt.show() @@ -64,7 +133,13 @@ def display_image(image_name): # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to the isometric view +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) + +# Set the image export format and settings image_export_format = GraphicsImageExportFormat.PNG settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution @@ -73,135 +148,188 @@ def display_image(image_name): settings_720p.Height = 720 settings_720p.CurrentGraphicsDisplay = False - # %% -# Download geometry and import -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Download geometry +# Download and import the geometry file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the geometry file geometry_path = download_file("Valve.pmdb", "pymechanical", "embedding") # %% -# Import geometry +# Import the geometry -geometry_import = Model.GeometryImportGroup.AddGeometryImport() +# Define the model +model = app.Model + +# Add a geometry import to the geometry import group +geometry_import = model.GeometryImportGroup.AddGeometryImport() + +# Set the geometry import settings geometry_import_format = ( Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic ) geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() geometry_import_preferences.ProcessNamedSelections = True + +# Import the geometry file with the specified settings geometry_import.Import( geometry_path, geometry_import_format, geometry_import_preferences ) +# Visualize the model in 3D app.plot() # %% -# Assign materials and mesh the geometry -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -material_assignment = Model.Materials.AddMaterialAssignment() +# Assign the materials and mesh the geometry +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Add the material assignment to the model materials +material_assignment = model.Materials.AddMaterialAssignment() + +# Set the material to structural steel material_assignment.Material = "Structural Steel" -sel = ExtAPI.SelectionManager.CreateSelectionInfo( + +# Create selection information for the geometry entities +selection_info = app.ExtAPI.SelectionManager.CreateSelectionInfo( Ansys.ACT.Interfaces.Common.SelectionTypeEnum.GeometryEntities ) -sel.Ids = [ + +# Get the geometric bodies from the model and add their IDs to the selection info IDs list +selection_info.Ids = [ body.GetGeoBody().Id - for body in Model.Geometry.GetChildren( + for body in model.Geometry.GetChildren( Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True ) ] -material_assignment.Location = sel +# Set the material assignment location to the selected geometry entities +material_assignment.Location = selection_info # %% -# Define mesh settings, generate mesh +# Define the mesh settings and generate the mesh -mesh = Model.Mesh +# Define the mesh +mesh = model.Mesh +# Set the mesh element size to 25mm mesh.ElementSize = Quantity(25, "mm") + +# Generate the mesh mesh.GenerateMesh() -Tree.Activate([mesh]) -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") + +# Activate the mesh and display the image +app.Tree.Activate([mesh]) +set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") # %% -# Define analysis and boundary conditions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add a static structural analysis and apply boundary conditions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -analysis = Model.AddStaticStructuralAnalysis() +# Add a static structural analysis to the model +analysis = model.AddStaticStructuralAnalysis() +# Add a fixed support to the analysis fixed_support = analysis.AddFixedSupport() -fixed_support.Location = ExtAPI.DataModel.GetObjectsByName("NSFixedSupportFaces")[0] +# Set the fixed support location to the "NSFixedSupportFaces" object +fixed_support.Location = app.ExtAPI.DataModel.GetObjectsByName("NSFixedSupportFaces")[0] +# Add a frictionless support to the analysis frictionless_support = analysis.AddFrictionlessSupport() -frictionless_support.Location = ExtAPI.DataModel.GetObjectsByName( +# Set the frictionless support location to the "NSFrictionlessSupportFaces" object +frictionless_support.Location = app.ExtAPI.DataModel.GetObjectsByName( "NSFrictionlessSupportFaces" )[0] +# Add pressure to the analysis pressure = analysis.AddPressure() -pressure.Location = ExtAPI.DataModel.GetObjectsByName("NSInsideFaces")[0] +# Set the pressure location to the "NSInsideFaces" object +pressure.Location = app.ExtAPI.DataModel.GetObjectsByName("NSInsideFaces")[0] +# Set the pressure magnitude's input and output values pressure.Magnitude.Inputs[0].DiscreteValues = [Quantity("0 [s]"), Quantity("1 [s]")] pressure.Magnitude.Output.DiscreteValues = [Quantity("0 [Pa]"), Quantity("15 [MPa]")] +# Activate the analysis and display the image analysis.Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "boundary_conditions.png"), image_export_format, settings_720p +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "boundary_conditions.png" ) -display_image("boundary_conditions.png") - # %% -# Add results +# Add results to the analysis solution +# Define the solution for the analysis solution = analysis.Solution + +# Add the total deformation and equivalent stress results to the solution deformation = solution.AddTotalDeformation() stress = solution.AddEquivalentStress() # %% -# Solve +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ solution.Solve(True) # sphinx_gallery_start_ignore -assert str(solution.Status) == "Done", "Solution status is not 'Done'" +assert solution.Status == SolutionStatusType.Done, "Solution status is not 'Done'" # sphinx_gallery_end_ignore # %% -# Messages -# ~~~~~~~~ +# Show messages +# ~~~~~~~~~~~~~ -Messages = ExtAPI.Application.Messages -if Messages: - for message in Messages: - print(f"[{message.Severity}] {message.DisplayString}") -else: - print("No [Info]/[Warning]/[Error] Messages") +# Print all messages from Mechanical +app.messages.show() # %% -# Results -# ~~~~~~~ +# Display the results +# ~~~~~~~~~~~~~~~~~~~ # %% -# Total deformation +# Show the total deformation image -Tree.Activate([deformation]) -Graphics.ExportImage( - os.path.join(cwd, "totaldeformation_valve.png"), image_export_format, settings_720p +# Activate the total deformation result and display the image +app.Tree.Activate([deformation]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_deformation_valve.png" ) -display_image("totaldeformation_valve.png") # %% -# Stress +# Show the equivalent stress image -Tree.Activate([stress]) -Graphics.ExportImage( - os.path.join(cwd, "stress_valve.png"), image_export_format, settings_720p +# Activate the equivalent stress result and display the image +app.Tree.Activate([stress]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "stress_valve.png" ) -display_image("stress_valve.png") + + +# %% +# Create a function to update the animation frames +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. + + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] + # %% -# Export stress animation +# Export the stress animation +# Set the animation export format and settings animation_export_format = ( Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF ) @@ -209,58 +337,62 @@ def display_image(image_name): settings_720p.Width = 1280 settings_720p.Height = 720 -stress.ExportAnimation( - os.path.join(cwd, "Valve.gif"), animation_export_format, settings_720p +# Export the animation of the equivalent stress result +valve_gif = output_path / "valve.gif" +stress.ExportAnimation(str(valve_gif), animation_export_format, settings_720p) + +# Open the GIF file and create an animation +gif = Image.open(valve_gif) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) + +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation +FuncAnimation( + figure, + update_animation, + frames=range(gif.n_frames), + interval=100, + repeat=True, + blit=True, ) -gif = Image.open(os.path.join(cwd, "Valve.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) - - -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] - -ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=100, repeat=True, blit=True -) +# Show the animation plt.show() # %% -# Display output file from solve -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -def write_file_contents_to_console(path): - """Write file contents to console.""" - with open(path, "rt") as file: - for line in file: - print(line, end="") - +# Display the output file from the solve +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get the path to the solve output file solve_path = analysis.WorkingDir -solve_out_path = os.path.join(solve_path, "solve.out") +# Get the solve output file path +solve_out_path = solve_path + "solve.out" +# If the solve output file exists, print its contents if solve_out_path: - write_file_contents_to_console(solve_out_path) + with open(solve_out_path, "rt") as file: + for line in file: + print(line, end="") # %% -# Project tree -# ~~~~~~~~~~~~ +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ app.print_tree() # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "Valve.mechdat")) -app.new() +# Save the project +mechdat_file = output_path / "valve.mechdat" +app.save(str(mechdat_file)) -# %% -# delete example files +# Close the app +app.close() +# Delete the example files delete_downloads() diff --git a/examples/02_technology_showcase/Rotor_Blade_Inverse_solve.py b/examples/02_technology_showcase/Rotor_Blade_Inverse_solve.py index e544d2c4..508531af 100644 --- a/examples/02_technology_showcase/Rotor_Blade_Inverse_solve.py +++ b/examples/02_technology_showcase/Rotor_Blade_Inverse_solve.py @@ -69,59 +69,125 @@ """ -import os +# %% +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from pathlib import Path +from typing import TYPE_CHECKING from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file from matplotlib import image as mpimg from matplotlib import pyplot as plt +if TYPE_CHECKING: + import Ansys + # %% -# Embed mechanical and set global variables +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app = App() -app.update_globals(globals()) +app = App(globals=globals()) print(app) -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure plt.show() # %% -# Download required files -# ~~~~~~~~~~~~~~~~~~~~~~~ -# Download the geometry file +# Download the required files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the geometry file geometry_path = download_file( "example_10_td_055_Rotor_Blade_Geom.pmdb", "pymechanical", "embedding" ) -# %% # Download the material file - mat_path = download_file( "example_10_td_055_Rotor_Blade_Mat_File.xml", "pymechanical", "embedding" ) -# %% # Download the CFX pressure data - cfx_data_path = download_file( "example_10_CFX_ExportResults_FT_10P_EO2.csv", "pymechanical", "embedding" ) - -# %% -# Download required Temperature file - +# Download the temperature data file temp_data_path = download_file( "example_10_Temperature_Data.txt", "pymechanical", "embedding" ) @@ -130,29 +196,37 @@ def display_image(image_name): # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -cwd = os.path.join(os.getcwd(), "out") -Graphics.Camera.SetSpecificViewOrientation( - Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Iso -) -Graphics.Camera.SetFit() -image_export_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.PNG +# Define the graphics and camera +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to the isometric view and set the camera to fit the model +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +camera.SetFit() + +# Set the image export format and settings +image_export_format = GraphicsImageExportFormat.PNG settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() settings_720p.Resolution = ( Ansys.Mechanical.DataModel.Enums.GraphicsResolutionType.EnhancedResolution ) settings_720p.Background = Ansys.Mechanical.DataModel.Enums.GraphicsBackgroundType.White settings_720p.Width = 1280 -# settings_720p.Capture = Ansys.Mechanical.DataModel.Enums.GraphicsCaptureType.ImageOnly settings_720p.Height = 720 settings_720p.CurrentGraphicsDisplay = False # %% -# Import geometry -# ~~~~~~~~~~~~~~~ -# Reads geometry file and display +# Import the geometry +# ~~~~~~~~~~~~~~~~~~~ + +# Define the model +model = app.Model -geometry_import_group = Model.GeometryImportGroup +# Add the geometry import to the geometry import group +geometry_import_group = model.GeometryImportGroup geometry_import = geometry_import_group.AddGeometryImport() + +# Set the geometry import format and settings geometry_import_format = ( Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic ) @@ -161,74 +235,109 @@ def display_image(image_name): geometry_import_preferences.NamedSelectionKey = "" geometry_import_preferences.ProcessMaterialProperties = True geometry_import_preferences.ProcessCoordinateSystems = True + +# Import the geometry with the specified settings geometry_import.Import( geometry_path, geometry_import_format, geometry_import_preferences ) +# Visualize the model in 3D app.plot() # %% # Assign materials # ~~~~~~~~~~~~~~~~ -# Import material from xml file and assign it to bodies -materials = Model.Materials +# %% +# Import material from the xml file and assign it to bodies + +# Define and import the materials +materials = model.Materials materials.Import(mat_path) -PRT1 = [x for x in Tree.AllObjects if x.Name == "Component2\Rotor11"][0] -PRT2 = [x for x in Tree.AllObjects if x.Name == "Component3"][0] -PRT2_Blade_1 = PRT2.Children[0] -PRT2_Blade_2 = PRT2.Children[1] -PRT2_Blade_3 = PRT2.Children[2] -PRT1.Material = "MAT1 (Setup, File1)" -PRT2_Blade_1.Material = "MAT1 (Setup, File1)" -PRT2_Blade_2.Material = "MAT1 (Setup, File1)" -PRT2_Blade_3.Material = "MAT1 (Setup, File1)" +# Assign the imported material to the components +part1 = app.DataModel.GetObjectsByName(r"Component2\Rotor11")[0] +part2 = app.DataModel.GetObjectsByName("Component3")[0] +part2_blade1 = part2.Children[0] +part2_blade2 = part2.Children[1] +part2_blade3 = part2.Children[2] +part1.Material = "MAT1 (Setup, File1)" +part2_blade1.Material = "MAT1 (Setup, File1)" +part2_blade2.Material = "MAT1 (Setup, File1)" +part2_blade3.Material = "MAT1 (Setup, File1)" # %% -# Define units system and store variables -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define the unit system and store variables +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Select MKS units -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS # Store all main tree nodes as variables -MODEL = Model -GEOM = Model.Geometry -MESH = Model.Mesh -MAT_GRP = Model.Materials -CS = Model.CoordinateSystems -NS_GRP = Model.NamedSelections - -# %% -# Define named selection -# ~~~~~~~~~~~~~~~~~~~~~~ -# Create NS for named selection - -BLADE_NS = [x for x in Tree.AllObjects if x.Name == "Blade"][0] -BLADE_SURF_NS = [x for x in Tree.AllObjects if x.Name == "Blade_Surf"][0] -FIX_SUPPORT_NS = [x for x in Tree.AllObjects if x.Name == "Fix_Support"][0] -BLADE_HUB_NS = [x for x in Tree.AllObjects if x.Name == "Blade_Hub"][0] -HUB_CONTACT_NS = [x for x in Tree.AllObjects if x.Name == "Hub_Contact"][0] -BLADE_TARGET_NS = [x for x in Tree.AllObjects if x.Name == "Blade_Target"][0] -Hub_Low_NS = [x for x in Tree.AllObjects if x.Name == "Hub_Low"][0] -Hub_High_NS = [x for x in Tree.AllObjects if x.Name == "Hub_High"][0] -BLADE1_NS = [x for x in Tree.AllObjects if x.Name == "Blade1"][0] -BLADE1_Source_NS = [x for x in Tree.AllObjects if x.Name == "Blade1_Source"][0] -BLADE1_TARGET_NS = [x for x in Tree.AllObjects if x.Name == "Blade1_Target"][0] -BLADE2_NS = [x for x in Tree.AllObjects if x.Name == "Blade2"][0] -BLADE2_Source_NS = [x for x in Tree.AllObjects if x.Name == "Blade2_Source"][0] -BLADE2_TARGET_NS = [x for x in Tree.AllObjects if x.Name == "Blade2_Target"][0] -BLADE3_NS = [x for x in Tree.AllObjects if x.Name == "Blade3"][0] -BLADE3_Source_NS = [x for x in Tree.AllObjects if x.Name == "Blade3_Source"][0] -BLADE3_TARGET_NS = [x for x in Tree.AllObjects if x.Name == "Blade3_Target"][0] - -# %% -# Define coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Create cylindrical coordinate system +geometry = model.Geometry +mesh = model.Mesh +materials = model.Materials +coordinate_systems = model.CoordinateSystems +named_selections = model.NamedSelections + +# %% +# Define the named selections +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Create a function to get named selections by name + + +def get_named_selection(ns_list: list) -> dict: + """Get the named selection by name. + + Parameters + ---------- + ns_list : list + A list of named selection names to retrieve. -coordinate_systems = Model.CoordinateSystems + Returns + ------- + dict + A dictionary containing the named selection objects. + """ + ns_dict = {} + for name in ns_list: + ns_dict[name] = app.DataModel.GetObjectsByName(name)[0] + return ns_dict + + +# %% +# Create a dictionary of named selections + +named_selections_names = [ + "Blade", + "Blade_Surf", + "Fix_Support", + "Blade_Hub", + "Hub_Contact", + "Blade_Target", + "Hub_Low", + "Hub_High", + "Blade1", + "Blade1_Source", + "Blade1_Target", + "Blade2", + "Blade2_Source", + "Blade2_Target", + "Blade3", + "Blade3_Source", + "Blade3_Target", +] +ns_dict = get_named_selection(named_selections_names) + +# %% +# Add a coordinate system +# ~~~~~~~~~~~~~~~~~~~~~~~ + +coordinate_systems = model.CoordinateSystems coord_system = coordinate_systems.AddCoordinateSystem() +# Create cylindrical coordinate system coord_system.CoordinateSystemType = ( Ansys.ACT.Interfaces.Analysis.CoordinateSystemTypeEnum.Cylindrical ) @@ -236,328 +345,342 @@ def display_image(image_name): coord_system.OriginDefineBy = CoordinateSystemAlignmentType.Fixed # %% -# Define contacts -# ~~~~~~~~~~~~~~~ - -# Define connections - -CONN_GRP = Model.Connections -CONT_REG1 = CONN_GRP.AddContactRegion() -CONT_REG1.SourceLocation = NS_GRP.Children[6] -CONT_REG1.TargetLocation = NS_GRP.Children[5] -CONT_REG1.Behavior = ContactBehavior.AutoAsymmetric -CONT_REG1.ContactFormulation = ContactFormulation.MPC - -# %% -# Define mesh settings and generate mesh -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MSH = Model.Mesh -MSH.ElementSize = Quantity(0.004, "m") -MSH.UseAdaptiveSizing = False -MSH.MaximumSize = Quantity(0.004, "m") -MSH.ShapeChecking = 0 -automatic_method_Hub = MSH.AddAutomaticMethod() -automatic_method_Hub.Location = NS_GRP.Children[0] -automatic_method_Hub.Method = MethodType.Sweep -automatic_method_Hub.SweepNumberDivisions = 6 - -match_control_Hub = MSH.AddMatchControl() -match_control_Hub.LowNamedSelection = NS_GRP.Children[7] -match_control_Hub.HighNamedSelection = NS_GRP.Children[8] -cyc_coordinate_system = coordinate_systems.Children[1] -match_control_Hub.RotationAxis = cyc_coordinate_system - -sizing_Blade = MSH.AddSizing() -selection = NS_GRP.Children[5] -sizing_Blade.Location = selection -# sizing_Blade.ElementSize = Quantity(1e-3, "m") -sizing_Blade.ElementSize = Quantity(1e-2, "m") -sizing_Blade.CaptureCurvature = True -sizing_Blade.CurvatureNormalAngle = Quantity(0.31, "rad") -# sizing_Blade.LocalMinimumSize = Quantity(0.00025, "m") -sizing_Blade.LocalMinimumSize = Quantity(0.0005, "m") - -automatic_method_Blade1 = MSH.AddAutomaticMethod() -selection = NS_GRP.Children[9] -automatic_method_Blade1.Location = selection -automatic_method_Blade1.Method = MethodType.Sweep -automatic_method_Blade1.SourceTargetSelection = 2 -selection = NS_GRP.Children[10] -automatic_method_Blade1.SourceLocation = selection -selection = NS_GRP.Children[11] -automatic_method_Blade1.TargetLocation = selection -automatic_method_Blade1.SweepNumberDivisions = 5 - -automatic_method_Blade2 = MSH.AddAutomaticMethod() -selection = NS_GRP.Children[12] -automatic_method_Blade2.Location = selection -automatic_method_Blade2.Method = MethodType.Sweep -automatic_method_Blade2.SourceTargetSelection = 2 -selection = NS_GRP.Children[13] -automatic_method_Blade2.SourceLocation = selection -selection = NS_GRP.Children[14] -automatic_method_Blade2.TargetLocation = selection -automatic_method_Blade2.SweepNumberDivisions = 5 - -automatic_method_Blade3 = MSH.AddAutomaticMethod() -selection = NS_GRP.Children[15] -automatic_method_Blade3.Location = selection -automatic_method_Blade3.Method = MethodType.Sweep -automatic_method_Blade3.SourceTargetSelection = 2 -selection = NS_GRP.Children[16] -automatic_method_Blade3.SourceLocation = selection -selection = NS_GRP.Children[17] -automatic_method_Blade3.TargetLocation = selection -automatic_method_Blade3.SweepNumberDivisions = 5 - -MSH.GenerateMesh() - -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "blade_mesh.png"), image_export_format, settings_720p +# Add contact regions +# ~~~~~~~~~~~~~~~~~~~ + +connections = model.Connections +contact_region1 = connections.AddContactRegion() +contact_region1.SourceLocation = named_selections.Children[6] +contact_region1.TargetLocation = named_selections.Children[5] +contact_region1.Behavior = ContactBehavior.AutoAsymmetric +contact_region1.ContactFormulation = ContactFormulation.MPC + +# %% +# Define the mesh settings and generate the mesh +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Set the mesh settings + +mesh = model.Mesh + +# Set the mesh settings +mesh.ElementSize = Quantity(0.004, "m") +mesh.UseAdaptiveSizing = False +mesh.MaximumSize = Quantity(0.004, "m") +mesh.ShapeChecking = 0 + +# %% +# Create a function to add an automatic method to the mesh + + +def add_automatic_method( + mesh, + location_index: int, + source_loc_index: int = None, + target_loc_index: int = None, + method=MethodType.Sweep, + source_target_selection=2, + sweep_number_divisions=5, + set_src_target_properties: bool = True, +): + """Add an automatic method to the mesh.""" + automatic_method = mesh.AddAutomaticMethod() + automatic_method.Location = named_selections.Children[location_index] + automatic_method.Method = method + if set_src_target_properties: + automatic_method.SourceTargetSelection = source_target_selection + if source_loc_index: + automatic_method.SourceLocation = named_selections.Children[ + source_loc_index + ] + if target_loc_index: + automatic_method.TargetLocation = named_selections.Children[ + target_loc_index + ] + automatic_method.SweepNumberDivisions = sweep_number_divisions + + +# Add an automatic method for the hub +add_automatic_method( + mesh, location_index=0, sweep_number_divisions=6, set_src_target_properties=False ) -display_image("blade_mesh.png") # %% -# Define analysis settings -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Setup static structural settings with inverse solve +# Add match control and sizing to the mesh + +# Add match control to the mesh +match_control_hub = mesh.AddMatchControl() +# Set the low and high named selections to named selections' children at indices 7 and 8 +match_control_hub.LowNamedSelection = named_selections.Children[7] +match_control_hub.HighNamedSelection = named_selections.Children[8] +# Set the rotation axis to the second child of the coordinate systems +match_control_hub.RotationAxis = coordinate_systems.Children[1] + +# Add sizing to the mesh +sizing_blade = mesh.AddSizing() +# Set properties for the sizing blade +sizing_blade.Location = named_selections.Children[5] +sizing_blade.ElementSize = Quantity(1e-2, "m") +sizing_blade.CaptureCurvature = True +sizing_blade.CurvatureNormalAngle = Quantity(0.31, "rad") +sizing_blade.LocalMinimumSize = Quantity(0.0005, "m") -Model.AddStaticStructuralAnalysis() -STAT_STRUC = Model.Analyses[0] -ANA_SETTINGS = ExtAPI.DataModel.Project.Model.Analyses[0].AnalysisSettings -ANA_SETTINGS.AutomaticTimeStepping = AutomaticTimeStepping.On -ANA_SETTINGS.NumberOfSubSteps = 10 +# %% +# Add automatic methods for each blad -ANA_SETTINGS.Activate() +add_automatic_method(mesh, 9, 10, 11) +add_automatic_method(mesh, 12, 13, 14) +add_automatic_method(mesh, 15, 16, 17) -CMD1 = STAT_STRUC.AddCommandSnippet() -# Add convergence criterion using command snippet. -AWM = """CNVTOL,U,1.0,5e-5,1,,""" -CMD1.AppendText(AWM) +# %% +# Generate the mesh and display the image -ANA_SETTINGS.InverseOption = True -ANA_SETTINGS.LargeDeflection = True +mesh.GenerateMesh() +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "blade_mesh.png" +) # %% -# Define boundary conditions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Apply rotational velocity +# Define the analysis settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -ROT_VEL = STAT_STRUC.AddRotationalVelocity() -ROT_VEL.DefineBy = LoadDefineBy.Components -ROT_VEL.ZComponent.Inputs[0].DiscreteValues = [ +# Add a static structural analysis to the model +model.AddStaticStructuralAnalysis() +static_structural_analysis = model.Analyses[0] + +# Set the analysis settings +analysis_settings = app.ExtAPI.DataModel.Project.Model.Analyses[0].AnalysisSettings +analysis_settings.AutomaticTimeStepping = AutomaticTimeStepping.On +analysis_settings.NumberOfSubSteps = 10 + +# Activate the analysis settings +analysis_settings.Activate() + +# Add a command snippet to the static structural analysis with the archard wear model +cmd1 = static_structural_analysis.AddCommandSnippet() +# Add convergence criterion using command snippet. +archard_wear_model = """CNVTOL,U,1.0,5e-5,1,,""" +cmd1.AppendText(archard_wear_model) + +# Set the analysis settings for inverse solving +analysis_settings.InverseOption = True +analysis_settings.LargeDeflection = True + +# %% +# Define the boundary conditions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Add rotational velocity to the static structural analysis +rotational_velocity = static_structural_analysis.AddRotationalVelocity() +rotational_velocity.DefineBy = LoadDefineBy.Components +# Set z-component input values for the rotational velocity +rotational_velocity.ZComponent.Inputs[0].DiscreteValues = [ Quantity("0 [s]"), Quantity("1 [s]"), Quantity("2 [s]"), ] -ROT_VEL.ZComponent.Output.DiscreteValues = [ +# Set z-component output values for the rotational velocity +rotational_velocity.ZComponent.Output.DiscreteValues = [ Quantity("0 [rad/s]"), Quantity("1680 [rad/s]"), Quantity("1680 [rad/s]"), ] -# Apply Fixed Support Condition +# Add a fixed support to the static structural analysis +fixed_support = static_structural_analysis.AddFixedSupport() +# Set the fixed support location to the named selection at index 3 +fixed_support.Location = named_selections.Children[3] -Fixed_Support = STAT_STRUC.AddFixedSupport() -selection = NS_GRP.Children[3] -Fixed_Support.Location = selection +# %% +# Import and apply temperature and CFX pressure to the structural blade & its surface +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # %% -# Import CFX pressure -# ~~~~~~~~~~~~~~~~~~~ -# Import CFX pressure data and apply it to structural blade surface - -Imported_Load_Group = STAT_STRUC.AddImportedLoadExternalData() - -external_data_files = Ansys.Mechanical.ExternalData.ExternalDataFileCollection() -external_data_files.SaveFilesWithProject = False -external_data_file_1 = Ansys.Mechanical.ExternalData.ExternalDataFile() -external_data_files.Add(external_data_file_1) -external_data_file_1.Identifier = "File1" -external_data_file_1.Description = "" -external_data_file_1.IsMainFile = False -external_data_file_1.FilePath = cfx_data_path -external_data_file_1.ImportSettings = ( - Ansys.Mechanical.ExternalData.ImportSettingsFactory.GetSettingsForFormat( +# Create a function to process the CFX pressure and temperature data files + + +def process_external_data( + external_data_path: str, + skip_rows: int, + skip_footer: int, + data_type: str, + location_index: int, +): + """Process the external data file and set its properties.""" + # Add imported load external data to the static structural analysis + imported_load_group = static_structural_analysis.AddImportedLoadExternalData() + + external_data_files = Ansys.Mechanical.ExternalData.ExternalDataFileCollection() + external_data_files.SaveFilesWithProject = False + file = Ansys.Mechanical.ExternalData.ExternalDataFile() + external_data_files.Add(file) + + file.Identifier = "File1" + file.Description = "" + file.IsMainFile = False + file.FilePath = external_data_path + # Set the file format to delimited + file.ImportSettings = Ansys.Mechanical.ExternalData.ImportSettingsFactory.GetSettingsForFormat( Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.ImportFormat.Delimited ) -) -import_settings = external_data_file_1.ImportSettings -import_settings.SkipRows = 17 -import_settings.SkipFooter = 0 -import_settings.Delimiter = "," -import_settings.AverageCornerNodesToMidsideNodes = False -import_settings.UseColumn( - 0, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.XCoordinate, - "m", - "X Coordinate@A", -) -import_settings.UseColumn( - 1, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.YCoordinate, - "m", - "Y Coordinate@B", -) -import_settings.UseColumn( - 2, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.ZCoordinate, - "m", - "Z Coordinate@C", -) -import_settings.UseColumn( - 3, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.Pressure, - "Pa", - "Pressure@D", -) - -Imported_Load_Group.ImportExternalDataFiles(external_data_files) -Imported_Pressure = Imported_Load_Group.AddImportedPressure() -selection = NS_GRP.Children[2] -Imported_Pressure.Location = selection -Imported_Pressure.AppliedBy = LoadAppliedBy.Direct -Imported_Pressure.ImportLoad() - -Tree.Activate([Imported_Pressure]) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "imported_pressure.png"), image_export_format, settings_720p -) -display_image("imported_pressure.png") - -################################################################################### -# Import Temperature -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import temperature data and apply it to structural blade + # Set up import settings for the external data file + import_settings = file.ImportSettings + import_settings.SkipRows = skip_rows + import_settings.SkipFooter = skip_footer + import_settings.Delimiter = "," + import_settings.AverageCornerNodesToMidsideNodes = False + import_settings.UseColumn( + 0, + Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.XCoordinate, + "m", + "X Coordinate@A", + ) + import_settings.UseColumn( + 1, + Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.YCoordinate, + "m", + "Y Coordinate@B", + ) + import_settings.UseColumn( + 2, + Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.ZCoordinate, + "m", + "Z Coordinate@C", + ) + if data_type == "pressure": + import_settings.UseColumn( + 3, + Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.Pressure, + "Pa", + "Pressure@D", + ) + elif data_type == "temperature": + import_settings.UseColumn( + 3, + Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.Temperature, + "C", + "Temperature@D", + ) + # Import external data files to the imported load group + imported_load_group.ImportExternalDataFiles(external_data_files) + if data_type == "pressure": + # Add imported pressure to the imported load group + added_obj = imported_load_group.AddImportedPressure() + elif data_type == "temperature": + # Add imported body temperature to the imported load group + added_obj = imported_load_group.AddImportedBodyTemperature() + # Set properties for the imported pressure + added_obj.Location = named_selections.Children[location_index] + if data_type == "pressure": + added_obj.AppliedBy = LoadAppliedBy.Direct + added_obj.ImportLoad() + + return added_obj -Imported_Load_Group = STAT_STRUC.AddImportedLoadExternalData() -external_data_files = Ansys.Mechanical.ExternalData.ExternalDataFileCollection() -external_data_files.SaveFilesWithProject = False -external_data_file_1 = Ansys.Mechanical.ExternalData.ExternalDataFile() -external_data_files.Add(external_data_file_1) -external_data_file_1.Identifier = "File1" -external_data_file_1.Description = "" -external_data_file_1.IsMainFile = False -external_data_file_1.FilePath = temp_data_path +# %% +# Import and apply CFX pressure to the structural blade surface -external_data_file_1.ImportSettings = ( - Ansys.Mechanical.ExternalData.ImportSettingsFactory.GetSettingsForFormat( - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.ImportFormat.Delimited - ) -) -import_settings = external_data_file_1.ImportSettings -import_settings.SkipRows = 0 -import_settings.SkipFooter = 0 -import_settings.Delimiter = "," -import_settings.AverageCornerNodesToMidsideNodes = False -import_settings.UseColumn( - 0, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.XCoordinate, - "m", - "X Coordinate@A", -) -import_settings.UseColumn( - 1, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.YCoordinate, - "m", - "Y Coordinate@B", -) -import_settings.UseColumn( - 2, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.ZCoordinate, - "m", - "Z Coordinate@C", -) -import_settings.UseColumn( - 3, - Ansys.Mechanical.DataModel.MechanicalEnums.ExternalData.VariableType.Temperature, - "C", - "Temperature@D", +pressure = process_external_data( + cfx_data_path, skip_rows=17, skip_footer=0, data_type="pressure", location_index=2 ) -Imported_Load_Group.ImportExternalDataFiles(external_data_files) -imported_body_temperature = Imported_Load_Group.AddImportedBodyTemperature() +# Activate the imported pressure or temperature and display the image +app.Tree.Activate([pressure]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, f"imported_pressure.png" +) -selection = NS_GRP.Children[1] -imported_body_temperature.Location = selection -imported_body_temperature.ImportLoad() +# %% +# Import and apply temperature to the structural blade + +temperature = process_external_data( + temp_data_path, + skip_rows=0, + skip_footer=0, + data_type="temperature", + location_index=1, +) -Tree.Activate([imported_body_temperature]) -Graphics.Camera.SetFit() -Graphics.ExportImage( - os.path.join(cwd, "imported_temperature.png"), image_export_format, settings_720p +# Activate the imported temperature and display the image +app.Tree.Activate([temperature]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, f"imported_temperature.png" ) -display_image("imported_temperature.png") -# %% -# Postprocessing -# ~~~~~~~~~~~~~~ -# Insert results -SOLN = STAT_STRUC.Solution +# %% +# Add results to the solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -TOT_DEF1 = SOLN.AddTotalDeformation() -TOT_DEF1.DisplayTime = Quantity("1 [s]") +# Define the static structural analysis solution +solution = static_structural_analysis.Solution -EQV_STRS1 = SOLN.AddEquivalentStress() -EQV_STRS1.DisplayTime = Quantity("1 [s]") +# Add total deformation results to the solution +total_deformation1 = solution.AddTotalDeformation() +total_deformation1.DisplayTime = Quantity("1 [s]") -EQV_TOT_STRN1 = SOLN.AddEquivalentTotalStrain() -EQV_TOT_STRN1.DisplayTime = Quantity("1 [s]") +# Add equivalent stress results to the solution +equivalent_stress1 = solution.AddEquivalentStress() +equivalent_stress1.DisplayTime = Quantity("1 [s]") -THERM_STRN1 = SOLN.AddThermalStrain() -THERM_STRN1.DisplayTime = Quantity("1 [s]") +# Add equivalent total strain results to the solution +equivalent_total_strain1 = solution.AddEquivalentTotalStrain() +equivalent_total_strain1.DisplayTime = Quantity("1 [s]") +# Add thermal strain results to the solution +thermal_strain1 = solution.AddThermalStrain() +thermal_strain1.DisplayTime = Quantity("1 [s]") # %% -# Run Solution -# ~~~~~~~~~~~~ -# Solve inverse analysis on blade model +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ -SOLN.Solve(True) -STAT_STRUC_SS = SOLN.Status +# Solve the inverse analysis on the blade model +solution.Solve(True) +soln_status = solution.Status # %% # Postprocessing # ~~~~~~~~~~~~~~ -# Evaluate results and export screenshots # %% -# Total deformation +# Display the total deformation image -Tree.Activate([TOT_DEF1]) -Graphics.ViewOptions.ResultPreference.ExtraModelDisplay = ( +# Activate the total deformation results +app.Tree.Activate([total_deformation1]) +# Set the extra model display to no wireframe +graphics.ViewOptions.ResultPreference.ExtraModelDisplay = ( Ansys.Mechanical.DataModel.MechanicalEnums.Graphics.ExtraModelDisplay.NoWireframe ) -Graphics.ExportImage( - os.path.join(cwd, "deformation.png"), image_export_format, settings_720p +# Set the camera to fit the model and export the image +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_deformation.png" ) -display_image("deformation.png") # %% -# Equivalent stress +# Display the thermal strain image -Tree.Activate([THERM_STRN1]) -Graphics.ExportImage( - os.path.join(cwd, "thermal_strain.png"), image_export_format, settings_720p +# Activate the thermal strain results +app.Tree.Activate([thermal_strain1]) +# Set the camera to fit the model and export the image +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "thermal_strain.png" ) -display_image("thermal_strain.png") # %% -# Cleanup -# ~~~~~~~ -# Save project +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ -app.save(os.path.join(cwd, "blade_inverse.mechdat")) -app.new() +# Save the project +mechdat_file = output_path / "blade_inverse.mechdat" +app.save(str(mechdat_file)) -# %% -# Delete example file +# Close the app +app.close() +# Delete the example file delete_downloads() diff --git a/examples/02_technology_showcase/conact_wear_simulation.py b/examples/02_technology_showcase/conact_wear_simulation.py deleted file mode 100644 index 389b1bdf..00000000 --- a/examples/02_technology_showcase/conact_wear_simulation.py +++ /dev/null @@ -1,407 +0,0 @@ -# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""".. _ref_contact_wear_simulation: - -Contact Surface Wear Simulation -------------------------------- - -Using a Archard wear model, this example demonstrates contact sliding -of a hemispherical ring on a flat ring to produce wear. - -The model includes: - -- Hemispherical ring with a radius of 30 mm made of copper. -- Flat ring with an inner radius of 50 mm and an outer radius of 150 mm made of steel. - -The hemispherical ring is in contact with the flat ring at the center -from the axis of rotation at 100 mm and is subjected to a -1) pressure of 4000 N/mm2 and 2) a rotation with a frequency -of 100,000 revolutions/sec. - -The application evaluates total deformation and normal stress results, -in loading direction, prior to and following wear. In addition, -contact pressure prior to wear is evaluated. -""" - -# %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ - -import os - -from PIL import Image -from ansys.mechanical.core import App -from ansys.mechanical.core.examples import delete_downloads, download_file -from matplotlib import image as mpimg -from matplotlib import pyplot as plt -from matplotlib.animation import FuncAnimation - -# %% -# Embed mechanical and set global variables - -app = App() -app.update_globals(globals()) -print(app) - -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") - plt.show() - - -# %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Front) -image_export_format = GraphicsImageExportFormat.PNG -settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() -settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False -Graphics.Camera.Rotate(180, CameraAxisType.ScreenY) - -# %% -# Download geometry and materials files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -geometry_path = download_file("example_07_td43_wear.agdb", "pymechanical", "00_basic") -mat1_path = download_file("example_07_Mat_Copper.xml", "pymechanical", "00_basic") -mat2_path = download_file("example_07_Mat_Steel.xml", "pymechanical", "00_basic") - -# %% -# Import geometry -# ~~~~~~~~~~~~~~~ - -geometry_import = Model.GeometryImportGroup.AddGeometryImport() -geometry_import_format = ( - Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic -) -geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() -geometry_import_preferences.ProcessNamedSelections = True -geometry_import_preferences.ProcessCoordinateSystems = True -geometry_import.Import( - geometry_path, geometry_import_format, geometry_import_preferences -) - -app.plot() - -# %% -# Import materials -# ~~~~~~~~~~~~~~~~ - -MAT = Model.Materials -MAT.Import(mat1_path) -MAT.Import(mat2_path) - -print("Material import done !") - -# %% -# Setup the Analysis -# ~~~~~~~~~~~~~~~~~~ -# Set up the unit system - -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM - -# %% -# Store all main tree nodes as variables - -MODEL = Model -GEOM = Model.Geometry -CS_GRP = Model.CoordinateSystems -CONN_GRP = Model.Connections -MSH = Model.Mesh -NS_GRP = Model.NamedSelections - -# %% -# Add static structural analysis - -Model.AddStaticStructuralAnalysis() -STAT_STRUC = Model.Analyses[0] -STAT_STRUC_SOLN = STAT_STRUC.Solution -STAT_STRUC_ANA_SETTING = STAT_STRUC.Children[0] - -# %% -# Store name selection - -CURVE_NS = [x for x in Tree.AllObjects if x.Name == "curve"][0] -DIA_NS = [x for x in Tree.AllObjects if x.Name == "dia"][0] -VER_EDGE1 = [x for x in Tree.AllObjects if x.Name == "v1"][0] -VER_EDGE2 = [x for x in Tree.AllObjects if x.Name == "v2"][0] -HOR_EDGE1 = [x for x in Tree.AllObjects if x.Name == "h1"][0] -HOR_EDGE2 = [x for x in Tree.AllObjects if x.Name == "h2"][0] -ALL_BODIES_NS = [x for x in Tree.AllObjects if x.Name == "all_bodies"][0] - -# %% -# Assign material to bodies and change behavior to axisymmetric - -GEOM.Model2DBehavior = Model2DBehavior.AxiSymmetric - -SURFACE1 = GEOM.Children[0].Children[0] -SURFACE1.Material = "Steel" -SURFACE1.Dimension = ShellBodyDimension.Two_D - -SURFACE2 = GEOM.Children[1].Children[0] -SURFACE2.Material = "Copper" -SURFACE2.Dimension = ShellBodyDimension.Two_D - -# %% -# Change contact settings - -CONT_REG = CONN_GRP.AddContactRegion() -CONT_REG.SourceLocation = NS_GRP.Children[6] -CONT_REG.TargetLocation = NS_GRP.Children[3] -# CONT_REG.FlipContactTarget() -CONT_REG.ContactType = ContactType.Frictionless -CONT_REG.Behavior = ContactBehavior.Asymmetric -CONT_REG.ContactFormulation = ContactFormulation.AugmentedLagrange -CONT_REG.DetectionMethod = ContactDetectionPoint.NodalNormalToTarget - -# %% -# Add a command snippet to use Archard Wear Model - -AWM = """keyo,cid,5,1 -keyo,cid,10,2 -pi=acos(-1) -slide_velocity=1e5 -Uring_offset=100 -kcopper=10e-13*slide_velocity*2*pi*Uring_offset -TB,WEAR,cid,,,ARCD -TBFIELD,TIME,0 -TBDATA,1,0,1,1,0,0 -TBFIELD,TIME,1 -TBDATA,1,0,1,1,0,0 -TBFIELD,TIME,1.01 -TBDATA,1,kcopper,1,1,0,0 -TBFIELD,TIME,4 -TBDATA,1,kcopper,1,1,0,0""" -CMD1 = CONT_REG.AddCommandSnippet() -CMD1.AppendText(AWM) - -# %% -# Insert remote point - -REM_PT = MODEL.AddRemotePoint() -REM_PT.Location = DIA_NS -REM_PT.Behavior = LoadBehavior.Rigid - -# %% -# Mesh -# ~~~~ - -MSH.ElementOrder = ElementOrder.Linear -MSH.ElementSize = Quantity("1 [mm]") - -EDGE_SIZING1 = MSH.AddSizing() -EDGE_SIZING1.Location = HOR_EDGE1 -EDGE_SIZING1.Type = SizingType.NumberOfDivisions -EDGE_SIZING1.NumberOfDivisions = 70 - -EDGE_SIZING2 = MSH.AddSizing() -EDGE_SIZING2.Location = HOR_EDGE2 -EDGE_SIZING2.Type = SizingType.NumberOfDivisions -EDGE_SIZING2.NumberOfDivisions = 70 - -EDGE_SIZING3 = MSH.AddSizing() -EDGE_SIZING3.Location = VER_EDGE1 -EDGE_SIZING3.Type = SizingType.NumberOfDivisions -EDGE_SIZING3.NumberOfDivisions = 35 - -EDGE_SIZING4 = MSH.AddSizing() -EDGE_SIZING4.Location = VER_EDGE2 -EDGE_SIZING4.Type = SizingType.NumberOfDivisions -EDGE_SIZING4.NumberOfDivisions = 35 - -EDGE_SIZING5 = MSH.AddSizing() -EDGE_SIZING5.Location = DIA_NS -EDGE_SIZING5.Type = SizingType.NumberOfDivisions -EDGE_SIZING5.NumberOfDivisions = 40 - -EDGE_SIZING6 = MSH.AddSizing() -EDGE_SIZING6.Location = CURVE_NS -EDGE_SIZING6.Type = SizingType.NumberOfDivisions -EDGE_SIZING6.NumberOfDivisions = 60 - -MSH.GenerateMesh() - -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") - -# %% -# Analysis settings -# ~~~~~~~~~~~~~~~~~ - -STAT_STRUC_ANA_SETTING.NumberOfSteps = 2 -STAT_STRUC_ANA_SETTING.CurrentStepNumber = 1 -STAT_STRUC_ANA_SETTING.AutomaticTimeStepping = AutomaticTimeStepping.On -STAT_STRUC_ANA_SETTING.DefineBy = TimeStepDefineByType.Time -STAT_STRUC_ANA_SETTING.InitialTimeStep = Quantity("0.1 [s]") -STAT_STRUC_ANA_SETTING.MinimumTimeStep = Quantity("0.0001 [s]") -STAT_STRUC_ANA_SETTING.MaximumTimeStep = Quantity("1 [s]") -STAT_STRUC_ANA_SETTING.CurrentStepNumber = 2 -STAT_STRUC_ANA_SETTING.Activate() -STAT_STRUC_ANA_SETTING.StepEndTime = Quantity("4 [s]") -STAT_STRUC_ANA_SETTING.AutomaticTimeStepping = AutomaticTimeStepping.On -STAT_STRUC_ANA_SETTING.DefineBy = TimeStepDefineByType.Time -STAT_STRUC_ANA_SETTING.InitialTimeStep = Quantity("0.01 [s]") -STAT_STRUC_ANA_SETTING.MinimumTimeStep = Quantity("0.000001 [s]") -STAT_STRUC_ANA_SETTING.MaximumTimeStep = Quantity("0.02 [s]") - -STAT_STRUC_ANA_SETTING.LargeDeflection = True - -# %% -# Insert loading and boundary conditions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -FIX_SUP = STAT_STRUC.AddFixedSupport() -FIX_SUP.Location = HOR_EDGE1 - -REM_DISP = STAT_STRUC.AddRemoteDisplacement() -REM_DISP.Location = REM_PT -REM_DISP.XComponent.Output.DiscreteValues = [Quantity("0[mm]")] -REM_DISP.RotationZ.Output.DiscreteValues = [Quantity("0[deg]")] - -REM_FRC = STAT_STRUC.AddRemoteForce() -REM_FRC.Location = REM_PT -REM_FRC.DefineBy = LoadDefineBy.Components -REM_FRC.YComponent.Output.DiscreteValues = [Quantity("-150796320 [N]")] - -# Nonlinear Adaptivity does not support contact criterion yet hence command snippet used - -NLAD = """NLADAPTIVE,all,add,contact,wear,0.50 -NLADAPTIVE,all,on,all,all,1,,4 -NLADAPTIVE,all,list,all,all""" -CMD2 = STAT_STRUC.AddCommandSnippet() -CMD2.AppendText(NLAD) -CMD2.StepSelectionMode = SequenceSelectionType.All - -STAT_STRUC.Activate() -Graphics.Camera.SetFit() -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") - -# %% -# Insert results -# ~~~~~~~~~~~~~~ - -TOT_DEF = STAT_STRUC_SOLN.AddTotalDeformation() - -NORM_STRS1 = STAT_STRUC_SOLN.AddNormalStress() -NORM_STRS1.NormalOrientation = NormalOrientationType.YAxis -NORM_STRS1.DisplayTime = Quantity("1 [s]") -NORM_STRS1.DisplayOption = ResultAveragingType.Unaveraged - -NORM_STRS2 = STAT_STRUC_SOLN.AddNormalStress() -NORM_STRS2.NormalOrientation = NormalOrientationType.YAxis -NORM_STRS2.DisplayTime = Quantity("4 [s]") -NORM_STRS2.DisplayOption = ResultAveragingType.Unaveraged - -CONT_TOOL = STAT_STRUC_SOLN.AddContactTool() -CONT_TOOL.ScopingMethod = GeometryDefineByType.Geometry -SEL1 = ExtAPI.SelectionManager.AddSelection(ALL_BODIES_NS) -SEL2 = ExtAPI.SelectionManager.CurrentSelection -CONT_TOOL.Location = SEL2 -ExtAPI.SelectionManager.ClearSelection() -CONT_PRES1 = CONT_TOOL.AddPressure() -CONT_PRES1.DisplayTime = Quantity("1 [s]") - -CONT_PRES2 = CONT_TOOL.AddPressure() -CONT_PRES2.DisplayTime = Quantity("4 [s]") - -# %% -# Solve -# ~~~~~ - -STAT_STRUC_SOLN.Solve(True) -STAT_STRUC_SS = STAT_STRUC_SOLN.Status -# sphinx_gallery_start_ignore -assert str(STAT_STRUC_SS) == "Done", "Solution status is not 'Done'" -# sphinx_gallery_end_ignore - -# %% -# Postprocessing -# ~~~~~~~~~~~~~~ -# Normal stress - -Tree.Activate([NORM_STRS1]) -Graphics.ExportImage( - os.path.join(cwd, "normal_stresss.png"), image_export_format, settings_720p -) -display_image("normal_stresss.png") - -# %% -# Total deformation animation - -animation_export_format = ( - Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF -) -settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() -settings_720p.Width = 1280 -settings_720p.Height = 720 - -TOT_DEF.ExportAnimation( - os.path.join(cwd, "totaldeformation.gif"), animation_export_format, settings_720p -) -gif = Image.open(os.path.join(cwd, "totaldeformation.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) - - -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] - - -ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=200, repeat=True, blit=True -) -plt.show() - - -# %% -# Project tree -# ~~~~~~~~~~~~ - -app.print_tree() - -# %% -# Cleanup -# ~~~~~~~ -# Save project - -app.save(os.path.join(cwd, "contact_wear.mechdat")) -app.new() - -# delete example file -delete_downloads() diff --git a/examples/02_technology_showcase/contact_wear_simulation.py b/examples/02_technology_showcase/contact_wear_simulation.py new file mode 100644 index 00000000..9b57d840 --- /dev/null +++ b/examples/02_technology_showcase/contact_wear_simulation.py @@ -0,0 +1,611 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""".. _ref_contact_wear_simulation: + +Contact Surface Wear Simulation +------------------------------- + +Using a Archard wear model, this example demonstrates contact sliding +of a hemispherical ring on a flat ring to produce wear. + +The model includes: + +- Hemispherical ring with a radius of 30 mm made of copper. +- Flat ring with an inner radius of 50 mm and an outer radius of 150 mm made of steel. + +The hemispherical ring is in contact with the flat ring at the center +from the axis of rotation at 100 mm and is subjected to a +1) pressure of 4000 N/mm2 and 2) a rotation with a frequency +of 100,000 revolutions/sec. + +The application evaluates total deformation and normal stress results, +in loading direction, prior to and following wear. In addition, +contact pressure prior to wear is evaluated. +""" + +# %% +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from pathlib import Path +from typing import TYPE_CHECKING + +from PIL import Image +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file +from matplotlib import image as mpimg +from matplotlib import pyplot as plt +from matplotlib.animation import FuncAnimation + +if TYPE_CHECKING: + import Ansys + +# %% +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +app = App(globals=globals()) +print(app) + +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure + plt.show() + + +# %% +# Configure graphics for image export +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to the front view +camera.SetSpecificViewOrientation(ViewOrientationType.Front) + +# Set the image export format and settings +image_export_format = GraphicsImageExportFormat.PNG +settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() +settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution +settings_720p.Background = GraphicsBackgroundType.White +settings_720p.Width = 1280 +settings_720p.Height = 720 +settings_720p.CurrentGraphicsDisplay = False + +# Rotate the camera on the y-axis +camera.Rotate(180, CameraAxisType.ScreenY) + +# %% +# Download the geometry and material files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Download the geometry and material files from the specified paths +geometry_path = download_file("example_07_td43_wear.agdb", "pymechanical", "00_basic") +mat1_path = download_file("example_07_Mat_Copper.xml", "pymechanical", "00_basic") +mat2_path = download_file("example_07_Mat_Steel.xml", "pymechanical", "00_basic") + +# %% +# Import the geometry +# ~~~~~~~~~~~~~~~~~~~ + +# Define the model +model = app.Model + +# Add a geometry import to the geometry import group +geometry_import = model.GeometryImportGroup.AddGeometryImport() + +# Set the geometry import settings +geometry_import_format = ( + Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +) +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessCoordinateSystems = True + +# Import the geometry using the specified settings +geometry_import.Import( + geometry_path, geometry_import_format, geometry_import_preferences +) + +# Visualize the model in 3D +app.plot() + +# %% +# Import the materials +# ~~~~~~~~~~~~~~~~~~~~ + +# Define the materials for the model +materials = model.Materials + +# Import the copper and steel materials +materials.Import(mat1_path) +materials.Import(mat2_path) + +# %% +# Set up the analysis +# ~~~~~~~~~~~~~~~~~~~ + +# Set up the unit system +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM + +# %% +# Store all main tree nodes as variables + +geometry = model.Geometry +coordinate_systems = model.CoordinateSystems +connections = model.Connections +mesh = model.Mesh +named_selections = model.NamedSelections + +# %% +# Add the static structural analysis + +model.AddStaticStructuralAnalysis() +static_structural_analysis = model.Analyses[0] + +# Store the static structural analysis solution +stat_struct_soln = static_structural_analysis.Solution + +# Get the analysis settings for the static structural analysis +analysis_settings = static_structural_analysis.Children[0] + +# %% +# Store the named selections as variables + + +def get_named_selection(name: str): + """Get the named selection by name.""" + return app.DataModel.GetObjectsByName(name)[0] + + +curve_named_selection = get_named_selection("curve") +dia_named_selection = get_named_selection("dia") +ver_edge1 = get_named_selection("v1") +ver_edge2 = get_named_selection("v2") +hor_edge1 = get_named_selection("h1") +hor_edge2 = get_named_selection("h2") +all_bodies_named_selection = get_named_selection("all_bodies") + +# %% +# Assign material to the bodies + +# Set the model's 2D behavior to axi-symmetric +geometry.Model2DBehavior = Model2DBehavior.AxiSymmetric + + +def set_material_and_dimension( + surface_child_index, material, dimension=ShellBodyDimension.Two_D +): + """Set the material and dimension for a given surface.""" + surface = geometry.Children[surface_child_index].Children[0] + surface.Material = material + surface.Dimension = dimension + + +# Set the material and dimensions for the surface +set_material_and_dimension(0, "Steel") +set_material_and_dimension(1, "Copper") + +# %% +# Configure settings for the contact region + +# Add a contact region between the hemispherical ring and the flat ring +contact_region = connections.AddContactRegion() +# Set the source and target locations for the contact region +contact_region.SourceLocation = named_selections.Children[6] +contact_region.TargetLocation = named_selections.Children[3] +# Set contact region properties +contact_region.ContactType = ContactType.Frictionless +contact_region.Behavior = ContactBehavior.Asymmetric +contact_region.ContactFormulation = ContactFormulation.AugmentedLagrange +contact_region.DetectionMethod = ContactDetectionPoint.NodalNormalToTarget + +# %% +# Add a command snippet to use Archard Wear Model + +archard_wear_model = """keyo,cid,5,1 +keyo,cid,10,2 +pi=acos(-1) +slide_velocity=1e5 +Uring_offset=100 +kcopper=10e-13*slide_velocity*2*pi*Uring_offset +TB,WEAR,cid,,,ARCD +TBFIELD,TIME,0 +TBDATA,1,0,1,1,0,0 +TBFIELD,TIME,1 +TBDATA,1,0,1,1,0,0 +TBFIELD,TIME,1.01 +TBDATA,1,kcopper,1,1,0,0 +TBFIELD,TIME,4 +TBDATA,1,kcopper,1,1,0,0""" +cmd1 = contact_region.AddCommandSnippet() +cmd1.AppendText(archard_wear_model) + +# %% +# Insert a remote point + +# Add a remote point to the model +remote_point = model.AddRemotePoint() +# Set the remote point location to the center of the hemispherical ring +remote_point.Location = dia_named_selection +remote_point.Behavior = LoadBehavior.Rigid + +# %% +# Set properties for the mesh +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the mesh element order and size +mesh.ElementOrder = ElementOrder.Linear +mesh.ElementSize = Quantity("1 [mm]") + +# %% +# Create a function to add edge sizing and properties + + +def add_edge_sizing_and_properties( + mesh, location, divisions, sizing_type=SizingType.NumberOfDivisions +): + """Set the sizing properties for a given mesh. + + Parameters + ---------- + mesh : Ansys.Mechanical.DataModel.Mesh + The mesh object to set the properties for. + location : Ansys.Mechanical.DataModel.NamedSelection + The location of the edge to set the sizing for. + divisions : int + The number of divisions for the edge. + sizing_type : SizingType + The type of sizing to apply (default is NumberOfDivisions). + """ + edge_sizing = mesh.AddSizing() + edge_sizing.Location = location + edge_sizing.Type = sizing_type + edge_sizing.NumberOfDivisions = divisions + + +# %% +# Add edge sizing and properties to the mesh for each named selection + +add_edge_sizing_and_properties(mesh, hor_edge1, 70) +add_edge_sizing_and_properties(mesh, hor_edge2, 70) +add_edge_sizing_and_properties(mesh, ver_edge1, 35) +add_edge_sizing_and_properties(mesh, ver_edge2, 35) +add_edge_sizing_and_properties(mesh, dia_named_selection, 40) +add_edge_sizing_and_properties(mesh, curve_named_selection, 60) + +# %% +# Generate the mesh and display the image + +mesh.GenerateMesh() +set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") + +# %% +# Set the analysis settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Create a function to set time steps for the analysis settings + + +def set_time_steps(initial: str, min: str, max: str) -> None: + """Set the time step properties for the analysis settings. + + Parameters + ---------- + initial : str + The initial time step value. + min : str + The minimum time step value. + max : str + The maximum time step value. + """ + analysis_settings.InitialTimeStep = Quantity(initial) + analysis_settings.MinimumTimeStep = Quantity(min) + analysis_settings.MaximumTimeStep = Quantity(max) + + +# %% +# Set the analysis settings for the static structural analysis + +analysis_settings.NumberOfSteps = 2 +analysis_settings.CurrentStepNumber = 1 +analysis_settings.AutomaticTimeStepping = AutomaticTimeStepping.On +analysis_settings.DefineBy = TimeStepDefineByType.Time +set_time_steps(initial="0.1 [s]", min="0.0001 [s]", max="1 [s]") +analysis_settings.CurrentStepNumber = 2 +analysis_settings.Activate() +analysis_settings.StepEndTime = Quantity("4 [s]") +analysis_settings.AutomaticTimeStepping = AutomaticTimeStepping.On +analysis_settings.DefineBy = TimeStepDefineByType.Time +set_time_steps(initial="0.01 [s]", min="0.000001 [s]", max="0.02 [s]") +analysis_settings.LargeDeflection = True + +# %% +# Insert loading and boundary conditions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Add a fixed support to the model +fixed_support = static_structural_analysis.AddFixedSupport() +# Set the fixed support location to the first horizontal edge +fixed_support.Location = hor_edge1 + +# Add a remote displacement to the model +remote_displacement = static_structural_analysis.AddRemoteDisplacement() +# Set the remote displacement location to the remote point +remote_displacement.Location = remote_point +# Add the values for the x-component and rotation about the z-axis +remote_displacement.XComponent.Output.DiscreteValues = [Quantity("0[mm]")] +remote_displacement.RotationZ.Output.DiscreteValues = [Quantity("0[deg]")] + +# Add a remote force to the model +remote_force = static_structural_analysis.AddRemoteForce() +# Set the remote force location to the remote point +remote_force.Location = remote_point +# Set the remote force values for the y-component +remote_force.DefineBy = LoadDefineBy.Components +remote_force.YComponent.Output.DiscreteValues = [Quantity("-150796320 [N]")] + +# Nonlinear adaptivity does not support contact criterion yet so a command snippet is used instead +nonlinear_adaptivity = """NLADAPTIVE,all,add,contact,wear,0.50 +NLADAPTIVE,all,on,all,all,1,,4 +NLADAPTIVE,all,list,all,all""" + +# Add the nonlinear adaptivity command snippet to the static structural analysis +cmd2 = static_structural_analysis.AddCommandSnippet() +cmd2.AppendText(nonlinear_adaptivity) +cmd2.StepSelectionMode = SequenceSelectionType.All + +# Activate the static structural analysis and display the mesh image +static_structural_analysis.Activate() +set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") + +# %% +# Add results to the solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +def set_properties_for_result( + result, + display_time, + orientation_type=NormalOrientationType.YAxis, + display_option=ResultAveragingType.Unaveraged, +): + """Set the properties for a given result.""" + result.NormalOrientation = orientation_type + result.DisplayTime = Quantity(display_time) + result.DisplayOption = display_option + + +# Add total deformation to the solution +total_deformation = stat_struct_soln.AddTotalDeformation() + +# Add normal stress to the solution +normal_stress1 = stat_struct_soln.AddNormalStress() +set_properties_for_result(normal_stress1, display_time="1 [s]") +normal_stress2 = stat_struct_soln.AddNormalStress() +set_properties_for_result(normal_stress1, display_time="4 [s]") + +# Add a contact tool to the solution +contact_tool = stat_struct_soln.AddContactTool() +contact_tool.ScopingMethod = GeometryDefineByType.Geometry +# Add selections for the contact tool +selection1 = app.ExtAPI.SelectionManager.AddSelection(all_bodies_named_selection) +selection2 = app.ExtAPI.SelectionManager.CurrentSelection +# Set the contact tool location to the current selection +contact_tool.Location = selection2 +# Clear the selection +app.ExtAPI.SelectionManager.ClearSelection() + +# %% +# Add contact pressure to the contact tool + + +def add_contact_pressure(contact_tool, display_time): + """Add a contact pressure to the contact tool.""" + contact_pressure = contact_tool.AddPressure() + contact_pressure.DisplayTime = Quantity(display_time) + + +# Add pressure to the contact tool +add_contact_pressure(contact_tool, display_time="0 [s]") +add_contact_pressure(contact_tool, display_time="4 [s]") + +# %% +# Solve the solution +# ~~~~~~~~~~~~~~~~~~ + +stat_struct_soln.Solve(True) +# sphinx_gallery_start_ignore +assert ( + stat_struct_soln.Status == SolutionStatusType.Done +), "Solution status is not 'Done'" +# sphinx_gallery_end_ignore + +# %% +# Postprocessing +# ~~~~~~~~~~~~~~ + +# %% +# Activate the first normal stress result and display the image + +app.Tree.Activate([normal_stress1]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "normal_stress.png" +) + +# %% +# Create a function to update the animation frame + + +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. + + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] + + +# %% +# Display the total deformation animation + +# Set the animation export format +animation_export_format = ( + Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF +) +# Set the animation export settings +settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() +settings_720p.Width = 1280 +settings_720p.Height = 720 + +# Export the animation +total_deformation_gif = output_path / "total_deformation.gif" +total_deformation.ExportAnimation( + str(total_deformation_gif), animation_export_format, settings_720p +) + +# Open the GIF file and create an animation +gif = Image.open(total_deformation_gif) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) + +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation +FuncAnimation( + figure, + update_animation, + frames=range(gif.n_frames), + interval=200, + repeat=True, + blit=True, +) + +# Show the animation +plt.show() + +# %% +# Print the project tree +# ~~~~~~~~~~~~~~~~~~~~~~ + +app.print_tree() + +# %% +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ + +# Save the project file +mechdat_file = output_path / "contact_wear.mechdat" +app.save(str(mechdat_file)) + +# Close the app +app.close() + +# Delete the downloaded files +delete_downloads() diff --git a/examples/02_technology_showcase/non_linear_analsis_rubber_boot_seal.py b/examples/02_technology_showcase/non_linear_analsis_rubber_boot_seal.py deleted file mode 100644 index acab37e0..00000000 --- a/examples/02_technology_showcase/non_linear_analsis_rubber_boot_seal.py +++ /dev/null @@ -1,495 +0,0 @@ -# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""".. _ref_non_linear_analysis_rubber_boot_seal: - -Nonlinear Analysis of a Rubber Boot Seal Model ----------------------------------------------- - -This example demonstrates a nonlinear 3D analysis of a rubber boot seal to: - -- Create a rigid-flexible contact pair between a rigid shaft and a - rubber boot part. -- Specify ramped effects using the On Gauss Point Detection Method - to update contact stiffness at each iteration. -- Specify contact pairs at the inner and outer surfaces of the rubber boot. -- Specify non-ramped effects using the Nodal-Projected Normal From Contact - Detection Method to update contact stiffness at each iteration. -""" - -# %% -# Import necessary libraries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ - -import os - -from PIL import Image -from ansys.mechanical.core import App -from ansys.mechanical.core.examples import delete_downloads, download_file -from matplotlib import image as mpimg -from matplotlib import pyplot as plt -from matplotlib.animation import FuncAnimation - -# %% -# Embed mechanical and set global variables - -app = App() -app.update_globals(globals()) -print(app) - -cwd = os.path.join(os.getcwd(), "out") - - -def display_image(image_name): - plt.figure(figsize=(16, 9)) - plt.imshow(mpimg.imread(os.path.join(cwd, image_name))) - plt.xticks([]) - plt.yticks([]) - plt.axis("off") - plt.show() - - -# %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Iso) -Graphics.Camera.SetFit() -image_export_format = GraphicsImageExportFormat.PNG -settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() -settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False - -# %% -# Download geometry and materials files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -geometry_path = download_file( - "example_05_td26_Rubber_Boot_Seal.agdb", "pymechanical", "00_basic" -) -mat_path = download_file("example_05_Boot_Mat.xml", "pymechanical", "00_basic") - -# %% -# Import geometry and material -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import material - -materials = Model.Materials -materials.Import(mat_path) -print("Material import done !") -# %% -# Import geometry - -geometry_import = Model.GeometryImportGroup.AddGeometryImport() -geometry_import_format = ( - Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic -) -geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() -geometry_import_preferences.ProcessNamedSelections = True -geometry_import_preferences.ProcessCoordinateSystems = True -geometry_import.Import( - geometry_path, geometry_import_format, geometry_import_preferences -) - -app.plot() - -# %% -# Setup the Analysis -# ~~~~~~~~~~~~~~~~~~ -# Set up the unit system - -ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM -ExtAPI.Application.ActiveAngleUnit = AngleUnitType.Radian - -# %% -# Store all main tree nodes as variables - -GEOM = Model.Geometry -PRT1 = [x for x in Tree.AllObjects if x.Name == "Part"][0] -PRT2 = [x for x in Tree.AllObjects if x.Name == "Solid"][1] -CS_GRP = Model.CoordinateSystems -GCS = CS_GRP.Children[0] - -# %% -# Add static structural analysis - -Model.AddStaticStructuralAnalysis() -STAT_STRUC = Model.Analyses[0] -ANA_SETTING = STAT_STRUC.Children[0] -STAT_STRUC_SOLN = STAT_STRUC.Solution -SOLN_INFO = STAT_STRUC_SOLN.SolutionInformation - -# %% -# Define named selection and coordinate system - -NS_GRP = ExtAPI.DataModel.Project.Model.NamedSelections -TOP_FACE = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Top_Face" -][0] -BOTTOM_FACE = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Bottom_Face" -][0] -SYMM_FACES30 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Symm_Faces30" -][0] -FACES2 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Faces2" -][0] -CYL_FACES2 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Cyl_Faces2" -][0] -RUBBER_BODIES30 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Rubber_Bodies30" -][0] -INNER_FACES30 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Inner_Faces30" -][0] -OUTER_FACES30 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Outer_Faces30" -][0] -SHAFT_FACE = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Shaft_Face" -][0] -SYMM_FACES15 = [ - i - for i in NS_GRP.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) - if i.Name == "Symm_Faces15" -][0] - -LCS1 = CS_GRP.AddCoordinateSystem() -LCS1.OriginY = Quantity("97[mm]") - -# %% -# Assign material - -PRT1.Material = "Boot" -PRT2.StiffnessBehavior = StiffnessBehavior.Rigid - -# %% -# Define connections - -CONN_GRP = Model.Connections -CONT_REG1 = CONN_GRP.AddContactRegion() -CONT_REG1.TargetLocation = SHAFT_FACE -CONT_REG1.SourceLocation = INNER_FACES30 -CONT_REG1.ContactType = ContactType.Frictional -CONT_REG1.FrictionCoefficient = 0.2 -CONT_REG1.Behavior = ContactBehavior.Asymmetric -CONT_REG1.SmallSliding = ContactSmallSlidingType.Off -CONT_REG1.DetectionMethod = ContactDetectionPoint.OnGaussPoint -CONT_REG1.UpdateStiffness = UpdateContactStiffness.EachIteration -CONT_REG1.InterfaceTreatment = ContactInitialEffect.AddOffsetRampedEffects -CONT_REG1.TargetGeometryCorrection = TargetCorrection.Smoothing -CONT_REG1.TargetOrientation = TargetOrientation.Cylinder -CONT_REG1.TargetStartingPoint = GCS -CONT_REG1.TargetEndingPoint = LCS1 - -CONTS = CONN_GRP.Children[0] -CONT_REG2 = CONTS.AddContactRegion() -CONT_REG2.SourceLocation = INNER_FACES30 -CONT_REG2.TargetLocation = INNER_FACES30 -CONT_REG2.ContactType = ContactType.Frictional -CONT_REG2.FrictionCoefficient = 0.2 -CONT_REG2.Behavior = ContactBehavior.Asymmetric -CONT_REG2.SmallSliding = ContactSmallSlidingType.Off -CONT_REG2.DetectionMethod = ContactDetectionPoint.NodalProjectedNormalFromContact -CONT_REG2.UpdateStiffness = UpdateContactStiffness.EachIteration -CONT_REG2.NormalStiffnessValueType = ElementControlsNormalStiffnessType.Factor -CONT_REG2.NormalStiffnessFactor = 1 - -CONT_REG3 = CONTS.AddContactRegion() -CONT_REG3.SourceLocation = OUTER_FACES30 -CONT_REG3.TargetLocation = OUTER_FACES30 -CONT_REG3.ContactType = ContactType.Frictional -CONT_REG3.FrictionCoefficient = 0.2 -CONT_REG3.Behavior = ContactBehavior.Asymmetric -CONT_REG3.SmallSliding = ContactSmallSlidingType.Off -CONT_REG3.DetectionMethod = ContactDetectionPoint.NodalProjectedNormalFromContact -CONT_REG3.UpdateStiffness = UpdateContactStiffness.EachIteration -CONT_REG3.NormalStiffnessValueType = ElementControlsNormalStiffnessType.Factor -CONT_REG3.NormalStiffnessFactor = 1 - -# %% -# Mesh -# ~~~~ - -MSH = Model.Mesh -FACE_MSH = MSH.AddFaceMeshing() -FACE_MSH.Location = SHAFT_FACE -FACE_MSH.InternalNumberOfDivisions = 1 - -MSH_SIZE = MSH.AddSizing() -MSH_SIZE.Location = SYMM_FACES15 -MSH_SIZE.ElementSize = Quantity("2 [mm]") - -MSH.ElementOrder = ElementOrder.Linear -MSH.Resolution = 2 - -MSH.GenerateMesh() - -Graphics.ExportImage(os.path.join(cwd, "mesh.png"), image_export_format, settings_720p) -display_image("mesh.png") - -# %% -# Define remote points -# ~~~~~~~~~~~~~~~~~~~~ -# scope them to the top and bottom faces of rigid shaft - -RMPT01 = Model.AddRemotePoint() -RMPT01.Location = BOTTOM_FACE -RMPT01.Behavior = LoadBehavior.Rigid - -RMPT02 = Model.AddRemotePoint() -RMPT02.Location = TOP_FACE -RMPT02.Behavior = LoadBehavior.Rigid - -# %% -# Analysis settings -# ~~~~~~~~~~~~~~~~~ - -ANA_SETTING.Activate() -ANA_SETTING.LargeDeflection = True -ANA_SETTING.Stabilization = StabilizationType.Off - -ANA_SETTING.NumberOfSteps = 2 -ANA_SETTING.CurrentStepNumber = 1 -ANA_SETTING.AutomaticTimeStepping = AutomaticTimeStepping.On -ANA_SETTING.DefineBy = TimeStepDefineByType.Substeps -ANA_SETTING.InitialSubsteps = 5 -ANA_SETTING.MinimumSubsteps = 5 -ANA_SETTING.MaximumSubsteps = 1000 -ANA_SETTING.StoreResultsAt = TimePointsOptions.EquallySpacedPoints -ANA_SETTING.StoreResulsAtValue = 5 - -ANA_SETTING.CurrentStepNumber = 2 -ANA_SETTING.AutomaticTimeStepping = AutomaticTimeStepping.On -ANA_SETTING.DefineBy = TimeStepDefineByType.Substeps -ANA_SETTING.InitialSubsteps = 10 -ANA_SETTING.MinimumSubsteps = 10 -ANA_SETTING.MaximumSubsteps = 1000 -ANA_SETTING.StoreResultsAt = TimePointsOptions.EquallySpacedPoints -ANA_SETTING.StoreResulsAtValue = 10 - -ANA_SETTING.CurrentStepNumber = 3 -ANA_SETTING.AutomaticTimeStepping = AutomaticTimeStepping.On -ANA_SETTING.DefineBy = TimeStepDefineByType.Substeps -ANA_SETTING.InitialSubsteps = 30 -ANA_SETTING.MinimumSubsteps = 30 -ANA_SETTING.MaximumSubsteps = 1000 -ANA_SETTING.StoreResultsAt = TimePointsOptions.EquallySpacedPoints -ANA_SETTING.StoreResulsAtValue = 20 - -SOLN_INFO.NewtonRaphsonResiduals = 4 - -# %% -# Loads and boundary conditions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -REM_DISP = STAT_STRUC.AddRemoteDisplacement() -REM_DISP.Location = RMPT01 -REM_DISP.XComponent.Inputs[0].DiscreteValues = [ - Quantity("0 [s]"), - Quantity("1 [s]"), - Quantity("2 [s]"), - Quantity("3 [s]"), -] -REM_DISP.XComponent.Output.DiscreteValues = [ - Quantity("0 [mm]"), - Quantity("0 [mm]"), - Quantity("0 [mm]"), - Quantity("0 [mm]"), -] -REM_DISP.YComponent.Inputs[0].DiscreteValues = [ - Quantity("0 [s]"), - Quantity("1 [s]"), - Quantity("2 [s]"), - Quantity("3 [s]"), -] -REM_DISP.YComponent.Output.DiscreteValues = [ - Quantity("0 [mm]"), - Quantity("0 [mm]"), - Quantity("-10 [mm]"), - Quantity("-10 [mm]"), -] -REM_DISP.ZComponent.Inputs[0].DiscreteValues = [ - Quantity("0 [s]"), - Quantity("1 [s]"), - Quantity("2 [s]"), - Quantity("3 [s]"), -] -REM_DISP.ZComponent.Output.DiscreteValues = [ - Quantity("0 [mm]"), - Quantity("0 [mm]"), - Quantity("0 [mm]"), - Quantity("0 [mm]"), -] - -REM_DISP.RotationX.Inputs[0].DiscreteValues = [ - Quantity("0 [s]"), - Quantity("1 [s]"), - Quantity("2 [s]"), - Quantity("3 [s]"), -] -REM_DISP.RotationX.Output.DiscreteValues = [ - Quantity("0 [rad]"), - Quantity("0 [rad]"), - Quantity("0 [rad]"), - Quantity("0 [rad]"), -] -REM_DISP.RotationY.Inputs[0].DiscreteValues = [ - Quantity("0 [s]"), - Quantity("1 [s]"), - Quantity("2 [s]"), - Quantity("3 [s]"), -] -REM_DISP.RotationY.Output.DiscreteValues = [ - Quantity("0 [rad]"), - Quantity("0 [rad]"), - Quantity("0 [rad]"), - Quantity("0 [rad]"), -] -REM_DISP.RotationZ.Inputs[0].DiscreteValues = [ - Quantity("0 [s]"), - Quantity("1 [s]"), - Quantity("2 [s]"), - Quantity("3 [s]"), -] -REM_DISP.RotationZ.Output.DiscreteValues = [ - Quantity("0 [rad]"), - Quantity("0 [rad]"), - Quantity("0 [rad]"), - Quantity("0.55 [rad]"), -] - -FRIC_SUP01 = STAT_STRUC.AddFrictionlessSupport() -FRIC_SUP01.Location = SYMM_FACES30 -FRIC_SUP01.Name = "Symmetry_BC" -FRIC_SUP02 = STAT_STRUC.AddFrictionlessSupport() -FRIC_SUP02.Location = FACES2 -FRIC_SUP02.Name = "Boot_Bottom_BC" -FRIC_SUP03 = STAT_STRUC.AddFrictionlessSupport() -FRIC_SUP03.Location = CYL_FACES2 -FRIC_SUP03.Name = "Boot_Radial_BC" - -# %% -# Add results -# ~~~~~~~~~~~ - -TOT_DEF = STAT_STRUC.Solution.AddTotalDeformation() -TOT_DEF.Location = RUBBER_BODIES30 - -EQV_STRS = STAT_STRUC.Solution.AddEquivalentStress() -EQV_STRS.Location = RUBBER_BODIES30 - -# %% -# Solve -# ~~~~~ - -STAT_STRUC.Solution.Solve(True) - -# sphinx_gallery_start_ignore -assert str(STAT_STRUC_SOLN.Status) == "Done", "Solution status is not 'Done'" -# sphinx_gallery_end_ignore - -# %% -# Postprocessing -# ~~~~~~~~~~~~~~ -# Total deformation - - -Tree.Activate([TOT_DEF]) -Graphics.ExportImage( - os.path.join(cwd, "totaldeformation.png"), image_export_format, settings_720p -) -display_image("totaldeformation.png") - -# %% -# Equivalent stress - -Tree.Activate([EQV_STRS]) -Graphics.ExportImage( - os.path.join(cwd, "equivalent_stress.png"), image_export_format, settings_720p -) -display_image("equivalent_stress.png") - -# %% -# Total deformation animation - -animation_export_format = ( - Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF -) -settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() -settings_720p.Width = 1280 -settings_720p.Height = 720 - -TOT_DEF.ExportAnimation( - os.path.join(cwd, "totaldeformation.gif"), animation_export_format, settings_720p -) -gif = Image.open(os.path.join(cwd, "totaldeformation.gif")) -fig, ax = plt.subplots(figsize=(16, 9)) -ax.axis("off") -img = ax.imshow(gif.convert("RGBA")) - - -def update(frame): - gif.seek(frame) - img.set_array(gif.convert("RGBA")) - return [img] - - -ani = FuncAnimation( - fig, update, frames=range(gif.n_frames), interval=200, repeat=True, blit=True -) -plt.show() - - -# %% -# Cleanup -# ~~~~~~~ -# Save project - -app.save(os.path.join(cwd, "non-linear-rubber-boot-seal.mechdat")) -app.new() - -# delete example file -delete_downloads() diff --git a/examples/02_technology_showcase/non_linear_analysis_rubber_boot_seal.py b/examples/02_technology_showcase/non_linear_analysis_rubber_boot_seal.py new file mode 100644 index 00000000..47698685 --- /dev/null +++ b/examples/02_technology_showcase/non_linear_analysis_rubber_boot_seal.py @@ -0,0 +1,760 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""".. _ref_non_linear_analysis_rubber_boot_seal: + +Nonlinear Analysis of a Rubber Boot Seal Model +---------------------------------------------- + +This example demonstrates a nonlinear 3D analysis of a rubber boot seal to: + +- Create a rigid-flexible contact pair between a rigid shaft and a + rubber boot part. +- Specify ramped effects using the On Gauss Point Detection Method + to update contact stiffness at each iteration. +- Specify contact pairs at the inner and outer surfaces of the rubber boot. +- Specify non-ramped effects using the Nodal-Projected Normal From Contact + Detection Method to update contact stiffness at each iteration. +""" + +# %% +# Import the necessary libraries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from pathlib import Path +from typing import TYPE_CHECKING + +from PIL import Image +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file +from matplotlib import image as mpimg +from matplotlib import pyplot as plt +from matplotlib.animation import FuncAnimation + +if TYPE_CHECKING: + import Ansys + +# %% +# Initialize the embedded application +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +app = App(globals=globals()) +print(app) + +# %% +# Create functions to set camera and display images +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" + + +def set_camera_and_display_image( + camera, + graphics, + graphics_image_export_settings, + image_output_path: Path, + image_name: str, +) -> None: + """Set the camera to fit the model and display the image. + + Parameters + ---------- + camera : Ansys.ACT.Common.Graphics.MechanicalCameraWrapper + The camera object to set the view. + graphics : Ansys.ACT.Common.Graphics.MechanicalGraphicsWrapper + The graphics object to export the image. + graphics_image_export_settings : Ansys.Mechanical.Graphics.GraphicsImageExportSettings + The settings for exporting the image. + image_output_path : Path + The path to save the exported image. + image_name : str + The name of the exported image file. + """ + # Set the camera to fit the mesh + camera.SetFit() + # Export the mesh image with the specified settings + image_path = image_output_path / image_name + graphics.ExportImage( + str(image_path), image_export_format, graphics_image_export_settings + ) + # Display the exported mesh image + display_image(image_path) + + +def display_image( + image_path: str, + pyplot_figsize_coordinates: tuple = (16, 9), + plot_xticks: list = [], + plot_yticks: list = [], + plot_axis: str = "off", +) -> None: + """Display the image with the specified parameters. + + Parameters + ---------- + image_path : str + The path to the image file to display. + pyplot_figsize_coordinates : tuple + The size of the figure in inches (width, height). + plot_xticks : list + The x-ticks to display on the plot. + plot_yticks : list + The y-ticks to display on the plot. + plot_axis : str + The axis visibility setting ('on' or 'off'). + """ + # Set the figure size based on the coordinates specified + plt.figure(figsize=pyplot_figsize_coordinates) + # Read the image from the file into an array + plt.imshow(mpimg.imread(image_path)) + # Get or set the current tick locations and labels of the x-axis + plt.xticks(plot_xticks) + # Get or set the current tick locations and labels of the y-axis + plt.yticks(plot_yticks) + # Turn off the axis + plt.axis(plot_axis) + # Display the figure + plt.show() + + +# %% +# Configure graphics for image export +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to the isometric view +camera.SetSpecificViewOrientation( + Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Iso +) +camera.SetFit() + +# Set the image export format and settings +image_export_format = GraphicsImageExportFormat.PNG +settings_720p = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() +settings_720p.Resolution = GraphicsResolutionType.EnhancedResolution +settings_720p.Background = GraphicsBackgroundType.White +settings_720p.Width = 1280 +settings_720p.Height = 720 +settings_720p.CurrentGraphicsDisplay = False + +# %% +# Download the geometry and material files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +geometry_path = download_file( + "example_05_td26_Rubber_Boot_Seal.agdb", "pymechanical", "00_basic" +) +mat_path = download_file("example_05_Boot_Mat.xml", "pymechanical", "00_basic") + +# %% +# Import the geometry and material +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Import the material + +model = app.Model +materials = model.Materials +materials.Import(mat_path) + +# %% +# Import the geometry + +# Add a geometry import to the geometry import group for the model +geometry_import = model.GeometryImportGroup.AddGeometryImport() + +# Set the geometry import format and preferences +geometry_import_format = ( + Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +) +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessCoordinateSystems = True + +# Import the geometry with the specified format and preferences +geometry_import.Import( + geometry_path, geometry_import_format, geometry_import_preferences +) + +# Visualize the imported geometry in 3D +app.plot() + +# %% +# Set up the analysis +# ~~~~~~~~~~~~~~~~~~~ + +# Set the active unit system and angle unit +app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM +app.ExtAPI.Application.ActiveAngleUnit = AngleUnitType.Radian + +# %% +# Store all main tree nodes as variables + +# Define the geometry for the model +geometry = model.Geometry +# Get the part and solid objects from the geometry +part1 = app.DataModel.GetObjectsByName("Part")[0] +part2 = app.DataModel.GetObjectsByName("Solid")[1] + +# Define the coordinate systems +coordinate_systems = model.CoordinateSystems +geometry_coordinate_systems = coordinate_systems.Children[0] + +# %% +# Add a static structural analysis + +# Add a static structural analysis to the model +model.AddStaticStructuralAnalysis() + +# Get the static structural analysis from the model +static_structural_analysis = model.Analyses[0] + +# Get the analysis settings, solution, and solution information +analysis_settings = static_structural_analysis.Children[0] +stat_struct_soln = static_structural_analysis.Solution +soln_info = stat_struct_soln.SolutionInformation + +# %% +# Create a function to get named selections + + +def get_named_selection( + named_selections, name: str +) -> Ansys.ACT.Automation.Mechanical.NamedSelection: + """Get a named selection by its name. + + Parameters + ---------- + named_selections : Ansys.ACT.Automation.Mechanical.NamedSelections + The named selections object to search in. + name : str + The name of the named selection to retrieve. + + Returns + ------- + Ansys.ACT.Automation.Mechanical.NamedSelection + The named selection object. + """ + return [ + child + for child in named_selections.GetChildren[ + Ansys.ACT.Automation.Mechanical.NamedSelection + ](True) + if child.Name == name + ][0] + + +# %% +# Define named selections and coordinate systems + +named_selections = app.ExtAPI.DataModel.Project.Model.NamedSelections +top_face = get_named_selection(named_selections, "Top_Face") +bottom_face = get_named_selection(named_selections, "Bottom_Face") +symm_faces30 = get_named_selection(named_selections, "Symm_Faces30") +faces2 = get_named_selection(named_selections, "Faces2") +cyl_faces2 = get_named_selection(named_selections, "Cyl_Faces2") +rubber_bodies30 = get_named_selection(named_selections, "Rubber_Bodies30") +inner_faces30 = get_named_selection(named_selections, "Inner_Faces30") +outer_faces30 = get_named_selection(named_selections, "Outer_Faces30") +shaft_face = get_named_selection(named_selections, "Shaft_Face") +symm_faces15 = get_named_selection(named_selections, "Symm_Faces15") + +# Add a coordinate system and set its origin y-coordinate +lcs1 = coordinate_systems.AddCoordinateSystem() +lcs1.OriginY = Quantity("97[mm]") + +# %% +# Assign material + +# Set the material for the rubber boot part +part1.Material = "Boot" +# Set the stiffness behavior for the rubber boot part +part2.StiffnessBehavior = StiffnessBehavior.Rigid + +# %% +# Create a function to add a contact region and set its properties + + +def add_contact_region_and_props( + body, + target_location, + src_location, + set_src_first: bool = True, + contact_type=ContactType.Frictional, + friction_coefficient=0.2, + behavior=ContactBehavior.Asymmetric, + small_sliding=ContactSmallSlidingType.Off, + detection_method=ContactDetectionPoint.OnGaussPoint, + update_stiffness=UpdateContactStiffness.EachIteration, +): + """Add a contact region and set its properties. + + Parameters + ---------- + body : Ansys.ACT.Automation.Mechanical.Body + The body to which the contact region is added. + target_location : Ansys.ACT.Automation.Mechanical.NamedSelection + The target location for the contact region. + src_location : Ansys.ACT.Automation.Mechanical.NamedSelection + The source location for the contact region. + set_src_first : bool + Whether to set the source location first. + contact_type : ContactType + The type of contact (default is Frictional). + friction_coefficient : float + The friction coefficient for the contact region (default is 0.2). + behavior : ContactBehavior + The behavior of the contact region (default is Asymmetric). + small_sliding : ContactSmallSlidingType + The small sliding type for the contact region (default is Off). + detection_method : ContactDetectionPoint + The detection method for the contact region (default is OnGaussPoint). + update_stiffness : UpdateContactStiffness + The update stiffness method for the contact region (default is EachIteration). + + Returns + ------- + Ansys.ACT.Automation.Mechanical.ContactRegion + The created contact region. + """ + # Add a contact region to the connection or child connection + contact_region = body.AddContactRegion() + # Set the source and target locations for the contact region + if set_src_first: + contact_region.SourceLocation = src_location + contact_region.TargetLocation = target_location + else: + contact_region.TargetLocation = target_location + contact_region.SourceLocation = src_location + # Set the contact type, friction coefficient, behavior, small sliding, + # detection method, and update stiffness for the contact region + contact_region.ContactType = contact_type + contact_region.FrictionCoefficient = friction_coefficient + contact_region.Behavior = behavior + contact_region.SmallSliding = small_sliding + contact_region.DetectionMethod = detection_method + contact_region.UpdateStiffness = update_stiffness + + return contact_region + + +# %% +# Add contact regions + +# Add a contact region to the connections +connections = model.Connections +contact_region1 = add_contact_region_and_props( + connections, + target_location=shaft_face, + src_location=inner_faces30, + set_src_first=False, +) +# Set interface treatment and target properties +contact_region1.InterfaceTreatment = ContactInitialEffect.AddOffsetRampedEffects +contact_region1.TargetGeometryCorrection = TargetCorrection.Smoothing +contact_region1.TargetOrientation = TargetOrientation.Cylinder +contact_region1.TargetStartingPoint = geometry_coordinate_systems +contact_region1.TargetEndingPoint = lcs1 + +# Add a contact region to the child connections +conts = connections.Children[0] +contact_region2 = add_contact_region_and_props( + conts, + target_location=inner_faces30, + src_location=inner_faces30, + detection_method=ContactDetectionPoint.NodalProjectedNormalFromContact, +) +# Set the stiffness value type and factor +contact_region2.NormalStiffnessValueType = ElementControlsNormalStiffnessType.Factor +contact_region2.NormalStiffnessFactor = 1 + +# Add a contact region to the child connections +contact_region3 = add_contact_region_and_props( + conts, + target_location=outer_faces30, + src_location=outer_faces30, + detection_method=ContactDetectionPoint.NodalProjectedNormalFromContact, +) +# Set the stiffness value type and factor +contact_region3.NormalStiffnessValueType = ElementControlsNormalStiffnessType.Factor +contact_region3.NormalStiffnessFactor = 1 + +# %% +# Add face meshing and sizing +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Define the mesh for the model +mesh = model.Mesh + +# Add face meshing to the mesh +face_mesh = mesh.AddFaceMeshing() +# Set the location of the face mesh to the shaft face +# and set the internal number of divisions to 1 +face_mesh.Location = shaft_face +face_mesh.InternalNumberOfDivisions = 1 + +# Add sizing to the mesh +mesh_size = mesh.AddSizing() +# Set the location of the mesh size to the symmetry faces +# and set the element size to 2 mm +mesh_size.Location = symm_faces15 +mesh_size.ElementSize = Quantity("2 [mm]") +mesh.ElementOrder = ElementOrder.Linear +mesh.Resolution = 2 + +# Generate the mesh and display the image +mesh.GenerateMesh() +set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") + +# %% +# Add remote points +# ~~~~~~~~~~~~~~~~~ + + +def add_remote_point( + model, + location, + behavior=LoadBehavior.Rigid, +): + """Add a remote point to the model. + + Parameters + ---------- + model : Ansys.ACT.Automation.Mechanical.Model + The model to which the remote point is added. + location : Ansys.ACT.Automation.Mechanical.NamedSelection + The location of the remote point. + behavior : LoadBehavior + The behavior of the remote point (default is Rigid). + + Returns + ------- + Ansys.ACT.Automation.Mechanical.RemotePoint + The created remote point. + """ + remote_point = model.AddRemotePoint() + remote_point.Location = location + remote_point.Behavior = behavior + + return remote_point + + +remote_point01 = add_remote_point(model, bottom_face) +remote_point02 = add_remote_point(model, top_face) + +# %% +# Configure the analysis settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# Define a function to set the analysis settings + + +def set_analysis_settings( + analysis_settings, + current_step_number: int, + initial_substeps: int, + minimum_substeps: int, + store_results_at_value: int, + automatic_time_stepping: bool = AutomaticTimeStepping.On, + define_by: TimeStepDefineByType = TimeStepDefineByType.Substeps, + maximum_substeps: int = 1000, + store_results_at: TimePointsOptions = TimePointsOptions.EquallySpacedPoints, +): + analysis_settings.CurrentStepNumber = current_step_number + analysis_settings.AutomaticTimeStepping = automatic_time_stepping + analysis_settings.DefineBy = define_by + analysis_settings.InitialSubsteps = initial_substeps + analysis_settings.MinimumSubsteps = minimum_substeps + analysis_settings.MaximumSubsteps = maximum_substeps + analysis_settings.StoreResultsAt = store_results_at + analysis_settings.StoreResulsAtValue = store_results_at_value + + +# %% +# Configure the analysis settings + +analysis_settings.Activate() +analysis_settings.LargeDeflection = True +analysis_settings.Stabilization = StabilizationType.Off +analysis_settings.NumberOfSteps = 2 + +set_analysis_settings( + analysis_settings, + current_step_number=1, + initial_substeps=5, + minimum_substeps=5, + store_results_at_value=5, +) + +set_analysis_settings( + analysis_settings, + current_step_number=2, + initial_substeps=10, + minimum_substeps=10, + store_results_at_value=10, +) + +set_analysis_settings( + analysis_settings, + current_step_number=3, + initial_substeps=30, + minimum_substeps=30, + store_results_at_value=20, +) + +soln_info.NewtonRaphsonResiduals = 4 + +# %% +# Set load and boundary conditions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +remote_displacement = static_structural_analysis.AddRemoteDisplacement() +remote_displacement.Location = remote_point01 + +# %% +# Define a function to convert a list of values to quantities + + +def convert_to_quantity(quantity_list: tuple) -> list: + """Convert a list of values to quantities. + + Parameters + ---------- + quantity_list : tuple + A tuple containing a list of values and the unit. + + Returns + ------- + list + A list of quantities with the specified unit. + """ + values, unit = quantity_list + return [Quantity(f"{value} [{unit}]") for value in values] + + +# %% +# Set the input values for all remote displacement components + +input_values = convert_to_quantity(([0, 1, 2, 3], "s")) + +# %% +# Set the X component input and output values + +x_component = remote_displacement.XComponent +x_component.Inputs[0].DiscreteValues = input_values +x_component.Output.DiscreteValues = convert_to_quantity(([0, 0, 0, 0], "mm")) + +# %% +# Set the Y component input and output values + +y_component = remote_displacement.YComponent +y_component.Inputs[0].DiscreteValues = input_values +y_component.Output.DiscreteValues = convert_to_quantity(([0, 0, -10, -10], "mm")) + +# %% +# Set the Z component input and output values + +z_component = remote_displacement.ZComponent +z_component.Inputs[0].DiscreteValues = input_values +z_component.Output.DiscreteValues = convert_to_quantity(([0, 0, 0, 0], "mm")) + +# %% +# Set the rotation X component input and output values + +rotation_x = remote_displacement.RotationX +rotation_x.Inputs[0].DiscreteValues = input_values +rotation_x.Output.DiscreteValues = convert_to_quantity(([0, 0, 0, 0], "rad")) + +# %% +# Set the rotation X component input and output values + +rotation_y = remote_displacement.RotationY +rotation_y.Inputs[0].DiscreteValues = input_values +rotation_y.Output.DiscreteValues = convert_to_quantity(([0, 0, 0, 0], "rad")) + +# %% +# Set the rotation Z component input and output values + +rotation_z = remote_displacement.RotationZ +rotation_z.Inputs[0].DiscreteValues = input_values +rotation_z.Output.DiscreteValues = convert_to_quantity(([0, 0, 0, 0.55], "rad")) + +# %% +# Add frictionless support to the static structural analysis + + +def add_frictionless_support( + static_structural_analysis, + location, + name: str, +): + """Add frictionless support to the static structural analysis. + + Parameters + ---------- + static_structural_analysis : Ansys.ACT.Automation.Mechanical.StaticStructuralAnalysis + The static structural analysis object. + location : Ansys.ACT.Automation.Mechanical.NamedSelection + The location of the frictionless support. + name : str + The name of the frictionless support. + + Returns + ------- + Ansys.ACT.Automation.Mechanical.FrictionlessSupport + The created frictionless support. + """ + frictionless_support = static_structural_analysis.AddFrictionlessSupport() + frictionless_support.Location = location + frictionless_support.Name = name + + return frictionless_support + + +add_frictionless_support(static_structural_analysis, symm_faces30, "Symmetry_BC") +add_frictionless_support(static_structural_analysis, faces2, "Boot_Bottom_BC") +add_frictionless_support(static_structural_analysis, cyl_faces2, "Boot_Radial_BC") + +# %% +# Add results +# ~~~~~~~~~~~ + +# Add total deformation results to the solution +total_deformation = static_structural_analysis.Solution.AddTotalDeformation() +total_deformation.Location = rubber_bodies30 + +# Add equivalent stress results to the solution +equivalent_stress = static_structural_analysis.Solution.AddEquivalentStress() +equivalent_stress.Location = rubber_bodies30 + +# %% +# Solve the static structural analysis +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +static_structural_analysis.Solution.Solve(True) + +# sphinx_gallery_start_ignore +assert ( + stat_struct_soln.Status == SolutionStatusType.Done +), "Solution status is not 'Done'" +# sphinx_gallery_end_ignore + +# %% +# Postprocessing +# ~~~~~~~~~~~~~~ + +# Activate the total deformation result and display the image +app.Tree.Activate([total_deformation]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "total_deformation.png" +) + +# %% +# Equivalent stress + +# Activate the equivalent stress result and display the image +app.Tree.Activate([equivalent_stress]) +set_camera_and_display_image( + camera, graphics, settings_720p, output_path, "equivalent_stress.png" +) + +# %% +# Create a function to set the animation for the GIF + + +def update_animation(frame: int) -> list[mpimg.AxesImage]: + """Update the animation frame for the GIF. + + Parameters + ---------- + frame : int + The frame number to update the animation. + + Returns + ------- + list[mpimg.AxesImage] + A list containing the updated image for the animation. + """ + # Seeks to the given frame in this sequence file + gif.seek(frame) + # Set the image array to the current frame of the GIF + image.set_data(gif.convert("RGBA")) + # Return the updated image + return [image] + + +# %% +# Show the total deformation animation + +# Set the animation export format and settings +animation_export_format = ( + Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF +) +settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() +settings_720p.Width = 1280 +settings_720p.Height = 720 + +# Export the total deformation animation as a GIF +total_deformation_gif = output_path / "total_deformation.gif" +total_deformation.ExportAnimation( + str(total_deformation_gif), animation_export_format, settings_720p +) + +# Open the GIF file and create an animation +gif = Image.open(total_deformation_gif) +# Set the subplots for the animation and turn off the axis +figure, axes = plt.subplots(figsize=(16, 9)) +axes.axis("off") +# Change the color of the image +image = axes.imshow(gif.convert("RGBA")) + +# Create the animation using the figure, update_animation function, and the GIF frames +# Set the interval between frames to 200 milliseconds and repeat the animation +FuncAnimation( + figure, + update_animation, + frames=range(gif.n_frames), + interval=200, + repeat=True, + blit=True, +) + +# Show the animation +plt.show() + +# %% +# Clean up the project +# ~~~~~~~~~~~~~~~~~~~~ + +# Save the mechdat file +mechdat_file = output_path / "non_linear_rubber_boot_seal.mechdat" +app.save(str(mechdat_file)) + +# Close the app +app.close() + +# Delete the example files +delete_downloads() diff --git a/requirements/requirements_doc.txt b/requirements/requirements_doc.txt index ef9be155..999d5096 100644 --- a/requirements/requirements_doc.txt +++ b/requirements/requirements_doc.txt @@ -1,3 +1,3 @@ #PyMechanical -ansys-mechanical-core[doc]==0.11.14 -ansys-mechanical-core[viz]==0.11.14 \ No newline at end of file +ansys-mechanical-core[doc]==0.11.17 +ansys-mechanical-core[graphics]==0.11.17