ansys-mechanical-core 0.11.7__py3-none-any.whl → 0.11.8__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.
@@ -52,9 +52,3 @@ class AddinConfiguration:
52
52
  @addin_configuration.setter
53
53
  def addin_configuration(self, value: str):
54
54
  self._addin_configuration = value
55
-
56
-
57
- def configure(configuration: AddinConfiguration):
58
- """Apply the given configuration."""
59
- if configuration.no_act_addins:
60
- os.environ["ANSYS_MECHANICAL_STANDALONE_NO_ACT_EXTENSIONS"] = "1"
@@ -31,6 +31,7 @@ from ansys.mechanical.core.embedding.addins import AddinConfiguration
31
31
  from ansys.mechanical.core.embedding.appdata import UniqueUserProfile
32
32
  from ansys.mechanical.core.embedding.imports import global_entry_points, global_variables
33
33
  from ansys.mechanical.core.embedding.poster import Poster
34
+ from ansys.mechanical.core.embedding.ui import launch_ui
34
35
  from ansys.mechanical.core.embedding.warnings import connect_warnings, disconnect_warnings
35
36
 
36
37
  try:
@@ -191,6 +192,10 @@ class App:
191
192
  """Save the project as."""
192
193
  self.DataModel.Project.SaveAs(path)
193
194
 
195
+ def launch_gui(self, delete_tmp_on_close: bool = True, dry_run: bool = False):
196
+ """Launch the GUI."""
197
+ launch_ui(self, delete_tmp_on_close, dry_run)
198
+
194
199
  def new(self):
195
200
  """Clear to a new application."""
196
201
  self.DataModel.Project.New()
@@ -227,7 +232,21 @@ class App:
227
232
  light_mode = True
228
233
  args = None
229
234
  rets = None
230
- return self.script_engine.ExecuteCode(script, SCRIPT_SCOPE, light_mode, args, rets)
235
+ script_result = self.script_engine.ExecuteCode(script, SCRIPT_SCOPE, light_mode, args, rets)
236
+ error_msg = f"Failed to execute the script"
237
+ if script_result is None:
238
+ raise Exception(error_msg)
239
+ if script_result.Error is not None:
240
+ error_msg += f": {script_result.Error.Message}"
241
+ raise Exception(error_msg)
242
+ return script_result.Value
243
+
244
+ def execute_script_from_file(self, file_path=None):
245
+ """Execute the given script from file with the internal IronPython engine."""
246
+ text_file = open(file_path, "r", encoding="utf-8")
247
+ data = text_file.read()
248
+ text_file.close()
249
+ return self.execute_script(data)
231
250
 
232
251
  def plotter(self) -> None:
233
252
  """Return ``ansys.tools.visualization_interface.Plotter`` object."""
@@ -0,0 +1,61 @@
1
+ # Copyright (C) 2022 - 2024 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)
@@ -28,15 +28,13 @@ 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
35
  """Constant for the initialized version."""
38
36
 
39
- SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS_WINDOWS = {242: "2024R2", 241: "2024R1", 232: "2023R2"}
37
+ SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS = {242: "2024R2", 241: "2024R1", 232: "2023R2"}
40
38
  """Supported Mechanical embedding versions on Windows."""
41
39
 
42
40
 
@@ -59,48 +57,68 @@ def __workaround_material_server(version: int) -> None:
59
57
  os.environ["ENGRDATA_SERVER_SERIAL"] = "1"
60
58
 
61
59
 
62
- def _get_default_linux_version() -> int:
63
- """Try to get the active linux version from the environment.
60
+ def __check_for_supported_version(version) -> None:
61
+ """Check if Mechanical version is supported with current version of PyMechanical.
64
62
 
65
- On linux, embedding is only possible by setting environment variables before starting python.
66
- The version will then be fixed to a specific version, based on those env vars.
67
- The documented way to set those variables is to run python using the ``mechanical-env`` script,
68
- which can be used after installing the ``ansys-mechanical-env`` package with this command:
69
- ``pip install ansys-mechanical-env``. The script takes user input of a version. If the user
70
- does not provide a version, the ``find_mechanical()`` function from the ``ansys-tools-path``
71
- package is used to find a version of Mechanical.
63
+ If specific environment variable is enabled, then users can overwrite the supported versions.
64
+ However, using unsupported versions may cause issues.
72
65
  """
73
- supported_versions = [232, 241, 242]
74
- awp_roots = {ver: os.environ.get(f"AWP_ROOT{ver}", "") for ver in supported_versions}
75
- installed_versions = {
76
- ver: path for ver, path in awp_roots.items() if path and os.path.isdir(path)
77
- }
78
- assert len(installed_versions) == 1, "multiple AWP_ROOT environment variables found!"
79
- return next(iter(installed_versions))
66
+ allow_old_version = os.getenv("ANSYS_MECHANICAL_EMBEDDING_SUPPORT_OLD_VERSIONS") == "1"
67
+
68
+ # Check if the version is supported
69
+ if not allow_old_version and version < min(SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS):
70
+ raise ValueError(f"Mechanical version {version} is not supported.")
71
+
72
+ return version
80
73
 
81
74
 
82
- def _get_default_version() -> int:
83
- if os.name == "posix":
84
- return _get_default_linux_version()
75
+ def _get_latest_default_version() -> int:
76
+ """Try to get the latest Mechanical version from the environment.
77
+
78
+ Checks if multiple versions of Mechanical found in system.
79
+ For Linux it will be only one since ``mechanical-env`` takes care of that.
80
+ If multiple versions are detected, select the latest one, as no specific version is provided.
81
+ """
82
+ awp_roots = [value for key, value in os.environ.items() if key.startswith("AWP_ROOT")]
83
+
84
+ if not awp_roots:
85
+ raise Exception("No Mechanical installations found.")
85
86
 
86
- if os.name != "nt": # pragma: no cover
87
- raise Exception("Unexpected platform!")
87
+ versions_found = []
88
+ for path in awp_roots:
89
+ folder = os.path.basename(os.path.normpath(path))
90
+ version = folder.split("v")[-1]
91
+ versions_found.append(int(version))
92
+ latest_version = max(versions_found)
88
93
 
89
- _, version = atp.find_mechanical(
90
- supported_versions=SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS_WINDOWS
91
- )
94
+ if len(awp_roots) > 1:
95
+ warnings.warn(
96
+ f"Multiple versions of Mechanical found! Using latest version {latest_version} ..."
97
+ )
92
98
 
93
- # version is of the form 23.2
94
- int_version = int(str(version).replace(".", ""))
95
- return int_version
99
+ return latest_version
96
100
 
97
101
 
98
- def __check_python_interpreter_architecture():
102
+ def __check_python_interpreter_architecture() -> None:
99
103
  """Embedding support only 64 bit architecture."""
100
104
  if platform.architecture()[0] != "64bit":
101
105
  raise Exception("Mechanical Embedding requires a 64-bit Python environment.")
102
106
 
103
107
 
108
+ def __set_environment(version: int) -> None:
109
+ """Set environment variables to configure embedding."""
110
+ if os.name == "nt": # pragma: no cover
111
+ if version < 251:
112
+ os.environ["MECHANICAL_STARTUP_UNOPTIMIZED"] = "1"
113
+
114
+ # Set an environment variable to use the custom CLR host
115
+ # for embedding.
116
+ # In the future (>251), it would always be used.
117
+ if version == 251:
118
+ if "PYMECHANICAL_NO_CLR_HOST_LITE" not in os.environ:
119
+ os.environ["ANSYS_MECHANICAL_EMBEDDING_CLR_HOST"] = "1"
120
+
121
+
104
122
  def __check_for_mechanical_env():
105
123
  """Embedding in linux platform must use mechanical-env."""
106
124
  if platform.system() == "Linux" and os.environ.get("PYMECHANICAL_EMBEDDING") != "TRUE":
@@ -113,21 +131,60 @@ def __check_for_mechanical_env():
113
131
  )
114
132
 
115
133
 
134
+ def __is_lib_loaded(libname: str): # pragma: no cover
135
+ """Return whether a library is loaded."""
136
+ import ctypes
137
+
138
+ RTLD_NOLOAD = 4
139
+ try:
140
+ ctypes.CDLL(libname, RTLD_NOLOAD)
141
+ except:
142
+ return False
143
+ return True
144
+
145
+
146
+ def __check_loaded_libs(version: int = None): # pragma: no cover
147
+ """Ensure that incompatible libraries aren't loaded prior to PyMechanical load."""
148
+ if platform.system() != "Linux":
149
+ return
150
+
151
+ if version < 251:
152
+ return
153
+
154
+ # For 2025 R1, PyMechanical will crash on shutdown if libX11.so is already loaded
155
+ # before starting Mechanical
156
+ if __is_lib_loaded("libX11.so"):
157
+ warnings.warn(
158
+ "libX11.so is loaded prior to initializing the Embedded Instance of Mechanical.\
159
+ Python will crash on shutdown..."
160
+ )
161
+
162
+
116
163
  def initialize(version: int = None):
117
164
  """Initialize Mechanical embedding."""
118
165
  __check_python_interpreter_architecture() # blocks 32 bit python
119
166
  __check_for_mechanical_env() # checks for mechanical-env in linux embedding
120
167
 
121
168
  global INITIALIZED_VERSION
122
- if INITIALIZED_VERSION != None:
123
- assert INITIALIZED_VERSION == version
169
+ if INITIALIZED_VERSION is not None:
170
+ if INITIALIZED_VERSION != version:
171
+ raise ValueError(
172
+ f"Initialized version {INITIALIZED_VERSION} "
173
+ f"does not match the expected version {version}."
174
+ )
124
175
  return
125
176
 
126
177
  if version == None:
127
- version = _get_default_version()
178
+ version = _get_latest_default_version()
179
+
180
+ version = __check_for_supported_version(version=version)
128
181
 
129
182
  INITIALIZED_VERSION = version
130
183
 
184
+ __set_environment(version)
185
+
186
+ __check_loaded_libs(version)
187
+
131
188
  __workaround_material_server(version)
132
189
 
133
190
  # need to add system path in order to import the assembly with the resolver
