ansys-mechanical-core 0.10.11__py3-none-any.whl → 0.11.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. ansys/mechanical/core/__init__.py +11 -4
  2. ansys/mechanical/core/_version.py +48 -47
  3. ansys/mechanical/core/embedding/__init__.py +1 -1
  4. ansys/mechanical/core/embedding/addins.py +1 -7
  5. ansys/mechanical/core/embedding/app.py +610 -281
  6. ansys/mechanical/core/embedding/app_libraries.py +2 -2
  7. ansys/mechanical/core/embedding/appdata.py +11 -4
  8. ansys/mechanical/core/embedding/background.py +106 -0
  9. ansys/mechanical/core/embedding/cleanup_gui.py +61 -0
  10. ansys/mechanical/core/embedding/enum_importer.py +2 -2
  11. ansys/mechanical/core/embedding/imports.py +27 -7
  12. ansys/mechanical/core/embedding/initializer.py +104 -51
  13. ansys/mechanical/core/embedding/loader.py +1 -1
  14. ansys/mechanical/core/embedding/logger/__init__.py +219 -216
  15. ansys/mechanical/core/embedding/logger/environ.py +1 -1
  16. ansys/mechanical/core/embedding/logger/linux_api.py +1 -1
  17. ansys/mechanical/core/embedding/logger/sinks.py +1 -1
  18. ansys/mechanical/core/embedding/logger/windows_api.py +2 -2
  19. ansys/mechanical/core/embedding/poster.py +34 -2
  20. ansys/mechanical/core/embedding/resolver.py +41 -44
  21. ansys/mechanical/core/embedding/runtime.py +1 -1
  22. ansys/mechanical/core/embedding/shims.py +9 -8
  23. ansys/mechanical/core/embedding/ui.py +228 -0
  24. ansys/mechanical/core/embedding/utils.py +1 -1
  25. ansys/mechanical/core/embedding/viz/__init__.py +1 -1
  26. ansys/mechanical/core/embedding/viz/{pyvista_plotter.py → embedding_plotter.py} +24 -12
  27. ansys/mechanical/core/embedding/viz/usd_converter.py +59 -25
  28. ansys/mechanical/core/embedding/viz/utils.py +32 -2
  29. ansys/mechanical/core/embedding/warnings.py +1 -1
  30. ansys/mechanical/core/errors.py +2 -1
  31. ansys/mechanical/core/examples/__init__.py +1 -1
  32. ansys/mechanical/core/examples/downloads.py +10 -5
  33. ansys/mechanical/core/feature_flags.py +1 -1
  34. ansys/mechanical/core/ide_config.py +212 -0
  35. ansys/mechanical/core/launcher.py +9 -9
  36. ansys/mechanical/core/logging.py +14 -2
  37. ansys/mechanical/core/mechanical.py +2324 -2237
  38. ansys/mechanical/core/misc.py +176 -176
  39. ansys/mechanical/core/pool.py +712 -712
  40. ansys/mechanical/core/run.py +321 -291
  41. {ansys_mechanical_core-0.10.11.dist-info → ansys_mechanical_core-0.11.12.dist-info}/LICENSE +1 -1
  42. {ansys_mechanical_core-0.10.11.dist-info → ansys_mechanical_core-0.11.12.dist-info}/METADATA +55 -54
  43. ansys_mechanical_core-0.11.12.dist-info/RECORD +45 -0
  44. {ansys_mechanical_core-0.10.11.dist-info → ansys_mechanical_core-0.11.12.dist-info}/WHEEL +1 -1
  45. {ansys_mechanical_core-0.10.11.dist-info → ansys_mechanical_core-0.11.12.dist-info}/entry_points.txt +1 -0
  46. ansys_mechanical_core-0.10.11.dist-info/RECORD +0 -41
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -74,7 +74,7 @@ def add_mechanical_python_libraries(app_or_version):
74
74
  elif isinstance(app_or_version, App):
75
75
  installdir.append(os.environ[f"AWP_ROOT{app_or_version.version}"])
76
76
  else:
77
- raise ValueError(f"Invalid input: expected an integer (version) or an instance of App().")
77
+ raise ValueError("Invalid input: expected an integer (version) or an instance of App().")
78
78
 
79
79
  location = os.path.join(installdir[0], "Addins", "ACT", "libraries", "Mechanical")
80
80
  sys.path.append(location)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -31,21 +31,28 @@ import warnings
31
31
  class UniqueUserProfile:
32
32
  """Create Unique User Profile (for AppData)."""
33
33
 
