From 349dd0bf67cf0f38c43f7454f3fa76810072602d Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Tue, 3 Feb 2026 23:29:08 -0600 Subject: [PATCH 01/30] add helper methods --- src/ansys/mechanical/core/embedding/app.py | 105 ++--- .../mechanical/core/embedding/helpers.py | 427 ++++++++++++++++++ src/ansys/mechanical/core/embedding/utils.py | 23 + 3 files changed, 478 insertions(+), 77 deletions(-) create mode 100644 src/ansys/mechanical/core/embedding/helpers.py diff --git a/src/ansys/mechanical/core/embedding/app.py b/src/ansys/mechanical/core/embedding/app.py index 1aa29ad2f9..eba6e92dfc 100644 --- a/src/ansys/mechanical/core/embedding/app.py +++ b/src/ansys/mechanical/core/embedding/app.py @@ -42,6 +42,7 @@ from ansys.mechanical.core.embedding.poster import Poster import ansys.mechanical.core.embedding.shell as shell from ansys.mechanical.core.embedding.ui import launch_ui +from ansys.mechanical.core.embedding.utils import GetterWrapper from ansys.mechanical.core.feature_flags import get_command_line_arguments if typing.TYPE_CHECKING: @@ -127,29 +128,6 @@ def is_initialized(): return len(INSTANCES) != 0 -class GetterWrapper(object): - """Wrapper class around an attribute of an object.""" - - def __init__(self, obj, getter): - """Create a new instance of GetterWrapper.""" - # immortal class which provides wrapped object - self.__dict__["_immortal_object"] = obj - # function to get the wrapped object from the immortal class - self.__dict__["_get_wrapped_object"] = getter - - def __getattr__(self, attr): - """Wrap getters to the wrapped object.""" - if attr in self.__dict__: - return getattr(self, attr) - return getattr(self._get_wrapped_object(self._immortal_object), attr) - - def __setattr__(self, attr, value): - """Wrap setters to the wrapped object.""" - if attr in self.__dict__: - setattr(self, attr, value) - setattr(self._get_wrapped_object(self._immortal_object), attr, value) - - class App: """Mechanical embedding Application. @@ -316,6 +294,9 @@ def __init__(self, db_file=None, private_appdata=False, **kwargs): if self.version >= 252: self._license_manager = LicenseManager(self) + # Initialize helpers + self._helpers = None + def __repr__(self): """Get the product info.""" import clr @@ -621,6 +602,15 @@ def license_manager(self): raise Exception("LicenseManager is only available for version 252 and later.") return self._license_manager + @property + def helpers(self): + """Return the Helpers instance.""" + if self._helpers is None: + from ansys.mechanical.core.embedding.helpers import Helpers + + self._helpers = Helpers(self) + return self._helpers + def _share(self, other) -> None: """Shares the state of self with other. @@ -644,6 +634,20 @@ def _share(self, other) -> None: other._version = self._version other._poster = self._poster other._updated_scopes = self._updated_scopes + other._helpers = self._helpers + + # Share logging configuration + other._enable_logging = self._enable_logging + other._log = self._log + other._log_level = self._log_level + + # Share interactive mode and shell state + other._interactive_mode = self._interactive_mode + other._started_interactive_shell = self._started_interactive_shell + + # Share lazy-loaded instances + other._messages = self._messages + other._license_manager = self._license_manager # all events will be handled by the original App instance other._subscribed = False @@ -695,56 +699,6 @@ def _update_all_globals(self) -> None: for scope in self._updated_scopes: scope.update(global_entry_points(self)) - def _print_tree(self, node, max_lines, lines_count, indentation): - """Recursively print till provided maximum lines limit. - - Each object in the tree is expected to have the following attributes: - - Name: The name of the object. - - Suppressed : Print as suppressed, if object is suppressed. - - Children: Checks if object have children. - Each child node is expected to have the all these attributes. - - Parameters - ---------- - lines_count: int, optional - The current count of lines printed. Default is 0. - indentation: str, optional - The indentation string used for printing the tree structure. Default is "". - """ - if lines_count >= max_lines and max_lines != -1: - print(f"... truncating after {max_lines} lines") - return lines_count - - if not hasattr(node, "Name"): - raise AttributeError("Object must have a 'Name' attribute") - - node_name = node.Name - if hasattr(node, "Suppressed") and node.Suppressed is True: - node_name += " (Suppressed)" - if hasattr(node, "ObjectState"): - if str(node.ObjectState) == "UnderDefined": - node_name += " (?)" - elif str(node.ObjectState) == "Solved" or str(node.ObjectState) == "FullyDefined": - node_name += " (✓)" - elif str(node.ObjectState) == "NotSolved" or str(node.ObjectState) == "Obsolete": - node_name += " (⚡︎)" - elif str(node.ObjectState) == "SolveFailed": - node_name += " (✕)" - print(f"{indentation}├── {node_name}") - lines_count += 1 - - if lines_count >= max_lines and max_lines != -1: - print(f"... truncating after {max_lines} lines") - return lines_count - - if hasattr(node, "Children") and node.Children is not None and node.Children.Count > 0: - for child in node.Children: - lines_count = self._print_tree(child, max_lines, lines_count, indentation + "| ") - if lines_count >= max_lines and max_lines != -1: - break - - return lines_count - def print_tree(self, node=None, max_lines=80, lines_count=0, indentation=""): """ Print the hierarchical tree representation of the Mechanical project structure. @@ -787,10 +741,7 @@ def print_tree(self, node=None, max_lines=80, lines_count=0, indentation=""): ... | ├── Model ... ... truncating after 2 lines """ - if node is None: - node = self.DataModel.Project - - self._print_tree(node, max_lines, lines_count, indentation) + self.helpers.print_tree(node, max_lines) def log_debug(self, message): """Log the debug message.""" diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py new file mode 100644 index 0000000000..f374324237 --- /dev/null +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -0,0 +1,427 @@ +# Copyright (C) 2022 - 2026 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. + +"""Helper functions for Embedded App.""" + + +class Helpers: + """Helper utilities for Mechanical embedding application. + + Parameters + ---------- + app : App + The Mechanical embedding application instance. + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> helpers = app.helpers + >>> helpers.print_tree() + """ + + def __init__(self, app): + """Initialize the Helpers class with the app instance.""" + self._app = app + + # Import Ansys module for use across helper methods + from ansys.mechanical.core.embedding.global_importer import Ansys + + self.Ansys = Ansys + + def print_tree(self, node=None, max_lines=80): + """ + Print the hierarchical tree representation of the Mechanical project structure. + + Parameters + ---------- + node : DataModel object, optional + The starting object of the tree. If not provided, starts from the Project. + max_lines : int, optional + The maximum number of lines to print. Default is 80. + If set to -1, no limit is applied. + + Raises + ------ + AttributeError + If the node does not have the required attributes. + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> app.helpers.print_tree() + ... ├── Project + ... | ├── Model + ... | | ├── Geometry Imports (⚡︎) + + >>> app.helpers.print_tree(app.Model, max_lines=3) + ... ├── Model + ... | ├── Geometry Imports (⚡︎) + ... | ├── Geometry (?) + ... ... truncating after 3 lines + """ + if node is None: + node = self._app.DataModel.Project + + _print_tree(node, max_lines, 0, "") + + def import_geometry(self, file_path: str, process_named_selections: bool = True): + r"""Import geometry file into the current Mechanical model. + + Parameters + ---------- + file_path : str + The path to the geometry file to be imported. + process_named_selections : bool, optional + Whether to process named selections during import. Default is True. + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> app.helpers.import_geometry("C:\\path\\to\\geometry.pmdb") + + >>> # Import without processing named selections + >>> app.helpers.import_geometry( + ... "C:\\path\\to\\geometry.step", process_named_selections=False + ... ) + """ + # Import Ansys and enums - same way as when App(globals=globals()) is used + + # Create a geometry import group for the model + geometry_import_group = self._app.Model.GeometryImportGroup + # Add the geometry import to the group + geometry_import = geometry_import_group.AddGeometryImport() + # Set the geometry import format + geometry_import_format = ( + self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic + ) + # Set the geometry import preferences + geometry_import_preferences = ( + self.Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + ) + geometry_import_preferences.ProcessNamedSelections = process_named_selections + try: + geometry_import.Import(file_path, geometry_import_format, geometry_import_preferences) + self._app.log_info( + f"Imported geometry from {file_path} successfully." + f"Object State: {geometry_import.ObjectState}" + ) + except Exception as e: + raise RuntimeError(f"Geometry Import unsuccessful: {e}") + + def export_image( + self, + obj=None, + file_path: str = None, + width: int = 1920, + height: int = 1080, + background: str = "White", + resolution: str = "Enhanced", + current_graphics_display: bool = False, + image_format: str = "PNG", + ): + r"""Export an image of the specified object. + + Parameters + ---------- + obj : optional + The object to activate and export. If None, exports the current graphics display. + Can be any Mechanical object such as Geometry, Mesh, or Results. + file_path : str, optional + The path where the image will be saved. If None, defaults to "mechanical_export.png" + in the project directory. + width : int, optional + The width of the exported image in pixels. Default is 1920. + height : int, optional + The height of the exported image in pixels. Default is 1080. + background : str, optional + Background type for the exported image. Options are: + - "White": White background + - "Appearance": Use graphics appearance setting + Default is "White". + resolution : str, optional + Resolution type for the exported image. Options are: + - "Normal": Normal resolution (1:1) + - "Enhanced": Enhanced resolution (2:1) - Default + - "High": High resolution (4:1) + Default is "Enhanced". + current_graphics_display : bool, optional + Whether to use current graphics display. Default is False. + image_format : str, optional + Image format for export. Options are: + - "PNG": PNG image format - Default + - "JPG": JPG image format + - "BMP": BMP image format + - "TIF": TIFF image format + - "EPS": EPS image format + Default is "PNG". + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> # Export the geometry + >>> app.helpers.export_image(app.Model.Geometry, "C:\\path\\to\\geometry.png") + + >>> # Export a specific result with custom settings + >>> result = app.Model.Analyses[0].Solution.Children[0] + >>> app.helpers.export_image( + ... result, + ... "C:\\path\\to\\result.jpg", + ... background="Appearance", + ... resolution="High", + ... image_format="JPG", + ... ) + """ + from pathlib import Path + + from ansys.mechanical.core.embedding.enum_importer import ( + GraphicsBackgroundType, + GraphicsImageExportFormat, + GraphicsResolutionType, + ) + + # Set default file path if not provided + if file_path is None: + raise ValueError("file_path must be provided for image export.") + else: + file_path = Path(file_path) + # If only filename provided (no directory), save to current working directory + if not file_path.parent or file_path.parent == Path(): + file_path = Path.cwd() / file_path.name + + # Convert to string for API call + file_path = str(file_path) + + # Activate the object if provided + if obj is not None: + self._app.Tree.Activate([obj]) + + # Create graphics image export settings + graphics_image_export_settings = ( + self.Ansys.Mechanical.Graphics.GraphicsImageExportSettings() + ) + + # Set resolution type + resolution_lower = resolution.lower() + if resolution_lower == "enhanced": + graphics_image_export_settings.Resolution = GraphicsResolutionType.EnhancedResolution + elif resolution_lower == "high": + graphics_image_export_settings.Resolution = GraphicsResolutionType.HighResolution + elif resolution_lower == "normal": + graphics_image_export_settings.Resolution = GraphicsResolutionType.NormalResolution + else: + raise ValueError( + f"Invalid resolution type: {resolution}. " + "Valid options are 'Normal', 'Enhanced', or 'High'." + ) + + # Set background type + if background.lower() == "white": + graphics_image_export_settings.Background = GraphicsBackgroundType.White + elif background.lower() == "appearance": + graphics_image_export_settings.Background = ( + GraphicsBackgroundType.GraphicsAppearanceSetting + ) + else: + raise ValueError( + f"Invalid background type: {background}. Valid options are 'White' or 'Appearance'." + ) + + # Set image format + format_lower = image_format.lower() + if format_lower == "png": + export_format = GraphicsImageExportFormat.PNG + elif format_lower == "jpg" or format_lower == "jpeg": + export_format = GraphicsImageExportFormat.JPG + elif format_lower == "bmp": + export_format = GraphicsImageExportFormat.BMP + elif format_lower == "tif" or format_lower == "tiff": + export_format = GraphicsImageExportFormat.TIF + elif format_lower == "eps": + export_format = GraphicsImageExportFormat.EPS + else: + raise ValueError( + f"Invalid image format: {image_format}. " + "Valid options are 'PNG', 'JPG', 'BMP', 'TIF', or 'EPS'." + ) + + graphics_image_export_settings.CurrentGraphicsDisplay = current_graphics_display + graphics_image_export_settings.Width = width + graphics_image_export_settings.Height = height + + try: + self._app.Graphics.ExportImage(file_path, export_format, graphics_image_export_settings) + self._app.log_info(f"Exported image to {file_path} successfully.") + except Exception as e: + raise RuntimeError(f"Image export unsuccessful: {e}") + + def export_animation( + self, + obj=None, + file_path: str = None, + width: int = 1280, + height: int = 720, + animation_format: str = "GIF", + ): + r"""Export an animation of the specified object. + + Parameters + ---------- + obj : optional + The object to activate and export animation. If None, exports animation of the + current graphics display. Can be any Mechanical object that supports animation + such as results with multiple time steps or modal analysis results. + file_path : str, optional + The path where the animation will be saved. If None, raises ValueError. + If only a filename is provided, saves to current working directory. + width : int, optional + The width of the exported animation in pixels. Default is 1280. + height : int, optional + The height of the exported animation in pixels. Default is 720. + animation_format : str, optional + Animation format for export. Options are: + - "GIF": GIF animation format - Default + - "AVI": AVI video format + - "MP4": MP4 video format + - "WMV": WMV video format + Default is "GIF". + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> # Export animation of a result + >>> result = app.Model.Analyses[0].Solution.Children[0] + >>> app.helpers.export_animation(result, "result_animation.gif") + + >>> # Export as MP4 with custom resolution + >>> app.helpers.export_animation( + ... result, "result_animation.mp4", width=1920, height=1080, animation_format="MP4" + ... ) + """ + from pathlib import Path + + from ansys.mechanical.core.embedding.enum_importer import ( + GraphicsAnimationExportFormat, + ) + + # Set default file path if not provided + if file_path is None: + raise ValueError("file_path must be provided for animation export.") + else: + file_path = Path(file_path) + # If only filename provided (no directory), save to current working directory + if not file_path.parent or file_path.parent == Path(): + file_path = Path.cwd() / file_path.name + + # Convert to string for API call + file_path = str(file_path) + + # Activate the object if provided + if obj is None: + self._app.log_info( + "No object provided for animation export; using first active object." + ) + obj = self._app.Tree.FirstActiveObject + + # Create animation export settings + animation_export_settings = self.Ansys.Mechanical.Graphics.AnimationExportSettings( + width, height + ) + + # Set animation format + format_lower = animation_format.lower() + if format_lower == "gif": + export_format = GraphicsAnimationExportFormat.GIF + elif format_lower == "avi": + export_format = GraphicsAnimationExportFormat.AVI + elif format_lower == "mp4": + export_format = GraphicsAnimationExportFormat.MP4 + elif format_lower == "wmv": + export_format = GraphicsAnimationExportFormat.WMV + else: + raise ValueError( + f"Invalid animation format: {animation_format}. " + "Valid options are 'GIF', 'AVI', 'MP4', or 'WMV'." + ) + + try: + self._app.Tree.Activate([obj]) + obj.ExportAnimation(file_path, export_format, animation_export_settings) + self._app.log_info(f"Exported animation to {file_path} successfully.") + except Exception as e: + raise RuntimeError(f"Animation export unsuccessful: {e}") + + +# Helper function to print the tree recursively +def _print_tree(node, max_lines, lines_count, indentation): + """Recursively print till provided maximum lines limit. + + Each object in the tree is expected to have the following attributes: + - Name: The name of the object. + - Suppressed : Print as suppressed, if object is suppressed. + - Children: Checks if object have children. + Each child node is expected to have the all these attributes. + + Parameters + ---------- + lines_count: int, optional + The current count of lines printed. Default is 0. + indentation: str, optional + The indentation string used for printing the tree structure. Default is "". + """ + if lines_count >= max_lines and max_lines != -1: + print(f"... truncating after {max_lines} lines") + return lines_count + + if not hasattr(node, "Name"): + raise AttributeError("Object must have a 'Name' attribute") + + node_name = node.Name + if hasattr(node, "Suppressed") and node.Suppressed is True: + node_name += " (Suppressed)" + if hasattr(node, "ObjectState"): + if str(node.ObjectState) == "UnderDefined": + node_name += " (?)" + elif str(node.ObjectState) == "Solved" or str(node.ObjectState) == "FullyDefined": + node_name += " (✓)" + elif str(node.ObjectState) == "NotSolved" or str(node.ObjectState) == "Obsolete": + node_name += " (⚡︎)" + elif str(node.ObjectState) == "SolveFailed": + node_name += " (✕)" + print(f"{indentation}├── {node_name}") + lines_count += 1 + + if lines_count >= max_lines and max_lines != -1: + print(f"... truncating after {max_lines} lines") + return lines_count + + if hasattr(node, "Children") and node.Children is not None and node.Children.Count > 0: + for child in node.Children: + lines_count = _print_tree(child, max_lines, lines_count, indentation + "| ") + if lines_count >= max_lines and max_lines != -1: + break + + return lines_count diff --git a/src/ansys/mechanical/core/embedding/utils.py b/src/ansys/mechanical/core/embedding/utils.py index e5f5781c0a..0fb5642d26 100644 --- a/src/ansys/mechanical/core/embedding/utils.py +++ b/src/ansys/mechanical/core/embedding/utils.py @@ -28,6 +28,29 @@ TEST_HELPER = None +class GetterWrapper(object): + """Wrapper class around an attribute of an object.""" + + def __init__(self, obj, getter): + """Create a new instance of GetterWrapper.""" + # immortal class which provides wrapped object + self.__dict__["_immortal_object"] = obj + # function to get the wrapped object from the immortal class + self.__dict__["_get_wrapped_object"] = getter + + def __getattr__(self, attr): + """Wrap getters to the wrapped object.""" + if attr in self.__dict__: + return getattr(self, attr) + return getattr(self._get_wrapped_object(self._immortal_object), attr) + + def __setattr__(self, attr, value): + """Wrap setters to the wrapped object.""" + if attr in self.__dict__: + setattr(self, attr, value) + setattr(self._get_wrapped_object(self._immortal_object), attr, value) + + def _get_test_helper(): global TEST_HELPER import clr From 6a9e83265449efec9e6b92aec48e2c1726a2597d Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 4 Feb 2026 05:30:56 +0000 Subject: [PATCH 02/30] chore: adding changelog file 1507.added.md [dependabot-skip] --- doc/changelog.d/1507.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1507.added.md diff --git a/doc/changelog.d/1507.added.md b/doc/changelog.d/1507.added.md new file mode 100644 index 0000000000..abe2e0b421 --- /dev/null +++ b/doc/changelog.d/1507.added.md @@ -0,0 +1 @@ +Add helper methods From ccf5a11450558aefb80edfa739c6708c393db857 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Wed, 4 Feb 2026 08:58:13 -0600 Subject: [PATCH 03/30] update string format --- .../mechanical/core/embedding/helpers.py | 243 ++++++++++++++++-- 1 file changed, 217 insertions(+), 26 deletions(-) diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index f374324237..a43fb719a8 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -130,16 +130,42 @@ def import_geometry(self, file_path: str, process_named_selections: bool = True) except Exception as e: raise RuntimeError(f"Geometry Import unsuccessful: {e}") + def import_materials(self, file_path: str): + r"""Import materials from a specified material database file. + + Parameters + ---------- + file_path : str + The path to the material database file to be imported. + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> app.helpers.import_materials("C:\\path\\to\\materials.xml") + """ + # Add materials to the model and import the material files + materials = self._app.Model.Materials + + try: + materials.Import(file_path) + self._app.log_info( + f"Imported materials from {file_path} successfully." + f"Object State: {materials.ObjectState}" + ) + except Exception as e: + raise RuntimeError(f"Material Import unsuccessful: {e}") + def export_image( self, obj=None, file_path: str = None, width: int = 1920, height: int = 1080, - background: str = "White", - resolution: str = "Enhanced", + background: str = "white", + resolution: str = "enhanced", current_graphics_display: bool = False, - image_format: str = "PNG", + image_format: str = "png", ): r"""Export an image of the specified object. @@ -157,25 +183,25 @@ def export_image( The height of the exported image in pixels. Default is 1080. background : str, optional Background type for the exported image. Options are: - - "White": White background - - "Appearance": Use graphics appearance setting - Default is "White". + - "white": White background + - "appearance": Use graphics appearance setting + Default is "white". resolution : str, optional Resolution type for the exported image. Options are: - - "Normal": Normal resolution (1:1) - - "Enhanced": Enhanced resolution (2:1) - Default - - "High": High resolution (4:1) - Default is "Enhanced". + - "normal": Normal resolution (1:1) + - "enhanced": Enhanced resolution (2:1) - Default + - "high": High resolution (4:1) + Default is "enhanced". current_graphics_display : bool, optional Whether to use current graphics display. Default is False. image_format : str, optional Image format for export. Options are: - - "PNG": PNG image format - Default - - "JPG": JPG image format - - "BMP": BMP image format - - "TIF": TIFF image format - - "EPS": EPS image format - Default is "PNG". + - "png": PNG image format - Default + - "jpg": JPG image format + - "bmp": BMP image format + - "tif": TIFF image format + - "eps": EPS image format + Default is "png". Examples -------- @@ -189,9 +215,9 @@ def export_image( >>> app.helpers.export_image( ... result, ... "C:\\path\\to\\result.jpg", - ... background="Appearance", - ... resolution="High", - ... image_format="JPG", + ... background="appearance", + ... resolution="high", + ... image_format="jpg", ... ) """ from pathlib import Path @@ -283,7 +309,7 @@ def export_animation( file_path: str = None, width: int = 1280, height: int = 720, - animation_format: str = "GIF", + animation_format: str = "gif", ): r"""Export an animation of the specified object. @@ -302,11 +328,11 @@ def export_animation( The height of the exported animation in pixels. Default is 720. animation_format : str, optional Animation format for export. Options are: - - "GIF": GIF animation format - Default - - "AVI": AVI video format - - "MP4": MP4 video format - - "WMV": WMV video format - Default is "GIF". + - "gif": GIF animation format - Default + - "avi": AVI video format + - "mp4": MP4 video format + - "wmv": WMV video format + Default is "gif". Examples -------- @@ -318,7 +344,7 @@ def export_animation( >>> # Export as MP4 with custom resolution >>> app.helpers.export_animation( - ... result, "result_animation.mp4", width=1920, height=1080, animation_format="MP4" + ... result, "result_animation.mp4", width=1920, height=1080, animation_format="mp4" ... ) """ from pathlib import Path @@ -374,6 +400,171 @@ def export_animation( except Exception as e: raise RuntimeError(f"Animation export unsuccessful: {e}") + def setup_graphics( + self, + orientation: str = "iso", + fit: bool = True, + width: int = 1280, + height: int = 720, + resolution: str = "enhanced", + background: str = "white", + image_format: str = "png", + ): + """Configure graphics settings for image export. + + This is a convenience method that sets up camera orientation and creates + pre-configured image export settings. Commonly used at the start of examples + to prepare for exporting images. + + Parameters + ---------- + orientation : str, optional + Camera view orientation. Options are: + - "iso": Isometric view - Default + - "front": Front view + - "back": Back view + - "top": Top view + - "bottom": Bottom view + - "left": Left view + - "right": Right view + Default is "iso". + fit : bool, optional + Whether to fit the camera to the model. Default is True. + width : int, optional + Width of exported images in pixels. Default is 1280. + height : int, optional + Height of exported images in pixels. Default is 720. + resolution : str, optional + Resolution type for exported images. Options are: + - "normal": Normal resolution (1:1) + - "enhanced": Enhanced resolution (2:1) - Default + - "high": High resolution (4:1) + Default is "enhanced". + background : str, optional + Background type for exported images. Options are: + - "white": White background - Default + - "appearance": Use graphics appearance setting + Default is "white". + image_format : str, optional + Default image format for exports. Options are: + - "png": PNG image format - Default + - "jpg": JPG image format + - "bmp": BMP image format + - "tif": TIFF image format + - "eps": EPS image format + Default is "png". + + Returns + ------- + tuple + A tuple containing (camera, settings, format) where: + - camera: The configured camera object + - settings: GraphicsImageExportSettings with specified parameters + - format: GraphicsImageExportFormat enum value + + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> # Set up graphics with default settings (Iso view, 720p) + >>> camera, settings, img_format = app.helpers.setup_graphics() + + >>> # Set up graphics with custom orientation and 1080p resolution + >>> camera, settings, img_format = app.helpers.setup_graphics( + ... orientation="front", width=1920, height=1080, resolution="high" + ... ) + + >>> # Use the configured settings to export an image + >>> app.Graphics.ExportImage("output.png", img_format, settings) + """ + from ansys.mechanical.core.embedding.enum_importer import ( + GraphicsBackgroundType, + GraphicsImageExportFormat, + GraphicsResolutionType, + ViewOrientationType, + ) + + # Get graphics and camera + graphics = self._app.Graphics + camera = graphics.Camera + + # Set camera orientation + orientation_lower = orientation.lower() + if orientation_lower == "iso": + camera.SetSpecificViewOrientation(ViewOrientationType.Iso) + elif orientation_lower == "front": + camera.SetSpecificViewOrientation(ViewOrientationType.Front) + elif orientation_lower == "back": + camera.SetSpecificViewOrientation(ViewOrientationType.Back) + elif orientation_lower == "top": + camera.SetSpecificViewOrientation(ViewOrientationType.Top) + elif orientation_lower == "bottom": + camera.SetSpecificViewOrientation(ViewOrientationType.Bottom) + elif orientation_lower == "left": + camera.SetSpecificViewOrientation(ViewOrientationType.Left) + elif orientation_lower == "right": + camera.SetSpecificViewOrientation(ViewOrientationType.Right) + else: + raise ValueError( + f"Invalid orientation: {orientation}. " + "Valid options are 'Iso', 'Front', 'Back', 'Top', 'Bottom', 'Left', or 'Right'." + ) + + # Fit camera if requested + if fit: + camera.SetFit() + + # Create graphics image export settings + settings = self.Ansys.Mechanical.Graphics.GraphicsImageExportSettings() + settings.Width = width + settings.Height = height + settings.CurrentGraphicsDisplay = False + + # Set resolution + resolution_lower = resolution.lower() + if resolution_lower == "enhanced": + settings.Resolution = GraphicsResolutionType.EnhancedResolution + elif resolution_lower == "high": + settings.Resolution = GraphicsResolutionType.HighResolution + elif resolution_lower == "normal": + settings.Resolution = GraphicsResolutionType.NormalResolution + else: + raise ValueError( + f"Invalid resolution: {resolution}. " + "Valid options are 'Normal', 'Enhanced', or 'High'." + ) + + # Set background + background_lower = background.lower() + if background_lower == "white": + settings.Background = GraphicsBackgroundType.White + elif background_lower == "appearance": + settings.Background = GraphicsBackgroundType.GraphicsAppearanceSetting + else: + raise ValueError( + f"Invalid background: {background}. Valid options are 'White' or 'Appearance'." + ) + + # Set image format + format_lower = image_format.lower() + if format_lower == "png": + img_format = GraphicsImageExportFormat.PNG + elif format_lower == "jpg" or format_lower == "jpeg": + img_format = GraphicsImageExportFormat.JPG + elif format_lower == "bmp": + img_format = GraphicsImageExportFormat.BMP + elif format_lower == "tif" or format_lower == "tiff": + img_format = GraphicsImageExportFormat.TIF + elif format_lower == "eps": + img_format = GraphicsImageExportFormat.EPS + else: + raise ValueError( + f"Invalid image format: {image_format}. " + "Valid options are 'PNG', 'JPG', 'BMP', 'TIF', or 'EPS'." + ) + + return camera, settings, img_format + # Helper function to print the tree recursively def _print_tree(node, max_lines, lines_count, indentation): From 3bd6c8f85eca2982cb9fa01e59b88540474a74d0 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Thu, 5 Feb 2026 14:50:28 -0600 Subject: [PATCH 04/30] update more methods --- src/ansys/mechanical/core/embedding/app.py | 2 + .../mechanical/core/embedding/helpers.py | 200 +++++++++--------- 2 files changed, 105 insertions(+), 97 deletions(-) diff --git a/src/ansys/mechanical/core/embedding/app.py b/src/ansys/mechanical/core/embedding/app.py index eba6e92dfc..d521edb27f 100644 --- a/src/ansys/mechanical/core/embedding/app.py +++ b/src/ansys/mechanical/core/embedding/app.py @@ -741,6 +741,8 @@ def print_tree(self, node=None, max_lines=80, lines_count=0, indentation=""): ... | ├── Model ... ... truncating after 2 lines """ + if node is None: + node = self.DataModel.Project self.helpers.print_tree(node, max_lines) def log_debug(self, message): diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index a43fb719a8..ed834732ff 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -85,15 +85,33 @@ def print_tree(self, node=None, max_lines=80): _print_tree(node, max_lines, 0, "") - def import_geometry(self, file_path: str, process_named_selections: bool = True): + def import_geometry( + self, + file_path: str, + process_named_selections: bool = False, + named_selection_key: str = "NS", + process_material_properties: bool = False, + process_coordinate_systems: bool = False, + ): r"""Import geometry file into the current Mechanical model. + Returns + ------- + Ansys.ACT.Automation.Mechanical.GeometryImport + The geometry import object. + Parameters ---------- file_path : str The path to the geometry file to be imported. process_named_selections : bool, optional - Whether to process named selections during import. Default is True. + Whether to process named selections during import. Default is False. + named_selection_key : str, optional + Named selection key for filtering. Default is "NS". + process_material_properties : bool, optional + Whether to process material properties during import. Default is False. + process_coordinate_systems : bool, optional + Whether to process coordinate systems during import. Default is False. Examples -------- @@ -105,6 +123,15 @@ def import_geometry(self, file_path: str, process_named_selections: bool = True) >>> app.helpers.import_geometry( ... "C:\\path\\to\\geometry.step", process_named_selections=False ... ) + + >>> # Import with all options specified + >>> app.helpers.import_geometry( + ... "C:\\path\\to\\geometry.pmdb", + ... process_named_selections=True, + ... named_selection_key="", + ... process_material_properties=True, + ... process_coordinate_systems=True, + ... ) """ # Import Ansys and enums - same way as when App(globals=globals()) is used @@ -121,12 +148,16 @@ def import_geometry(self, file_path: str, process_named_selections: bool = True) self.Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() ) geometry_import_preferences.ProcessNamedSelections = process_named_selections + geometry_import_preferences.NamedSelectionKey = named_selection_key + geometry_import_preferences.ProcessMaterialProperties = process_material_properties + geometry_import_preferences.ProcessCoordinateSystems = process_coordinate_systems try: geometry_import.Import(file_path, geometry_import_format, geometry_import_preferences) self._app.log_info( f"Imported geometry from {file_path} successfully." f"Object State: {geometry_import.ObjectState}" ) + return geometry_import except Exception as e: raise RuntimeError(f"Geometry Import unsuccessful: {e}") @@ -141,7 +172,6 @@ def import_materials(self, file_path: str): Examples -------- >>> from ansys.mechanical.core import App - >>> app = App() >>> app.helpers.import_materials("C:\\path\\to\\materials.xml") """ # Add materials to the model and import the material files @@ -400,15 +430,13 @@ def export_animation( except Exception as e: raise RuntimeError(f"Animation export unsuccessful: {e}") - def setup_graphics( + def setup_view( self, orientation: str = "iso", fit: bool = True, - width: int = 1280, - height: int = 720, - resolution: str = "enhanced", - background: str = "white", - image_format: str = "png", + rotation: int = None, + axis: str = "x", + scene_height=None, ): """Configure graphics settings for image export. @@ -430,57 +458,26 @@ def setup_graphics( Default is "iso". fit : bool, optional Whether to fit the camera to the model. Default is True. - width : int, optional - Width of exported images in pixels. Default is 1280. - height : int, optional - Height of exported images in pixels. Default is 720. - resolution : str, optional - Resolution type for exported images. Options are: - - "normal": Normal resolution (1:1) - - "enhanced": Enhanced resolution (2:1) - Default - - "high": High resolution (4:1) - Default is "enhanced". - background : str, optional - Background type for exported images. Options are: - - "white": White background - Default - - "appearance": Use graphics appearance setting - Default is "white". - image_format : str, optional - Default image format for exports. Options are: - - "png": PNG image format - Default - - "jpg": JPG image format - - "bmp": BMP image format - - "tif": TIFF image format - - "eps": EPS image format - Default is "png". - - Returns - ------- - tuple - A tuple containing (camera, settings, format) where: - - camera: The configured camera object - - settings: GraphicsImageExportSettings with specified parameters - - format: GraphicsImageExportFormat enum value + rotation : int, optional + Rotation angle in degrees. Default is None (no rotation). + axis : str, optional + Axis to rotate around. Options are "x", "y", or "z". Default is "x". + scene_height : Quantity, optional + Scene height for the camera view. Default is None (no scene height adjustment). Examples -------- >>> from ansys.mechanical.core import App >>> app = App() - >>> # Set up graphics with default settings (Iso view, 720p) - >>> camera, settings, img_format = app.helpers.setup_graphics() - >>> # Set up graphics with custom orientation and 1080p resolution - >>> camera, settings, img_format = app.helpers.setup_graphics( - ... orientation="front", width=1920, height=1080, resolution="high" - ... ) + >>> # Set up view with scene height + >>> app.helpers.setup_view(scene_height=Quantity(2.0, "in")) >>> # Use the configured settings to export an image >>> app.Graphics.ExportImage("output.png", img_format, settings) """ from ansys.mechanical.core.embedding.enum_importer import ( - GraphicsBackgroundType, - GraphicsImageExportFormat, - GraphicsResolutionType, + CameraAxisType, ViewOrientationType, ) @@ -507,63 +504,72 @@ def setup_graphics( else: raise ValueError( f"Invalid orientation: {orientation}. " - "Valid options are 'Iso', 'Front', 'Back', 'Top', 'Bottom', 'Left', or 'Right'." + "Valid options are 'iso', 'front', 'back', 'top', 'bottom', 'left', or 'right'." ) + # Set scene height if provided + if scene_height is not None: + camera.SceneHeight = scene_height # Fit camera if requested if fit: camera.SetFit() - # Create graphics image export settings - settings = self.Ansys.Mechanical.Graphics.GraphicsImageExportSettings() - settings.Width = width - settings.Height = height - settings.CurrentGraphicsDisplay = False - - # Set resolution - resolution_lower = resolution.lower() - if resolution_lower == "enhanced": - settings.Resolution = GraphicsResolutionType.EnhancedResolution - elif resolution_lower == "high": - settings.Resolution = GraphicsResolutionType.HighResolution - elif resolution_lower == "normal": - settings.Resolution = GraphicsResolutionType.NormalResolution - else: - raise ValueError( - f"Invalid resolution: {resolution}. " - "Valid options are 'Normal', 'Enhanced', or 'High'." - ) + if rotation is not None: + if axis.lower() == "x": + camera.Rotate(rotation, CameraAxisType.ScreenX) + elif axis.lower() == "y": + camera.Rotate(rotation, CameraAxisType.ScreenY) + elif axis.lower() == "z": + camera.Rotate(rotation, CameraAxisType.ScreenZ) + else: + raise ValueError(f"Invalid axis: {axis}. Valid options are 'x', 'y', or 'z'.") + + def display_image( + self, + image_path: str, + figsize: tuple = (16, 9), + xticks: list = [], + yticks: list = [], + axis: str = "off", + ): + """Display an image using matplotlib. - # Set background - background_lower = background.lower() - if background_lower == "white": - settings.Background = GraphicsBackgroundType.White - elif background_lower == "appearance": - settings.Background = GraphicsBackgroundType.GraphicsAppearanceSetting - else: - raise ValueError( - f"Invalid background: {background}. Valid options are 'White' or 'Appearance'." - ) + Parameters + ---------- + image_path : str + The path to the image file to display. + figsize : tuple, optional + The size of the figure in inches (width, height). Default is (16, 9). + xticks : list, optional + The x-ticks to display on the plot. Default is []. + yticks : list, optional + The y-ticks to display on the plot. Default is []. + axis : str, optional + The axis visibility setting ('on' or 'off'). Default is "off". - # Set image format - format_lower = image_format.lower() - if format_lower == "png": - img_format = GraphicsImageExportFormat.PNG - elif format_lower == "jpg" or format_lower == "jpeg": - img_format = GraphicsImageExportFormat.JPG - elif format_lower == "bmp": - img_format = GraphicsImageExportFormat.BMP - elif format_lower == "tif" or format_lower == "tiff": - img_format = GraphicsImageExportFormat.TIF - elif format_lower == "eps": - img_format = GraphicsImageExportFormat.EPS - else: - raise ValueError( - f"Invalid image format: {image_format}. " - "Valid options are 'PNG', 'JPG', 'BMP', 'TIF', or 'EPS'." - ) + Examples + -------- + >>> from ansys.mechanical.core import App + >>> app = App() + >>> app.helpers.export_image(app.Model.Geometry, "geometry.png") + >>> app.helpers.display_image("geometry.png") - return camera, settings, img_format + >>> # Display with custom figure size + >>> app.helpers.display_image("result.png", figsize=(10, 6)) + """ + from matplotlib import image as mpimg, pyplot as plt + + # Set the figure size + plt.figure(figsize=figsize) + # Read and display the image + plt.imshow(mpimg.imread(image_path)) + # Set the tick locations and labels + plt.xticks(xticks) + plt.yticks(yticks) + # Turn axis on or off + plt.axis(axis) + # Display the figure + plt.show() # Helper function to print the tree recursively From 682f2ce406a9f0060bffd43bc48509ce2877c6d6 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Thu, 5 Feb 2026 15:05:50 -0600 Subject: [PATCH 05/30] udpate examples --- doc/source/conf.py | 1 + examples/01_basic/bolt_pretension.py | 218 ++++-------------- .../cooling_holes_thermal_analysis.py | 138 ++--------- 3 files changed, 67 insertions(+), 290 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 1be0cfe01c..33c0c1cf66 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -203,6 +203,7 @@ # Files to ignore "ignore_pattern": "flycheck*", # noqa: E501 "thumbnail_size": (350, 350), + "matplotlib_animations": True, } # -- Options for HTML output ------------------------------------------------- diff --git a/examples/01_basic/bolt_pretension.py b/examples/01_basic/bolt_pretension.py index 7f025a4bb3..1bf563e51d 100644 --- a/examples/01_basic/bolt_pretension.py +++ b/examples/01_basic/bolt_pretension.py @@ -39,8 +39,8 @@ from pathlib import Path import typing -from matplotlib import image as mpimg, pyplot as plt -from matplotlib.animation import FuncAnimation +from matplotlib import pyplot as plt +import matplotlib.animation as animation from PIL import Image from ansys.mechanical.core import App @@ -60,40 +60,13 @@ from ansys.mechanical.core.embedding.transaction import Transaction # %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# 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 - -# %% -# Set the geometry import group for the model -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Configure view and path for image export +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set the model -model = app.Model +app.helpers.setup_view(orientation="iso", fit=True, rotation=180, axis="y") +# Set the path for the output files (images, gifs, mechdat) +output_path = Path.cwd() / "out" -# 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 @@ -103,8 +76,7 @@ geometry_path = download_file("example_06_bolt_pret_geom.pmdb", "pymechanical", "00_basic") # Import/reload the geometry from the CAD (pmdb) file using the provided preferences -geometry_import.Import(geometry_path, geometry_import_format, geometry_import_preferences) - +geometry_import = app.helpers.import_geometry(geometry_path) # sphinx_gallery_start_ignore # Assert the geometry import was successful assert geometry_import.ObjectState == ObjectState.Solved, "Geometry Import unsuccessful" @@ -124,14 +96,8 @@ # %% # 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 the materials are defined -assert model_materials.ObjectState == ObjectState.FullyDefined, "Materials are not defined" -# sphinx_gallery_end_ignore +app.helpers.import_materials(copper_material_file_path) +app.helpers.import_materials(steel_material_file_path) # %% # Define analysis and unit system @@ -139,6 +105,7 @@ # %% # Add static structural analysis to the model +model = app.Model model.AddStaticStructuralAnalysis() static_structural = model.Analyses[0] static_structural_solution = static_structural.Solution @@ -505,45 +472,14 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: 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" +# Set the image export format, path and export the image 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() - +app.helpers.setup_view() +app.helpers.export_image(mesh, mesh_image_path) # %% # Display the mesh image -display_image(mesh_image_path) +app.helpers.display_image(mesh_image_path) # %% # Analysis settings @@ -635,14 +571,11 @@ def display_image( # 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, - graphics_image_export_settings, -) +app.helpers.setup_view() +app.helpers.export_image(bolt_presentation, loads_boundary_conditions_image_path) # Display the image of the loads and boundary conditions -display_image(loads_boundary_conditions_image_path) +app.helpers.display_image(loads_boundary_conditions_image_path) # %% # Insert results @@ -702,126 +635,75 @@ def display_image( # %% # Total deformation -# 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 / "total_deformation.png") # Export the image of the object -app.Graphics.ExportImage(image_path, image_export_format, graphics_image_export_settings) +app.helpers.setup_view() +app.helpers.export_image(total_deformation, file_path=image_path) # Display the image of the object -display_image(image_path) +app.helpers.display_image(image_path) # %% # Equivalent stress on all bodies -# 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 / "equivalent_stress_all_bodies.png") # Export the image of the object -app.Graphics.ExportImage(image_path, image_export_format, graphics_image_export_settings) +app.helpers.setup_view() +app.helpers.export_image(equivalent_stress_1, file_path=image_path) # Display the image of the object -display_image(image_path) +app.helpers.display_image(image_path) # %% # 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 / "equivalent_stress_shank.png") # Export the image of the object -app.Graphics.ExportImage(image_path, image_export_format, graphics_image_export_settings) +app.helpers.setup_view() +app.helpers.export_image(equivalent_stress_2, file_path=image_path) # Display the image of the object -display_image(image_path) - -# %% -# Export and display the contact status animation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +app.helpers.display_image(image_path) # %% -# 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 and display the contact status animation +# Export the contact status animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get the post contact tool status post_contact_tool_status = post_contact_tool.Children[0] -# 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") +app.helpers.setup_view() +app.helpers.export_animation(post_contact_tool_status, contact_status_gif_path) -# Export the contact status animation to a GIF file -post_contact_tool_status.ExportAnimation( - contact_status_gif_path, animation_export_format, animation_export_settings -) +# %% +# Display the contact status animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 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, +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) + + +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) + + +# Create and display animation +ani = animation.FuncAnimation( + fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True ) # Show the animation plt.show() -# %% -# Print the project tree -# ~~~~~~~~~~~~~~~~~~~~~~ - -app.print_tree() # %% # Clean up the project diff --git a/examples/01_basic/cooling_holes_thermal_analysis.py b/examples/01_basic/cooling_holes_thermal_analysis.py index bf949d7b5e..aa08006ab5 100644 --- a/examples/01_basic/cooling_holes_thermal_analysis.py +++ b/examples/01_basic/cooling_holes_thermal_analysis.py @@ -60,8 +60,6 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt - from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file @@ -75,81 +73,10 @@ 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 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 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 the required files # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -160,38 +87,12 @@ def display_image( # Download the material file mat_path = download_file("cooling_holes_material_file.xml", "pymechanical", "embedding") - -# %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# Define the graphics and camera objects -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.Height = 720 -settings_720p.CurrentGraphicsDisplay = False - -# Define the model -model = app.Model - # %% # Define Python variables # ~~~~~~~~~~~~~~~~~~~~~~~ # Store all main tree nodes as variables +model = app.Model geometry = model.Geometry mesh = model.Mesh materials = model.Materials @@ -202,21 +103,14 @@ def display_image( # Import the geometry # ~~~~~~~~~~~~~~~~~~~ -# 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 -geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() -geometry_import_preferences.ProcessNamedSelections = True -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) - +geometry_import = app.helpers.import_geometry( + geometry_path, + process_named_selections=True, + process_coordinate_systems=True, + named_selection_key="", + process_material_properties=True, +) # sphinx_gallery_start_ignore assert str(geometry_import.ObjectState) == "Solved", "Geometry Import unsuccessful" # sphinx_gallery_end_ignore @@ -339,7 +233,7 @@ def display_image( app.Tree.Activate([mesh]) # Set the camera to fit the model and export the image -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") +app.helpers.setup_view() # %% # Define analysis @@ -519,22 +413,22 @@ def display_image( # Postprocessing # ~~~~~~~~~~~~~~ -camera.SetFit() -camera.SceneHeight = Quantity(2.0, "in") +app.helpers.setup_view(scene_height=Quantity(2.0, "in")) + # %% # Display the temperature plots for both plates # Activate the temperature results for both plates app.Tree.Activate([temp_plot_both_plates]) # Set the extra model display to no wireframe -graphics.ViewOptions.ResultPreference.ExtraModelDisplay = ( +app.Graphics.ViewOptions.ResultPreference.ExtraModelDisplay = ( Ansys.Mechanical.DataModel.MechanicalEnums.Graphics.ExtraModelDisplay.NoWireframe ) # Set the camera to fit the model and export the image image_path = output_path / "temp_plot_both_plates.png" -graphics.ExportImage(str(image_path), image_export_format, settings_720p) +app.helpers.export_image(temp_plot_both_plates, image_path) # Display the exported image -display_image(image_path) +app.helpers.display_image(image_path) # %% # Display the temperature plots for fluid lines @@ -544,9 +438,9 @@ def display_image( # Set the camera to fit the model and export the image # Set the camera to fit the model and export the image image_path = output_path / "temp_plot_fluidlines.png" -graphics.ExportImage(str(image_path), image_export_format, settings_720p) +app.helpers.export_image(temp_plot_fluidlines, image_path) # Display the exported image -display_image(image_path) +app.helpers.display_image(image_path) # %% # Clean up the project # ~~~~~~~~~~~~~~~~~~~~ From 2c1734e3327892466832dcd33143e58df3acc24c Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 10:05:57 -0600 Subject: [PATCH 06/30] udpate more examples --- .../fracture_analysis_contact_debonding.py | 211 ++++-------------- examples/01_basic/harmonic_acoustics.py | 205 ++++------------- .../mechanical/core/embedding/helpers.py | 12 + 3 files changed, 96 insertions(+), 332 deletions(-) diff --git a/examples/01_basic/fracture_analysis_contact_debonding.py b/examples/01_basic/fracture_analysis_contact_debonding.py index 729fbc6d04..72d6f60443 100644 --- a/examples/01_basic/fracture_analysis_contact_debonding.py +++ b/examples/01_basic/fracture_analysis_contact_debonding.py @@ -38,7 +38,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt +from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation from PIL import Image @@ -60,18 +60,8 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set camera orientation -graphics = app.Graphics -camera = graphics.Camera -camera.SetSpecificViewOrientation(ViewOrientationType.Front) +app.helpers.setup_view("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 # %% # Create functions to set camera and display images @@ -80,96 +70,17 @@ # 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() - - # %% # 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) +app.helpers.import_geometry(geometry_path, analysis_type="2d") +# Set the model +model = app.Model # Visualize the model in 3D app.plot() @@ -184,9 +95,8 @@ def display_image( # Add materials to the model and import the material files model_materials = model.Materials -model_materials.Import(mat1_path) -model_materials.Import(mat2_path) - +app.helpers.import_materials(mat1_path) +app.helpers.import_materials(mat2_path) # %% # Add connections to the model # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -351,9 +261,10 @@ def add_sizing( mesh.GenerateMesh() # Display the mesh image -set_camera_and_display_image( - camera, graphics, graphics_image_export_settings, output_path, "mesh.png" -) +app.helpers.setup_view("front") +image_path = Path(output_path) / "boundary_conditions.png" +app.helpers.export_image(mesh, image_path) +app.helpers.display_image(image_path) # %% # Add a contact debonding object @@ -460,13 +371,10 @@ def add_displacement( static_structural_analysis.Activate() -set_camera_and_display_image( - camera, - graphics, - graphics_image_export_settings, - output_path, - "boundary_conditions.png", -) +app.helpers.setup_view("front") +image_path = Path(output_path) / "boundary_conditions.png" +app.helpers.export_image(static_structural_analysis, image_path) +app.helpers.display_image(image_path) # %% # Add results to the solution @@ -515,86 +423,51 @@ def add_displacement( # Directional deformation directional_deformation.Activate() -set_camera_and_display_image( - camera, - graphics, - graphics_image_export_settings, - output_path, - "directional_deformation.png", -) +image_path = Path(output_path) / "directional_deformation.png" +app.helpers.export_image(directional_deformation, image_path) +app.helpers.display_image(image_path) # %% # Force reaction force_reaction.Activate() -set_camera_and_display_image( - camera, graphics, graphics_image_export_settings, output_path, "force_reaction.png" -) +image_path = Path(output_path) / "force_reaction.png" +app.helpers.export_image(force_reaction, image_path) +app.helpers.display_image(image_path) # %% # Export the animation # ~~~~~~~~~~~~~~~~~~~~ -# %% -# 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. +# Set the path for the contact status GIF +force_reaction_gif_path = output_path / "force_reaction.gif" - 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 the force reaction animation to a GIF file +app.helpers.export_animation(force_reaction, force_reaction_gif_path) +# Open the GIF file and create an animation # %% -# Display the animation of the force reaction +# Display the contact status animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 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 +# Open the GIF file and create an animation +gif = Image.open(force_reaction_gif_path) +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) -# 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 -) +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) -# 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, -) + +# Create and display animation +ani = FuncAnimation(fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True) # Show the animation plt.show() diff --git a/examples/01_basic/harmonic_acoustics.py b/examples/01_basic/harmonic_acoustics.py index 082d28d856..9f5c7ac768 100644 --- a/examples/01_basic/harmonic_acoustics.py +++ b/examples/01_basic/harmonic_acoustics.py @@ -38,7 +38,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt +from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation from PIL import Image @@ -63,94 +63,11 @@ 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() - - # %% # Configure graphics for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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 -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False -camera.Rotate(180, CameraAxisType.ScreenY) +app.helpers.setup_view(orientation="iso", rotation=180, axis="y") # %% # Download geometry and materials files @@ -168,15 +85,7 @@ def display_image( # 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) - +app.helpers.import_geometry(geometry_path, process_named_selections=True) # Define the geometry in the model geometry = model.Geometry @@ -210,8 +119,7 @@ def display_image( # %% # Import and assign the materials -mat.Import(mat_path) - +app.helpers.import_materials(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: @@ -413,10 +321,10 @@ def add_generation_criteria( # 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 -) - +app.helpers.setup_view(fit=True) +image_path = output_path / "boundary_conditions.png" +app.helpers.export_image(file_path=image_path) +app.helpers.display_image(image_path) # %% # Add results to the harmonic acoustics solution # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -556,94 +464,65 @@ def set_properties( # Display the total acoustic pressure result app.Tree.Activate([acoustic_pressure_result_1]) -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "pressure.png") +image_path = output_path / "pressure.png" +app.helpers.export_image(file_path=image_path) +app.helpers.display_image(image_path) # %% # Display the total acoustic velocity app.Tree.Activate([acoustic_pressure_result_1]) -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "total_velocity.png") - -# %% -# Display the acoustic sound pressure level +image_path = output_path / "total_velocity.png" +app.helpers.export_image(file_path=image_path) +app.helpers.display_image(image_path) app.Tree.Activate([acoustic_spl]) -set_camera_and_display_image( - camera, graphics, settings_720p, output_path, "sound_pressure_level.png" -) +image_path = output_path / "sound_pressure_level.png" +app.helpers.export_image(file_path=image_path) +app.helpers.display_image(image_path) # %% # Display the acoustic directional velocity app.Tree.Activate([acoustic_directional_velocity_3]) -set_camera_and_display_image( - camera, graphics, settings_720p, output_path, "directional_velocity.png" -) +image_path = output_path / "directional_velocity.png" +app.helpers.export_image(file_path=image_path) +app.helpers.display_image(image_path) # %% # Display the acoustic kinetic energy app.Tree.Activate([acoustic_ke]) -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "kinetic_energy.png") - -# %% -# Create a function to update the animation frames - - -def update_animation(frame: int) -> list[mpimg.AxesImage]: - """Update the animation frame for the GIF. +image_path = output_path / "kinetic_energy.png" +app.helpers.export_image(file_path=image_path) +app.helpers.display_image(image_path) - 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 the animation of the acoustic pressure result +press_gif = output_path / "press.gif" +app.helpers.export_animation(acoustic_pressure_result_1, press_gif) # %% -# Display the total acoustic pressure animation +# Display the contact status animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set the animation export format to GIF -animation_export_format = Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF +# Open the GIF file and create an animation +gif = Image.open(press_gif) +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) -# Configure the export settings for the animation -settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() -settings_720p.Width = 1280 -settings_720p.Height = 720 -# 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) +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) -# 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( - figure, - update_animation, - frames=range(gif.n_frames), - interval=200, - repeat=True, - blit=True, -) + +# Create and display animation +ani = FuncAnimation(fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True) # Show the animation plt.show() diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index ed834732ff..280cc8f798 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -92,6 +92,7 @@ def import_geometry( named_selection_key: str = "NS", process_material_properties: bool = False, process_coordinate_systems: bool = False, + analysis_type: str = "3d", ): r"""Import geometry file into the current Mechanical model. @@ -112,6 +113,9 @@ def import_geometry( Whether to process material properties during import. Default is False. process_coordinate_systems : bool, optional Whether to process coordinate systems during import. Default is False. + analysis_type : str, optional + The type of analysis for the geometry import. Default is "3d". + Options are "2d" or "3d". Examples -------- @@ -151,6 +155,14 @@ def import_geometry( geometry_import_preferences.NamedSelectionKey = named_selection_key geometry_import_preferences.ProcessMaterialProperties = process_material_properties geometry_import_preferences.ProcessCoordinateSystems = process_coordinate_systems + if analysis_type.lower() == "2d": + geometry_import_preferences.AnalysisType = ( + self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.AnalysisType.Type2D + ) + else: + geometry_import_preferences.AnalysisType = ( + self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.AnalysisType.Type3D + ) try: geometry_import.Import(file_path, geometry_import_format, geometry_import_preferences) self._app.log_info( From 45d3b3dce9f26715b9c33671eac1622a750a8a78 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 11:23:31 -0600 Subject: [PATCH 07/30] udpate rest of the examples --- .../01_basic/steady_state_thermal_analysis.py | 214 ++++-------------- .../topology_optimization_cantilever_beam.py | 133 ++--------- examples/01_basic/valve.py | 194 ++++------------ 3 files changed, 103 insertions(+), 438 deletions(-) diff --git a/examples/01_basic/steady_state_thermal_analysis.py b/examples/01_basic/steady_state_thermal_analysis.py index 51a8577499..4a2af0871d 100644 --- a/examples/01_basic/steady_state_thermal_analysis.py +++ b/examples/01_basic/steady_state_thermal_analysis.py @@ -40,7 +40,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt +from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation from PIL import Image @@ -57,100 +57,11 @@ 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 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 -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False +app.helpers.setup_view("iso") # %% # Download the geometry file @@ -163,18 +74,7 @@ def display_image( # Import the geometry # ~~~~~~~~~~~~~~~~~~~ -# Define the model -model = app.Model - -# 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) +app.helpers.import_geometry(geometry_path, process_named_selections=True) # Visualize the model in 3D app.plot() @@ -184,6 +84,7 @@ def display_image( # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Add a steady state thermal analysis to the model +model = app.Model model.AddSteadyStateThermalAnalysis() # Set the Mechanical unit system to Standard MKS app.ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardMKS @@ -481,8 +382,10 @@ def set_inputs_and_outputs( 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") +image_path = output_path / "bc_steady_state.png" +app.helpers.setup_view() +app.helpers.export_image(stat_therm, image_path) +app.helpers.display_image(image_path) # %% # Add results @@ -599,102 +502,73 @@ def set_inputs_and_outputs( # ~~~~~~~~~~~~~~~~~~~ # 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") +image_path = output_path / "total_body_temp.png" +app.helpers.setup_view() +app.helpers.export_image(stat_therm, image_path) +app.helpers.display_image(image_path) # %% # Temperature on part of the body # 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") +image_path = output_path / "part_temp_body.png" +app.helpers.setup_view() +app.helpers.export_image(stat_therm, image_path) +app.helpers.display_image(image_path) # %% # Temperature distribution along the specific path # 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" -) +image_path = output_path / "path_temp_distribution.png" +app.helpers.setup_view() +app.helpers.export_image(stat_therm, image_path) +app.helpers.display_image(image_path) # %% # Temperature of bottom surface # 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" -) +image_path = output_path / "bottom_surface_temp.png" +app.helpers.setup_view() +app.helpers.export_image(stat_therm, image_path) +app.helpers.display_image(image_path) # %% # 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] +# Export the directional heat flux animation as a GIF +directional_heat_flux_gif = output_path / "directional_heat_flux.gif" +app.helpers.export_animation(directional_heat_flux_gif) # %% -# Show the directional heat flux animation +# Display the heat flux animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Activate the directional heat flux -app.Tree.Activate([directional_heat_flux]) +# Open the GIF file and create an animation +gif = Image.open(directional_heat_flux_gif) +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) -# 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 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 -) +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) -# 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( - figure, - update_animation, - frames=range(gif.n_frames), - interval=100, - repeat=True, - blit=True, -) + +# Create and display animation +ani = FuncAnimation(fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True) # Show the animation plt.show() + # %% # Display the output file from the solve # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/01_basic/topology_optimization_cantilever_beam.py b/examples/01_basic/topology_optimization_cantilever_beam.py index 90206578f3..274e4bb340 100644 --- a/examples/01_basic/topology_optimization_cantilever_beam.py +++ b/examples/01_basic/topology_optimization_cantilever_beam.py @@ -37,13 +37,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt - from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file if TYPE_CHECKING: - import Ansys + pass # %% # Initialize the embedded application @@ -53,97 +51,14 @@ print(app) # %% -# Create functions to set camera and display images -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Setup the output path and camera +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 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 +app.helpers.setup_view("front") # %% # Import the structural analysis model @@ -181,13 +96,18 @@ def display_image( # Activate the total deformation result and display the image struct_sln.Children[1].Activate() -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "total_deformation.png") - +image_path = output_path / "total_deformation.png" +app.helpers.setup_view() +app.helpers.export_image(struct_sln.Children[1], image_path) +app.helpers.display_image(image_path) # %% # Activate the equivalent stress result and display the image struct_sln.Children[2].Activate() -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "equivalent_stress.png") +image_path = output_path / "equivalent_stress.png" +app.helpers.setup_view() +app.helpers.export_image(struct_sln.Children[2], image_path) +app.helpers.display_image(image_path) # %% # Topology optimization @@ -227,9 +147,9 @@ def display_image( # 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" -) +app.helpers.setup_view() +app.helpers.export_image(topology_optimization, output_path / "boundary_conditions.png") +app.helpers.display_image(output_path / "boundary_conditions.png") # %% # Solve the solution @@ -270,29 +190,16 @@ def display_image( # 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" -) +image_path = output_path / "topo_opitimized_smooth.png" +app.helpers.setup_view() +app.helpers.export_image(topology_density.Children[0], image_path) +app.helpers.display_image(image_path) # %% # Export the animation -app.Tree.Activate([topology_density]) - -# 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 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 -) - -# %% -# .. image:: /_static/basic/Topo_opitimized.gif +app.helpers.export_animation(topology_density, topology_optimized_gif) # %% # Review the results diff --git a/examples/01_basic/valve.py b/examples/01_basic/valve.py index abf5df42c5..d8701930f0 100644 --- a/examples/01_basic/valve.py +++ b/examples/01_basic/valve.py @@ -35,7 +35,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt +from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation from PIL import Image @@ -59,93 +59,6 @@ # 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(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 -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False - # %% # Download and import the geometry file # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -159,16 +72,7 @@ def display_image( # 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) +app.helpers.import_geometry(geometry_path, process_named_selections=True) # Visualize the model in 3D app.plot() @@ -210,8 +114,10 @@ def display_image( mesh.GenerateMesh() # Activate the mesh and display the image -app.Tree.Activate([mesh]) -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") +image_path = output_path / "mesh.png" +app.helpers.setup_view() +app.helpers.export_image(mesh, image_path) +app.helpers.display_image(image_path) # %% # Add a static structural analysis and apply boundary conditions @@ -242,10 +148,10 @@ def display_image( pressure.Magnitude.Output.DiscreteValues = [Quantity("0 [Pa]"), Quantity("15 [MPa]")] # Activate the analysis and display the image -analysis.Activate() -set_camera_and_display_image( - camera, graphics, settings_720p, output_path, "boundary_conditions.png" -) +image_path = output_path / "boundary_conditions.png" +app.helpers.setup_view() +app.helpers.export_image(analysis, image_path) +app.helpers.display_image(image_path) # %% # Add results to the analysis solution @@ -283,72 +189,50 @@ def display_image( # 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" -) +image_path = output_path / "total_deformation_valve.png" +app.helpers.setup_view() +app.helpers.export_image(deformation, image_path) +app.helpers.display_image(image_path) # %% # Show the equivalent stress image # 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") - - -# %% -# 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] +image_path = output_path / "stress_valve.png" +app.helpers.setup_view() +app.helpers.export_image(stress, image_path) +app.helpers.display_image(image_path) # %% # Export the stress 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 animation of the equivalent stress result +app.helpers.setup_view() valve_gif = output_path / "valve.gif" -stress.ExportAnimation(str(valve_gif), animation_export_format, settings_720p) +app.helpers.export_animation(stress, valve_gif) + +# %% +# Display the stress animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 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, -) +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) + + +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) + + +# Create and display animation +ani = FuncAnimation(fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True) # Show the animation plt.show() From 5d9bcf4b383af1df134e1727354599d7832f2c5f Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 12:16:35 -0600 Subject: [PATCH 08/30] last example update --- examples/01_basic/steady_state_thermal_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/01_basic/steady_state_thermal_analysis.py b/examples/01_basic/steady_state_thermal_analysis.py index 4a2af0871d..275b6cb5aa 100644 --- a/examples/01_basic/steady_state_thermal_analysis.py +++ b/examples/01_basic/steady_state_thermal_analysis.py @@ -540,7 +540,7 @@ def set_inputs_and_outputs( # Export the directional heat flux animation as a GIF directional_heat_flux_gif = output_path / "directional_heat_flux.gif" -app.helpers.export_animation(directional_heat_flux_gif) +app.helpers.export_animation(directional_heat_flux, directional_heat_flux_gif) # %% From 81b96f2a5cd267eb48d314784f1467592c106f55 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 12:40:53 -0600 Subject: [PATCH 09/30] update modal acoustics --- .../cooling_holes_thermal_analysis.py | 6 +- .../fracture_analysis_contact_debonding.py | 7 - examples/01_basic/harmonic_acoustics.py | 15 +- examples/01_basic/modal_acoustics_analysis.py | 220 ++++-------------- examples/01_basic/valve.py | 6 + 5 files changed, 56 insertions(+), 198 deletions(-) diff --git a/examples/01_basic/cooling_holes_thermal_analysis.py b/examples/01_basic/cooling_holes_thermal_analysis.py index aa08006ab5..52f7ebe527 100644 --- a/examples/01_basic/cooling_holes_thermal_analysis.py +++ b/examples/01_basic/cooling_holes_thermal_analysis.py @@ -234,6 +234,9 @@ # Set the camera to fit the model and export the image app.helpers.setup_view() +image_path = output_path / "mesh.png" +app.helpers.export_image(mesh, image_path) +app.helpers.display_image(image_path) # %% # Define analysis @@ -436,11 +439,12 @@ # Activate the temperature results for fluid lines app.Tree.Activate([temp_plot_fluidlines]) # Set the camera to fit the model and export the image -# Set the camera to fit the model and export the image image_path = output_path / "temp_plot_fluidlines.png" app.helpers.export_image(temp_plot_fluidlines, image_path) # Display the exported image app.helpers.display_image(image_path) + + # %% # Clean up the project # ~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/01_basic/fracture_analysis_contact_debonding.py b/examples/01_basic/fracture_analysis_contact_debonding.py index 72d6f60443..11d4d1d62b 100644 --- a/examples/01_basic/fracture_analysis_contact_debonding.py +++ b/examples/01_basic/fracture_analysis_contact_debonding.py @@ -163,9 +163,6 @@ def get_child_object(body, child_type, name: str): # Define the contact and contact regions # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# %% -# Activate the contact region - # Get the contact from the connection group contact = get_child_object( connections, Ansys.ACT.Automation.Mechanical.Connections.ConnectionGroup, "Contacts" @@ -439,13 +436,9 @@ def add_displacement( # Export the animation # ~~~~~~~~~~~~~~~~~~~~ -# 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 app.helpers.export_animation(force_reaction, force_reaction_gif_path) -# Open the GIF file and create an animation # %% # Display the contact status animation diff --git a/examples/01_basic/harmonic_acoustics.py b/examples/01_basic/harmonic_acoustics.py index 9f5c7ac768..7e931d3537 100644 --- a/examples/01_basic/harmonic_acoustics.py +++ b/examples/01_basic/harmonic_acoustics.py @@ -55,18 +55,10 @@ 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" - -# %% -# Configure graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +# Set the camera orientation app.helpers.setup_view(orientation="iso", rotation=180, axis="y") # %% @@ -471,11 +463,6 @@ def set_properties( # %% # Display the total acoustic velocity -app.Tree.Activate([acoustic_pressure_result_1]) -image_path = output_path / "total_velocity.png" -app.helpers.export_image(file_path=image_path) -app.helpers.display_image(image_path) - app.Tree.Activate([acoustic_spl]) image_path = output_path / "sound_pressure_level.png" app.helpers.export_image(file_path=image_path) diff --git a/examples/01_basic/modal_acoustics_analysis.py b/examples/01_basic/modal_acoustics_analysis.py index 30eef46b05..d7cc3aa456 100644 --- a/examples/01_basic/modal_acoustics_analysis.py +++ b/examples/01_basic/modal_acoustics_analysis.py @@ -44,7 +44,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from matplotlib import image as mpimg, pyplot as plt +from matplotlib import pyplot as plt from matplotlib.animation import FuncAnimation from PIL import Image @@ -61,104 +61,9 @@ 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, - 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 the graphics for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -graphics = app.Graphics -camera = graphics.Camera - -# 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 -settings_720p.Background = GraphicsBackgroundType.White -settings_720p.Width = 1280 -settings_720p.Height = 720 -settings_720p.CurrentGraphicsDisplay = False +app.helpers.setup_view() # %% # Download the geometry and material files @@ -173,28 +78,14 @@ def display_image( # Import and display the geometry # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Define the model -model = app.Model - -# 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) - -# 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 -) +app.helpers.import_geometry(geometry_path, process_named_selections=True) +app.plot() # %% # Store all variables necessary for analysis # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +model = app.Model geometry = model.Geometry mesh = model.Mesh named_selections = model.NamedSelections @@ -210,7 +101,7 @@ def display_image( # 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) +app.helpers.import_materials(mat_path) # %% # Assign material to solid bodies @@ -355,7 +246,11 @@ def set_mesh_properties( # Generate the mesh and display the image mesh.GenerateMesh() -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "mesh.png") + +image_path = output_path / "mesh.png" +app.helpers.setup_view() +app.helpers.export_image(mesh, image_path) +app.helpers.display_image(image_path) # %% # Set up the contact regions in the connection group @@ -526,9 +421,12 @@ def set_contact_region_properties( # Set the location of the fixed support to the geometry entities fixed_support.Location = fvert -# Activate the modal acoustic analysis and display the image +# Activate the modal acoustic analysis and display boundary conditions modal_acst.Activate() -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "geometry.png") +image_path = output_path / "geometry.png" +app.helpers.setup_view() +app.helpers.export_image(modal_acst, image_path) +app.helpers.display_image(image_path) # %% # Add results to the solution @@ -584,16 +482,18 @@ def set_contact_region_properties( # Activate the first total deformation result and display the image app.Tree.Activate([total_deformation_results[0]]) -set_camera_and_display_image( - camera, graphics, settings_720p, output_path, "total_deformation.png", set_fit=True -) +image_path = output_path / "total_deformation.png" +app.helpers.setup_view() +app.helpers.export_image(total_deformation_results[0], image_path) +app.helpers.display_image(image_path) # %% # Activate the acoustic pressure result and display the image -app.Tree.Activate([acoustic_pressure_result]) -set_camera_and_display_image(camera, graphics, settings_720p, output_path, "acoustic_pressure.png") - +image_path = output_path / "acoustic_pressure.png" +app.helpers.setup_view() +app.helpers.export_image(acoustic_pressure_result, image_path) +app.helpers.display_image(image_path) # %% # Display all modal frequency, force reaction, and acoustic pressure values @@ -620,65 +520,33 @@ def set_contact_region_properties( print("Force reaction z-axis : ", force_reaction_1_z) # %% -# 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 deformation animation +deformation_gif = output_path / "total_deformation_results.gif" +app.helpers.setup_view() +app.helpers.export_animation(total_deformation_results[-1], deformation_gif) # %% -# Play the total deformation animation +# Display the total deformation animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set the animation export format to GIF -animation_export_format = Ansys.Mechanical.DataModel.Enums.GraphicsAnimationExportFormat.GIF +# Open the GIF file and create an animation +gif = Image.open(deformation_gif) +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) -# Set the export settings for the animation -settings_720p = Ansys.Mechanical.Graphics.AnimationExportSettings() -settings_720p.Width = 1280 -settings_720p.Height = 720 -# 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 -) +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) -# 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, -) + +# Create and display animation +ani = FuncAnimation(fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True) # Show the animation plt.show() diff --git a/examples/01_basic/valve.py b/examples/01_basic/valve.py index d8701930f0..96c02dc33f 100644 --- a/examples/01_basic/valve.py +++ b/examples/01_basic/valve.py @@ -68,6 +68,7 @@ # %% # Import the geometry +# ~~~~~~~~~~~~~~~~~~~ # Define the model model = app.Model @@ -104,6 +105,7 @@ # %% # Define the mesh settings and generate the mesh +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Define the mesh mesh = model.Mesh @@ -155,6 +157,7 @@ # %% # Add results to the analysis solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Define the solution for the analysis solution = analysis.Solution @@ -186,6 +189,7 @@ # %% # Show the total deformation image +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Activate the total deformation result and display the image app.Tree.Activate([deformation]) @@ -196,6 +200,7 @@ # %% # Show the equivalent stress image +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Activate the equivalent stress result and display the image app.Tree.Activate([stress]) @@ -207,6 +212,7 @@ # %% # Export the stress animation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ app.helpers.setup_view() valve_gif = output_path / "valve.gif" From 3de47e7ebb5b73de691ce9f02c1761b0823efa26 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 13:48:59 -0600 Subject: [PATCH 10/30] update gif --- examples/01_basic/bolt_pretension.py | 2 +- .../topology_optimization_cantilever_beam.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/examples/01_basic/bolt_pretension.py b/examples/01_basic/bolt_pretension.py index 1bf563e51d..02180b99a2 100644 --- a/examples/01_basic/bolt_pretension.py +++ b/examples/01_basic/bolt_pretension.py @@ -61,7 +61,7 @@ # %% # Configure view and path for image export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ app.helpers.setup_view(orientation="iso", fit=True, rotation=180, axis="y") # Set the path for the output files (images, gifs, mechdat) diff --git a/examples/01_basic/topology_optimization_cantilever_beam.py b/examples/01_basic/topology_optimization_cantilever_beam.py index 274e4bb340..9869564903 100644 --- a/examples/01_basic/topology_optimization_cantilever_beam.py +++ b/examples/01_basic/topology_optimization_cantilever_beam.py @@ -37,6 +37,10 @@ from pathlib import Path from typing import TYPE_CHECKING +from matplotlib import pyplot as plt +import matplotlib.animation as animation +from PIL import Image + from ansys.mechanical.core import App from ansys.mechanical.core.examples import delete_downloads, download_file @@ -201,6 +205,32 @@ topology_optimized_gif = output_path / "topology_opitimized.gif" app.helpers.export_animation(topology_density, topology_optimized_gif) +# %% +# Display the topology optimized animation + +# Open the GIF file and create an animation +gif = Image.open(topology_optimized_gif) +fig, ax = plt.subplots(figsize=(8, 4)) +ax.axis("off") +image = ax.imshow(gif.convert("RGBA")) + + +# Animation update function +def update_frame(frame): + """Update the frame for the animation.""" + gif.seek(frame) + image.set_array(gif.convert("RGBA")) + return (image,) + + +# Create and display animation +ani = animation.FuncAnimation( + fig, update_frame, frames=gif.n_frames, interval=200, blit=True, repeat=True +) + +# Show the animation +plt.show() + # %% # Review the results From fa50851178fdf8cbdaae2c06c9fabedcbc242473 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 14:24:48 -0600 Subject: [PATCH 11/30] add linkcheck_ignore --- doc/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 33c0c1cf66..b5d042cd6a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -378,6 +378,7 @@ def intersphinx_pymechanical(switcher_version: str): "https://support.ansys.com/*", "https://discuss.ansys.com/*", "https://www.ansys.com/*", + "https://developer.ansys.com/.*", "../api/*", ] From 323044a14f53c86a3e4c40ece9303abff1e74702 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 22:28:27 -0600 Subject: [PATCH 12/30] add tests --- doc/source/user_guide/howto/helpers.rst | 488 ++++++++++++++++++++++++ doc/source/user_guide/index.rst | 1 + tests/embedding/test_helper.py | 419 ++++++++++++++++++++ 3 files changed, 908 insertions(+) create mode 100644 doc/source/user_guide/howto/helpers.rst create mode 100644 tests/embedding/test_helper.py diff --git a/doc/source/user_guide/howto/helpers.rst b/doc/source/user_guide/howto/helpers.rst new file mode 100644 index 0000000000..edbfd7e20d --- /dev/null +++ b/doc/source/user_guide/howto/helpers.rst @@ -0,0 +1,488 @@ +.. _ref_embedding_user_guide_helpers: + +Helpers +======= + +The `Helpers <../api/ansys/mechanical/core/embedding/helpers/Helpers.html>`_ class provides +convenient utility methods for common Mechanical operations. These helpers simplify tasks such as +importing geometry and materials, exporting images and animations, configuring views, and +visualizing the project tree structure. + +The Helpers class is accessible through the ``helpers`` attribute of the +`App <../api/ansys/mechanical/core/embedding/app/App.html>`_ instance: + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + helpers = app.helpers + +All helper methods are designed to work seamlessly with the embedded Mechanical instance and +provide clear error messages when operations fail. + +Visualizing project structure +------------------------------ + +The ``print_tree()`` method displays a hierarchical tree representation of your Mechanical project, +making it easy to understand the structure and status of objects. + +**Basic usage** + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + app.helpers.print_tree() + +This prints the entire project tree starting from the Project node, showing object states with +visual indicators: + +- ``(?)`` - UnderDefined +- ``(✓)`` - Solved or FullyDefined +- ``(⚡︎)`` - NotSolved or Obsolete +- ``(✕)`` - SolveFailed +- ``(Suppressed)`` - Object is suppressed + +**Custom starting node** + +You can print a sub-tree starting from any node: + +.. code:: python + + # Print only the Model sub-tree + app.helpers.print_tree(node=app.Model) + +**Limiting output** + +Control the number of lines printed to avoid overwhelming output: + +.. code:: python + + # Print only first 20 lines + app.helpers.print_tree(max_lines=20) + + # Print unlimited lines + app.helpers.print_tree(max_lines=-1) + +Importing geometry +------------------ + +The ``import_geometry()`` method simplifies importing geometry files into your Mechanical model. +It supports various CAD formats and provides options for processing named selections, material +properties, and coordinate systems. + +**Basic geometry import** + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + geometry_import = app.helpers.import_geometry("path/to/geometry.x_t") + +**Import with named selections** + +Process named selections from the geometry file: + +.. code:: python + + geometry_import = app.helpers.import_geometry( + "path/to/geometry.pmdb", + process_named_selections=True, + named_selection_key="NS" + ) + +**Import with all options** + +Import geometry with material properties and coordinate systems: + +.. code:: python + + geometry_import = app.helpers.import_geometry( + "path/to/geometry.step", + process_named_selections=True, + named_selection_key="", + process_material_properties=True, + process_coordinate_systems=True, + analysis_type="3d" + ) + +**2D analysis** + +For 2D analyses, specify the analysis type: + +.. code:: python + + geometry_import = app.helpers.import_geometry( + "path/to/geometry.agdb", + analysis_type="2d" + ) + +.. note:: + The method automatically determines the geometry format and returns the geometry import object. + If the import fails, a ``RuntimeError`` is raised with details about the failure. + +Importing materials +------------------- + +The ``import_materials()`` method imports materials from XML material database files. + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + app.helpers.import_materials("path/to/materials.xml") + +This method adds the materials to the ``Model.Materials`` collection. If the import fails, +a ``RuntimeError`` is raised. + +Exporting images +---------------- + +The ``export_image()`` method exports high-quality images of your model, geometry, mesh, or results. +It provides extensive control over image resolution, format, and appearance. + +**Basic image export** + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + # Import geometry first + app.helpers.import_geometry("path/to/geometry.x_t") + + # Export an image of the geometry + app.helpers.export_image( + obj=app.Model.Geometry, + file_path="geometry_image.png" + ) + +**Custom image settings** + +Control image dimensions, resolution, background, and format: + +.. code:: python + + app.helpers.export_image( + obj=app.Model.Geometry, + file_path="custom_image.jpg", + width=1920, + height=1080, + background="appearance", # or "white" + resolution="high", # "normal", "enhanced", or "high" + image_format="jpg" # "png", "jpg", "bmp", "tif", or "eps" + ) + +**Export current graphics display** + +Export whatever is currently displayed without specifying an object: + +.. code:: python + + app.helpers.export_image( + file_path="current_view.png", + current_graphics_display=True + ) + +**Supported formats** + +- **PNG** - Recommended for technical documentation (lossless) +- **JPG** - Good for photographs and presentations (compressed format) +- **BMP** - Uncompressed bitmap +- **TIF** - Tagged image format (lossless) +- **EPS** - Vector format for publications + +**Resolution options** + +- **normal** - 1:1 pixel ratio (fastest) +- **enhanced** - 2:1 pixel ratio (default, good quality) +- **high** - 4:1 pixel ratio (best quality, slower) + +Exporting animations +-------------------- + +The ``export_animation()`` method exports animations of results that have multiple time steps +or mode shapes, such as transient analyses or modal analyses. + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + # Set up and solve an analysis... + analysis = app.Model.AddStaticStructuralAnalysis() + result = analysis.Solution.AddTotalDeformation() + + # After solving, export animation + app.helpers.export_animation( + obj=result, + file_path="deformation.gif" + ) + +**Custom animation settings** + +.. code:: python + + app.helpers.export_animation( + obj=result, + file_path="deformation.mp4", + width=1920, + height=1080, + animation_format="mp4" # "gif", "avi", "mp4", or "wmv" + ) + +**Supported animation formats** + +- **GIF** - Widely supported, good for web +- **AVI** - Uncompressed video +- **MP4** - Compressed video, good for presentations +- **WMV** - Windows Media Video format + +.. note:: + Animation export requires that the result has been solved and has multiple steps or modes + to animate. Attempting to export an animation of unsolved results raises a ``RuntimeError``. + +Setting up camera views +----------------------- + +The ``setup_view()`` method configures the camera orientation and view settings for your graphics +display. This is particularly useful before exporting images to ensure consistent viewpoints. + +**Basic view orientations** + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + # Import geometry first + app.helpers.import_geometry("path/to/geometry.x_t") + + # Set isometric view + app.helpers.setup_view(orientation="iso") + + # Set front view + app.helpers.setup_view(orientation="front") + +**Available orientations** + +- ``"iso"`` - Isometric view (default) +- ``"front"`` - Front view +- ``"back"`` - Back view +- ``"top"`` - Top view +- ``"bottom"`` - Bottom view +- ``"left"`` - Left side view +- ``"right"`` - Right side view + +**View with rotation** + +Add rotation to any standard view: + +.. code:: python + + # Isometric view rotated 45 degrees around X axis + app.helpers.setup_view(orientation="iso", rotation=45, axis="x") + + # Front view rotated 90 degrees around Y axis + app.helpers.setup_view(orientation="front", rotation=90, axis="y") + + # Top view rotated 180 degrees around Z axis + app.helpers.setup_view(orientation="top", rotation=180, axis="z") + +**Controlling camera fit** + +.. code:: python + + # Set view and fit to model (default) + app.helpers.setup_view(orientation="iso", fit=True) + + # Set view without auto-fit + app.helpers.setup_view(orientation="iso", fit=False) + +**Advanced: Scene height** + +Control the zoom level by setting the scene height: + +.. code:: python + + # Requires Quantity type from Mechanical + app.update_globals(globals()) + + app.helpers.setup_view( + orientation="iso", + scene_height=Quantity(2.0, "in") + ) + +Displaying images +----------------- + +The ``display_image()`` method uses ``matplotlib`` to display exported images directly in your +Python environment. This is particularly useful in Jupyter notebooks or interactive sessions. + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + # Export an image + app.helpers.export_image( + obj=app.Model.Geometry, + file_path="geometry.png" + ) + + # Display it + app.helpers.display_image("geometry.png") + +**Custom display settings** + +.. code:: python + + app.helpers.display_image( + "geometry.png", + figsize=(12, 8), # Figure size in inches + xticks=[], # Hide x-axis ticks + yticks=[], # Hide y-axis ticks + axis="off" # Hide axes completely + ) + +Workflow examples +----------------- + +**Complete geometry visualization workflow** + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + + # Step 1: Import geometry + app.helpers.import_geometry("bracket.x_t") + + # Step 2: Import materials + app.helpers.import_materials("materials.xml") + + # Step 3: Set up the view + app.helpers.setup_view(orientation="iso", fit=True) + + # Step 4: Export image + app.helpers.export_image( + obj=app.Model.Geometry, + file_path="bracket_iso.png", + width=1920, + height=1080, + resolution="enhanced" + ) + + # Step 5: Display it + app.helpers.display_image("bracket_iso.png") + +**Multiple view angles** + +Export images from different angles for documentation: + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + app.helpers.import_geometry("part.step") + + views = ["front", "top", "iso"] + for view in views: + app.helpers.setup_view(orientation=view) + app.helpers.export_image( + obj=app.Model.Geometry, + file_path=f"part_{view}.png" + ) + +**Project inspection workflow** + +Use ``print_tree()`` to inspect and document your project structure: + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + # Build your model... + app.helpers.import_geometry("assembly.x_t") + analysis = app.Model.AddStaticStructuralAnalysis() + + # Print the complete project tree + print("Complete Project Structure:") + app.helpers.print_tree() + + # Print just the analysis branch + print("\nAnalysis Details:") + app.helpers.print_tree(node=analysis, max_lines=50) + +Error handling +-------------- + +All helper methods raise descriptive exceptions when operations fail: + +**Geometry import errors** + +.. code:: python + + from ansys.mechanical.core import App + + app = App(globals=globals()) + try: + app.helpers.import_geometry("nonexistent.x_t") + except RuntimeError as e: + print(f"Geometry import failed: {e}") + +**Image export errors** + +.. code:: python + + try: + app.helpers.export_image( + obj=app.Model.Geometry, + file_path=None # Missing required parameter + ) + except ValueError as e: + print(f"Invalid parameter: {e}") + +**Invalid options** + +.. code:: python + + try: + app.helpers.setup_view(orientation="invalid") + except ValueError as e: + print(f"Invalid orientation: {e}") + +Best practices +-------------- + +1. **Always check paths**: Use absolute paths or ``pathlib.Path`` objects for file operations + to avoid path-related errors. + +2. **Set up views before exporting**: Use ``setup_view()`` before ``export_image()`` to ensure + consistent viewpoints across multiple exports. + +3. **Use appropriate image formats**: PNG for technical documentation, JPG for presentations, + EPS for publications. + +4. **Use ``print_tree()`` for debugging**: When troubleshooting model issues, use ``print_tree()`` + to inspect object states and hierarchy. + +5. **Handle errors gracefully**: Wrap helper method calls in try-except blocks to handle + potential failures gracefully in production scripts. + +6. **Verify imports**: After importing geometry or materials, verify the object state: + + .. code:: python + + geometry_import = app.helpers.import_geometry("part.x_t") + if str(geometry_import.ObjectState) == "FullyDefined": + print("Geometry imported successfully") + else: + print(f"Warning: Geometry state is {geometry_import.ObjectState}") diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index af16ffb633..7279116ab1 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -29,6 +29,7 @@ detailed how-to guides on specific topics. The user guide is divided into the fo howto/overview howto/configuration howto/globals + howto/helpers howto/licensing howto/libraries howto/logging diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py new file mode 100644 index 0000000000..8bc399c02d --- /dev/null +++ b/tests/embedding/test_helper.py @@ -0,0 +1,419 @@ +# Copyright (C) 2022 - 2026 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. + +"""Tests for embedding helper methods.""" + +from pathlib import Path + +import pytest + + +def _cleanup_file(filepath): + """Clean up test file after verification.""" + try: + Path(filepath).unlink(missing_ok=True) + except Exception: + pass # Ignore cleanup errors + + +@pytest.mark.embedding +def test_helpers_initialization(embedded_app): + """Test that helpers are properly initialized with the app.""" + assert embedded_app.helpers is not None + assert hasattr(embedded_app.helpers, "_app") + assert embedded_app.helpers._app == embedded_app + assert hasattr(embedded_app.helpers, "Ansys") + + +@pytest.mark.embedding +def test_print_tree(embedded_app, assets, capsys, printer): + """Test print_tree with various options and scenarios.""" + printer("Testing print_tree functionality") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + # Test 1: Default parameters - prints entire project tree + embedded_app.helpers.print_tree() + captured = capsys.readouterr() + assert "├── Project" in captured.out + printer("Default tree print successful") + + # Test 2: Custom starting node + embedded_app.helpers.print_tree(node=embedded_app.Model) + captured = capsys.readouterr() + assert "├── Model" in captured.out + printer("Custom node tree print successful") + + # Test 3: Max lines limit + embedded_app.Model.AddStaticStructuralAnalysis() + embedded_app.helpers.print_tree(max_lines=3) + captured = capsys.readouterr() + assert "truncating after 3 lines" in captured.out + printer("Max lines limit working") + + # Test 4: Unlimited lines + embedded_app.helpers.print_tree(max_lines=-1) + captured = capsys.readouterr() + assert "truncating" not in captured.out + printer("Unlimited lines working") + + # Test 5: Suppressed objects display + embedded_app.update_globals(globals()) + allbodies = embedded_app.Model.GetChildren(DataModelObjectCategory.Body, True) # noqa: F821 + if allbodies.Count > 0: + allbodies[0].Suppressed = True + embedded_app.helpers.print_tree() + captured = capsys.readouterr() + assert "(Suppressed)" in captured.out or "├──" in captured.out + printer("Suppressed objects shown correctly") + + # Test 6: Object state indicators + embedded_app.helpers.print_tree() + captured = capsys.readouterr() + # Check for object state symbols: (?), (✓), (⚡︎), (✕) + assert any(marker in captured.out for marker in ["(?)", "(✓)", "(⚡︎)", "(✕)", "├──"]), ( + "Expected object state markers in tree output" + ) + printer("Object state indicators working correctly") + + +@pytest.mark.embedding +def test_import_geometry(embedded_app, assets, printer): + """Test geometry import with basic and all options.""" + printer("Testing geometry import") + geometry_file = str(Path(assets) / "Eng157.x_t") + + # Test basic import + printer("Testing basic geometry import") + geometry_import = embedded_app.helpers.import_geometry(geometry_file) + assert geometry_import is not None + assert str(geometry_import.ObjectState) in ["FullyDefined", "Solved"] + printer(f"Basic import successful: {geometry_import.ObjectState}") + + # Start fresh for comprehensive test + embedded_app.new() + + # Test with all options enabled + printer("Testing geometry import with all options") + geometry_import = embedded_app.helpers.import_geometry( + geometry_file, + process_named_selections=True, + named_selection_key="", + process_material_properties=True, + process_coordinate_systems=True, + analysis_type="3d", + ) + assert geometry_import is not None + assert str(geometry_import.ObjectState) in ["FullyDefined", "Solved"] + printer("Import with all options successful") + + +@pytest.mark.embedding +def test_import_geometry_invalid_file(embedded_app, printer): + """Test geometry import with invalid file path.""" + printer("Testing geometry import with invalid file") + invalid_file = "C:\\nonexistent\\file.x_t" + + with pytest.raises(RuntimeError, match="Geometry Import unsuccessful"): + embedded_app.helpers.import_geometry(invalid_file) + + +@pytest.mark.embedding +def test_import_materials(embedded_app, assets, printer): + """Test material import.""" + printer("Testing material import") + material_file = str(Path(assets) / "eng200_material.xml") + + embedded_app.helpers.import_materials(material_file) + + # Verify materials were imported + materials = embedded_app.Model.Materials + assert materials.Children is not None + # Materials collection doesn't have Count, but import should succeed without error + printer("Materials imported successfully") + + +@pytest.mark.embedding +def test_import_materials_invalid_file(embedded_app, printer): + """Test material import with invalid file.""" + printer("Testing material import with invalid file") + invalid_file = "C:\\nonexistent\\materials.xml" + + with pytest.raises(RuntimeError, match="Material Import unsuccessful"): + embedded_app.helpers.import_materials(invalid_file) + + +@pytest.mark.embedding +def test_export_image(embedded_app, assets, tmp_path, printer): + """Test image export with default and custom settings.""" + printer("Testing image export") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + # Test with default parameters + printer("Testing image export with defaults") + image_path = tmp_path / "test_image.png" + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path), + ) + assert image_path.exists() + assert image_path.stat().st_size > 0 + printer(f"Default export successful: {image_path}") + _cleanup_file(image_path) + + # Test with custom settings + printer("Testing image export with custom settings") + image_path_custom = tmp_path / "test_custom.jpg" + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path_custom), + width=1280, + height=720, + background="appearance", + resolution="high", + image_format="jpg", + ) + assert image_path_custom.exists() + assert image_path_custom.stat().st_size > 0 + printer("Custom export successful") + _cleanup_file(image_path_custom) + + +@pytest.mark.embedding +def test_export_image_all_formats(embedded_app, assets, tmp_path, printer): + """Test image export with different formats.""" + printer("Testing image export with all formats") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + formats = ["png", "jpg", "bmp", "tif", "eps"] + for fmt in formats: + image_path = tmp_path / f"test_image.{fmt}" + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path), + image_format=fmt, + ) + assert image_path.exists() + printer(f"Exported {fmt} successfully") + _cleanup_file(image_path) + + +@pytest.mark.embedding +def test_export_image_validation_errors(embedded_app, assets, tmp_path, printer): + """Test image export validation errors.""" + printer("Testing image export validation errors") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + # Test missing file path + printer("Testing missing file path error") + with pytest.raises(ValueError, match="file_path must be provided"): + embedded_app.helpers.export_image(obj=embedded_app.Model.Geometry) + + # Test invalid resolution + printer("Testing invalid resolution error") + image_path = tmp_path / "test_image.png" + with pytest.raises(ValueError, match="Invalid resolution type"): + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path), + resolution="invalid", + ) + + # Test invalid background + printer("Testing invalid background error") + with pytest.raises(ValueError, match="Invalid background type"): + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path), + background="invalid", + ) + + # Test invalid format + printer("Testing invalid format error") + image_path_xyz = tmp_path / "test_image.xyz" + with pytest.raises(ValueError, match="Invalid image format"): + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path_xyz), + image_format="xyz", + ) + + printer("All validation errors handled correctly") + + +@pytest.mark.embedding +def test_export_animation_basic(embedded_app, tmp_path, printer, graphics_test_mechdb_file): + """Test basic animation export with graphics fixture.""" + printer("Testing basic animation export") + + # Open the mechdb file with solved results + embedded_app.open(str(graphics_test_mechdb_file)) + embedded_app.Model.Analyses[0].Solution.ClearGeneratedData() + printer("Solving the model") + embedded_app.Model.Analyses[0].Solution.Solve() + printer("Model solved") + # Get the deformation result + result = embedded_app.Model.Analyses[0].Solution.Children[1] + printer(result.Name) + assert result is not None + + # Test all animation formats + formats = ["gif", "avi", "mp4", "wmv"] + for fmt in formats: + animation_path = tmp_path / f"test_animation.{fmt}" + embedded_app.helpers.export_animation( + obj=result, + file_path=str(animation_path), + animation_format=fmt, + width=640, + height=480, + ) + assert animation_path.exists() + assert animation_path.stat().st_size > 0 + printer(f"Exported {fmt} successfully") + _cleanup_file(animation_path) + + +@pytest.mark.embedding +def test_export_animation_validation_errors(embedded_app, assets, tmp_path, printer): + """Test animation export validation errors.""" + printer("Testing animation export validation errors") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + analysis = embedded_app.Model.AddStaticStructuralAnalysis() + result = analysis.Solution.AddTotalDeformation() + + # Test missing file path + printer("Testing missing file path error") + with pytest.raises(ValueError, match="file_path must be provided"): + embedded_app.helpers.export_animation(obj=result) + + # Test invalid format + printer("Testing invalid format error") + animation_path = tmp_path / "test_animation.xyz" + with pytest.raises(ValueError, match="Invalid animation format"): + embedded_app.helpers.export_animation( + obj=result, + file_path=str(animation_path), + animation_format="xyz", + ) + + printer("All animation validation errors handled correctly") + + +@pytest.mark.embedding +def test_setup_view(embedded_app, assets, printer): + """Test setup_view with various options.""" + printer("Testing setup_view") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + # Test default parameters + printer("Testing default view setup") + embedded_app.helpers.setup_view() + printer("Default setup successful") + + # Test all orientations + printer("Testing all orientations") + orientations = ["iso", "front", "back", "top", "bottom", "left", "right"] + for orientation in orientations: + embedded_app.helpers.setup_view(orientation=orientation) + printer(f"Set {orientation} view successfully") + + # Test with rotation on different axes + printer("Testing rotation on different axes") + embedded_app.helpers.setup_view(orientation="iso", rotation=45, axis="x") + embedded_app.helpers.setup_view(orientation="front", rotation=90, axis="y") + embedded_app.helpers.setup_view(orientation="top", rotation=180, axis="z") + printer("All view setups successful") + + +@pytest.mark.embedding +def test_setup_view_validation_errors(embedded_app, assets, printer): + """Test setup_view validation errors.""" + printer("Testing setup_view validation errors") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + # Test invalid orientation + printer("Testing invalid orientation error") + with pytest.raises(ValueError, match="Invalid orientation"): + embedded_app.helpers.setup_view(orientation="invalid") + + # Test invalid axis + printer("Testing invalid axis error") + with pytest.raises(ValueError, match="Invalid axis"): + embedded_app.helpers.setup_view(rotation=45, axis="invalid") + + printer("All validation errors handled correctly") + + +@pytest.mark.embedding +def test_display_image(embedded_app, assets, tmp_path, printer, monkeypatch): + """Test display_image with default and custom settings.""" + printer("Testing display_image") + geometry_file = str(Path(assets) / "Eng157.x_t") + embedded_app.helpers.import_geometry(geometry_file) + + # Mock pyplot.show to avoid displaying during tests + show_called = [] + + def mock_show(): + show_called.append(True) + + import matplotlib.pyplot as plt + + monkeypatch.setattr(plt, "show", mock_show) + + # Test with default settings + printer("Testing display with defaults") + image_path = tmp_path / "test_display.png" + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path), + ) + embedded_app.helpers.display_image(str(image_path)) + assert len(show_called) == 1 + printer("Default display successful") + _cleanup_file(image_path) + + # Test with custom settings + printer("Testing display with custom settings") + image_path_custom = tmp_path / "test_display_custom.png" + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path_custom), + ) + embedded_app.helpers.display_image( + str(image_path_custom), + figsize=(10, 6), + xticks=[0, 100, 200], + yticks=[0, 100, 200], + axis="on", + ) + assert len(show_called) == 2 + printer("Custom display successful") + _cleanup_file(image_path_custom) From 6915a35586fa2dad82e8ddae86085b6a3f581a72 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 22:33:17 -0600 Subject: [PATCH 13/30] whatsnew --- doc/changelog.d/whatsnew.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/changelog.d/whatsnew.yml b/doc/changelog.d/whatsnew.yml index d6aeecb5ab..10714c8ebb 100644 --- a/doc/changelog.d/whatsnew.yml +++ b/doc/changelog.d/whatsnew.yml @@ -1,4 +1,39 @@ fragments: +- title: Helper functions for common workflows + version: 0.12.3 + content: | + New helper functions simplify common Mechanical workflows through the + `Helpers` class. + These methods streamline geometry import, material management, visualization, and export operations. + + Available helper functions: + + - **print_tree()**: Display hierarchical project structure with object states + - **import_geometry()**: Import CAD geometry with flexible options + - **import_materials()**: Load material definitions from XML files + - **export_image()**: Export high-quality images of the current view + - **export_animation()**: Create animations in multiple formats (GIF, MP4, AVI, WMV) + - **setup_view()**: Configure camera and display settings + - **display_image()**: Show exported images inline in notebooks + + Usage: + .. code:: python + + from ansys.mechanical.core import App + + app = App() + helpers = app.DataModel.Project.Helpers + + # Import geometry and materials + helpers.import_geometry("bracket.x_t") + helpers.import_materials("steel_aluminum.xml") + + # Configure and export visualization + helpers.setup_view(fit=True, view_type="iso") + helpers.export_image("result.png", width=1920, height=1080) + + For complete details, see the `Helpers User Guide `_. + - title: gRPC Security with mTLS, WNUA, and Insecure Transport Modes version: 0.12.0 content: | From 558b27640fd6872251c908b98995c4206ecefc40 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 6 Feb 2026 22:36:14 -0600 Subject: [PATCH 14/30] remove duplicate tests --- tests/embedding/test_graphics_export.py | 80 ------------------------- 1 file changed, 80 deletions(-) delete mode 100644 tests/embedding/test_graphics_export.py diff --git a/tests/embedding/test_graphics_export.py b/tests/embedding/test_graphics_export.py deleted file mode 100644 index 8e3feb062e..0000000000 --- a/tests/embedding/test_graphics_export.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (C) 2022 - 2026 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. - -"""Graphics export tests.""" - -from pathlib import Path - -import pytest - - -def _is_readable(filepath: str): - filepath = Path(filepath) - try: - with filepath.open("rb") as file: - file.read() - except Exception as e: - assert False, f"Failed to read file {filepath}: {e}" - finally: - filepath.unlink(missing_ok=True) - - -@pytest.mark.embedding -@pytest.mark.parametrize("image_format", ["PNG", "JPG", "BMP"]) -def test_graphics_export_image(printer, embedded_app, image_format, graphics_test_mechdb_file): - """Tests to check image export.""" - printer(f"{image_format} export") - embedded_app.update_globals(globals()) - embedded_app.open(graphics_test_mechdb_file) - image_settings = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() - image_format = getattr(GraphicsImageExportFormat, image_format) - image_settings.Resolution = GraphicsResolutionType.EnhancedResolution - image_settings.Background = GraphicsBackgroundType.White - image_settings.Width = 1280 - image_settings.Height = 720 - dir_deformation = DataModel.GetObjectsByType(DataModelObjectCategory.DeformationResult)[0] - Tree.Activate([dir_deformation]) - ExtAPI.Graphics.Camera.SetFit() - image_file = str(Path.cwd() / f"image.{image_format}") - ExtAPI.Graphics.ExportImage(image_file, image_format, image_settings) - _is_readable(image_file) - - -@pytest.mark.embedding -@pytest.mark.parametrize("animation_format", ["GIF", "AVI", "MP4", "WMV"]) -def test_graphics_export_animation( - printer, embedded_app, animation_format, graphics_test_mechdb_file -): - """Tests to check animation export.""" - printer(f"{animation_format} export") - embedded_app.update_globals(globals()) - embedded_app.open(graphics_test_mechdb_file) - animation_settings = Ansys.Mechanical.Graphics.AnimationExportSettings() - animation_format = getattr(GraphicsAnimationExportFormat, animation_format) - animation_settings.Width = 1280 - animation_settings.Height = 720 - dir_deformation = DataModel.GetObjectsByType(DataModelObjectCategory.DeformationResult)[0] - Tree.Activate([dir_deformation]) - ExtAPI.Graphics.Camera.SetFit() - animation_file = str(Path.cwd() / f"animation.{animation_format}") - dir_deformation.ExportAnimation(animation_file, animation_format, animation_settings) - _is_readable(animation_file) From 546c6fbf09f1d76ff6b8a6d6ae73ca8d1e17a503 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 7 Feb 2026 20:00:38 -0600 Subject: [PATCH 15/30] add matplotlib for pytests --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5f8c459e62..fa0b0cd2b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ tests = [ "pytest-cov==7.0.0", "pytest-print==1.2.0", "psutil==7.2.1", + "matplotlib==3.10.8", ] doc = [ From a7792dfcd71d581ff947c9788ee4aa4837a0921c Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 9 Feb 2026 11:23:41 -0600 Subject: [PATCH 16/30] remove prrint_tree --- doc/changelog.d/whatsnew.yml | 3 +- .../mechanical/core/embedding/helpers.py | 90 ------------------- tests/embedding/test_helper.py | 52 ----------- 3 files changed, 1 insertion(+), 144 deletions(-) diff --git a/doc/changelog.d/whatsnew.yml b/doc/changelog.d/whatsnew.yml index 10714c8ebb..5f0f9d5484 100644 --- a/doc/changelog.d/whatsnew.yml +++ b/doc/changelog.d/whatsnew.yml @@ -8,7 +8,6 @@ fragments: Available helper functions: - - **print_tree()**: Display hierarchical project structure with object states - **import_geometry()**: Import CAD geometry with flexible options - **import_materials()**: Load material definitions from XML files - **export_image()**: Export high-quality images of the current view @@ -22,7 +21,7 @@ fragments: from ansys.mechanical.core import App app = App() - helpers = app.DataModel.Project.Helpers + helpers = app.helpers # Import geometry and materials helpers.import_geometry("bracket.x_t") diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index 280cc8f798..eed4c7bece 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -36,7 +36,6 @@ class Helpers: >>> from ansys.mechanical.core import App >>> app = App() >>> helpers = app.helpers - >>> helpers.print_tree() """ def __init__(self, app): @@ -48,43 +47,6 @@ def __init__(self, app): self.Ansys = Ansys - def print_tree(self, node=None, max_lines=80): - """ - Print the hierarchical tree representation of the Mechanical project structure. - - Parameters - ---------- - node : DataModel object, optional - The starting object of the tree. If not provided, starts from the Project. - max_lines : int, optional - The maximum number of lines to print. Default is 80. - If set to -1, no limit is applied. - - Raises - ------ - AttributeError - If the node does not have the required attributes. - - Examples - -------- - >>> from ansys.mechanical.core import App - >>> app = App() - >>> app.helpers.print_tree() - ... ├── Project - ... | ├── Model - ... | | ├── Geometry Imports (⚡︎) - - >>> app.helpers.print_tree(app.Model, max_lines=3) - ... ├── Model - ... | ├── Geometry Imports (⚡︎) - ... | ├── Geometry (?) - ... ... truncating after 3 lines - """ - if node is None: - node = self._app.DataModel.Project - - _print_tree(node, max_lines, 0, "") - def import_geometry( self, file_path: str, @@ -582,55 +544,3 @@ def display_image( plt.axis(axis) # Display the figure plt.show() - - -# Helper function to print the tree recursively -def _print_tree(node, max_lines, lines_count, indentation): - """Recursively print till provided maximum lines limit. - - Each object in the tree is expected to have the following attributes: - - Name: The name of the object. - - Suppressed : Print as suppressed, if object is suppressed. - - Children: Checks if object have children. - Each child node is expected to have the all these attributes. - - Parameters - ---------- - lines_count: int, optional - The current count of lines printed. Default is 0. - indentation: str, optional - The indentation string used for printing the tree structure. Default is "". - """ - if lines_count >= max_lines and max_lines != -1: - print(f"... truncating after {max_lines} lines") - return lines_count - - if not hasattr(node, "Name"): - raise AttributeError("Object must have a 'Name' attribute") - - node_name = node.Name - if hasattr(node, "Suppressed") and node.Suppressed is True: - node_name += " (Suppressed)" - if hasattr(node, "ObjectState"): - if str(node.ObjectState) == "UnderDefined": - node_name += " (?)" - elif str(node.ObjectState) == "Solved" or str(node.ObjectState) == "FullyDefined": - node_name += " (✓)" - elif str(node.ObjectState) == "NotSolved" or str(node.ObjectState) == "Obsolete": - node_name += " (⚡︎)" - elif str(node.ObjectState) == "SolveFailed": - node_name += " (✕)" - print(f"{indentation}├── {node_name}") - lines_count += 1 - - if lines_count >= max_lines and max_lines != -1: - print(f"... truncating after {max_lines} lines") - return lines_count - - if hasattr(node, "Children") and node.Children is not None and node.Children.Count > 0: - for child in node.Children: - lines_count = _print_tree(child, max_lines, lines_count, indentation + "| ") - if lines_count >= max_lines and max_lines != -1: - break - - return lines_count diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py index 8bc399c02d..b18b3faa97 100644 --- a/tests/embedding/test_helper.py +++ b/tests/embedding/test_helper.py @@ -44,58 +44,6 @@ def test_helpers_initialization(embedded_app): assert hasattr(embedded_app.helpers, "Ansys") -@pytest.mark.embedding -def test_print_tree(embedded_app, assets, capsys, printer): - """Test print_tree with various options and scenarios.""" - printer("Testing print_tree functionality") - geometry_file = str(Path(assets) / "Eng157.x_t") - embedded_app.helpers.import_geometry(geometry_file) - - # Test 1: Default parameters - prints entire project tree - embedded_app.helpers.print_tree() - captured = capsys.readouterr() - assert "├── Project" in captured.out - printer("Default tree print successful") - - # Test 2: Custom starting node - embedded_app.helpers.print_tree(node=embedded_app.Model) - captured = capsys.readouterr() - assert "├── Model" in captured.out - printer("Custom node tree print successful") - - # Test 3: Max lines limit - embedded_app.Model.AddStaticStructuralAnalysis() - embedded_app.helpers.print_tree(max_lines=3) - captured = capsys.readouterr() - assert "truncating after 3 lines" in captured.out - printer("Max lines limit working") - - # Test 4: Unlimited lines - embedded_app.helpers.print_tree(max_lines=-1) - captured = capsys.readouterr() - assert "truncating" not in captured.out - printer("Unlimited lines working") - - # Test 5: Suppressed objects display - embedded_app.update_globals(globals()) - allbodies = embedded_app.Model.GetChildren(DataModelObjectCategory.Body, True) # noqa: F821 - if allbodies.Count > 0: - allbodies[0].Suppressed = True - embedded_app.helpers.print_tree() - captured = capsys.readouterr() - assert "(Suppressed)" in captured.out or "├──" in captured.out - printer("Suppressed objects shown correctly") - - # Test 6: Object state indicators - embedded_app.helpers.print_tree() - captured = capsys.readouterr() - # Check for object state symbols: (?), (✓), (⚡︎), (✕) - assert any(marker in captured.out for marker in ["(?)", "(✓)", "(⚡︎)", "(✕)", "├──"]), ( - "Expected object state markers in tree output" - ) - printer("Object state indicators working correctly") - - @pytest.mark.embedding def test_import_geometry(embedded_app, assets, printer): """Test geometry import with basic and all options.""" From 5af0c48cc7f9d895a65524b16173eec63093e5b8 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 9 Feb 2026 12:29:13 -0600 Subject: [PATCH 17/30] update whatsnew --- doc/source/user_guide/howto/helpers.rst | 44 ------------------------- 1 file changed, 44 deletions(-) diff --git a/doc/source/user_guide/howto/helpers.rst b/doc/source/user_guide/howto/helpers.rst index edbfd7e20d..8cc4344230 100644 --- a/doc/source/user_guide/howto/helpers.rst +++ b/doc/source/user_guide/howto/helpers.rst @@ -21,50 +21,6 @@ The Helpers class is accessible through the ``helpers`` attribute of the All helper methods are designed to work seamlessly with the embedded Mechanical instance and provide clear error messages when operations fail. -Visualizing project structure ------------------------------- - -The ``print_tree()`` method displays a hierarchical tree representation of your Mechanical project, -making it easy to understand the structure and status of objects. - -**Basic usage** - -.. code:: python - - from ansys.mechanical.core import App - - app = App(globals=globals()) - app.helpers.print_tree() - -This prints the entire project tree starting from the Project node, showing object states with -visual indicators: - -- ``(?)`` - UnderDefined -- ``(✓)`` - Solved or FullyDefined -- ``(⚡︎)`` - NotSolved or Obsolete -- ``(✕)`` - SolveFailed -- ``(Suppressed)`` - Object is suppressed - -**Custom starting node** - -You can print a sub-tree starting from any node: - -.. code:: python - - # Print only the Model sub-tree - app.helpers.print_tree(node=app.Model) - -**Limiting output** - -Control the number of lines printed to avoid overwhelming output: - -.. code:: python - - # Print only first 20 lines - app.helpers.print_tree(max_lines=20) - - # Print unlimited lines - app.helpers.print_tree(max_lines=-1) Importing geometry ------------------ From 9a372570302340f6718cee32c157700b9c744617 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Tue, 10 Feb 2026 08:57:14 -0600 Subject: [PATCH 18/30] update --- src/ansys/mechanical/core/embedding/app.py | 52 +++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ansys/mechanical/core/embedding/app.py b/src/ansys/mechanical/core/embedding/app.py index d521edb27f..b457138184 100644 --- a/src/ansys/mechanical/core/embedding/app.py +++ b/src/ansys/mechanical/core/embedding/app.py @@ -699,6 +699,56 @@ def _update_all_globals(self) -> None: for scope in self._updated_scopes: scope.update(global_entry_points(self)) + def _print_tree(self, node, max_lines, lines_count, indentation): + """Recursively print till provided maximum lines limit. + + Each object in the tree is expected to have the following attributes: + - Name: The name of the object. + - Suppressed : Print as suppressed, if object is suppressed. + - Children: Checks if object have children. + Each child node is expected to have the all these attributes. + + Parameters + ---------- + lines_count: int, optional + The current count of lines printed. Default is 0. + indentation: str, optional + The indentation string used for printing the tree structure. Default is "". + """ + if lines_count >= max_lines and max_lines != -1: + print(f"... truncating after {max_lines} lines") + return lines_count + + if not hasattr(node, "Name"): + raise AttributeError("Object must have a 'Name' attribute") + + node_name = node.Name + if hasattr(node, "Suppressed") and node.Suppressed is True: + node_name += " (Suppressed)" + if hasattr(node, "ObjectState"): + if str(node.ObjectState) == "UnderDefined": + node_name += " (?)" + elif str(node.ObjectState) == "Solved" or str(node.ObjectState) == "FullyDefined": + node_name += " (✓)" + elif str(node.ObjectState) == "NotSolved" or str(node.ObjectState) == "Obsolete": + node_name += " (⚡︎)" + elif str(node.ObjectState) == "SolveFailed": + node_name += " (✕)" + print(f"{indentation}├── {node_name}") + lines_count += 1 + + if lines_count >= max_lines and max_lines != -1: + print(f"... truncating after {max_lines} lines") + return lines_count + + if hasattr(node, "Children") and node.Children is not None and node.Children.Count > 0: + for child in node.Children: + lines_count = self._print_tree(child, max_lines, lines_count, indentation + "| ") + if lines_count >= max_lines and max_lines != -1: + break + + return lines_count + def print_tree(self, node=None, max_lines=80, lines_count=0, indentation=""): """ Print the hierarchical tree representation of the Mechanical project structure. @@ -743,7 +793,7 @@ def print_tree(self, node=None, max_lines=80, lines_count=0, indentation=""): """ if node is None: node = self.DataModel.Project - self.helpers.print_tree(node, max_lines) + self._print_tree(node, max_lines, lines_count, indentation) def log_debug(self, message): """Log the debug message.""" From 9b2c751ea4367c2061dd9bc49678ccfc90668619 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Tue, 10 Feb 2026 10:29:15 -0600 Subject: [PATCH 19/30] update doc --- doc/source/user_guide/howto/helpers.rst | 32 +++---------------- .../mechanical/core/embedding/helpers.py | 9 ------ tests/embedding/test_helper.py | 2 -- 3 files changed, 4 insertions(+), 39 deletions(-) diff --git a/doc/source/user_guide/howto/helpers.rst b/doc/source/user_guide/howto/helpers.rst index 8cc4344230..747923c459 100644 --- a/doc/source/user_guide/howto/helpers.rst +++ b/doc/source/user_guide/howto/helpers.rst @@ -3,13 +3,13 @@ Helpers ======= -The `Helpers <../api/ansys/mechanical/core/embedding/helpers/Helpers.html>`_ class provides +The `Helpers <../api/ansys/mechanical/core/embedding/helpers/Helpers.html#ansys.mechanical.core.embedding.helpers.Helpers>`_ class provides convenient utility methods for common Mechanical operations. These helpers simplify tasks such as importing geometry and materials, exporting images and animations, configuring views, and visualizing the project tree structure. The Helpers class is accessible through the ``helpers`` attribute of the -`App <../api/ansys/mechanical/core/embedding/app/App.html>`_ instance: +`App <../api/ansys/mechanical/core/embedding/app/App.html#ansys.mechanical.core.embedding.app.App>`_ instance: .. code:: python @@ -300,8 +300,6 @@ Python environment. This is particularly useful in Jupyter notebooks or interact app.helpers.display_image( "geometry.png", figsize=(12, 8), # Figure size in inches - xticks=[], # Hide x-axis ticks - yticks=[], # Hide y-axis ticks axis="off" # Hide axes completely ) @@ -356,26 +354,7 @@ Export images from different angles for documentation: file_path=f"part_{view}.png" ) -**Project inspection workflow** -Use ``print_tree()`` to inspect and document your project structure: - -.. code:: python - - from ansys.mechanical.core import App - - app = App(globals=globals()) - # Build your model... - app.helpers.import_geometry("assembly.x_t") - analysis = app.Model.AddStaticStructuralAnalysis() - - # Print the complete project tree - print("Complete Project Structure:") - app.helpers.print_tree() - - # Print just the analysis branch - print("\nAnalysis Details:") - app.helpers.print_tree(node=analysis, max_lines=50) Error handling -------------- @@ -427,13 +406,10 @@ Best practices 3. **Use appropriate image formats**: PNG for technical documentation, JPG for presentations, EPS for publications. -4. **Use ``print_tree()`` for debugging**: When troubleshooting model issues, use ``print_tree()`` - to inspect object states and hierarchy. - -5. **Handle errors gracefully**: Wrap helper method calls in try-except blocks to handle +4. **Handle errors gracefully**: Wrap helper method calls in try-except blocks to handle potential failures gracefully in production scripts. -6. **Verify imports**: After importing geometry or materials, verify the object state: +5. **Verify imports**: After importing geometry or materials, verify the object state: .. code:: python diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index eed4c7bece..c1d0d3c217 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -502,8 +502,6 @@ def display_image( self, image_path: str, figsize: tuple = (16, 9), - xticks: list = [], - yticks: list = [], axis: str = "off", ): """Display an image using matplotlib. @@ -514,10 +512,6 @@ def display_image( The path to the image file to display. figsize : tuple, optional The size of the figure in inches (width, height). Default is (16, 9). - xticks : list, optional - The x-ticks to display on the plot. Default is []. - yticks : list, optional - The y-ticks to display on the plot. Default is []. axis : str, optional The axis visibility setting ('on' or 'off'). Default is "off". @@ -537,9 +531,6 @@ def display_image( plt.figure(figsize=figsize) # Read and display the image plt.imshow(mpimg.imread(image_path)) - # Set the tick locations and labels - plt.xticks(xticks) - plt.yticks(yticks) # Turn axis on or off plt.axis(axis) # Display the figure diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py index b18b3faa97..c388785454 100644 --- a/tests/embedding/test_helper.py +++ b/tests/embedding/test_helper.py @@ -358,8 +358,6 @@ def mock_show(): embedded_app.helpers.display_image( str(image_path_custom), figsize=(10, 6), - xticks=[0, 100, 200], - yticks=[0, 100, 200], axis="on", ) assert len(show_called) == 2 From f07df5d94fb22809464d3b73971bbb88394e2024 Mon Sep 17 00:00:00 2001 From: Dipin <26918585+dipinknair@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:42:40 -0600 Subject: [PATCH 20/30] Update pyproject.toml --- pyproject.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 85c927f781..bb93646c02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,12 +52,8 @@ tests = [ "pytest==9.0.2", "pytest-cov==7.0.0", "pytest-print==1.2.0", -<<<<<<< feat/utilities - "psutil==7.2.1", "matplotlib==3.10.8", -======= "psutil==7.2.2", ->>>>>>> main ] doc = [ @@ -311,4 +307,4 @@ passenv = * extras = doc commands = sphinx-build -d "{toxworkdir}/doc_doctree" doc/source "{toxinidir}/doc/_build/html" --color -vW -bhtml -""" \ No newline at end of file +""" From 83efaa6f9977f611f5178f05bd2d56088e11bd66 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 28 Feb 2026 23:00:13 -0600 Subject: [PATCH 21/30] fix mypy --- examples/01_basic/bolt_pretension.py | 20 ++- .../cooling_holes_thermal_analysis.py | 8 +- .../fracture_analysis_contact_debonding.py | 9 +- examples/01_basic/harmonic_acoustics.py | 9 +- examples/01_basic/modal_acoustics_analysis.py | 13 +- .../01_basic/steady_state_thermal_analysis.py | 16 ++- .../topology_optimization_cantilever_beam.py | 14 +- examples/01_basic/valve.py | 19 +-- .../mechanical/core/embedding/helpers.py | 122 ++---------------- 9 files changed, 80 insertions(+), 150 deletions(-) diff --git a/examples/01_basic/bolt_pretension.py b/examples/01_basic/bolt_pretension.py index 02180b99a2..8cb289b848 100644 --- a/examples/01_basic/bolt_pretension.py +++ b/examples/01_basic/bolt_pretension.py @@ -63,7 +63,13 @@ # Configure view and path for image export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app.helpers.setup_view(orientation="iso", fit=True, rotation=180, axis="y") +# Set camera orientation +graphics = app.Graphics +camera = graphics.Camera +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +camera.SetFit() +camera.Rotate(180, CameraAxisType.ScreenY) + # Set the path for the output files (images, gifs, mechdat) output_path = Path.cwd() / "out" @@ -474,7 +480,7 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: # Set the image export format, path and export the image mesh_image_path = str(output_path / "mesh.png") -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(mesh, mesh_image_path) # %% @@ -572,7 +578,7 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: loads_boundary_conditions_image_path = str(output_path / "loads_boundary_conditions.png") # Export the image of the loads and boundary conditions -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(bolt_presentation, loads_boundary_conditions_image_path) # Display the image of the loads and boundary conditions app.helpers.display_image(loads_boundary_conditions_image_path) @@ -638,7 +644,7 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: # Set the image name and path for the object image_path = str(output_path / "total_deformation.png") # Export the image of the object -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(total_deformation, file_path=image_path) # Display the image of the object app.helpers.display_image(image_path) @@ -649,7 +655,7 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: # Set the image name and path for the object image_path = str(output_path / "equivalent_stress_all_bodies.png") # Export the image of the object -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(equivalent_stress_1, file_path=image_path) # Display the image of the object app.helpers.display_image(image_path) @@ -660,7 +666,7 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: # Set the image name and path for the object image_path = str(output_path / "equivalent_stress_shank.png") # Export the image of the object -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(equivalent_stress_2, file_path=image_path) # Display the image of the object app.helpers.display_image(image_path) @@ -674,7 +680,7 @@ def add_mesh_sizing(mesh, object_name: str, element_size: Quantity) -> None: # Set the path for the contact status GIF contact_status_gif_path = str(output_path / "contact_status.gif") -app.helpers.setup_view() +camera.SetFit() app.helpers.export_animation(post_contact_tool_status, contact_status_gif_path) # %% diff --git a/examples/01_basic/cooling_holes_thermal_analysis.py b/examples/01_basic/cooling_holes_thermal_analysis.py index 52f7ebe527..3328f92233 100644 --- a/examples/01_basic/cooling_holes_thermal_analysis.py +++ b/examples/01_basic/cooling_holes_thermal_analysis.py @@ -75,6 +75,8 @@ # Set the path for the output files (images, gifs, mechdat) output_path = Path.cwd() / "out" +graphics = app.Graphics +camera = graphics.Camera # %% @@ -233,7 +235,7 @@ app.Tree.Activate([mesh]) # Set the camera to fit the model and export the image -app.helpers.setup_view() +camera.SetFit() image_path = output_path / "mesh.png" app.helpers.export_image(mesh, image_path) app.helpers.display_image(image_path) @@ -416,8 +418,8 @@ # Postprocessing # ~~~~~~~~~~~~~~ -app.helpers.setup_view(scene_height=Quantity(2.0, "in")) - +camera.SetFit() +camera.SceneHeight = Quantity(2.0, "in") # %% # Display the temperature plots for both plates diff --git a/examples/01_basic/fracture_analysis_contact_debonding.py b/examples/01_basic/fracture_analysis_contact_debonding.py index 11d4d1d62b..e57e3e4ff3 100644 --- a/examples/01_basic/fracture_analysis_contact_debonding.py +++ b/examples/01_basic/fracture_analysis_contact_debonding.py @@ -60,8 +60,9 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set camera orientation -app.helpers.setup_view("front") - +graphics = app.Graphics +camera = graphics.Camera +camera.SetSpecificViewOrientation(ViewOrientationType.Front) # %% # Create functions to set camera and display images @@ -258,7 +259,7 @@ def add_sizing( mesh.GenerateMesh() # Display the mesh image -app.helpers.setup_view("front") +camera.SetFit() image_path = Path(output_path) / "boundary_conditions.png" app.helpers.export_image(mesh, image_path) app.helpers.display_image(image_path) @@ -368,7 +369,7 @@ def add_displacement( static_structural_analysis.Activate() -app.helpers.setup_view("front") +camera.SetFit() image_path = Path(output_path) / "boundary_conditions.png" app.helpers.export_image(static_structural_analysis, image_path) app.helpers.display_image(image_path) diff --git a/examples/01_basic/harmonic_acoustics.py b/examples/01_basic/harmonic_acoustics.py index 7e931d3537..9609a43c37 100644 --- a/examples/01_basic/harmonic_acoustics.py +++ b/examples/01_basic/harmonic_acoustics.py @@ -59,7 +59,12 @@ output_path = Path.cwd() / "out" # Set the camera orientation -app.helpers.setup_view(orientation="iso", rotation=180, axis="y") +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to isometric view +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) +camera.Rotate(180, CameraAxisType.ScreenY) # %% # Download geometry and materials files @@ -313,7 +318,7 @@ def add_generation_criteria( # Activate the harmonic acoustics analysis harmonic_acoustics.Activate() # Set the camera to fit the mesh and export the image -app.helpers.setup_view(fit=True) +camera.SetFit() image_path = output_path / "boundary_conditions.png" app.helpers.export_image(file_path=image_path) app.helpers.display_image(image_path) diff --git a/examples/01_basic/modal_acoustics_analysis.py b/examples/01_basic/modal_acoustics_analysis.py index d7cc3aa456..7317f10bd8 100644 --- a/examples/01_basic/modal_acoustics_analysis.py +++ b/examples/01_basic/modal_acoustics_analysis.py @@ -63,7 +63,8 @@ # Set the path for the output files (images, gifs, mechdat) output_path = Path.cwd() / "out" -app.helpers.setup_view() +graphics = app.Graphics +camera = graphics.Camera # %% # Download the geometry and material files @@ -248,7 +249,7 @@ def set_mesh_properties( mesh.GenerateMesh() image_path = output_path / "mesh.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(mesh, image_path) app.helpers.display_image(image_path) @@ -424,7 +425,7 @@ def set_contact_region_properties( # Activate the modal acoustic analysis and display boundary conditions modal_acst.Activate() image_path = output_path / "geometry.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(modal_acst, image_path) app.helpers.display_image(image_path) @@ -483,7 +484,7 @@ def set_contact_region_properties( app.Tree.Activate([total_deformation_results[0]]) image_path = output_path / "total_deformation.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(total_deformation_results[0], image_path) app.helpers.display_image(image_path) @@ -491,7 +492,7 @@ def set_contact_region_properties( # Activate the acoustic pressure result and display the image image_path = output_path / "acoustic_pressure.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(acoustic_pressure_result, image_path) app.helpers.display_image(image_path) # %% @@ -522,7 +523,7 @@ def set_contact_region_properties( # %% # Display the total deformation animation deformation_gif = output_path / "total_deformation_results.gif" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_animation(total_deformation_results[-1], deformation_gif) diff --git a/examples/01_basic/steady_state_thermal_analysis.py b/examples/01_basic/steady_state_thermal_analysis.py index 275b6cb5aa..fc1bd9598c 100644 --- a/examples/01_basic/steady_state_thermal_analysis.py +++ b/examples/01_basic/steady_state_thermal_analysis.py @@ -61,7 +61,11 @@ output_path = Path.cwd() / "out" # Set the camera orientation to isometric view -app.helpers.setup_view("iso") +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to isometric view +camera.SetSpecificViewOrientation(ViewOrientationType.Iso) # %% # Download the geometry file @@ -383,7 +387,7 @@ def set_inputs_and_outputs( # Activate the static thermal analysis and display the image image_path = output_path / "bc_steady_state.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(stat_therm, image_path) app.helpers.display_image(image_path) @@ -503,7 +507,7 @@ def set_inputs_and_outputs( # Activate the total body temperature and display the image image_path = output_path / "total_body_temp.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(stat_therm, image_path) app.helpers.display_image(image_path) @@ -512,7 +516,7 @@ def set_inputs_and_outputs( # Activate the temperature on part of the body and display the image image_path = output_path / "part_temp_body.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(stat_therm, image_path) app.helpers.display_image(image_path) @@ -521,7 +525,7 @@ def set_inputs_and_outputs( # Activate the temperature distribution along the specific path and display the image image_path = output_path / "path_temp_distribution.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(stat_therm, image_path) app.helpers.display_image(image_path) @@ -530,7 +534,7 @@ def set_inputs_and_outputs( # Activate the temperature of the bottom surface and display the image image_path = output_path / "bottom_surface_temp.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(stat_therm, image_path) app.helpers.display_image(image_path) diff --git a/examples/01_basic/topology_optimization_cantilever_beam.py b/examples/01_basic/topology_optimization_cantilever_beam.py index 9869564903..c51422c7c3 100644 --- a/examples/01_basic/topology_optimization_cantilever_beam.py +++ b/examples/01_basic/topology_optimization_cantilever_beam.py @@ -62,7 +62,11 @@ output_path = Path.cwd() / "out" # Set the camera orientation to the front view -app.helpers.setup_view("front") +graphics = app.Graphics +camera = graphics.Camera + +# Set the camera orientation to the front view +camera.SetSpecificViewOrientation(ViewOrientationType.Front) # %% # Import the structural analysis model @@ -101,7 +105,7 @@ struct_sln.Children[1].Activate() image_path = output_path / "total_deformation.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(struct_sln.Children[1], image_path) app.helpers.display_image(image_path) # %% @@ -109,7 +113,7 @@ struct_sln.Children[2].Activate() image_path = output_path / "equivalent_stress.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(struct_sln.Children[2], image_path) app.helpers.display_image(image_path) @@ -151,7 +155,7 @@ # Activate the topology optimization analysis and display the image topology_optimization.Activate() -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(topology_optimization, output_path / "boundary_conditions.png") app.helpers.display_image(output_path / "boundary_conditions.png") @@ -195,7 +199,7 @@ # Activate the topology density result after smoothing and display the image topology_density.Children[0].Activate() image_path = output_path / "topo_opitimized_smooth.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(topology_density.Children[0], image_path) app.helpers.display_image(image_path) diff --git a/examples/01_basic/valve.py b/examples/01_basic/valve.py index 96c02dc33f..48c38c551b 100644 --- a/examples/01_basic/valve.py +++ b/examples/01_basic/valve.py @@ -52,13 +52,14 @@ 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" +# Set camera and graphics + +graphics = app.Graphics +camera = graphics.Camera + # %% # Download and import the geometry file # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -117,7 +118,7 @@ # Activate the mesh and display the image image_path = output_path / "mesh.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(mesh, image_path) app.helpers.display_image(image_path) @@ -151,7 +152,7 @@ # Activate the analysis and display the image image_path = output_path / "boundary_conditions.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(analysis, image_path) app.helpers.display_image(image_path) @@ -194,7 +195,7 @@ # Activate the total deformation result and display the image app.Tree.Activate([deformation]) image_path = output_path / "total_deformation_valve.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(deformation, image_path) app.helpers.display_image(image_path) @@ -205,7 +206,7 @@ # Activate the equivalent stress result and display the image app.Tree.Activate([stress]) image_path = output_path / "stress_valve.png" -app.helpers.setup_view() +camera.SetFit() app.helpers.export_image(stress, image_path) app.helpers.display_image(image_path) @@ -214,7 +215,7 @@ # Export the stress animation # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -app.helpers.setup_view() +camera.SetFit() valve_gif = output_path / "valve.gif" app.helpers.export_animation(stress, valve_gif) diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index c1d0d3c217..5a4cf09306 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -163,7 +163,7 @@ def import_materials(self, file_path: str): def export_image( self, obj=None, - file_path: str = None, + file_path: "str | None" = None, width: int = 1920, height: int = 1080, background: str = "white", @@ -235,14 +235,14 @@ def export_image( # Set default file path if not provided if file_path is None: raise ValueError("file_path must be provided for image export.") - else: - file_path = Path(file_path) - # If only filename provided (no directory), save to current working directory - if not file_path.parent or file_path.parent == Path(): - file_path = Path.cwd() / file_path.name + + resolved_path = Path(file_path) + # If only filename provided (no directory), save to current working directory + if not resolved_path.parent or resolved_path.parent == Path(): + resolved_path = Path.cwd() / resolved_path.name # Convert to string for API call - file_path = str(file_path) + file_path = str(resolved_path) # Activate the object if provided if obj is not None: @@ -310,7 +310,7 @@ def export_image( def export_animation( self, obj=None, - file_path: str = None, + file_path: "str | None" = None, width: int = 1280, height: int = 720, animation_format: str = "gif", @@ -360,14 +360,14 @@ def export_animation( # Set default file path if not provided if file_path is None: raise ValueError("file_path must be provided for animation export.") - else: - file_path = Path(file_path) - # If only filename provided (no directory), save to current working directory - if not file_path.parent or file_path.parent == Path(): - file_path = Path.cwd() / file_path.name + + resolved_path = Path(file_path) + # If only filename provided (no directory), save to current working directory + if not resolved_path.parent or resolved_path.parent == Path(): + resolved_path = Path.cwd() / resolved_path.name # Convert to string for API call - file_path = str(file_path) + file_path = str(resolved_path) # Activate the object if provided if obj is None: @@ -404,100 +404,6 @@ def export_animation( except Exception as e: raise RuntimeError(f"Animation export unsuccessful: {e}") - def setup_view( - self, - orientation: str = "iso", - fit: bool = True, - rotation: int = None, - axis: str = "x", - scene_height=None, - ): - """Configure graphics settings for image export. - - This is a convenience method that sets up camera orientation and creates - pre-configured image export settings. Commonly used at the start of examples - to prepare for exporting images. - - Parameters - ---------- - orientation : str, optional - Camera view orientation. Options are: - - "iso": Isometric view - Default - - "front": Front view - - "back": Back view - - "top": Top view - - "bottom": Bottom view - - "left": Left view - - "right": Right view - Default is "iso". - fit : bool, optional - Whether to fit the camera to the model. Default is True. - rotation : int, optional - Rotation angle in degrees. Default is None (no rotation). - axis : str, optional - Axis to rotate around. Options are "x", "y", or "z". Default is "x". - scene_height : Quantity, optional - Scene height for the camera view. Default is None (no scene height adjustment). - - Examples - -------- - >>> from ansys.mechanical.core import App - >>> app = App() - - >>> # Set up view with scene height - >>> app.helpers.setup_view(scene_height=Quantity(2.0, "in")) - - >>> # Use the configured settings to export an image - >>> app.Graphics.ExportImage("output.png", img_format, settings) - """ - from ansys.mechanical.core.embedding.enum_importer import ( - CameraAxisType, - ViewOrientationType, - ) - - # Get graphics and camera - graphics = self._app.Graphics - camera = graphics.Camera - - # Set camera orientation - orientation_lower = orientation.lower() - if orientation_lower == "iso": - camera.SetSpecificViewOrientation(ViewOrientationType.Iso) - elif orientation_lower == "front": - camera.SetSpecificViewOrientation(ViewOrientationType.Front) - elif orientation_lower == "back": - camera.SetSpecificViewOrientation(ViewOrientationType.Back) - elif orientation_lower == "top": - camera.SetSpecificViewOrientation(ViewOrientationType.Top) - elif orientation_lower == "bottom": - camera.SetSpecificViewOrientation(ViewOrientationType.Bottom) - elif orientation_lower == "left": - camera.SetSpecificViewOrientation(ViewOrientationType.Left) - elif orientation_lower == "right": - camera.SetSpecificViewOrientation(ViewOrientationType.Right) - else: - raise ValueError( - f"Invalid orientation: {orientation}. " - "Valid options are 'iso', 'front', 'back', 'top', 'bottom', 'left', or 'right'." - ) - # Set scene height if provided - if scene_height is not None: - camera.SceneHeight = scene_height - - # Fit camera if requested - if fit: - camera.SetFit() - - if rotation is not None: - if axis.lower() == "x": - camera.Rotate(rotation, CameraAxisType.ScreenX) - elif axis.lower() == "y": - camera.Rotate(rotation, CameraAxisType.ScreenY) - elif axis.lower() == "z": - camera.Rotate(rotation, CameraAxisType.ScreenZ) - else: - raise ValueError(f"Invalid axis: {axis}. Valid options are 'x', 'y', or 'z'.") - def display_image( self, image_path: str, From 467f4aa0497a5c1187c140c45595637b70ac37da Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 2 Mar 2026 14:46:59 -0600 Subject: [PATCH 22/30] remove setup_view all occurance --- doc/changelog.d/whatsnew.yml | 1 - doc/source/user_guide/howto/helpers.rst | 113 ++---------------------- tests/embedding/test_helper.py | 47 ---------- 3 files changed, 5 insertions(+), 156 deletions(-) diff --git a/doc/changelog.d/whatsnew.yml b/doc/changelog.d/whatsnew.yml index 5f0f9d5484..537b637163 100644 --- a/doc/changelog.d/whatsnew.yml +++ b/doc/changelog.d/whatsnew.yml @@ -12,7 +12,6 @@ fragments: - **import_materials()**: Load material definitions from XML files - **export_image()**: Export high-quality images of the current view - **export_animation()**: Create animations in multiple formats (GIF, MP4, AVI, WMV) - - **setup_view()**: Configure camera and display settings - **display_image()**: Show exported images inline in notebooks Usage: diff --git a/doc/source/user_guide/howto/helpers.rst b/doc/source/user_guide/howto/helpers.rst index 747923c459..21d7ea2c2e 100644 --- a/doc/source/user_guide/howto/helpers.rst +++ b/doc/source/user_guide/howto/helpers.rst @@ -202,76 +202,6 @@ or mode shapes, such as transient analyses or modal analyses. Animation export requires that the result has been solved and has multiple steps or modes to animate. Attempting to export an animation of unsolved results raises a ``RuntimeError``. -Setting up camera views ------------------------ - -The ``setup_view()`` method configures the camera orientation and view settings for your graphics -display. This is particularly useful before exporting images to ensure consistent viewpoints. - -**Basic view orientations** - -.. code:: python - - from ansys.mechanical.core import App - - app = App(globals=globals()) - # Import geometry first - app.helpers.import_geometry("path/to/geometry.x_t") - - # Set isometric view - app.helpers.setup_view(orientation="iso") - - # Set front view - app.helpers.setup_view(orientation="front") - -**Available orientations** - -- ``"iso"`` - Isometric view (default) -- ``"front"`` - Front view -- ``"back"`` - Back view -- ``"top"`` - Top view -- ``"bottom"`` - Bottom view -- ``"left"`` - Left side view -- ``"right"`` - Right side view - -**View with rotation** - -Add rotation to any standard view: - -.. code:: python - - # Isometric view rotated 45 degrees around X axis - app.helpers.setup_view(orientation="iso", rotation=45, axis="x") - - # Front view rotated 90 degrees around Y axis - app.helpers.setup_view(orientation="front", rotation=90, axis="y") - - # Top view rotated 180 degrees around Z axis - app.helpers.setup_view(orientation="top", rotation=180, axis="z") - -**Controlling camera fit** - -.. code:: python - - # Set view and fit to model (default) - app.helpers.setup_view(orientation="iso", fit=True) - - # Set view without auto-fit - app.helpers.setup_view(orientation="iso", fit=False) - -**Advanced: Scene height** - -Control the zoom level by setting the scene height: - -.. code:: python - - # Requires Quantity type from Mechanical - app.update_globals(globals()) - - app.helpers.setup_view( - orientation="iso", - scene_height=Quantity(2.0, "in") - ) Displaying images ----------------- @@ -320,10 +250,7 @@ Workflow examples # Step 2: Import materials app.helpers.import_materials("materials.xml") - # Step 3: Set up the view - app.helpers.setup_view(orientation="iso", fit=True) - - # Step 4: Export image + # Step 3: Export image app.helpers.export_image( obj=app.Model.Geometry, file_path="bracket_iso.png", @@ -332,28 +259,9 @@ Workflow examples resolution="enhanced" ) - # Step 5: Display it + # Step 4: Display it app.helpers.display_image("bracket_iso.png") -**Multiple view angles** - -Export images from different angles for documentation: - -.. code:: python - - from ansys.mechanical.core import App - - app = App(globals=globals()) - app.helpers.import_geometry("part.step") - - views = ["front", "top", "iso"] - for view in views: - app.helpers.setup_view(orientation=view) - app.helpers.export_image( - obj=app.Model.Geometry, - file_path=f"part_{view}.png" - ) - Error handling @@ -385,31 +293,20 @@ All helper methods raise descriptive exceptions when operations fail: except ValueError as e: print(f"Invalid parameter: {e}") -**Invalid options** - -.. code:: python - - try: - app.helpers.setup_view(orientation="invalid") - except ValueError as e: - print(f"Invalid orientation: {e}") - Best practices -------------- 1. **Always check paths**: Use absolute paths or ``pathlib.Path`` objects for file operations to avoid path-related errors. -2. **Set up views before exporting**: Use ``setup_view()`` before ``export_image()`` to ensure - consistent viewpoints across multiple exports. -3. **Use appropriate image formats**: PNG for technical documentation, JPG for presentations, +2. **Use appropriate image formats**: PNG for technical documentation, JPG for presentations, EPS for publications. -4. **Handle errors gracefully**: Wrap helper method calls in try-except blocks to handle +3. **Handle errors gracefully**: Wrap helper method calls in try-except blocks to handle potential failures gracefully in production scripts. -5. **Verify imports**: After importing geometry or materials, verify the object state: +4. **Verify imports**: After importing geometry or materials, verify the object state: .. code:: python diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py index c388785454..d14347cf29 100644 --- a/tests/embedding/test_helper.py +++ b/tests/embedding/test_helper.py @@ -272,53 +272,6 @@ def test_export_animation_validation_errors(embedded_app, assets, tmp_path, prin printer("All animation validation errors handled correctly") -@pytest.mark.embedding -def test_setup_view(embedded_app, assets, printer): - """Test setup_view with various options.""" - printer("Testing setup_view") - geometry_file = str(Path(assets) / "Eng157.x_t") - embedded_app.helpers.import_geometry(geometry_file) - - # Test default parameters - printer("Testing default view setup") - embedded_app.helpers.setup_view() - printer("Default setup successful") - - # Test all orientations - printer("Testing all orientations") - orientations = ["iso", "front", "back", "top", "bottom", "left", "right"] - for orientation in orientations: - embedded_app.helpers.setup_view(orientation=orientation) - printer(f"Set {orientation} view successfully") - - # Test with rotation on different axes - printer("Testing rotation on different axes") - embedded_app.helpers.setup_view(orientation="iso", rotation=45, axis="x") - embedded_app.helpers.setup_view(orientation="front", rotation=90, axis="y") - embedded_app.helpers.setup_view(orientation="top", rotation=180, axis="z") - printer("All view setups successful") - - -@pytest.mark.embedding -def test_setup_view_validation_errors(embedded_app, assets, printer): - """Test setup_view validation errors.""" - printer("Testing setup_view validation errors") - geometry_file = str(Path(assets) / "Eng157.x_t") - embedded_app.helpers.import_geometry(geometry_file) - - # Test invalid orientation - printer("Testing invalid orientation error") - with pytest.raises(ValueError, match="Invalid orientation"): - embedded_app.helpers.setup_view(orientation="invalid") - - # Test invalid axis - printer("Testing invalid axis error") - with pytest.raises(ValueError, match="Invalid axis"): - embedded_app.helpers.setup_view(rotation=45, axis="invalid") - - printer("All validation errors handled correctly") - - @pytest.mark.embedding def test_display_image(embedded_app, assets, tmp_path, printer, monkeypatch): """Test display_image with default and custom settings.""" From 0c0775b1f5d67eee2b2d1bde361d0ca1df9d0eaa Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 2 Mar 2026 14:48:03 -0600 Subject: [PATCH 23/30] remove setup_view all occurance --- doc/changelog.d/whatsnew.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/changelog.d/whatsnew.yml b/doc/changelog.d/whatsnew.yml index 537b637163..ad5919f1b3 100644 --- a/doc/changelog.d/whatsnew.yml +++ b/doc/changelog.d/whatsnew.yml @@ -27,7 +27,6 @@ fragments: helpers.import_materials("steel_aluminum.xml") # Configure and export visualization - helpers.setup_view(fit=True, view_type="iso") helpers.export_image("result.png", width=1920, height=1080) For complete details, see the `Helpers User Guide `_. From d96866d8b55e67781a0f185a6b6b6f8c51090a33 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:26:02 +0000 Subject: [PATCH 24/30] chore: auto fixes from pre-commit hooks --- src/ansys/mechanical/core/embedding/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/mechanical/core/embedding/utils.py b/src/ansys/mechanical/core/embedding/utils.py index 08f65b3e07..ff793e18a8 100644 --- a/src/ansys/mechanical/core/embedding/utils.py +++ b/src/ansys/mechanical/core/embedding/utils.py @@ -28,7 +28,7 @@ TEST_HELPER = None -class GetterWrapper(object): +class GetterWrapper: """Wrapper class around an attribute of an object.""" def __init__(self, obj, getter): From 102d4f732580673acad743a398b5d7a9e7b645a7 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 6 Apr 2026 11:27:54 -0500 Subject: [PATCH 25/30] update getwrapper --- src/ansys/mechanical/core/embedding/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ansys/mechanical/core/embedding/utils.py b/src/ansys/mechanical/core/embedding/utils.py index 08f65b3e07..4bb15c3fb8 100644 --- a/src/ansys/mechanical/core/embedding/utils.py +++ b/src/ansys/mechanical/core/embedding/utils.py @@ -28,7 +28,7 @@ TEST_HELPER = None -class GetterWrapper(object): +class GetterWrapper: """Wrapper class around an attribute of an object.""" def __init__(self, obj, getter): @@ -50,6 +50,10 @@ def __setattr__(self, attr, value): setattr(self, attr, value) setattr(self._get_wrapped_object(self._immortal_object), attr, value) + def __repr__(self): + """Return the repr of the wrapped object.""" + return repr(self._get_wrapped_object(self._immortal_object)) + def _get_test_helper(): global TEST_HELPER From 9832736de354cdda0e5f1ae1af7cac21f8b3f4dd Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 6 Apr 2026 12:07:57 -0500 Subject: [PATCH 26/30] update enums --- doc/source/user_guide/embedding/helpers.rst | 47 ++--- .../fracture_analysis_contact_debonding.py | 4 +- .../mechanical/core/embedding/helpers.py | 197 ++++++------------ tests/embedding/test_helper.py | 122 +++++------ 4 files changed, 138 insertions(+), 232 deletions(-) diff --git a/doc/source/user_guide/embedding/helpers.rst b/doc/source/user_guide/embedding/helpers.rst index 21d7ea2c2e..c212eb142f 100644 --- a/doc/source/user_guide/embedding/helpers.rst +++ b/doc/source/user_guide/embedding/helpers.rst @@ -62,18 +62,17 @@ Import geometry with material properties and coordinate systems: named_selection_key="", process_material_properties=True, process_coordinate_systems=True, - analysis_type="3d" ) **2D analysis** -For 2D analyses, specify the analysis type: +For 2D analyses, specify the analysis type using the ``GeometryImportPreference`` enum: .. code:: python geometry_import = app.helpers.import_geometry( "path/to/geometry.agdb", - analysis_type="2d" + analysis_type=GeometryImportPreference.AnalysisType.Type2D ) .. note:: @@ -119,7 +118,7 @@ It provides extensive control over image resolution, format, and appearance. **Custom image settings** -Control image dimensions, resolution, background, and format: +Control image dimensions, resolution, background, and format using Mechanical enums: .. code:: python @@ -128,9 +127,9 @@ Control image dimensions, resolution, background, and format: file_path="custom_image.jpg", width=1920, height=1080, - background="appearance", # or "white" - resolution="high", # "normal", "enhanced", or "high" - image_format="jpg" # "png", "jpg", "bmp", "tif", or "eps" + background=GraphicsBackgroundType.GraphicsAppearanceSetting, + resolution=GraphicsResolutionType.HighResolution, + image_format=GraphicsImageExportFormat.JPG, ) **Export current graphics display** @@ -144,19 +143,19 @@ Export whatever is currently displayed without specifying an object: current_graphics_display=True ) -**Supported formats** +**Supported image formats (``GraphicsImageExportFormat``)** -- **PNG** - Recommended for technical documentation (lossless) -- **JPG** - Good for photographs and presentations (compressed format) -- **BMP** - Uncompressed bitmap -- **TIF** - Tagged image format (lossless) -- **EPS** - Vector format for publications +- ``GraphicsImageExportFormat.PNG`` - Recommended for technical documentation (lossless, default) +- ``GraphicsImageExportFormat.JPG`` - Good for photographs and presentations +- ``GraphicsImageExportFormat.BMP`` - Uncompressed bitmap +- ``GraphicsImageExportFormat.TIF`` - Tagged image format (lossless) +- ``GraphicsImageExportFormat.EPS`` - Vector format for publications -**Resolution options** +**Resolution options (``GraphicsResolutionType``)** -- **normal** - 1:1 pixel ratio (fastest) -- **enhanced** - 2:1 pixel ratio (default, good quality) -- **high** - 4:1 pixel ratio (best quality, slower) +- ``GraphicsResolutionType.NormalResolution`` - 1:1 pixel ratio (fastest) +- ``GraphicsResolutionType.EnhancedResolution`` - 2:1 pixel ratio (default, good quality) +- ``GraphicsResolutionType.HighResolution`` - 4:1 pixel ratio (best quality, slower) Exporting animations -------------------- @@ -188,15 +187,15 @@ or mode shapes, such as transient analyses or modal analyses. file_path="deformation.mp4", width=1920, height=1080, - animation_format="mp4" # "gif", "avi", "mp4", or "wmv" + animation_format=GraphicsAnimationExportFormat.MP4, ) -**Supported animation formats** +**Supported animation formats (``GraphicsAnimationExportFormat``)** -- **GIF** - Widely supported, good for web -- **AVI** - Uncompressed video -- **MP4** - Compressed video, good for presentations -- **WMV** - Windows Media Video format +- ``GraphicsAnimationExportFormat.GIF`` - Widely supported, good for web (default) +- ``GraphicsAnimationExportFormat.AVI`` - Uncompressed video +- ``GraphicsAnimationExportFormat.MP4`` - Compressed video, good for presentations +- ``GraphicsAnimationExportFormat.WMV`` - Windows Media Video format .. note:: Animation export requires that the result has been solved and has multiple steps or modes @@ -256,7 +255,7 @@ Workflow examples file_path="bracket_iso.png", width=1920, height=1080, - resolution="enhanced" + resolution=GraphicsResolutionType.EnhancedResolution, ) # Step 4: Display it diff --git a/examples/01_basic/fracture_analysis_contact_debonding.py b/examples/01_basic/fracture_analysis_contact_debonding.py index e57e3e4ff3..42b0cd6f78 100644 --- a/examples/01_basic/fracture_analysis_contact_debonding.py +++ b/examples/01_basic/fracture_analysis_contact_debonding.py @@ -79,7 +79,9 @@ # Download the geometry file from the ansys/example-data repository geometry_path = download_file("Contact_Debonding_Example.agdb", "pymechanical", "embedding") -app.helpers.import_geometry(geometry_path, analysis_type="2d") +app.helpers.import_geometry( + geometry_path, analysis_type=GeometryImportPreference.AnalysisType.Type2D +) # Set the model model = app.Model diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index 5a4cf09306..350f5dc0e2 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -54,7 +54,7 @@ def import_geometry( named_selection_key: str = "NS", process_material_properties: bool = False, process_coordinate_systems: bool = False, - analysis_type: str = "3d", + analysis_type=None, ): r"""Import geometry file into the current Mechanical model. @@ -75,9 +75,9 @@ def import_geometry( Whether to process material properties during import. Default is False. process_coordinate_systems : bool, optional Whether to process coordinate systems during import. Default is False. - analysis_type : str, optional - The type of analysis for the geometry import. Default is "3d". - Options are "2d" or "3d". + analysis_type : GeometryImportPreference.AnalysisType, optional + The analysis type enum for the geometry import. Default is + ``GeometryImportPreference.AnalysisType.Type3D``. Examples -------- @@ -85,9 +85,11 @@ def import_geometry( >>> app = App() >>> app.helpers.import_geometry("C:\\path\\to\\geometry.pmdb") - >>> # Import without processing named selections + >>> # Import with 2D analysis type + >>> from ansys.mechanical.core.embedding.enum_importer import GeometryImportPreference >>> app.helpers.import_geometry( - ... "C:\\path\\to\\geometry.step", process_named_selections=False + ... "C:\\path\\to\\geometry.agdb", + ... analysis_type=GeometryImportPreference.AnalysisType.Type2D, ... ) >>> # Import with all options specified @@ -99,17 +101,16 @@ def import_geometry( ... process_coordinate_systems=True, ... ) """ - # Import Ansys and enums - same way as when App(globals=globals()) is used + GeometryImportPreference = ( + self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference + ) + + if analysis_type is None: + analysis_type = GeometryImportPreference.AnalysisType.Type3D - # Create a geometry import group for the model geometry_import_group = self._app.Model.GeometryImportGroup - # Add the geometry import to the group geometry_import = geometry_import_group.AddGeometryImport() - # Set the geometry import format - geometry_import_format = ( - self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic - ) - # Set the geometry import preferences + geometry_import_format = GeometryImportPreference.Format.Automatic geometry_import_preferences = ( self.Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() ) @@ -117,14 +118,8 @@ def import_geometry( geometry_import_preferences.NamedSelectionKey = named_selection_key geometry_import_preferences.ProcessMaterialProperties = process_material_properties geometry_import_preferences.ProcessCoordinateSystems = process_coordinate_systems - if analysis_type.lower() == "2d": - geometry_import_preferences.AnalysisType = ( - self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.AnalysisType.Type2D - ) - else: - geometry_import_preferences.AnalysisType = ( - self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.AnalysisType.Type3D - ) + geometry_import_preferences.AnalysisType = analysis_type + try: geometry_import.Import(file_path, geometry_import_format, geometry_import_preferences) self._app.log_info( @@ -166,10 +161,10 @@ def export_image( file_path: "str | None" = None, width: int = 1920, height: int = 1080, - background: str = "white", - resolution: str = "enhanced", + background=None, + resolution=None, current_graphics_display: bool = False, - image_format: str = "png", + image_format=None, ): r"""Export an image of the specified object. @@ -179,49 +174,38 @@ def export_image( The object to activate and export. If None, exports the current graphics display. Can be any Mechanical object such as Geometry, Mesh, or Results. file_path : str, optional - The path where the image will be saved. If None, defaults to "mechanical_export.png" - in the project directory. + The path where the image will be saved. width : int, optional The width of the exported image in pixels. Default is 1920. height : int, optional The height of the exported image in pixels. Default is 1080. - background : str, optional - Background type for the exported image. Options are: - - "white": White background - - "appearance": Use graphics appearance setting - Default is "white". - resolution : str, optional - Resolution type for the exported image. Options are: - - "normal": Normal resolution (1:1) - - "enhanced": Enhanced resolution (2:1) - Default - - "high": High resolution (4:1) - Default is "enhanced". + background : GraphicsBackgroundType, optional + Background type for the exported image. Default is + ``GraphicsBackgroundType.White``. + resolution : GraphicsResolutionType, optional + Resolution type for the exported image. Default is + ``GraphicsResolutionType.EnhancedResolution``. current_graphics_display : bool, optional Whether to use current graphics display. Default is False. - image_format : str, optional - Image format for export. Options are: - - "png": PNG image format - Default - - "jpg": JPG image format - - "bmp": BMP image format - - "tif": TIFF image format - - "eps": EPS image format - Default is "png". + image_format : GraphicsImageExportFormat, optional + Image format for export. Default is ``GraphicsImageExportFormat.PNG``. Examples -------- >>> from ansys.mechanical.core import App >>> app = App() - >>> # Export the geometry >>> app.helpers.export_image(app.Model.Geometry, "C:\\path\\to\\geometry.png") - >>> # Export a specific result with custom settings + >>> from ansys.mechanical.core.embedding.enum_importer import ( + ... GraphicsBackgroundType, GraphicsImageExportFormat, GraphicsResolutionType, + ... ) >>> result = app.Model.Analyses[0].Solution.Children[0] >>> app.helpers.export_image( ... result, ... "C:\\path\\to\\result.jpg", - ... background="appearance", - ... resolution="high", - ... image_format="jpg", + ... background=GraphicsBackgroundType.GraphicsAppearanceSetting, + ... resolution=GraphicsResolutionType.HighResolution, + ... image_format=GraphicsImageExportFormat.JPG, ... ) """ from pathlib import Path @@ -232,77 +216,37 @@ def export_image( GraphicsResolutionType, ) - # Set default file path if not provided if file_path is None: raise ValueError("file_path must be provided for image export.") resolved_path = Path(file_path) - # If only filename provided (no directory), save to current working directory if not resolved_path.parent or resolved_path.parent == Path(): resolved_path = Path.cwd() / resolved_path.name - - # Convert to string for API call file_path = str(resolved_path) - # Activate the object if provided if obj is not None: self._app.Tree.Activate([obj]) - # Create graphics image export settings + if background is None: + background = GraphicsBackgroundType.White + if resolution is None: + resolution = GraphicsResolutionType.EnhancedResolution + if image_format is None: + image_format = GraphicsImageExportFormat.PNG + graphics_image_export_settings = ( self.Ansys.Mechanical.Graphics.GraphicsImageExportSettings() ) - - # Set resolution type - resolution_lower = resolution.lower() - if resolution_lower == "enhanced": - graphics_image_export_settings.Resolution = GraphicsResolutionType.EnhancedResolution - elif resolution_lower == "high": - graphics_image_export_settings.Resolution = GraphicsResolutionType.HighResolution - elif resolution_lower == "normal": - graphics_image_export_settings.Resolution = GraphicsResolutionType.NormalResolution - else: - raise ValueError( - f"Invalid resolution type: {resolution}. " - "Valid options are 'Normal', 'Enhanced', or 'High'." - ) - - # Set background type - if background.lower() == "white": - graphics_image_export_settings.Background = GraphicsBackgroundType.White - elif background.lower() == "appearance": - graphics_image_export_settings.Background = ( - GraphicsBackgroundType.GraphicsAppearanceSetting - ) - else: - raise ValueError( - f"Invalid background type: {background}. Valid options are 'White' or 'Appearance'." - ) - - # Set image format - format_lower = image_format.lower() - if format_lower == "png": - export_format = GraphicsImageExportFormat.PNG - elif format_lower == "jpg" or format_lower == "jpeg": - export_format = GraphicsImageExportFormat.JPG - elif format_lower == "bmp": - export_format = GraphicsImageExportFormat.BMP - elif format_lower == "tif" or format_lower == "tiff": - export_format = GraphicsImageExportFormat.TIF - elif format_lower == "eps": - export_format = GraphicsImageExportFormat.EPS - else: - raise ValueError( - f"Invalid image format: {image_format}. " - "Valid options are 'PNG', 'JPG', 'BMP', 'TIF', or 'EPS'." - ) - + graphics_image_export_settings.Resolution = resolution + graphics_image_export_settings.Background = background graphics_image_export_settings.CurrentGraphicsDisplay = current_graphics_display graphics_image_export_settings.Width = width graphics_image_export_settings.Height = height try: - self._app.Graphics.ExportImage(file_path, export_format, graphics_image_export_settings) + self._app.Graphics.ExportImage( + file_path, image_format, graphics_image_export_settings + ) self._app.log_info(f"Exported image to {file_path} successfully.") except Exception as e: raise RuntimeError(f"Image export unsuccessful: {e}") @@ -313,7 +257,7 @@ def export_animation( file_path: "str | None" = None, width: int = 1280, height: int = 720, - animation_format: str = "gif", + animation_format=None, ): r"""Export an animation of the specified object. @@ -330,25 +274,23 @@ def export_animation( The width of the exported animation in pixels. Default is 1280. height : int, optional The height of the exported animation in pixels. Default is 720. - animation_format : str, optional - Animation format for export. Options are: - - "gif": GIF animation format - Default - - "avi": AVI video format - - "mp4": MP4 video format - - "wmv": WMV video format - Default is "gif". + animation_format : GraphicsAnimationExportFormat, optional + Animation format for export. Default is ``GraphicsAnimationExportFormat.GIF``. Examples -------- >>> from ansys.mechanical.core import App >>> app = App() - >>> # Export animation of a result >>> result = app.Model.Analyses[0].Solution.Children[0] >>> app.helpers.export_animation(result, "result_animation.gif") - >>> # Export as MP4 with custom resolution + >>> from ansys.mechanical.core.embedding.enum_importer import ( + ... GraphicsAnimationExportFormat, + ... ) >>> app.helpers.export_animation( - ... result, "result_animation.mp4", width=1920, height=1080, animation_format="mp4" + ... result, + ... "result_animation.mp4", + ... animation_format=GraphicsAnimationExportFormat.MP4, ... ) """ from pathlib import Path @@ -357,49 +299,30 @@ def export_animation( GraphicsAnimationExportFormat, ) - # Set default file path if not provided if file_path is None: raise ValueError("file_path must be provided for animation export.") resolved_path = Path(file_path) - # If only filename provided (no directory), save to current working directory if not resolved_path.parent or resolved_path.parent == Path(): resolved_path = Path.cwd() / resolved_path.name - - # Convert to string for API call file_path = str(resolved_path) - # Activate the object if provided if obj is None: self._app.log_info( "No object provided for animation export; using first active object." ) obj = self._app.Tree.FirstActiveObject - # Create animation export settings + if animation_format is None: + animation_format = GraphicsAnimationExportFormat.GIF + animation_export_settings = self.Ansys.Mechanical.Graphics.AnimationExportSettings( width, height ) - # Set animation format - format_lower = animation_format.lower() - if format_lower == "gif": - export_format = GraphicsAnimationExportFormat.GIF - elif format_lower == "avi": - export_format = GraphicsAnimationExportFormat.AVI - elif format_lower == "mp4": - export_format = GraphicsAnimationExportFormat.MP4 - elif format_lower == "wmv": - export_format = GraphicsAnimationExportFormat.WMV - else: - raise ValueError( - f"Invalid animation format: {animation_format}. " - "Valid options are 'GIF', 'AVI', 'MP4', or 'WMV'." - ) - try: self._app.Tree.Activate([obj]) - obj.ExportAnimation(file_path, export_format, animation_export_settings) + obj.ExportAnimation(file_path, animation_format, animation_export_settings) self._app.log_info(f"Exported animation to {file_path} successfully.") except Exception as e: raise RuntimeError(f"Animation export unsuccessful: {e}") diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py index d14347cf29..ccdf485f1f 100644 --- a/tests/embedding/test_helper.py +++ b/tests/embedding/test_helper.py @@ -27,12 +27,16 @@ import pytest -def _cleanup_file(filepath): - """Clean up test file after verification.""" +def _is_readable(filepath): + """Assert that the file is non-empty and readable, then delete it.""" + filepath = Path(filepath) try: - Path(filepath).unlink(missing_ok=True) - except Exception: - pass # Ignore cleanup errors + with filepath.open("rb") as file: + file.read() + except Exception as e: + assert False, f"Failed to read file {filepath}: {e}" + finally: + filepath.unlink(missing_ok=True) @pytest.mark.embedding @@ -62,13 +66,16 @@ def test_import_geometry(embedded_app, assets, printer): # Test with all options enabled printer("Testing geometry import with all options") + GeometryImportPreference = ( + embedded_app.helpers.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference + ) geometry_import = embedded_app.helpers.import_geometry( geometry_file, process_named_selections=True, named_selection_key="", process_material_properties=True, process_coordinate_systems=True, - analysis_type="3d", + analysis_type=GeometryImportPreference.AnalysisType.Type3D, ) assert geometry_import is not None assert str(geometry_import.ObjectState) in ["FullyDefined", "Solved"] @@ -124,12 +131,16 @@ def test_export_image(embedded_app, assets, tmp_path, printer): obj=embedded_app.Model.Geometry, file_path=str(image_path), ) - assert image_path.exists() - assert image_path.stat().st_size > 0 + _is_readable(image_path) printer(f"Default export successful: {image_path}") - _cleanup_file(image_path) - # Test with custom settings + # Test with custom settings using enums + from ansys.mechanical.core.embedding.enum_importer import ( + GraphicsBackgroundType, + GraphicsImageExportFormat, + GraphicsResolutionType, + ) + printer("Testing image export with custom settings") image_path_custom = tmp_path / "test_custom.jpg" embedded_app.helpers.export_image( @@ -137,14 +148,12 @@ def test_export_image(embedded_app, assets, tmp_path, printer): file_path=str(image_path_custom), width=1280, height=720, - background="appearance", - resolution="high", - image_format="jpg", + background=GraphicsBackgroundType.GraphicsAppearanceSetting, + resolution=GraphicsResolutionType.HighResolution, + image_format=GraphicsImageExportFormat.JPG, ) - assert image_path_custom.exists() - assert image_path_custom.stat().st_size > 0 + _is_readable(image_path_custom) printer("Custom export successful") - _cleanup_file(image_path_custom) @pytest.mark.embedding @@ -154,17 +163,24 @@ def test_export_image_all_formats(embedded_app, assets, tmp_path, printer): geometry_file = str(Path(assets) / "Eng157.x_t") embedded_app.helpers.import_geometry(geometry_file) - formats = ["png", "jpg", "bmp", "tif", "eps"] - for fmt in formats: - image_path = tmp_path / f"test_image.{fmt}" + from ansys.mechanical.core.embedding.enum_importer import GraphicsImageExportFormat + + format_map = { + "png": GraphicsImageExportFormat.PNG, + "jpg": GraphicsImageExportFormat.JPG, + "bmp": GraphicsImageExportFormat.BMP, + "tif": GraphicsImageExportFormat.TIF, + "eps": GraphicsImageExportFormat.EPS, + } + for name, fmt in format_map.items(): + image_path = tmp_path / f"test_image.{name}" embedded_app.helpers.export_image( obj=embedded_app.Model.Geometry, file_path=str(image_path), image_format=fmt, ) - assert image_path.exists() - printer(f"Exported {fmt} successfully") - _cleanup_file(image_path) + _is_readable(image_path) + printer(f"Exported {name} successfully") @pytest.mark.embedding @@ -179,35 +195,6 @@ def test_export_image_validation_errors(embedded_app, assets, tmp_path, printer) with pytest.raises(ValueError, match="file_path must be provided"): embedded_app.helpers.export_image(obj=embedded_app.Model.Geometry) - # Test invalid resolution - printer("Testing invalid resolution error") - image_path = tmp_path / "test_image.png" - with pytest.raises(ValueError, match="Invalid resolution type"): - embedded_app.helpers.export_image( - obj=embedded_app.Model.Geometry, - file_path=str(image_path), - resolution="invalid", - ) - - # Test invalid background - printer("Testing invalid background error") - with pytest.raises(ValueError, match="Invalid background type"): - embedded_app.helpers.export_image( - obj=embedded_app.Model.Geometry, - file_path=str(image_path), - background="invalid", - ) - - # Test invalid format - printer("Testing invalid format error") - image_path_xyz = tmp_path / "test_image.xyz" - with pytest.raises(ValueError, match="Invalid image format"): - embedded_app.helpers.export_image( - obj=embedded_app.Model.Geometry, - file_path=str(image_path_xyz), - image_format="xyz", - ) - printer("All validation errors handled correctly") @@ -228,9 +215,16 @@ def test_export_animation_basic(embedded_app, tmp_path, printer, graphics_test_m assert result is not None # Test all animation formats - formats = ["gif", "avi", "mp4", "wmv"] - for fmt in formats: - animation_path = tmp_path / f"test_animation.{fmt}" + from ansys.mechanical.core.embedding.enum_importer import GraphicsAnimationExportFormat + + format_map = { + "gif": GraphicsAnimationExportFormat.GIF, + "avi": GraphicsAnimationExportFormat.AVI, + "mp4": GraphicsAnimationExportFormat.MP4, + "wmv": GraphicsAnimationExportFormat.WMV, + } + for name, fmt in format_map.items(): + animation_path = tmp_path / f"test_animation.{name}" embedded_app.helpers.export_animation( obj=result, file_path=str(animation_path), @@ -238,10 +232,8 @@ def test_export_animation_basic(embedded_app, tmp_path, printer, graphics_test_m width=640, height=480, ) - assert animation_path.exists() - assert animation_path.stat().st_size > 0 - printer(f"Exported {fmt} successfully") - _cleanup_file(animation_path) + _is_readable(animation_path) + printer(f"Exported {name} successfully") @pytest.mark.embedding @@ -259,16 +251,6 @@ def test_export_animation_validation_errors(embedded_app, assets, tmp_path, prin with pytest.raises(ValueError, match="file_path must be provided"): embedded_app.helpers.export_animation(obj=result) - # Test invalid format - printer("Testing invalid format error") - animation_path = tmp_path / "test_animation.xyz" - with pytest.raises(ValueError, match="Invalid animation format"): - embedded_app.helpers.export_animation( - obj=result, - file_path=str(animation_path), - animation_format="xyz", - ) - printer("All animation validation errors handled correctly") @@ -299,7 +281,7 @@ def mock_show(): embedded_app.helpers.display_image(str(image_path)) assert len(show_called) == 1 printer("Default display successful") - _cleanup_file(image_path) + Path(image_path).unlink(missing_ok=True) # Test with custom settings printer("Testing display with custom settings") @@ -315,4 +297,4 @@ def mock_show(): ) assert len(show_called) == 2 printer("Custom display successful") - _cleanup_file(image_path_custom) + Path(image_path_custom).unlink(missing_ok=True) From 985ff3b1e8af33b119a05f0d95650712522e0eae Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Mon, 6 Apr 2026 12:51:42 -0500 Subject: [PATCH 27/30] update tests --- src/ansys/mechanical/core/embedding/helpers.py | 12 +++++------- tests/embedding/test_helper.py | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ansys/mechanical/core/embedding/helpers.py b/src/ansys/mechanical/core/embedding/helpers.py index 350f5dc0e2..d73a501db9 100644 --- a/src/ansys/mechanical/core/embedding/helpers.py +++ b/src/ansys/mechanical/core/embedding/helpers.py @@ -101,9 +101,7 @@ def import_geometry( ... process_coordinate_systems=True, ... ) """ - GeometryImportPreference = ( - self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference - ) + GeometryImportPreference = self.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference # noqa: N806 if analysis_type is None: analysis_type = GeometryImportPreference.AnalysisType.Type3D @@ -197,7 +195,9 @@ def export_image( >>> app.helpers.export_image(app.Model.Geometry, "C:\\path\\to\\geometry.png") >>> from ansys.mechanical.core.embedding.enum_importer import ( - ... GraphicsBackgroundType, GraphicsImageExportFormat, GraphicsResolutionType, + ... GraphicsBackgroundType, + ... GraphicsImageExportFormat, + ... GraphicsResolutionType, ... ) >>> result = app.Model.Analyses[0].Solution.Children[0] >>> app.helpers.export_image( @@ -244,9 +244,7 @@ def export_image( graphics_image_export_settings.Height = height try: - self._app.Graphics.ExportImage( - file_path, image_format, graphics_image_export_settings - ) + self._app.Graphics.ExportImage(file_path, image_format, graphics_image_export_settings) self._app.log_info(f"Exported image to {file_path} successfully.") except Exception as e: raise RuntimeError(f"Image export unsuccessful: {e}") diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py index ccdf485f1f..a59d267224 100644 --- a/tests/embedding/test_helper.py +++ b/tests/embedding/test_helper.py @@ -66,7 +66,7 @@ def test_import_geometry(embedded_app, assets, printer): # Test with all options enabled printer("Testing geometry import with all options") - GeometryImportPreference = ( + GeometryImportPreference = ( # noqa: N806 embedded_app.helpers.Ansys.Mechanical.DataModel.Enums.GeometryImportPreference ) geometry_import = embedded_app.helpers.import_geometry( From 0d7dce38be7c82449b7caf63bee89d70766d3d12 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 10 Apr 2026 21:13:07 -0500 Subject: [PATCH 28/30] fix typo --- doc/source/user_guide/embedding/helpers.rst | 6 +++--- examples/01_basic/topology_optimization_cantilever_beam.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/user_guide/embedding/helpers.rst b/doc/source/user_guide/embedding/helpers.rst index cf7c3c692c..ed876766d3 100644 --- a/doc/source/user_guide/embedding/helpers.rst +++ b/doc/source/user_guide/embedding/helpers.rst @@ -66,12 +66,12 @@ Import geometry with material properties and coordinate systems: **2D analysis** -For 2D analyses, specify the analysis type using the ``GeometryImportPreference`` enum: +For 2D analyses, specify the analysis type using the ``GeometryImportPreference`` enum. +``GeometryImportPreference`` is automatically available as a global variable when +``App(globals=globals())`` is used: .. code:: python - from ansys.mechanical.core.embedding.enum_importer import GeometryImportPreference - geometry_import = app.helpers.import_geometry( "path/to/geometry.agdb", analysis_type=GeometryImportPreference.AnalysisType.Type2D diff --git a/examples/01_basic/topology_optimization_cantilever_beam.py b/examples/01_basic/topology_optimization_cantilever_beam.py index c51422c7c3..d90beb05ae 100644 --- a/examples/01_basic/topology_optimization_cantilever_beam.py +++ b/examples/01_basic/topology_optimization_cantilever_beam.py @@ -198,7 +198,7 @@ # Activate the topology density result after smoothing and display the image topology_density.Children[0].Activate() -image_path = output_path / "topo_opitimized_smooth.png" +image_path = output_path / "topo_optimized_smooth.png" camera.SetFit() app.helpers.export_image(topology_density.Children[0], image_path) app.helpers.display_image(image_path) @@ -206,7 +206,7 @@ # %% # Export the animation -topology_optimized_gif = output_path / "topology_opitimized.gif" +topology_optimized_gif = output_path / "topology_optimized.gif" app.helpers.export_animation(topology_density, topology_optimized_gif) # %% From c09697d649eb374db144fe5987c8a3092c02d917 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 10 Apr 2026 21:29:20 -0500 Subject: [PATCH 29/30] fix tests --- tests/embedding/test_helper.py | 88 +++++++++++++++------------------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/tests/embedding/test_helper.py b/tests/embedding/test_helper.py index a59d267224..eeaf81658f 100644 --- a/tests/embedding/test_helper.py +++ b/tests/embedding/test_helper.py @@ -157,30 +157,24 @@ def test_export_image(embedded_app, assets, tmp_path, printer): @pytest.mark.embedding -def test_export_image_all_formats(embedded_app, assets, tmp_path, printer): +@pytest.mark.parametrize("image_format", ["PNG", "JPG", "BMP", "TIF", "EPS"]) +def test_export_image_all_formats(embedded_app, assets, tmp_path, printer, image_format): """Test image export with different formats.""" - printer("Testing image export with all formats") + printer(f"Testing image export with format: {image_format}") geometry_file = str(Path(assets) / "Eng157.x_t") embedded_app.helpers.import_geometry(geometry_file) from ansys.mechanical.core.embedding.enum_importer import GraphicsImageExportFormat - format_map = { - "png": GraphicsImageExportFormat.PNG, - "jpg": GraphicsImageExportFormat.JPG, - "bmp": GraphicsImageExportFormat.BMP, - "tif": GraphicsImageExportFormat.TIF, - "eps": GraphicsImageExportFormat.EPS, - } - for name, fmt in format_map.items(): - image_path = tmp_path / f"test_image.{name}" - embedded_app.helpers.export_image( - obj=embedded_app.Model.Geometry, - file_path=str(image_path), - image_format=fmt, - ) - _is_readable(image_path) - printer(f"Exported {name} successfully") + fmt = getattr(GraphicsImageExportFormat, image_format) + image_path = tmp_path / f"test_image.{image_format.lower()}" + embedded_app.helpers.export_image( + obj=embedded_app.Model.Geometry, + file_path=str(image_path), + image_format=fmt, + ) + _is_readable(image_path) + printer(f"Exported {image_format} successfully") @pytest.mark.embedding @@ -199,41 +193,35 @@ def test_export_image_validation_errors(embedded_app, assets, tmp_path, printer) @pytest.mark.embedding -def test_export_animation_basic(embedded_app, tmp_path, printer, graphics_test_mechdb_file): - """Test basic animation export with graphics fixture.""" - printer("Testing basic animation export") +@pytest.mark.parametrize("animation_format", ["GIF", "AVI", "MP4", "WMV"]) +def test_export_animation_basic( + embedded_app, tmp_path, printer, graphics_test_mechdb_file, animation_format +): + """Test animation export with graphics fixture.""" + printer(f"Testing animation export with format: {animation_format}") - # Open the mechdb file with solved results + from ansys.mechanical.core.embedding.enum_importer import ( + DataModelObjectCategory, + GraphicsAnimationExportFormat, + ) + + # Open the mechdb file with pre-solved results embedded_app.open(str(graphics_test_mechdb_file)) - embedded_app.Model.Analyses[0].Solution.ClearGeneratedData() - printer("Solving the model") - embedded_app.Model.Analyses[0].Solution.Solve() - printer("Model solved") - # Get the deformation result - result = embedded_app.Model.Analyses[0].Solution.Children[1] - printer(result.Name) + result = embedded_app.DataModel.GetObjectsByType(DataModelObjectCategory.DeformationResult)[0] assert result is not None - - # Test all animation formats - from ansys.mechanical.core.embedding.enum_importer import GraphicsAnimationExportFormat - - format_map = { - "gif": GraphicsAnimationExportFormat.GIF, - "avi": GraphicsAnimationExportFormat.AVI, - "mp4": GraphicsAnimationExportFormat.MP4, - "wmv": GraphicsAnimationExportFormat.WMV, - } - for name, fmt in format_map.items(): - animation_path = tmp_path / f"test_animation.{name}" - embedded_app.helpers.export_animation( - obj=result, - file_path=str(animation_path), - animation_format=fmt, - width=640, - height=480, - ) - _is_readable(animation_path) - printer(f"Exported {name} successfully") + printer(f"Using result: {result.Name}") + + fmt = getattr(GraphicsAnimationExportFormat, animation_format) + animation_path = tmp_path / f"test_animation.{animation_format.lower()}" + embedded_app.helpers.export_animation( + obj=result, + file_path=str(animation_path), + animation_format=fmt, + width=640, + height=480, + ) + _is_readable(animation_path) + printer(f"Exported {animation_format} successfully") @pytest.mark.embedding From 4f1233d06c0098c5ffc563c3b06569c86c6905b1 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Fri, 10 Apr 2026 22:35:31 -0500 Subject: [PATCH 30/30] update cicd tests --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index fb01cb47cc..4d8945ab64 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -458,7 +458,7 @@ jobs: run: | . /env/bin/activate uv pip install --upgrade pip - uv pip install -e .[tests] + uv pip install -e .[tests, graphics] - name: Unit Testing and coverage env: