ansys-mechanical-core 0.11.14__py3-none-any.whl → 0.11.16__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.
- ansys/mechanical/core/__init__.py +3 -3
- ansys/mechanical/core/embedding/app.py +26 -12
- ansys/mechanical/core/embedding/appdata.py +26 -22
- ansys/mechanical/core/embedding/enum_importer.py +6 -0
- ansys/mechanical/core/embedding/{viz → graphics}/embedding_plotter.py +1 -1
- ansys/mechanical/core/embedding/initializer.py +70 -0
- ansys/mechanical/core/embedding/messages.py +27 -22
- ansys/mechanical/core/embedding/resolver.py +1 -1
- ansys/mechanical/core/embedding/rpc/__init__.py +3 -7
- ansys/mechanical/core/embedding/rpc/client.py +19 -5
- ansys/mechanical/core/embedding/rpc/default_server.py +131 -0
- ansys/mechanical/core/embedding/rpc/server.py +146 -158
- ansys/mechanical/core/embedding/rpc/utils.py +18 -0
- ansys/mechanical/core/embedding/runtime.py +6 -0
- ansys/mechanical/core/ide_config.py +1 -0
- ansys/mechanical/core/mechanical.py +24 -12
- {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/METADATA +15 -12
- {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/RECORD +24 -23
- {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/WHEEL +1 -1
- /ansys/mechanical/core/embedding/{viz → graphics}/__init__.py +0 -0
- /ansys/mechanical/core/embedding/{viz → graphics}/usd_converter.py +0 -0
- /ansys/mechanical/core/embedding/{viz → graphics}/utils.py +0 -0
- {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/entry_points.txt +0 -0
- {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/licenses/LICENSE +0 -0
@@ -46,7 +46,10 @@ LOG = Logger(level=logging.ERROR, to_file=False, to_stdout=True)
|
|
46
46
|
|
47
47
|
|
48
48
|
from ansys.mechanical.core._version import __version__
|
49
|
+
|
50
|
+
# import few classes / functions
|
49
51
|
from ansys.mechanical.core.mechanical import (
|
52
|
+
Mechanical,
|
50
53
|
change_default_mechanical_path,
|
51
54
|
close_all_local_instances,
|
52
55
|
connect_to_mechanical,
|
@@ -54,9 +57,6 @@ from ansys.mechanical.core.mechanical import (
|
|
54
57
|
launch_mechanical,
|
55
58
|
)
|
56
59
|
|
57
|
-
# import few classes / functions
|
58
|
-
from ansys.mechanical.core.mechanical import Mechanical as Mechanical
|
59
|
-
|
60
60
|
try:
|
61
61
|
from ansys.mechanical.core.embedding import App, global_variables
|
62
62
|
|
@@ -47,10 +47,10 @@ if typing.TYPE_CHECKING:
|
|
47
47
|
try:
|
48
48
|
import ansys.tools.visualization_interface # noqa: F401
|
49
49
|
|
50
|
-
|
50
|
+
HAS_ANSYS_GRAPHICS = True
|
51
51
|
"""Whether or not PyVista exists."""
|
52
52
|
except ImportError:
|
53
|
-
|
53
|
+
HAS_ANSYS_GRAPHICS = False
|
54
54
|
|
55
55
|
|
56
56
|
def _get_default_addin_configuration() -> AddinConfiguration:
|
@@ -186,10 +186,22 @@ class App:
|
|
186
186
|
|
187
187
|
self.log_info("Starting Mechanical Application")
|
188
188
|
|
189
|
+
# Get the globals dictionary from kwargs
|
190
|
+
globals = kwargs.get("globals")
|
191
|
+
|
192
|
+
# If the building gallery flag is set, we need to share the instance
|
193
|
+
# This can apply to running the `make -C doc html` command
|
189
194
|
if BUILDING_GALLERY:
|
190
195
|
if len(INSTANCES) != 0:
|
196
|
+
# Get the first instance of the app
|
191
197
|
instance: App = INSTANCES[0]
|
198
|
+
# Point to the same underlying application object
|
192
199
|
instance._share(self)
|
200
|
+
# Update the globals if provided in kwargs
|
201
|
+
if globals:
|
202
|
+
# The next line is covered by test_globals_kwarg_building_gallery
|
203
|
+
instance.update_globals(globals) # pragma: nocover
|
204
|
+
# Open the mechdb file if provided
|
193
205
|
if db_file is not None:
|
194
206
|
self.open(db_file)
|
195
207
|
return
|
@@ -212,7 +224,6 @@ class App:
|
|
212
224
|
new_profile_name = f"PyMechanical-{os.getpid()}"
|
213
225
|
profile = UniqueUserProfile(new_profile_name, copy_profile=copy_profile)
|
214
226
|
profile.update_environment(os.environ)
|
215
|
-
atexit.register(_cleanup_private_appdata, profile)
|
216
227
|
|
217
228
|
runtime.initialize(self._version)
|
218
229
|
self._app = _start_application(configuration, self._version, db_file)
|
@@ -222,11 +233,14 @@ class App:
|
|
222
233
|
self._disposed = False
|
223
234
|
atexit.register(_dispose_embedded_app, INSTANCES)
|
224
235
|
INSTANCES.append(self)
|
236
|
+
|
237
|
+
# Clean up the private appdata directory on exit if private_appdata is True
|
238
|
+
if private_appdata:
|
239
|
+
atexit.register(_cleanup_private_appdata, profile)
|
240
|
+
|
225
241
|
self._updated_scopes: typing.List[typing.Dict[str, typing.Any]] = []
|
226
242
|
self._subscribe()
|
227
243
|
self._messages = None
|
228
|
-
|
229
|
-
globals = kwargs.get("globals")
|
230
244
|
if globals:
|
231
245
|
self.update_globals(globals)
|
232
246
|
|
@@ -391,27 +405,27 @@ This may corrupt the project file.",
|
|
391
405
|
|
392
406
|
def plotter(self) -> None:
|
393
407
|
"""Return ``ansys.tools.visualization_interface.Plotter`` object."""
|
394
|
-
if not
|
395
|
-
|
396
|
-
"
|
408
|
+
if not HAS_ANSYS_GRAPHICS:
|
409
|
+
LOG.warning(
|
410
|
+
"Use ``pip install ansys-mechanical-core[graphics]`` to enable this option."
|
397
411
|
)
|
398
412
|
return
|
399
413
|
|
400
414
|
if self.version < 242:
|
401
|
-
|
415
|
+
LOG.warning("Plotting is only supported with version 2024R2 and later!")
|
402
416
|
return
|
403
417
|
|
404
418
|
# TODO Check if anything loaded inside app or else show warning and return
|
405
419
|
|
406
|
-
from ansys.mechanical.core.embedding.
|
420
|
+
from ansys.mechanical.core.embedding.graphics.embedding_plotter import to_plotter
|
407
421
|
|
408
422
|
return to_plotter(self)
|
409
423
|
|
410
424
|
def plot(self) -> None:
|
411
425
|
"""Visualize the model in 3d.
|
412
426
|
|
413
|
-
Requires installation using the
|
414
|
-
pip install ansys-mechanical-core[
|
427
|
+
Requires installation using the graphics option. E.g.
|
428
|
+
pip install ansys-mechanical-core[graphics]
|
415
429
|
|
416
430
|
Examples
|
417
431
|
--------
|
@@ -22,7 +22,7 @@
|
|
22
22
|
|
23
23
|
"""Temporary Appdata for Ansys Mechanical."""
|
24
24
|
|
25
|
-
import
|
25
|
+
from pathlib import Path
|
26
26
|
import shutil
|
27
27
|
import sys
|
28
28
|
import warnings
|
@@ -33,8 +33,8 @@ class UniqueUserProfile:
|
|
33
33
|
|
34
34
|
def __init__(self, profile_name: str, copy_profile: bool = True, dry_run: bool = False):
|
35
35
|
"""Initialize UniqueUserProfile class."""
|
36
|
-
self._default_profile =
|
37
|
-
self._location =
|
36
|
+
self._default_profile = Path("~").expanduser()
|
37
|
+
self._location = self._default_profile / "PyMechanical-AppData" / profile_name
|
38
38
|
self._dry_run = dry_run
|
39
39
|
self.copy_profile = copy_profile
|
40
40
|
self.initialize()
|
@@ -58,15 +58,14 @@ class UniqueUserProfile:
|
|
58
58
|
"""Cleanup unique user profile."""
|
59
59
|
if self._dry_run:
|
60
60
|
return
|
61
|
-
text = "The `private_appdata` option was used, but the following files were not removed: "
|
62
|
-
message = []
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
message.append(f"{text}{path}")
|
67
|
-
warnings.warn(message[0])
|
62
|
+
# Remove the appdata directory if it exists
|
63
|
+
shutil.rmtree(self.location, ignore_errors=True)
|
68
64
|
|
69
|
-
|
65
|
+
if self.location.is_dir():
|
66
|
+
warnings.warn(
|
67
|
+
f"The `private appdata` option was used, but {self.location} was not removed"
|
68
|
+
)
|
70
69
|
|
71
70
|
@property
|
72
71
|
def location(self) -> str:
|
@@ -77,28 +76,32 @@ class UniqueUserProfile:
|
|
77
76
|
"""Set environment variables for new user profile."""
|
78
77
|
home = self.location
|
79
78
|
if "win" in sys.platform:
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
env["
|
84
|
-
env["
|
79
|
+
appdata_dir = home / "AppData"
|
80
|
+
appdata_local_temp = str(appdata_dir / "Local" / "Temp")
|
81
|
+
|
82
|
+
env["USERPROFILE"] = str(home)
|
83
|
+
env["APPDATA"] = str(appdata_dir / "Roaming")
|
84
|
+
env["LOCALAPPDATA"] = str(appdata_dir / "Local")
|
85
|
+
env["TMP"] = appdata_local_temp
|
86
|
+
env["TEMP"] = appdata_local_temp
|
85
87
|
elif "lin" in sys.platform:
|
86
|
-
env["HOME"] = home
|
88
|
+
env["HOME"] = str(home)
|
87
89
|
|
88
90
|
def exists(self) -> bool:
|
89
91
|
"""Check if unique profile name already exists."""
|
90
|
-
return
|
92
|
+
return self.location.exists()
|
91
93
|
|
92
94
|
def mkdirs(self) -> None:
|
93
95
|
"""Create a unique user profile & set up the directory tree."""
|
94
|
-
|
96
|
+
self.location.mkdir(parents=True, exist_ok=True)
|
95
97
|
if "win" in sys.platform:
|
96
98
|
locs = ["AppData/Roaming", "AppData/Local", "Documents"]
|
97
99
|
elif "lin" in sys.platform:
|
98
100
|
locs = [".config", "temp/reports"]
|
99
101
|
|
100
102
|
for loc in locs:
|
101
|
-
|
103
|
+
dir_name = self.location / loc
|
104
|
+
dir_name.mkdir(parents=True, exist_ok=True)
|
102
105
|
|
103
106
|
def copy_profiles(self) -> None:
|
104
107
|
"""Copy current user directories into a new user profile."""
|
@@ -107,6 +110,7 @@ class UniqueUserProfile:
|
|
107
110
|
elif "lin" in sys.platform:
|
108
111
|
locs = [".mw/Application Data/Ansys", ".config/Ansys"]
|
109
112
|
for loc in locs:
|
110
|
-
|
111
|
-
|
112
|
-
)
|
113
|
+
default_profile_loc = self._default_profile / loc
|
114
|
+
temp_appdata_loc = self.location / loc
|
115
|
+
if default_profile_loc.exists():
|
116
|
+
shutil.copytree(default_profile_loc, temp_appdata_loc)
|
@@ -25,6 +25,12 @@
|
|
25
25
|
A useful subset of what is imported by
|
26
26
|
Ansys Inc/v{NNN}/ACT/apis/Mechanical.py
|
27
27
|
"""
|
28
|
+
|
29
|
+
from ansys.mechanical.core.embedding.app import is_initialized
|
30
|
+
|
31
|
+
if not is_initialized():
|
32
|
+
raise Exception("Enums cannot be imported until the embedded app is initialized.")
|
33
|
+
|
28
34
|
import clr
|
29
35
|
|
30
36
|
clr.AddReference("Ansys.Mechanical.DataModel")
|
@@ -82,7 +82,7 @@ def to_plotter(app: "ansys.mechanical.core.embedding.App"):
|
|
82
82
|
if np_coordinates is None or np_indices is None:
|
83
83
|
continue
|
84
84
|
pv_transform = _transform_to_pyvista(scenegraph_node.Transform)
|
85
|
-
polydata = pv.PolyData(np_coordinates, np_indices).transform(pv_transform)
|
85
|
+
polydata = pv.PolyData(np_coordinates, np_indices).transform(pv_transform, inplace=True)
|
86
86
|
color = pv.Color(bgr_to_rgb_tuple(body.Color))
|
87
87
|
plotter.plot(polydata, color=color, smooth_shading=True)
|
88
88
|
return plotter
|
@@ -112,6 +112,74 @@ def __check_python_interpreter_architecture() -> None:
|
|
112
112
|
raise Exception("Mechanical Embedding requires a 64-bit Python environment.")
|
113
113
|
|
114
114
|
|
115
|
+
def __windows_store_workaround(version: int) -> None:
|
116
|
+
"""Workaround for Windows store.
|
117
|
+
|
118
|
+
See https://github.com/ansys/pymechanical/issues/1136
|
119
|
+
|
120
|
+
Windows store Python uses the Win32 API SetDefaultDllDirectories
|
121
|
+
so that the PATH environment variable isn't scanned for any DLL
|
122
|
+
dependency.
|
123
|
+
|
124
|
+
PyMechanical loads the embedding library which automatically sets
|
125
|
+
these Paths, but this uses the PATH environment variable which doesn't
|
126
|
+
work for Windows store Python.
|
127
|
+
|
128
|
+
We provide a workaround for versions 2024R2 and 2025R1 that sets
|
129
|
+
these paths using `os.add_dll_directory`.
|
130
|
+
|
131
|
+
Note: This workaround does not include DLL paths used in FSI
|
132
|
+
mapping workflows.
|
133
|
+
|
134
|
+
Parameters
|
135
|
+
----------
|
136
|
+
version : int
|
137
|
+
The version of Mechanical to set the DLL paths for.
|
138
|
+
"""
|
139
|
+
# Nothing to do on Linux
|
140
|
+
if os.name != "nt":
|
141
|
+
return
|
142
|
+
|
143
|
+
# Nothing to do if it isn't a Windows store application
|
144
|
+
if r"Microsoft\WindowsApps" not in sys.executable:
|
145
|
+
return
|
146
|
+
|
147
|
+
# Get the AWP_ROOT environment variable for the specified version
|
148
|
+
awp_root = Path(os.environ[f"AWP_ROOT{version}"])
|
149
|
+
# Set paths to the aisol and framework DLLs
|
150
|
+
paths = [
|
151
|
+
awp_root / "aisol" / "bin" / "winx64",
|
152
|
+
awp_root / "Framework" / "bin" / "Win64",
|
153
|
+
]
|
154
|
+
# Set the path to the tp directory within the AWP_ROOTXYZ directory
|
155
|
+
awp_root_tp = awp_root / "tp"
|
156
|
+
# Add paths to the IntelCompiler, IntelMKL, HDF5, and Qt DLLs for 2024R2 and 2025R1
|
157
|
+
if version == 242:
|
158
|
+
paths.extend(
|
159
|
+
[
|
160
|
+
awp_root_tp / "IntelCompiler" / "2023.1.0" / "winx64",
|
161
|
+
awp_root_tp / "IntelMKL" / "2023.1.0" / "winx64",
|
162
|
+
awp_root_tp / "hdf5" / "1.12.2" / "winx64",
|
163
|
+
awp_root_tp / "qt" / "5.15.16" / "winx64" / "bin",
|
164
|
+
]
|
165
|
+
)
|
166
|
+
elif version == 251:
|
167
|
+
paths.extend(
|
168
|
+
[
|
169
|
+
awp_root_tp / "IntelCompiler" / "2023.1.0" / "winx64",
|
170
|
+
awp_root_tp / "IntelMKL" / "2023.1.0" / "winx64",
|
171
|
+
awp_root_tp / "hdf5" / "1.12.2" / "winx64",
|
172
|
+
awp_root_tp / "qt" / "5.15.17" / "winx64" / "bin",
|
173
|
+
]
|
174
|
+
)
|
175
|
+
else:
|
176
|
+
return
|
177
|
+
|
178
|
+
# Add each path to the DLL search path
|
179
|
+
for path in paths:
|
180
|
+
os.add_dll_directory(str(path))
|
181
|
+
|
182
|
+
|
115
183
|
def __set_environment(version: int) -> None:
|
116
184
|
"""Set environment variables to configure embedding."""
|
117
185
|
if os.name == "nt": # pragma: no cover
|
@@ -188,6 +256,8 @@ def initialize(version: int = None):
|
|
188
256
|
|
189
257
|
__set_environment(version)
|
190
258
|
|
259
|
+
__windows_store_workaround(version)
|
260
|
+
|
191
261
|
__check_loaded_libs(version)
|
192
262
|
|
193
263
|
__workaround_material_server(version)
|
@@ -142,7 +142,31 @@ class MessageManager:
|
|
142
142
|
_msg = self._messages[index]
|
143
143
|
self._messages.Remove(_msg)
|
144
144
|
|
145
|
-
def
|
145
|
+
def _show_string(self, filter: str = "Severity;DisplayString") -> str:
|
146
|
+
if self._messages.Count == 0:
|
147
|
+
return "No messages to display."
|
148
|
+
|
149
|
+
if filter == "*":
|
150
|
+
selected_columns = [
|
151
|
+
"TimeStamp",
|
152
|
+
"Severity",
|
153
|
+
"DisplayString",
|
154
|
+
"Source",
|
155
|
+
"StringID",
|
156
|
+
"Location",
|
157
|
+
"RelatedObjects",
|
158
|
+
]
|
159
|
+
else:
|
160
|
+
selected_columns = [col.strip() for col in filter.split(";")]
|
161
|
+
|
162
|
+
lines = []
|
163
|
+
for msg in self._messages:
|
164
|
+
for key in selected_columns:
|
165
|
+
line = f"{key}: {getattr(msg, key, 'Specified attribute not found.')}"
|
166
|
+
lines.append(line)
|
167
|
+
return "\n".join(lines)
|
168
|
+
|
169
|
+
def show(self, filter="Severity;DisplayString") -> None:
|
146
170
|
"""Print all messages with full details.
|
147
171
|
|
148
172
|
Parameters
|
@@ -163,27 +187,8 @@ class MessageManager:
|
|
163
187
|
... severity: info
|
164
188
|
... message: Sample message.
|
165
189
|
"""
|
166
|
-
|
167
|
-
|
168
|
-
return
|
169
|
-
|
170
|
-
if filter == "*":
|
171
|
-
selected_columns = [
|
172
|
-
"TimeStamp",
|
173
|
-
"Severity",
|
174
|
-
"DisplayString",
|
175
|
-
"Source",
|
176
|
-
"StringID",
|
177
|
-
"Location",
|
178
|
-
"RelatedObjects",
|
179
|
-
]
|
180
|
-
else:
|
181
|
-
selected_columns = [col.strip() for col in filter.split(";")]
|
182
|
-
|
183
|
-
for msg in self._messages:
|
184
|
-
for key in selected_columns:
|
185
|
-
print(f"{key}: {getattr(msg, key, 'Specified attribute not found.')}")
|
186
|
-
print()
|
190
|
+
show_string = self._show_string(filter)
|
191
|
+
print(show_string)
|
187
192
|
|
188
193
|
def clear(self):
|
189
194
|
"""Clear all messages."""
|
@@ -41,7 +41,7 @@ def resolve(version):
|
|
41
41
|
resolve_handler = assembly_resolver.MechanicalResolveEventHandler
|
42
42
|
System.AppDomain.CurrentDomain.AssemblyResolve += resolve_handler
|
43
43
|
except AttributeError:
|
44
|
-
error_msg =
|
44
|
+
error_msg = """Unable to resolve Mechanical assemblies. Please ensure the following:
|
45
45
|
1. Mechanical is installed.
|
46
46
|
2. A folder with the name "Ansys" does not exist in the same directory as the script being run.
|
47
47
|
"""
|
@@ -22,15 +22,11 @@
|
|
22
22
|
|
23
23
|
"""RPC and Mechanical service implementation."""
|
24
24
|
from .client import Client
|
25
|
-
|
26
|
-
# todo - provide an implementation of Server (RemoteMechancial) that installs the below
|
27
|
-
# from .default_server import RemoteMechanical
|
28
|
-
# and remove them from this import statement
|
29
|
-
# todo - combine Server and MechanicalService
|
25
|
+
from .default_server import DefaultServiceMethods, MechanicalDefaultServer
|
30
26
|
from .server import (
|
31
|
-
DefaultServiceMethods,
|
32
|
-
MechanicalDefaultServer,
|
33
27
|
MechanicalEmbeddedServer,
|
34
28
|
MechanicalService,
|
35
29
|
)
|
36
30
|
from .utils import get_remote_methods, remote_method
|
31
|
+
|
32
|
+
# todo - combine Server and MechanicalService
|
@@ -27,14 +27,16 @@ import time
|
|
27
27
|
|
28
28
|
import rpyc
|
29
29
|
|
30
|
-
from ansys.mechanical.core.embedding.rpc.
|
30
|
+
from ansys.mechanical.core.embedding.rpc.utils import PYMECHANICAL_DEFAULT_RPC_PORT
|
31
31
|
from ansys.mechanical.core.mechanical import DEFAULT_CHUNK_SIZE
|
32
32
|
|
33
33
|
|
34
34
|
class Client:
|
35
35
|
"""Client for connecting to Mechanical services."""
|
36
36
|
|
37
|
-
def __init__(
|
37
|
+
def __init__(
|
38
|
+
self, host: str, port: int, timeout: float = 120.0, cleanup_on_exit=True, process=None
|
39
|
+
):
|
38
40
|
"""Initialize the client.
|
39
41
|
|
40
42
|
Parameters
|
@@ -48,6 +50,8 @@ class Client:
|
|
48
50
|
timeout : float, optional
|
49
51
|
Maximum allowable time for connecting to the Mechanical server.
|
50
52
|
The default is ``60.0``.
|
53
|
+
process: subprocess.Popen, optional
|
54
|
+
The process object that was connected to
|
51
55
|
|
52
56
|
"""
|
53
57
|
if host is None:
|
@@ -56,11 +60,14 @@ class Client:
|
|
56
60
|
if port is None:
|
57
61
|
port = PYMECHANICAL_DEFAULT_RPC_PORT
|
58
62
|
self.port = port
|
63
|
+
self._process = process
|
59
64
|
self.timeout = timeout
|
60
65
|
self.connection = None
|
61
66
|
self.root = None
|
62
67
|
self._connect()
|
63
68
|
self._cleanup_on_exit = cleanup_on_exit
|
69
|
+
self._error_type = Exception
|
70
|
+
self._has_exited = False
|
64
71
|
|
65
72
|
def __getattr__(self, attr):
|
66
73
|
"""Get attribute from the root object."""
|
@@ -114,6 +121,7 @@ class Client:
|
|
114
121
|
|
115
122
|
def close(self):
|
116
123
|
"""Close the connection."""
|
124
|
+
print("Closing the connection")
|
117
125
|
self.connection.close()
|
118
126
|
print(f"Connection to {self.host}:{self.port} closed")
|
119
127
|
|
@@ -234,6 +242,11 @@ class Client:
|
|
234
242
|
list_of_files.extend(temp_files)
|
235
243
|
return list_of_files
|
236
244
|
|
245
|
+
@property
|
246
|
+
def backend(self) -> str:
|
247
|
+
"""Get the backend type."""
|
248
|
+
return "python"
|
249
|
+
|
237
250
|
@property
|
238
251
|
def is_alive(self):
|
239
252
|
"""Check if the Mechanical instance is alive."""
|
@@ -245,9 +258,10 @@ class Client:
|
|
245
258
|
|
246
259
|
def exit(self):
|
247
260
|
"""Shuts down the Mechanical instance."""
|
248
|
-
|
249
|
-
|
250
|
-
self.
|
261
|
+
if self._has_exited:
|
262
|
+
return
|
263
|
+
self.close()
|
264
|
+
self._has_exited = True
|
251
265
|
print("Disconnected from server")
|
252
266
|
|
253
267
|
def __del__(self): # pragma: no cover
|
@@ -0,0 +1,131 @@
|
|
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
|
+
"""Remote Procedure Call (RPC) server."""
|
23
|
+
|
24
|
+
import fnmatch
|
25
|
+
import os
|
26
|
+
|
27
|
+
from ansys.mechanical.core.embedding.app import App
|
28
|
+
|
29
|
+
from .server import MechanicalEmbeddedServer
|
30
|
+
from .utils import remote_method
|
31
|
+
|
32
|
+
|
33
|
+
class DefaultServiceMethods:
|
34
|
+
"""Default service methods for MechanicalEmbeddedServer."""
|
35
|
+
|
36
|
+
def __init__(self, app: App):
|
37
|
+
"""Initialize the DefaultServiceMethods."""
|
38
|
+
self._app = app
|
39
|
+
|
40
|
+
def __repr__(self):
|
41
|
+
"""Return the representation of the instance."""
|
42
|
+
return '"ServiceMethods instance"'
|
43
|
+
|
44
|
+
@remote_method
|
45
|
+
def run_python_script(
|
46
|
+
self, script: str, enable_logging=False, log_level="WARNING", progress_interval=2000
|
47
|
+
):
|
48
|
+
"""Run scripts using Internal python engine."""
|
49
|
+
result = self._app.execute_script(script)
|
50
|
+
return result
|
51
|
+
|
52
|
+
@remote_method
|
53
|
+
def run_python_script_from_file(
|
54
|
+
self,
|
55
|
+
file_path: str,
|
56
|
+
enable_logging=False,
|
57
|
+
log_level="WARNING",
|
58
|
+
progress_interval=2000,
|
59
|
+
):
|
60
|
+
"""Run scripts using Internal python engine."""
|
61
|
+
return self._app.execute_script_from_file(file_path)
|
62
|
+
|
63
|
+
@remote_method
|
64
|
+
def clear(self):
|
65
|
+
"""Clear the current project."""
|
66
|
+
self._app.new()
|
67
|
+
|
68
|
+
@property
|
69
|
+
@remote_method
|
70
|
+
def project_directory(self):
|
71
|
+
"""Get the project directory."""
|
72
|
+
return self._app.ExtAPI.DataModel.Project.ProjectDirectory
|
73
|
+
|
74
|
+
@remote_method
|
75
|
+
def list_files(self):
|
76
|
+
"""List all files in the project directory."""
|
77
|
+
list = []
|
78
|
+
mechdbPath = self._app.ExtAPI.DataModel.Project.FilePath
|
79
|
+
if mechdbPath != "":
|
80
|
+
list.append(mechdbPath)
|
81
|
+
rootDir = self._app.ExtAPI.DataModel.Project.ProjectDirectory
|
82
|
+
|
83
|
+
for dirPath, dirNames, fileNames in os.walk(rootDir):
|
84
|
+
for fileName in fileNames:
|
85
|
+
list.append(os.path.join(dirPath, fileName))
|
86
|
+
files_out = "\n".join(list).splitlines()
|
87
|
+
if not files_out: # pragma: no cover
|
88
|
+
print("No files listed")
|
89
|
+
return files_out
|
90
|
+
|
91
|
+
@remote_method
|
92
|
+
def _get_files(self, files, recursive=False):
|
93
|
+
self_files = self.list_files() # to avoid calling it too much
|
94
|
+
|
95
|
+
if isinstance(files, str):
|
96
|
+
if files in self_files:
|
97
|
+
list_files = [files]
|
98
|
+
elif "*" in files:
|
99
|
+
list_files = fnmatch.filter(self_files, files)
|
100
|
+
if not list_files:
|
101
|
+
raise ValueError(
|
102
|
+
f"The `'files'` parameter ({files}) didn't match any file using "
|
103
|
+
f"glob expressions in the remote server."
|
104
|
+
)
|
105
|
+
else:
|
106
|
+
raise ValueError(
|
107
|
+
f"The `'files'` parameter ('{files}') does not match any file or pattern."
|
108
|
+
)
|
109
|
+
|
110
|
+
elif isinstance(files, (list, tuple)):
|
111
|
+
if not all([isinstance(each, str) for each in files]):
|
112
|
+
raise ValueError(
|
113
|
+
"The parameter `'files'` can be a list or tuple, but it "
|
114
|
+
"should only contain strings."
|
115
|
+
)
|
116
|
+
list_files = files
|
117
|
+
else:
|
118
|
+
raise ValueError(
|
119
|
+
f"The `file` parameter type ({type(files)}) is not supported."
|
120
|
+
"Only strings, tuple of strings, or list of strings are allowed."
|
121
|
+
)
|
122
|
+
|
123
|
+
return list_files
|
124
|
+
|
125
|
+
|
126
|
+
class MechanicalDefaultServer(MechanicalEmbeddedServer):
|
127
|
+
"""Default server with default service methods."""
|
128
|
+
|
129
|
+
def __init__(self, **kwargs):
|
130
|
+
"""Initialize the MechanicalDefaultServer."""
|
131
|
+
super().__init__(impl=DefaultServiceMethods, **kwargs)
|