34
- def __init__(self, profile_name: str, dry_run: bool = False):
34
+ def __init__(self, profile_name: str, copy_profile: bool = True, dry_run: bool = False):
35
35
  """Initialize UniqueUserProfile class."""
36
36
  self._default_profile = os.path.expanduser("~")
37
37
  self._location = os.path.join(self._default_profile, "PyMechanical-AppData", profile_name)
38
38
  self._dry_run = dry_run
39
+ self.copy_profile = copy_profile
39
40
  self.initialize()
40
41
 
41
42
  def initialize(self) -> None:
42
- """Initialize the new profile location."""
43
+ """
44
+ Initialize the new profile location.
45
+
46
+ Args:
47
+ copy_profile (bool): If False, the copy_profile method will be skipped.
48
+ """
43
49
  if self._dry_run:
44
50
  return
45
51
  if self.exists():
46
52
  self.cleanup()
47
53
  self.mkdirs()
48
- self.copy_profiles()
54
+ if self.copy_profile:
55
+ self.copy_profiles()
49
56
 
50
57
  def cleanup(self) -> None:
51
58
  """Cleanup unique user profile."""
@@ -0,0 +1,106 @@
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: MIT
3
+ #
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ """Class for running Mechanical on a background thread."""
24
+
25
+ import atexit
26
+ import threading
27
+ import time
28
+ import typing
29
+
30
+ import ansys.mechanical.core as mech
31
+ from ansys.mechanical.core.embedding import initializer
32
+ from ansys.mechanical.core.embedding.poster import Poster
33
+ import ansys.mechanical.core.embedding.utils as utils
34
+
35
+
36
+ def _exit(background_app: "BackgroundApp"):
37
+ """Stop the thread serving the Background App."""
38
+ background_app.stop()
39
+
40
+
41
+ class BackgroundApp:
42
+ """Background App."""
43
+
44
+ __app: mech.App = None
45
+ __app_thread: threading.Thread = None
46
+ __stopped: bool = False
47
+ __stop_signaled: bool = False
48
+ __poster: Poster = None
49
+
50
+ def __init__(self, **kwargs):
51
+ """Construct an instance of BackgroundApp."""
52
+ if BackgroundApp.__app_thread is None:
53
+ initializer.initialize(kwargs.get("version"))
54
+ BackgroundApp.__app_thread = threading.Thread(
55
+ target=self._start_app, kwargs=kwargs, daemon=True
56
+ )
57
+ BackgroundApp.__app_thread.start()
58
+
59
+ while BackgroundApp.__poster is None:
60
+ time.sleep(0.05)
61
+ continue
62
+ else:
63
+ if BackgroundApp.__stopped:
64
+ raise RuntimeError("Cannot initialize a BackgroundApp once it has been stopped!")
65
+
66
+ def new():
67
+ BackgroundApp.__app.new()
68
+
69
+ self.post(new)
70
+
71
+ @property
72
+ def app(self) -> mech.App:
73
+ """Get the App instance of the background thread.
74
+
75
+ It is not meant to be used aside from passing to methods using `post`.
76
+ """
77
+ return BackgroundApp.__app
78
+
79
+ def post(self, callable: typing.Callable):
80
+ """Post callable method to the background app thread."""
81
+ if BackgroundApp.__stopped:
82
+ raise RuntimeError("Cannot use BackgroundApp after stopping it.")
83
+ return BackgroundApp.__poster.post(callable)
84
+
85
+ def stop(self) -> None:
86
+ """Stop the background app thread."""
87
+ if BackgroundApp.__stopped:
88
+ return
89
+ BackgroundApp.__stop_signaled = True
90
+ while True:
91
+ time.sleep(0.05)
92
+ if BackgroundApp.__stopped:
93
+ break
94
+
95
+ def _start_app(self, **kwargs) -> None:
96
+ BackgroundApp.__app = mech.App(**kwargs)
97
+ BackgroundApp.__poster = BackgroundApp.__app.poster
98
+ atexit.register(_exit, self)
99
+ while True:
100
+ if BackgroundApp.__stop_signaled:
101
+ break
102
+ try:
103
+ utils.sleep(40)
104
+ except:
105
+ raise Exception("BackgroundApp cannot sleep.") # pragma: no cover
106
+ BackgroundApp.__stopped = True
@@ -0,0 +1,61 @@
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: MIT
3
+ #
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+ """Clean up temporary mechdb files after GUI is closed."""
23
+
24
+ from pathlib import Path
25
+ import shutil
26
+ import sys
27
+ import time
28
+
29
+ import psutil
30
+
31
+
32
+ def cleanup_gui(pid, temp_mechdb) -> None:
33
+ """Remove the temporary mechdb file after it is closed.
34
+
35
+ Parameters
36
+ ----------
37
+ pid: int
38
+ The process ID of the open temporary mechdb file.
39
+ temp_mechdb: Path
40
+ The path of the temporary mechdb file.
41
+ """
42
+ # While the pid exists, sleep
43
+ while psutil.pid_exists(pid):
44
+ time.sleep(1)
45
+
46
+ # Delete the temporary mechdb file once the GUI is closed
47
+ temp_mechdb.unlink()
48
+
49
+ # Delete the temporary mechdb Mech_Files folder
50
+ temp_folder_path = temp_mechdb.parent / f"{temp_mechdb.name.split('.')[0]}_Mech_Files"
51
+ shutil.rmtree(temp_folder_path)
52
+
53
+
54
+ if __name__ == "__main__": # pragma: no cover
55
+ """Get the process ID and temporary file path to monitor and delete files after use."""
56
+ # Convert the process id (pid) argument into an integer
57
+ pid = int(sys.argv[1])
58
+ # Convert the temporary mechdb path into a Path
59
+ temp_mechdb_path = Path(sys.argv[2])
60
+ # Remove the temporary mechdb file when the GUI is closed
61
+ cleanup_gui(pid, temp_mechdb_path)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -33,5 +33,5 @@ clr.AddReference("Ansys.ACT.Interfaces")
33
33
 
