From a4119e2c3969b16dedb781a9ba48f1581a534ad3 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 28 Mar 2026 12:03:09 -0500 Subject: [PATCH 1/6] parallel example test --- .github/workflows/ci_cd.yml | 1 + doc/source/conf.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index d0eb8cb71c..808ba2d303 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -740,6 +740,7 @@ jobs: NUM_CORES: 1 ANSYS_WORKBENCH_LOGGING_FILTER_LEVEL: 0 BUILD_CHEATSHEET: true + PYMECHANICAL_GALLERY_PARALLEL: "4" CONTAINER_STABLE_EXIT: ${{ needs.container-stability-check.outputs.container_stable_exit }} run: | . /env/bin/activate diff --git a/doc/source/conf.py b/doc/source/conf.py index 2665ac0d4b..60ce7dc3cc 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -182,11 +182,21 @@ copybutton_prompt_is_regexp = True # -- Sphinx Gallery Options --------------------------------------------------- +# Parallel example execution (separate Python subprocess per running example). +# Each subprocess can host one embedded Mechanical App, so N workers means up +# to N concurrent Mechanical processes—only use N>1 when licenses and RAM allow. +# https://sphinx-gallery.github.io/stable/configuration.html#parallel-gallery-builds +try: + _gallery_parallel = max(1, int(os.environ.get("PYMECHANICAL_GALLERY_PARALLEL", "1"))) +except ValueError: + _gallery_parallel = 1 + sphinx_gallery_conf = { # convert rst to md for ipynb "pypandoc": True, # path to your examples scripts "examples_dirs": ["../../examples/"], + "abort_on_example_error": True, # path where to save gallery generated examples "gallery_dirs": ["examples/gallery_examples"], # Pattern to search for example files "filename_pattern": r"\.py", @@ -202,6 +212,7 @@ # Files to ignore "ignore_pattern": "flycheck*", # noqa: E501 "thumbnail_size": (350, 350), + "parallel": _gallery_parallel, } # -- Options for HTML output ------------------------------------------------- From 344c5b9e11c76a0bc01093333b407f82df30c3d9 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 28 Mar 2026 12:17:59 -0500 Subject: [PATCH 2/6] add joblib as depend --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f6b778d59d..f3335c094d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ tests = [ doc = [ "sphinx>=8.2.3,<9", + "joblib>=1.4.0,<2", "ansys-sphinx-theme[autoapi,changelog]>=1.7.0,<2", "imageio-ffmpeg>=0.6.0,<1", "imageio>=2.37.2,<3", From 119371e99f056218c38e2335e47982b568da4d64 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 28 Mar 2026 12:38:21 -0500 Subject: [PATCH 3/6] set env vars --- doc/source/conf.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 60ce7dc3cc..49100fd70e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -13,6 +13,12 @@ from pathlib import Path import warnings +# Sphinx-Gallery parallel workers are separate processes that never import this +# file. PyVista reads PYVISTA_* from the environment at package import time, so +# set these before importing pyvista; child processes inherit os.environ. +os.environ.setdefault("PYVISTA_BUILDING_GALLERY", "true") +os.environ.setdefault("PYVISTA_OFF_SCREEN", "true") + from ansys_sphinx_theme import ansys_favicon, get_version_match import pyvista from pyvista.plotting.utilities.sphinx_gallery import DynamicScraper From bd9ad9d9a0061dab7fd04a45b03d4342bbeafd00 Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 28 Mar 2026 12:53:00 -0500 Subject: [PATCH 4/6] set galleryexamples for pymech --- doc/source/conf.py | 6 +++--- src/ansys/mechanical/core/__init__.py | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 49100fd70e..7fe5635837 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -18,6 +18,9 @@ # set these before importing pyvista; child processes inherit os.environ. os.environ.setdefault("PYVISTA_BUILDING_GALLERY", "true") os.environ.setdefault("PYVISTA_OFF_SCREEN", "true") +# Parallel gallery workers may reuse a process; embedded App() then needs +# BUILDING_GALLERY so a later example can attach via _share instead of erroring. +os.environ.setdefault("PYMECHANICAL_BUILDING_GALLERY", "true") from ansys_sphinx_theme import ansys_favicon, get_version_match import pyvista @@ -28,9 +31,6 @@ import ansys.mechanical.core as pymechanical from ansys.mechanical.core.embedding.initializer import SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS -# necessary when building the sphinx gallery -pymechanical.BUILDING_GALLERY = True - # Ensure that offscreen rendering is used for docs generation pyvista.OFF_SCREEN = True diff --git a/src/ansys/mechanical/core/__init__.py b/src/ansys/mechanical/core/__init__.py index a6da4c7426..d75c561d81 100644 --- a/src/ansys/mechanical/core/__init__.py +++ b/src/ansys/mechanical/core/__init__.py @@ -75,5 +75,10 @@ from ansys.mechanical.core.pool import LocalMechanicalPool -BUILDING_GALLERY = False -"""Whether or not to build gallery examples.""" +_gallery_env = os.environ.get("PYMECHANICAL_BUILDING_GALLERY", "").strip().lower() +BUILDING_GALLERY = _gallery_env in ("1", "true", "yes") +"""Gallery mode for embedded ``App`` (shared instance when a process runs multiple examples). + +Parallel Sphinx-Gallery workers inherit ``PYMECHANICAL_BUILDING_GALLERY=true`` from the +environment; they do not execute ``doc/source/conf.py``. +""" From 8f4bd684f4007e7539ee397ae403e7a32fc9e07d Mon Sep 17 00:00:00 2001 From: dkunhamb Date: Sat, 28 Mar 2026 15:55:08 -0500 Subject: [PATCH 5/6] fix issue when ansys folder is there in cwd --- .../mechanical/core/embedding/resolver.py | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/ansys/mechanical/core/embedding/resolver.py b/src/ansys/mechanical/core/embedding/resolver.py index bb46a5b103..1588a7a958 100644 --- a/src/ansys/mechanical/core/embedding/resolver.py +++ b/src/ansys/mechanical/core/embedding/resolver.py @@ -27,6 +27,28 @@ starting in version 23.1 and on Linux starting in version 23.2 """ +from pathlib import Path +import sys + + +def _sys_path_has_shadow_ansys_dir(path_entry: str) -> bool: + """Check whether *path_entry* contains a subdirectory named exactly ``Ansys``. + + Such a folder is imported as a namespace package and shadows the CLR + ``Ansys`` module from ``clr.AddReference``, causing + ``module 'Ansys' has no attribute 'Mechanical'``. + + Only the exact spelling ``Ansys`` is treated as a shadow so + ``site-packages/ansys`` is not removed from ``sys.path``. + """ + base = Path(path_entry) if path_entry else Path.cwd() + try: + if not base.is_dir(): + return False + return any(p.is_dir() and p.name == "Ansys" for p in base.iterdir()) + except OSError: + return False + def resolve(version): """Resolve function for all versions of Ansys Mechanical.""" @@ -34,15 +56,23 @@ def resolve(version): import System # isort: skip clr.AddReference("Ansys.Mechanical.Embedding") - import Ansys # isort: skip + + _original_sys_path = sys.path[:] + try: + sys.path[:] = [p for p in sys.path if not _sys_path_has_shadow_ansys_dir(p)] + import Ansys # isort: skip + finally: + sys.path[:] = _original_sys_path try: assembly_resolver = Ansys.Mechanical.Embedding.AssemblyResolver resolve_handler = assembly_resolver.MechanicalResolveEventHandler System.AppDomain.CurrentDomain.AssemblyResolve += resolve_handler - except AttributeError: - error_msg = """Unable to resolve Mechanical assemblies. Please ensure the following: - 1. Mechanical is installed. - 2. A folder with the name "Ansys" does not exist in the same directory as the script being run. - """ - raise AttributeError(error_msg) + except AttributeError as err: + error_msg = ( + "Unable to resolve Mechanical assemblies. Please ensure the following:\n" + " 1. Mechanical is installed.\n" + ' 2. A subdirectory named exactly "Ansys" does not exist on sys.path ' + "(e.g. next to an example script), which shadows the CLR Ansys module.\n" + ) + raise AttributeError(error_msg) from err From c82be214e94758d325e83b62ac3a0d540e554d9f Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:25:28 +0000 Subject: [PATCH 6/6] chore: adding changelog file 1572.test.md [dependabot-skip] --- doc/changelog.d/1572.test.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1572.test.md diff --git a/doc/changelog.d/1572.test.md b/doc/changelog.d/1572.test.md new file mode 100644 index 0000000000..54b7e1d122 --- /dev/null +++ b/doc/changelog.d/1572.test.md @@ -0,0 +1 @@ +Parallel example test