@@ -0,0 +1,225 @@
1
+ # Copyright (C) 2022 - 2024 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
+ """Run Mechanical UI from Python."""
23
+
24
+ from pathlib import Path
25
+ from subprocess import Popen
26
+ import sys
27
+ import tempfile
28
+ import typing
29
+
30
+
31
+ class UILauncher:
32
+ """Launch the GUI using a temporary mechdb file."""
33
+
34
+ def __init__(self, dry_run: bool = False):
35
+ """Initialize UILauncher class."""
36
+ self._dry_run = dry_run
37
+
38
+ def save_original(self, app: "ansys.mechanical.core.embedding.App") -> None:
39
+ """Save the active mechdb file.
40
+
41
+ Parameters
42
+ ----------
43
+ app: ansys.mechanical.core.embedding.app.App
44
+ A Mechanical embedding application.
45
+ """
46
+ app.save()
47
+
48
+ def save_temp_copy(
49
+ self, app: "ansys.mechanical.core.embedding.App"
50
+ ) -> typing.Union[Path, Path]:
51
+ """Save a new mechdb file with a temporary name.
52
+
53
+ Parameters
54
+ ----------
55
+ app: ansys.mechanical.core.embedding.app.App
56
+ A Mechanical embedding application.
57
+ """
58
+ # Identify the mechdb of the saved session from save_original()
59
+ project_directory = Path(app.DataModel.Project.ProjectDirectory)
60
+ project_directory_parent = project_directory.parent
61
+ mechdb_file = (
62
+ project_directory_parent / f"{project_directory.parts[-1].split('_')[0]}.mechdb"
63
+ )
64
+
65
+ # Get name of NamedTemporaryFile
66
+ temp_file_name = tempfile.NamedTemporaryFile(
67
+ dir=project_directory_parent, suffix=".mechdb", delete=True
68
+ ).name
69
+
70
+ # Save app with name of temporary file
71
+ app.save_as(temp_file_name)
72
+
73
+ return mechdb_file, temp_file_name
74
+
75
+ def open_original(self, app: "ansys.mechanical.core.embedding.App", mechdb_file: str) -> None:
76
+ """Open the original mechdb file from save_original().
77
+
78
+ Parameters
79
+ ----------
80
+ app: ansys.mechanical.core.embedding.app.App
81
+ A Mechanical embedding application.
82
+ mechdb_file: str
83
+ The full path to the active mechdb file.
84
+ """
85
+ app.open(mechdb_file)
86
+
87
+ def graphically_launch_temp(
88
+ self, app: "ansys.mechanical.core.embedding.App", temp_file: Path
89
+ ) -> typing.Union[Popen, str]:
90
+ """Launch the GUI for the mechdb file with a temporary name from save_temp_copy().
91
+
92
+ Parameters
93
+ ----------
94
+ app: ansys.mechanical.core.embedding.app.App
95
+ A Mechanical embedding application.
96
+ temp_file: pathlib.Path
97
+ The full path to the temporary mechdb file.
98
+
99
+ Returns
100
+ -------
101
+ subprocess.Popen
102
+ The subprocess that launches the GUI for the temporary mechdb file.
103
+ """
104
+ # The ansys-mechanical command to launch the GUI in a subprocess
105
+ args = [
106
+ "ansys-mechanical",
107
+ "--project-file",
108
+ temp_file,
109
+ "--graphical",
110
+ "--revision",
111
+ str(app.version),
112
+ ]
113
+ if not self._dry_run:
114
+ # The subprocess that uses ansys-mechanical to launch the GUI of the temporary
115
+ # mechdb file
116
+ process = Popen(args)
117
+ return process
118
+ else:
119
+ # Return a string containing the args
120
+ return " ".join(args)
121
+
122
+ def _cleanup_gui(self, process: Popen, temp_mechdb_path: Path) -> None:
123
+ """Remove the temporary mechdb file and folder when the GUI is closed.
124
+
125
+ Parameters
126
+ ----------
127
+ process: subprocess.Popen
128
+ The subprocess that launched the GUI of the temporary mechdb file.
129
+ temp_mechdb_path: pathlib.Path
130
+ The full path to the temporary mechdb file.
131
+ """
132
+ # Get the path to the cleanup script
133
+ cleanup_script = Path(__file__).parent / "cleanup_gui.py" # pragma: no cover
134
+
135
+ if not self._dry_run:
136
+ # Open a subprocess to remove the temporary mechdb file and folder when the process ends
137
+ Popen(
138
+ [sys.executable, cleanup_script, str(process.pid), temp_mechdb_path]
139
+ ) # pragma: no cover
140
+
141
+
142
+ def _is_saved(app: "ansys.mechanical.core.embedding.App") -> bool:
143
+ """Check if the mechdb file has been saved and raise an exception if not.
144
+
145
+ Parameters
146
+ ----------
147
+ app: ansys.mechanical.core.embedding.app.App
148
+ A Mechanical embedding application.
149
+
150
+ Returns
151
+ -------
152
+ bool
153
+ ``True`` when the embedded app has been saved.
154
+ ``False`` when the embedded app has not been saved.
155
+ """
156
+ try:
157
+ app.save()
158
+ except:
159
+ raise Exception("The App must have already been saved before using launch_ui!")
160
+ return True
161
+
162
+
163
+ def _launch_ui(
164
+ app: "ansys.mechanical.core.embedding.App", delete_tmp_on_close: bool, launcher: UILauncher
165
+ ) -> None:
166
+ """Launch the Mechanical UI if the mechdb file has been saved.
167
+
168
+ Parameters
169
+ ----------
170
+ app: ansys.mechanical.core.embedding.app.App
171
+ A Mechanical embedding application.
172
+ delete_tmp_on_close: bool
173
+ Whether to delete the temporary mechdb file when the GUI is closed.
174
+ By default, this is ``True``.
175
+ launcher: UILauncher
176
+ Launch the GUI using a temporary mechdb file.
177
+ """
178
+ if _is_saved(app):
179
+ # Save the active mechdb file.
180
+ launcher.save_original(app)
181
+ # Save a new mechdb file with a temporary name.
182
+ mechdb_file, temp_file = launcher.save_temp_copy(app)
183
+ # Open the original mechdb file from save_original().
184
+ launcher.open_original(app, str(mechdb_file))
185
+ # Launch the GUI for the mechdb file with a temporary name from save_temp_copy().
186
+ process = launcher.graphically_launch_temp(app, temp_file)
187
+
188
+ # If it's a dry run and graphically_launch_temp returned a string, print the string
189
+ if isinstance(process, str):
190
+ print(process)
191
+
192
+ # If the user wants the temporary file to be deleted and graphically_launch_temp
193
+ # returned a process. By default, this is True
194
+ if delete_tmp_on_close and not isinstance(process, str):
195
+ # Remove the temporary mechdb file and folder when the GUI is closed.
196
+ launcher._cleanup_gui(process, temp_file) # pragma: no cover
197
+ else:
198
+ # Let the user know that the mechdb started above will not automatically get cleaned up
199
+ print(
200
+ f"""Opened a new mechanical session based on {mechdb_file} named {temp_file}.
201
+ PyMechanical will not delete it after use."""
202
+ )
203
+
204
+
205
+ def launch_ui(
206
+ app: "ansys.mechanical.core.embedding.App",
207
+ delete_tmp_on_close: bool = True,
208
+ dry_run: bool = False,
209
+ ) -> None:
210
+ """Launch the Mechanical UI.
211
+
212
+ Precondition: Mechanical has to have already been saved
213
+ Side effect: If Mechanical has ever been saved, it overwrites that save.
214
+
215
+ Parameters
216
+ ----------
217
+ app: ansys.mechanical.core.embedding.app.App
218
+ A Mechanical embedding application.
219
+ delete_tmp_on_close: bool
220
+ Whether to delete the temporary mechdb file when the GUI is closed.
221
+ By default, this is ``True``.
222
+ dry_run: bool
223
+ Whether or not to launch the GUI. By default, this is ``False``.
224
+ """
225
+ _launch_ui(app, delete_tmp_on_close, UILauncher(dry_run))
@@ -41,7 +41,8 @@ def _reshape_3cols(arr: np.array, name: str = "array"):
41
41
  """
42
42
  err = f"{name} must be of the form (x0,y0,z0,x1,y1,z1,...,xn,yn,zn).\
43
43
  Given {name} are not divisible by 3!"
44
- assert arr.size % 3 == 0, err
44
+ if arr.size % 3 != 0:
45
+ raise ValueError(err)
45
46
  numrows = int(arr.size / 3)
46
47
  numcols = 3
47
48
  arr = np.reshape(arr, (numrows, numcols))
@@ -0,0 +1,164 @@
1
+ # Copyright (C) 2022 - 2024 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
+ """Convenience CLI to run mechanical."""
24
+
25
+ import json
26
+ import os
27
+ from pathlib import Path
28
+ import sys
29
+ import sysconfig
30
+
31
+ import ansys.tools.path as atp
32
+ import click
33
+
34
+
35
+ def _vscode_impl(
36
+ target: str = "user",
37
+ revision: int = None,
38
+ ):
39
+ """Get the IDE configuration for autocomplete in VS Code.
40
+
41
+ Parameters
42
+ ----------
43
+ target: str
44
+ The type of settings to update. Either "user" or "workspace" in VS Code.
45
+ By default, it's ``user``.
46
+ revision: int
47
+ The Mechanical revision number. For example, "242".
48
+ If unspecified, it finds the default Mechanical version from ansys-tools-path.
49
+ """
50
+ # Update the user or workspace settings
51
+ if target == "user":
52
+ # Get the path to the user's settings.json file depending on the platform
53
+ if "win" in sys.platform:
54
+ settings_json = (
55
+ Path(os.environ.get("APPDATA")) / "Code" / "User" / "settings.json"
56
+ ) # pragma: no cover
57
+ elif "lin" in sys.platform:
58
+ settings_json = (
59
+ Path(os.environ.get("HOME")) / ".config" / "Code" / "User" / "settings.json"
60
+ )
61
+ elif target == "workspace":
62
+ # Get the current working directory
63
+ current_dir = Path.cwd()
64
+ # Get the path to the settings.json file based on the git root & .vscode folder
65
+ settings_json = current_dir / ".vscode" / "settings.json"
66
+
67
+ # Location where the stubs are installed -> .venv/Lib/site-packages, for example
68
+ stubs_location = (
69
+ Path(sysconfig.get_paths()["purelib"]) / "ansys" / "mechanical" / "stubs" / f"v{revision}"
70
+ )
71
+
72
+ # The settings to add to settings.json for autocomplete to work
73
+ settings_json_data = {
74
+ "python.autoComplete.extraPaths": [str(stubs_location)],
75
+ "python.analysis.extraPaths": [str(stubs_location)],
76
+ }
77
+ # Pretty print dictionary
78
+ pretty_dict = json.dumps(settings_json_data, indent=4)
79
+
80
+ print(f"Update {settings_json} with the following information:\n")
81
+
82
+ if target == "workspace":
83
+ print(
84
+ "Note: Please ensure the .vscode folder is in the root of your project or repository.\n"
85
+ )
86
+
87
+ print(pretty_dict)
88
+
89
+
90
+ def _cli_impl(
91
+ ide: str = "vscode",
92
+ target: str = "user",
93
+ revision: int = None,
94
+ ):
95
+ """Provide the user with the path to the settings.json file and IDE settings.
96
+
97
+ Parameters
98
+ ----------
99
+ ide: str
100
+ The IDE to set up autocomplete settings. By default, it's ``vscode``.
101
+ target: str
102
+ The type of settings to update. Either "user" or "workspace" in VS Code.
103
+ By default, it's ``user``.
104
+ revision: int
105
+ The Mechanical revision number. For example, "242".
106
+ If unspecified, it finds the default Mechanical version from ansys-tools-path.
107
+ """
108
+ # Check the IDE and raise an exception if it's not VS Code
109
+ if ide == "vscode":
110
+ return _vscode_impl(target, revision)
111
+ else:
112
+ raise Exception(f"{ide} is not supported at the moment.")
113
+
114
+
115
+ @click.command()
116
+ @click.help_option("--help", "-h")
117
+ @click.option(
118
+ "--ide",
119
+ default="vscode",
120
+ type=str,
121
+ help="The IDE being used.",
122
+ )
123
+ @click.option(
124
+ "--target",
125
+ default="user",
126
+ type=str,
127
+ help="The type of settings to update - either ``user`` or ``workspace`` settings.",
128
+ )
129
+ @click.option(
130
+ "--revision",
131
+ default=None,
132
+ type=int,
133
+ help='The Mechanical revision number, e.g. "242" or "241". If unspecified,\
134
+ it finds and uses the default version from ansys-tools-path.',
135
+ )
136
+ def cli(ide: str, target: str, revision: int) -> None:
137
+ """CLI tool to update settings.json files for autocomplete with ansys-mechanical-stubs.
138
+
139
+ Parameters
140
+ ----------
141
+ ide: str
142
+ The IDE to set up autocomplete settings. By default, it's ``vscode``.
143
+ target: str
144
+ The type of settings to update. Either "user" or "workspace" in VS Code.
145
+ By default, it's ``user``.
146
+ revision: int
147
+ The Mechanical revision number. For example, "242".
148
+ If unspecified, it finds the default Mechanical version from ansys-tools-path.
149
+
150
+ Usage
151
+ -----
152
+ The following example demonstrates the main use of this tool:
153
+
154
+ $ ansys-mechanical-ideconfig --ide vscode --location user --revision 242
155
+
156
+ """
157
+ exe = atp.get_mechanical_path(allow_input=False, version=revision)
158
+ version = atp.version_from_path("mechanical", exe)
159
+
160
+ return _cli_impl(
161
+ ide,
162
+ target,
163
+ version,
164
+ )
@@ -508,8 +508,8 @@ class Mechanical(object):
508
508
  return f"GRPC_{self._channel._channel._channel.target().decode()}"
509
509
  else:
510
510
  return f"GRPC_{self._channel._channel.target().decode()}"
511
- except Exception: # pragma: no cover
512
- pass
511
+ except Exception as e: # pragma: no cover
512
+ LOG.error(f"Error getting the Mechanical instance name: {str(e)}")
513
513
 
514
514
  return f"GRPC_instance_{id(self)}" # pragma: no cover
515
515
 
@@ -583,8 +583,8 @@ class LocalMechanicalPool:
583
583
  if instance_local:
584
584
  try:
585
585
  instance_local.exit()
586
- except: # pragma: no cover
587
- pass
586
+ except Exception as e: # pragma: no cover
587
+ LOG.error(f"Error while exiting instance {str(instance_local)}: {str(e)}")
588
588
  self._instances[index] = None
589
589
  LOG.debug(f"Exited instance: {str(instance_local)}")
590
590
 
@@ -54,7 +54,8 @@ async def _read_and_display(cmd, env, do_display: bool):
54
54
  }
55
55
  while tasks:
56
56
  done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
57
- assert done
57
+ if not done:
58
+ raise RuntimeError("Subprocess read failed: No tasks completed.")
58
59
  for future in done:
59
60
  buf, stream, display = tasks.pop(future)
60
61
  line = future.result()
@@ -67,7 +68,7 @@ async def _read_and_display(cmd, env, do_display: bool):
67
68
 
68
69
  # wait for the process to exit
69
70
  rc = await process.wait()
70
- return rc, b"".join(stdout), b"".join(stderr)
71
+ return rc, process, b"".join(stdout), b"".join(stderr)
71
72
 
72
73
 
73
74
  def _run(args, env, check=False, display=False):
@@ -77,13 +78,13 @@ def _run(args, env, check=False, display=False):
77
78
  else:
78
79
  loop = asyncio.get_event_loop()
79
80
  try:
80
- rc, *output = loop.run_until_complete(_read_and_display(args, env, display))
81
+ rc, process, *output = loop.run_until_complete(_read_and_display(args, env, display))
81
82
  if rc and check:
82
83
  sys.exit("child failed with '{}' exit code".format(rc))
83
84
  finally:
84
85
  if os.name == "nt":
85
86
  loop.close()
86
- return output
87
+ return process, output
87
88
 
88
89
 
89
90
  def _cli_impl(
@@ -1,23 +1,22 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ansys-mechanical-core
3
- Version: 0.11.7
3
+ Version: 0.11.8
4
4
  Summary: A python wrapper for Ansys Mechanical
5
5
  Keywords: pymechanical,mechanical,ansys,pyansys
6
6
  Author-email: "ANSYS, Inc." <pyansys.core@ansys.com>
7
7
  Maintainer-email: "ANSYS, Inc." <pyansys.core@ansys.com>
8
- Requires-Python: >=3.9,<4.0
8
+ Requires-Python: >=3.10,<4.0
9
9
  Description-Content-Type: text/x-rst
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Science/Research
12
12
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: License :: OSI Approved :: MIT License
18
17
  Classifier: Operating System :: OS Independent
19
18
  Requires-Dist: ansys-api-mechanical==0.1.2
20
- Requires-Dist: ansys-mechanical-env==0.1.7
19
+ Requires-Dist: ansys-mechanical-env==0.1.8
21
20
  Requires-Dist: ansys-platform-instancemanagement>=1.0.1
22
21
  Requires-Dist: ansys-pythonnet>=3.1.0rc2
23
22
  Requires-Dist: ansys-tools-path>=0.3.1
@@ -26,35 +25,37 @@ Requires-Dist: click>=8.1.3
26
25
  Requires-Dist: clr-loader==0.2.6
27
26
  Requires-Dist: grpcio>=1.30.0
28
27
  Requires-Dist: protobuf>=3.12.2,<6
28
+ Requires-Dist: psutil==6.0.0
29
29
  Requires-Dist: tqdm>=4.45.0
30
- Requires-Dist: sphinx==8.0.2 ; extra == "doc"
31
- Requires-Dist: ansys-sphinx-theme[autoapi]==1.0.7 ; extra == "doc"
32
- Requires-Dist: grpcio==1.66.0 ; extra == "doc"
30
+ Requires-Dist: sphinx==8.1.3 ; extra == "doc"
31
+ Requires-Dist: ansys-sphinx-theme[autoapi]==1.1.4 ; extra == "doc"
32
+ Requires-Dist: grpcio==1.66.2 ; extra == "doc"
33
33
  Requires-Dist: imageio-ffmpeg==0.5.1 ; extra == "doc"
34
- Requires-Dist: imageio==2.35.1 ; extra == "doc"
34
+ Requires-Dist: imageio==2.36.0 ; extra == "doc"
35
35
  Requires-Dist: jupyter_sphinx==0.5.3 ; extra == "doc"
36
36
  Requires-Dist: jupyterlab>=3.2.8 ; extra == "doc"
37
37
  Requires-Dist: matplotlib==3.9.2 ; extra == "doc"
38
- Requires-Dist: numpy==2.1.0 ; extra == "doc"
38
+ Requires-Dist: numpy==2.1.2 ; extra == "doc"
39
39
  Requires-Dist: numpydoc==1.8.0 ; extra == "doc"
40
- Requires-Dist: pandas==2.2.2 ; extra == "doc"
41
- Requires-Dist: panel==1.4.5 ; extra == "doc"
42
- Requires-Dist: plotly==5.23.0 ; extra == "doc"
43
- Requires-Dist: pypandoc==1.13 ; extra == "doc"
40
+ Requires-Dist: pandas==2.2.3 ; extra == "doc"
41
+ Requires-Dist: panel==1.5.2 ; extra == "doc"
42
+ Requires-Dist: plotly==5.24.1 ; extra == "doc"
43
+ Requires-Dist: pypandoc==1.14 ; extra == "doc"
44
44
  Requires-Dist: pytest-sphinx==0.6.3 ; extra == "doc"
45
45
  Requires-Dist: pythreejs==2.4.2 ; extra == "doc"
46
46
  Requires-Dist: pyvista>=0.39.1 ; extra == "doc"
47
- Requires-Dist: sphinx-autobuild==2024.4.16 ; extra == "doc"
48
- Requires-Dist: sphinx-autodoc-typehints==2.2.3 ; extra == "doc"
47
+ Requires-Dist: sphinx-autobuild==2024.10.3 ; extra == "doc"
48
+ Requires-Dist: sphinx-autodoc-typehints==2.5.0 ; extra == "doc"
49
49
  Requires-Dist: sphinx-copybutton==0.5.2 ; extra == "doc"
50
50
  Requires-Dist: sphinx_design==0.6.1 ; extra == "doc"
51
- Requires-Dist: sphinx-gallery==0.17.1 ; extra == "doc"
51
+ Requires-Dist: sphinx-gallery==0.18.0 ; extra == "doc"
52
52
  Requires-Dist: sphinx-notfound-page==1.0.4 ; extra == "doc"
53
53
  Requires-Dist: sphinxcontrib-websupport==2.0.0 ; extra == "doc"
54
54
  Requires-Dist: sphinxemoji==0.3.1 ; extra == "doc"
55
- Requires-Dist: pytest==8.3.2 ; extra == "tests"
55
+ Requires-Dist: pytest==8.3.3 ; extra == "tests"
56
56
  Requires-Dist: pytest-cov==5.0.0 ; extra == "tests"
57
- Requires-Dist: pytest-print==1.0.0 ; extra == "tests"
57
+ Requires-Dist: pytest-print==1.0.2 ; extra == "tests"
58
+ Requires-Dist: psutil==6.0.0 ; extra == "tests"
58
59
  Requires-Dist: ansys-tools-visualization-interface>=0.2.6 ; extra == "viz"
59
60
  Requires-Dist: usd-core==24.8 ; extra == "viz"
60
61
  Project-URL: Changelog, https://mechanical.docs.pyansys.com/version/stable/changelog.html
@@ -2,26 +2,29 @@ ansys/mechanical/core/__init__.py,sha256=91oPPatmqRyry_kzusIq522MQNiBgN5Of54zDMI
2
2
  ansys/mechanical/core/_version.py,sha256=V2aPQlSX4bCe1N1hLIkQaed84WN4s9wl6Q7890ZihNU,1744
3
3
  ansys/mechanical/core/errors.py,sha256=oGaBH-QZxen3YV3IChAFv8bwW5rK_IXTYgDqbX5lp1E,4508
4
4
  ansys/mechanical/core/feature_flags.py,sha256=L88vHrI2lRjZPPUTW5sqcdloeK3Ouh8vt1VPfZLs5Wc,2032
5
+ ansys/mechanical/core/ide_config.py,sha256=44xst5N67ekVq8GhFMhetJDLJOTJcPJfu1mSaZdxNzI,5619
5
6
  ansys/mechanical/core/launcher.py,sha256=-I1hoscyc5HY8EHL07gPUeoeRgUJaW8agdQWOpqK30A,6659
6
7
  ansys/mechanical/core/logging.py,sha256=wQ8QwKd2k0R6SkN7cv2nHAO7V5-BrElEOespDNMpSLo,24554
7
- ansys/mechanical/core/mechanical.py,sha256=rh8vClBiUZRxEsI3asYbvIznGrwNZiUM64MRt8mSpPU,79926
8
+ ansys/mechanical/core/mechanical.py,sha256=MOGEMYgT9Mz0GFnTbixw-5SlfDEfZ-f4iDJG8mtXAVE,79993
8
9
  ansys/mechanical/core/misc.py,sha256=edm2UnklbooYW_hQUgk4n_UFCtlSGAVYJmC2gag74vw,5370
9
- ansys/mechanical/core/pool.py,sha256=HzU64Ew5QgoKeoZkywdmS_lmuGSCwe-PvucI5y1Hqa0,26429
10
- ansys/mechanical/core/run.py,sha256=ScB-Ne8KkLuA0EQtquUa8JKFsA8Ert8nUUtxHTlQR4A,9814
10
+ ansys/mechanical/core/pool.py,sha256=jC3quT7fsGMkfrPJ-nZhiigYmzXlj80L9fH9emE7Rrw,26514
11
+ ansys/mechanical/core/run.py,sha256=KgSL2XEyCxK7iq_XVDNEv6fx7SN56RA-ihNg2dLyuZc,9920
11
12
  ansys/mechanical/core/embedding/__init__.py,sha256=y0yp3dnBW2oj9Jh_L_qfZstAbpON974EMmpV9w3kT3g,1356
12
- ansys/mechanical/core/embedding/addins.py,sha256=yRG8CT1f1MgQj0y_mP3ISePVp7t9erw0o5Sbn1tFNkc,2370
13
- ansys/mechanical/core/embedding/app.py,sha256=hnP-eAFFekfHPuHxHc7wJraDa1DO7s2J65dWtKzRJ9U,16195
13
+ ansys/mechanical/core/embedding/addins.py,sha256=2-de-sIOWjO5MCKdBHC2LFxTItr1DUztABIONTQhiWc,2167
14
+ ansys/mechanical/core/embedding/app.py,sha256=Bc3OlcZHzLDcmmfiDiwXRY_SJ6nSU3eIowm2Db3Xg_Y,17033
14
15
  ansys/mechanical/core/embedding/app_libraries.py,sha256=RiTO23AzjssAylIH2DaTa6mcJmxhfrlHW-yYvHpIkt0,2923
15
16
  ansys/mechanical/core/embedding/appdata.py,sha256=krcmcgHhraHIlORFr43QvUXlAiXg231g_2iOIxkW_aQ,4223
16
17
  ansys/mechanical/core/embedding/background.py,sha256=ICm87gO6CDA4Ot_6Yf3-8YPNUEa3PHWieOECXeUfZmM,3650
18
+ ansys/mechanical/core/embedding/cleanup_gui.py,sha256=GvWX2ylGBb5k1Hgz9vUywXNgWpDVwZ6L2M4gaOXyxl4,2354
17
19
  ansys/mechanical/core/embedding/enum_importer.py,sha256=3REw7SI_WmfPuzD0i9mdC7k53S-1jxhowqSxjzw7UGk,1543
18
20
  ansys/mechanical/core/embedding/imports.py,sha256=FcpePAi867YCuCH_lJotrLzYc1MW5VSAaLpYz7RejcA,4287
19
- ansys/mechanical/core/embedding/initializer.py,sha256=-LNJqDlxemHCggM5Ud5X7KDVpW4USoxc0djKcVAsdW0,6366
21
+ ansys/mechanical/core/embedding/initializer.py,sha256=I09XzY7QFhCvaHlrLDrx9U-8Bib9vS9yvFQnWOscSzw,8025
20
22
  ansys/mechanical/core/embedding/loader.py,sha256=UgWN7C4hGMWiHhoMUdRKRyWaOwcsgw5auoW1ZqLtZDs,2503
21
23
  ansys/mechanical/core/embedding/poster.py,sha256=V0-cm229HgpOgcYXa0bnz0U5BDGw8_AVE6LKXyPCEjo,2107
22
24
  ansys/mechanical/core/embedding/resolver.py,sha256=95jUvZhNFEJBlbAbclzpK1Wgk51KsnYOKa5HvC7Oeco,1878
23
25
  ansys/mechanical/core/embedding/runtime.py,sha256=zDxwKDTc23cR_kc63M9u4zDWVoJ2efEtF3djHGwicG4,2285
24
26
  ansys/mechanical/core/embedding/shims.py,sha256=IJHhUmfsCtYEUFmuf2LGgozTiu03D0OZn1Qq1nCxXiI,1732
27
+ ansys/mechanical/core/embedding/ui.py,sha256=4XCl9ay30D1sOzLyHFwG_bK7IboYBWNiCrbTM3cYhnc,8331
25
28
  ansys/mechanical/core/embedding/utils.py,sha256=UObL4XBvx19aAYV8iVM4eCQR9vfqNSDsdwqkb1VFwTY,1900
26
29
  ansys/mechanical/core/embedding/warnings.py,sha256=igXfTCkDb8IDQqYP02Cynsqr7ewnueR12Dr0zpku7Kw,3071
27
30
  ansys/mechanical/core/embedding/logger/__init__.py,sha256=XgC05i7r-YzotLtcZ5_rGtA0jDKzeuZiDB88d2pIL7o,7986
@@ -32,11 +35,11 @@ ansys/mechanical/core/embedding/logger/windows_api.py,sha256=OCJ-SJEY7EjigZiW6H5
32
35
  ansys/mechanical/core/embedding/viz/__init__.py,sha256=xgpBdf3yfEq3sn0bNewLwtje-SCH6vVWEmHfCdh6078,1206
33
36
  ansys/mechanical/core/embedding/viz/embedding_plotter.py,sha256=ausbFhezwmLCGhu61JZJDM_uxwpRRuM-XWw9mk4i0GQ,3689
34
37
  ansys/mechanical/core/embedding/viz/usd_converter.py,sha256=feDq2KrZhYL-RR1miECQL-y0VNDhnZQ9Wke5UOkYmp4,5329
35
- ansys/mechanical/core/embedding/viz/utils.py,sha256=NWc18HLshIpO7wgZpwfduKG5wReaYtVtJPChOosKXLU,3638
38
+ ansys/mechanical/core/embedding/viz/utils.py,sha256=FuGDh7a5mUqs2UZOaXZLD0vONdmDXl5JfDRilIVbjds,3660
36
39
  ansys/mechanical/core/examples/__init__.py,sha256=A1iS8nknTU1ylafHZpYC9LQJ0sY83x8m1cDXsgvFOBo,1267
37
40
  ansys/mechanical/core/examples/downloads.py,sha256=lJ2SrX9Qs0t2FSmMwUBB4UgSpk6Qb2-coY1PLlzPzHU,4144
38
- ansys_mechanical_core-0.11.7.dist-info/entry_points.txt,sha256=wRXPv0eRXSmodweJHjF_Y3x5wmeSmjWcY3Bo6B4tAiM,66
39
- ansys_mechanical_core-0.11.7.dist-info/LICENSE,sha256=gBJ2GQ6oDJwAWxcxmjx_0uXc-N0P4sHhA7BXsdPTfco,1098
40
- ansys_mechanical_core-0.11.7.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
41
- ansys_mechanical_core-0.11.7.dist-info/METADATA,sha256=C9roYcJNV8NVEN4br1yps8J_0PR3W_373Y1xolXlFjM,9783
42
- ansys_mechanical_core-0.11.7.dist-info/RECORD,,
41
+ ansys_mechanical_core-0.11.8.dist-info/entry_points.txt,sha256=tErx6bIM27HGgwyM6ryyTUTw30Ab2F9J3FFkX2TPkhI,130
42
+ ansys_mechanical_core-0.11.8.dist-info/LICENSE,sha256=gBJ2GQ6oDJwAWxcxmjx_0uXc-N0P4sHhA7BXsdPTfco,1098
43
+ ansys_mechanical_core-0.11.8.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
44
+ ansys_mechanical_core-0.11.8.dist-info/METADATA,sha256=WpLgvz8EIi3vt36XykzYWPtLSs8-Aj4etwPrqg4nJ8Q,9811
45
+ ansys_mechanical_core-0.11.8.dist-info/RECORD,,
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
2
  ansys-mechanical=ansys.mechanical.core.run:cli
3
+ ansys-mechanical-ideconfig=ansys.mechanical.core.ide_config:cli
3
4