34
34
  from Ansys.ACT.Interfaces.Common import * # noqa isort: skip
35
35
  from Ansys.Mechanical.DataModel.Enums import * # noqa isort: skip
36
-
36
+ from Ansys.ACT.Interfaces.Analysis import * # noqa isort: skip
37
37
  import Ansys # noqa isort: skip
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -24,20 +24,28 @@
24
24
  import typing
25
25
 
26
26
 
27
+ def global_entry_points(app: "ansys.mechanical.core.App") -> typing.Dict:
28
+ """Return the global entry points of the application."""
29
+ vars = {}
30
+ vars["ExtAPI"] = app.ExtAPI
31
+ vars["DataModel"] = app.DataModel
32
+ vars["Model"] = app.DataModel.Project.Model
33
+ vars["Tree"] = app.DataModel.Tree
34
+ vars["Graphics"] = app.ExtAPI.Graphics
35
+ return vars
36
+
37
+
27
38
  def global_variables(app: "ansys.mechanical.core.App", enums: bool = False) -> typing.Dict:
28
39
  """Return the Mechanical scripting global variables as a dict.
29
40
 
30
41
  It can be used to add all of these as global variables in python
31
42
  with this command:
32
- `globals().update(global_variables(embedded_app))`
43
+
44
+ ``globals().update(global_variables(embedded_app))``
33
45
 
34
46
  To also import all the enums, set the parameter enums to true.
35
47
  """
36
- vars = {}
37
- vars["ExtAPI"] = app.ExtAPI
38
- vars["DataModel"] = app.DataModel
39
- vars["Model"] = app.DataModel.Project.Model
40
- vars["Tree"] = app.DataModel.Tree
48
+ vars = global_entry_points(app)
41
49
  import clr # isort: skip
42
50
 
43
51
  clr.AddReference("System.Collections")
@@ -45,8 +53,12 @@ def global_variables(app: "ansys.mechanical.core.App", enums: bool = False) -> t
45
53
  clr.AddReference("Ansys.Mechanical.DataModel")
46
54
  # from Ansys.ACT.Mechanical import Transaction
47
55
  # When ansys-pythonnet issue #14 is fixed, uncomment above
56
+ from Ansys.ACT.Core.Math import Point2D, Point3D
57
+ from Ansys.ACT.Math import Vector3D
58
+ from Ansys.ACT.Mechanical.Fields import VariableDefinitionType
48
59
  from Ansys.Core.Units import Quantity
49
60
  from Ansys.Mechanical.DataModel import MechanicalEnums
61
+ from Ansys.Mechanical.Graphics import Point, SectionPlane
50
62
 
51
63
  import System # isort: skip
52
64
  import Ansys # isort: skip
@@ -56,6 +68,14 @@ def global_variables(app: "ansys.mechanical.core.App", enums: bool = False) -> t
56
68
  vars["Ansys"] = Ansys
57
69
  vars["Transaction"] = Transaction
58
70
  vars["MechanicalEnums"] = MechanicalEnums
71
+ # Graphics
72
+ vars["Point"] = Point
73
+ vars["SectionPlane"] = SectionPlane
74
+ # Math
75
+ vars["Point2D"] = Point2D
76
+ vars["Point3D"] = Point3D
77
+ vars["Vector3D"] = Vector3D
78
+ vars["VariableDefinitionType"] = VariableDefinitionType
59
79
 
60
80
  if enums:
61
81
  vars.update(get_all_enums())
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -28,13 +28,19 @@ import platform
28
28
  import sys
29
29
  import warnings
30
30
 
31
- import ansys.tools.path as atp
32
-
33
31
  from ansys.mechanical.core.embedding.loader import load_clr
34
32
  from ansys.mechanical.core.embedding.resolver import resolve
35
33
 
36
34
  INITIALIZED_VERSION = None
37
- SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS_WINDOWS = {241: "2024R1", 232: "2023R2", 231: "2023R1"}
35
+ """Constant for the initialized version."""
36
+
37
+ SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS = {
38
+ 251: "2025R1",
39
+ 242: "2024R2",
40
+ 241: "2024R1",
41
+ 232: "2023R2",
42
+ }
43
+ """Supported Mechanical embedding versions on Windows."""
38
44
 
39
45
 
40
46
  def __add_sys_path(version: int) -> str:
@@ -44,15 +50,6 @@ def __add_sys_path(version: int) -> str:
44
50
  sys.path.append(str(bin_path.resolve()))
45
51
 
46
52
 
47
- def __disable_sec() -> None:
48
- """SEC is part of RSM and is unstable with embedding.
49
-
50
- I'm not going to debug why that is since we are planning to support
51
- DCS/REP in the future instead of RSM.
52
- """
53
- os.environ["ANSYS_MECHANICAL_EMBEDDING_NO_SEC"] = "1"
54
-
55
-
56
53
  def __workaround_material_server(version: int) -> None:
57
54
  """Workaround material server bug in 2024 R1.
58
55
 
@@ -65,48 +62,68 @@ def __workaround_material_server(version: int) -> None:
65
62
  os.environ["ENGRDATA_SERVER_SERIAL"] = "1"
66
63
 
67
64
 
68
- def _get_default_linux_version() -> int:
69
- """Try to get the active linux version from the environment.
65
+ def __check_for_supported_version(version) -> None:
66
+ """Check if Mechanical version is supported with current version of PyMechanical.
70
67
 
71
- On linux, embedding is only possible by setting environment variables before starting python.
72
- The version will then be fixed to a specific version, based on those env vars.
73
- The documented way to set those variables is to run python using the ``mechanical-env`` script,
74
- which can be used after installing the ``ansys-mechanical-env`` package with this command:
75
- ``pip install ansys-mechanical-env``. The script takes user input of a version. If the user
76
- does not provide a version, the ``find_mechanical()`` function from the ``ansys-tools-path``
77
- package is used to find a version of Mechanical.
68
+ If specific environment variable is enabled, then users can overwrite the supported versions.
69
+ However, using unsupported versions may cause issues.
78
70
  """
79
- supported_versions = [232, 241]
80
- awp_roots = {ver: os.environ.get(f"AWP_ROOT{ver}", "") for ver in supported_versions}
81
- installed_versions = {
82
- ver: path for ver, path in awp_roots.items() if path and os.path.isdir(path)
83
- }
84
- assert len(installed_versions) == 1, "multiple AWP_ROOT environment variables found!"
85
- return next(iter(installed_versions))
71
+ allow_old_version = os.getenv("ANSYS_MECHANICAL_EMBEDDING_SUPPORT_OLD_VERSIONS") == "1"
72
+
73
+ # Check if the version is supported
74
+ if not allow_old_version and version < min(SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS):
75
+ raise ValueError(f"Mechanical version {version} is not supported.")
76
+
77
+ return version
78
+
86
79
 
80
+ def _get_latest_default_version() -> int:
81
+ """Try to get the latest Mechanical version from the environment.
87
82
 
88
- def _get_default_version() -> int:
89
- if os.name == "posix":
90
- return _get_default_linux_version()
83
+ Checks if multiple versions of Mechanical found in system.
84
+ For Linux it will be only one since ``mechanical-env`` takes care of that.
85
+ If multiple versions are detected, select the latest one, as no specific version is provided.
86
+ """
87
+ awp_roots = [value for key, value in os.environ.items() if key.startswith("AWP_ROOT")]
91
88
 
92
- if os.name != "nt": # pragma: no cover
93
- raise Exception("Unexpected platform!")
89
+ if not awp_roots:
90
+ raise Exception("No Mechanical installations found.")
94
91
 
95
- _, version = atp.find_mechanical(
96
- supported_versions=SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS_WINDOWS
97
- )
92
+ versions_found = []
93
+ for path in awp_roots:
94
+ folder = os.path.basename(os.path.normpath(path))
95
+ version = folder.split("v")[-1]
96
+ versions_found.append(int(version))
97
+ latest_version = max(versions_found)
98
98
 
99
- # version is of the form 23.2
100
- int_version = int(str(version).replace(".", ""))
101
- return int_version
99
+ if len(awp_roots) > 1:
100
+ warnings.warn(
101
+ f"Multiple versions of Mechanical found! Using latest version {latest_version} ..."
102
+ )
102
103
 
104
+ return latest_version
103
105
 
104
- def __check_python_interpreter_architecture():
106
+
107
+ def __check_python_interpreter_architecture() -> None:
105
108
  """Embedding support only 64 bit architecture."""
106
109
  if platform.architecture()[0] != "64bit":
107
110
  raise Exception("Mechanical Embedding requires a 64-bit Python environment.")
108
111
 
109
112
 
113
+ def __set_environment(version: int) -> None:
114
+ """Set environment variables to configure embedding."""
115
+ if os.name == "nt": # pragma: no cover
116
+ if version < 251:
117
+ os.environ["MECHANICAL_STARTUP_UNOPTIMIZED"] = "1"
118
+
119
+ # Set an environment variable to use the custom CLR host
120
+ # for embedding.
121
+ # In the future (>251), it would always be used.
122
+ if version == 251:
123
+ if "PYMECHANICAL_NO_CLR_HOST_LITE" not in os.environ:
124
+ os.environ["ANSYS_MECHANICAL_EMBEDDING_CLR_HOST"] = "1"
125
+
126
+
110
127
  def __check_for_mechanical_env():
111
128
  """Embedding in linux platform must use mechanical-env."""
112
129
  if platform.system() == "Linux" and os.environ.get("PYMECHANICAL_EMBEDDING") != "TRUE":
@@ -119,22 +136,57 @@ def __check_for_mechanical_env():
119
136
  )
120
137
 
121
138
 
139
+ def __is_lib_loaded(libname: str): # pragma: no cover
140
+ """Return whether a library is loaded."""
141
+ import ctypes
142
+
143
+ RTLD_NOLOAD = 4
144
+ try:
145
+ ctypes.CDLL(libname, RTLD_NOLOAD)
146
+ except OSError:
147
+ return False
148
+ return True
149
+
150
+
151
+ def __check_loaded_libs(version: int = None): # pragma: no cover
152
+ """Ensure that incompatible libraries aren't loaded prior to PyMechanical load."""
153
+ if platform.system() != "Linux":
154
+ return
155
+
156
+ if version < 251:
157
+ return
158
+
159
+ # For 2025 R1, PyMechanical will crash on shutdown if libX11.so is already loaded
160
+ # before starting Mechanical
161
+ if __is_lib_loaded("libX11.so"):
162
+ warnings.warn(
163
+ "libX11.so is loaded prior to initializing the Embedded Instance of Mechanical.\
164
+ Python will crash on shutdown..."
165
+ )
166
+
167
+
122
168
  def initialize(version: int = None):
123
169
  """Initialize Mechanical embedding."""
124
- __check_python_interpreter_architecture() # blocks 32 bit python
125
- __check_for_mechanical_env() # checks for mechanical-env in linux embedding
126
-
127
170
  global INITIALIZED_VERSION
128
- if INITIALIZED_VERSION != None:
129
- assert INITIALIZED_VERSION == version
130
- return
171
+ if version is None:
172
+ version = _get_latest_default_version()
131
173
 
132
- if version == None:
133
- version = _get_default_version()
174
+ version = __check_for_supported_version(version=version)
134
175
 
135
- INITIALIZED_VERSION = version
176
+ if INITIALIZED_VERSION is not None:
177
+ if INITIALIZED_VERSION != version:
178
+ raise ValueError(
179
+ f"Initialized version {INITIALIZED_VERSION} "
180
+ f"does not match the expected version {version}."
181
+ )
182
+ return INITIALIZED_VERSION
136
183
 
137
- __disable_sec()
184
+ __check_python_interpreter_architecture() # blocks 32 bit python
185
+ __check_for_mechanical_env() # checks for mechanical-env in linux embedding
186
+
187
+ __set_environment(version)
188
+
189
+ __check_loaded_libs(version)
138
190
 
139
191
  __workaround_material_server(version)
140
192
 
@@ -163,4 +215,5 @@ def initialize(version: int = None):
163
215
  # attach the resolver
164
216
  resolve(version)
165
217
 
218
+ INITIALIZED_VERSION = version
166
219
  return version
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #