ansys-mechanical-core 0.11.13__py3-none-any.whl → 0.11.15__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 -4
- ansys/mechanical/core/embedding/app.py +97 -12
- ansys/mechanical/core/embedding/appdata.py +26 -22
- ansys/mechanical/core/embedding/enum_importer.py +5 -0
- ansys/mechanical/core/embedding/global_importer.py +50 -0
- ansys/mechanical/core/embedding/{viz → graphics}/embedding_plotter.py +1 -1
- ansys/mechanical/core/embedding/imports.py +30 -58
- ansys/mechanical/core/embedding/initializer.py +76 -4
- ansys/mechanical/core/embedding/messages.py +195 -0
- ansys/mechanical/core/embedding/resolver.py +1 -1
- ansys/mechanical/core/embedding/rpc/__init__.py +3 -7
- ansys/mechanical/core/embedding/rpc/client.py +55 -19
- ansys/mechanical/core/embedding/rpc/default_server.py +131 -0
- ansys/mechanical/core/embedding/rpc/server.py +171 -162
- ansys/mechanical/core/embedding/rpc/utils.py +18 -2
- ansys/mechanical/core/embedding/runtime.py +6 -0
- ansys/mechanical/core/embedding/transaction.py +51 -0
- ansys/mechanical/core/ide_config.py +22 -7
- ansys/mechanical/core/mechanical.py +86 -18
- {ansys_mechanical_core-0.11.13.dist-info → ansys_mechanical_core-0.11.15.dist-info}/METADATA +21 -17
- ansys_mechanical_core-0.11.15.dist-info/RECORD +53 -0
- {ansys_mechanical_core-0.11.13.dist-info → ansys_mechanical_core-0.11.15.dist-info}/WHEEL +1 -1
- ansys_mechanical_core-0.11.13.dist-info/RECORD +0 -49
- /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.13.dist-info → ansys_mechanical_core-0.11.15.dist-info}/entry_points.txt +0 -0
- {ansys_mechanical_core-0.11.13.dist-info → ansys_mechanical_core-0.11.15.dist-info/licenses}/LICENSE +0 -0
@@ -21,61 +21,103 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
"""Remote Procedure Call (RPC) server."""
|
23
23
|
|
24
|
-
import fnmatch
|
25
24
|
import os
|
25
|
+
import threading
|
26
|
+
import time
|
26
27
|
import typing
|
27
28
|
|
28
29
|
import rpyc
|
29
30
|
from rpyc.utils.server import ThreadedServer
|
30
31
|
import toolz
|
31
32
|
|
33
|
+
from ansys.mechanical.core.embedding.app import App
|
32
34
|
from ansys.mechanical.core.embedding.background import BackgroundApp
|
33
|
-
|
35
|
+
import ansys.mechanical.core.embedding.utils
|
34
36
|
|
35
|
-
from .utils import MethodType,
|
37
|
+
from .utils import MethodType, get_free_port, get_remote_methods
|
36
38
|
|
37
39
|
# TODO : implement logging
|
38
40
|
|
39
|
-
|
41
|
+
|
42
|
+
class ForegroundAppBackend:
|
43
|
+
"""Backend for the python server where mechanical uses the main thread."""
|
44
|
+
|
45
|
+
def __init__(self, app: App):
|
46
|
+
"""Create a new instance of ForegroundAppBackend."""
|
47
|
+
self._app = app
|
48
|
+
self._poster = app.poster
|
49
|
+
|
50
|
+
def try_post(self, callable: typing.Callable) -> typing.Any:
|
51
|
+
"""Try to post to mechanical's main thread."""
|
52
|
+
return self._poster.try_post(callable)
|
53
|
+
|
54
|
+
def get_app(self) -> App:
|
55
|
+
"""Get the app object."""
|
56
|
+
return self._app
|
57
|
+
|
58
|
+
|
59
|
+
class BackgroundAppBackend:
|
60
|
+
"""Backend for the python server where mechanical uses the background thread."""
|
61
|
+
|
62
|
+
def __init__(self, backgroundapp: BackgroundApp):
|
63
|
+
"""Create a new instance of BackgroundAppBackend."""
|
64
|
+
self._backgroundapp = backgroundapp
|
65
|
+
|
66
|
+
def try_post(self, callable: typing.Callable) -> typing.Any:
|
67
|
+
"""Try to post to mechanical's main thread."""
|
68
|
+
return self._backgroundapp.try_post(callable)
|
69
|
+
|
70
|
+
def get_app(self) -> App:
|
71
|
+
"""Get the app object."""
|
72
|
+
return self._backgroundapp.app
|
40
73
|
|
41
74
|
|
42
75
|
class MechanicalService(rpyc.Service):
|
43
76
|
"""Starts Mechanical app services."""
|
44
77
|
|
45
|
-
def __init__(self,
|
78
|
+
def __init__(self, backend, functions=[], impl=[]):
|
46
79
|
"""Initialize the service."""
|
47
80
|
super().__init__()
|
48
|
-
self.
|
81
|
+
self._backend = backend
|
49
82
|
self._install_functions(functions)
|
50
|
-
self.
|
51
|
-
|
83
|
+
self._install_classes(impl)
|
84
|
+
|
85
|
+
def _try_post(self, callable: typing.Callable) -> typing.Any:
|
86
|
+
return self._backend.try_post(callable)
|
87
|
+
|
88
|
+
def _get_app(self) -> App:
|
89
|
+
return self._backend.get_app()
|
90
|
+
|
91
|
+
def on_connect(self, conn):
|
92
|
+
"""Handle client connection."""
|
93
|
+
print("Client connected")
|
94
|
+
|
95
|
+
def on_disconnect(self, conn):
|
96
|
+
"""Handle client disconnection."""
|
97
|
+
print("Client disconnected")
|
52
98
|
|
53
99
|
def _install_functions(self, methods):
|
54
100
|
"""Install the given list of methods."""
|
101
|
+
if not methods:
|
102
|
+
return
|
55
103
|
[self._install_function(method) for method in methods]
|
56
104
|
|
105
|
+
def _install_classes(self, impl):
|
106
|
+
"""Install the given list of classes."""
|
107
|
+
if not impl:
|
108
|
+
return
|
109
|
+
[self._install_class(_impl) for _impl in impl]
|
110
|
+
|
57
111
|
def _install_class(self, impl):
|
58
112
|
"""Install methods from the given implemented class."""
|
59
|
-
print("Install class")
|
60
113
|
if impl is None:
|
61
114
|
return
|
62
|
-
print("Installing methods from class")
|
63
115
|
for methodname, method, methodtype in get_remote_methods(impl):
|
64
|
-
print(f"installing {methodname} of {impl}")
|
65
116
|
if methodtype == MethodType.METHOD:
|
66
117
|
self._install_method(method)
|
67
118
|
elif methodtype == MethodType.PROP:
|
68
119
|
self._install_property(method, methodname)
|
69
120
|
|
70
|
-
def on_connect(self, conn):
|
71
|
-
"""Handle client connection."""
|
72
|
-
print("Client connected")
|
73
|
-
print(self._backgroundapp.app)
|
74
|
-
|
75
|
-
def on_disconnect(self, conn):
|
76
|
-
"""Handle client disconnection."""
|
77
|
-
print("Client disconnected")
|
78
|
-
|
79
121
|
def _curry_property(self, prop, propname, get: bool):
|
80
122
|
"""Curry the given property."""
|
81
123
|
|
@@ -86,7 +128,7 @@ class MechanicalService(rpyc.Service):
|
|
86
128
|
else:
|
87
129
|
setattr(prop._owner, propname, *arg)
|
88
130
|
|
89
|
-
return self.
|
131
|
+
return self._try_post(curried)
|
90
132
|
|
91
133
|
return posted
|
92
134
|
|
@@ -99,7 +141,7 @@ class MechanicalService(rpyc.Service):
|
|
99
141
|
result = original_method(*args, **kwargs)
|
100
142
|
return result
|
101
143
|
|
102
|
-
return self.
|
144
|
+
return self._try_post(curried)
|
103
145
|
|
104
146
|
return posted
|
105
147
|
|
@@ -110,9 +152,9 @@ class MechanicalService(rpyc.Service):
|
|
110
152
|
|
111
153
|
def posted(*args, **kwargs):
|
112
154
|
def curried():
|
113
|
-
return curried_method(self.
|
155
|
+
return curried_method(self._get_app(), *args, **kwargs)
|
114
156
|
|
115
|
-
return self.
|
157
|
+
return self._try_post(curried)
|
116
158
|
|
117
159
|
return posted
|
118
160
|
|
@@ -157,7 +199,6 @@ class MechanicalService(rpyc.Service):
|
|
157
199
|
|
158
200
|
def _install_function(self, function):
|
159
201
|
"""Install a functions with inner and exposed pairs."""
|
160
|
-
print(f"Installing {function}")
|
161
202
|
exposed_name = f"exposed_{function.__name__}"
|
162
203
|
inner_name = f"inner_{function.__name__}"
|
163
204
|
|
@@ -211,8 +252,9 @@ class MechanicalService(rpyc.Service):
|
|
211
252
|
|
212
253
|
def exposed_service_exit(self):
|
213
254
|
"""Exit the server."""
|
214
|
-
|
215
|
-
self.
|
255
|
+
print("Shutting down server ...")
|
256
|
+
self._exit_f()
|
257
|
+
print("Server stopped")
|
216
258
|
|
217
259
|
|
218
260
|
class MechanicalEmbeddedServer:
|
@@ -220,163 +262,130 @@ class MechanicalEmbeddedServer:
|
|
220
262
|
|
221
263
|
def __init__(
|
222
264
|
self,
|
223
|
-
service: typing.Type[rpyc.Service] = MechanicalService,
|
224
265
|
port: int = None,
|
225
266
|
version: int = None,
|
226
267
|
methods: typing.List[typing.Callable] = [],
|
227
|
-
impl=
|
268
|
+
impl: typing.List = [],
|
228
269
|
):
|
229
270
|
"""Initialize the server."""
|
230
271
|
self._exited = False
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
self._port = self.get_free_port(port)
|
237
|
-
if impl is None:
|
238
|
-
self._impl = None
|
272
|
+
use_background_app = False
|
273
|
+
if use_background_app:
|
274
|
+
self._app_instance = BackgroundApp(version=version)
|
275
|
+
self._backend = BackgroundAppBackend(self._app_instance)
|
239
276
|
else:
|
240
|
-
self.
|
277
|
+
self._app_instance = App(version=version)
|
278
|
+
self._backend = ForegroundAppBackend(self._app_instance)
|
279
|
+
self._port = get_free_port(port)
|
280
|
+
self._install_methods(methods)
|
281
|
+
self._install_classes(impl)
|
282
|
+
self._server = ThreadedServer(self._create_service(), port=self._port)
|
241
283
|
|
242
|
-
|
243
|
-
|
284
|
+
def _create_service(self):
|
285
|
+
service = MechanicalService(self._backend, self._methods, self._impl)
|
244
286
|
|
245
|
-
|
246
|
-
|
247
|
-
"""Get free port.
|
287
|
+
def exit_f():
|
288
|
+
self.stop()
|
248
289
|
|
249
|
-
|
250
|
-
|
251
|
-
if port is None:
|
252
|
-
port = PYMECHANICAL_DEFAULT_RPC_PORT
|
290
|
+
service._exit_f = exit_f
|
291
|
+
return service
|
253
292
|
|
254
|
-
|
255
|
-
|
293
|
+
def _is_stopped(self):
|
294
|
+
return self._app_instance is None
|
256
295
|
|
257
|
-
|
296
|
+
def _wait_exit(self) -> None:
|
297
|
+
if self._exit_thread is None:
|
298
|
+
return
|
299
|
+
self._exit_thread.join()
|
258
300
|
|
259
|
-
def
|
301
|
+
def _start_background_app(self) -> None:
|
260
302
|
"""Start server on specified port."""
|
261
|
-
|
262
|
-
f"Starting mechanical application in server.\n"
|
263
|
-
f"Listening on port {self._port}\n{self._background_app.app}"
|
264
|
-
)
|
303
|
+
self._exit_thread: threading.Thread = None
|
265
304
|
self._server.start()
|
266
|
-
""
|
267
|
-
|
268
|
-
conn.serve_all()
|
269
|
-
except KeyboardInterrupt:
|
270
|
-
print("User interrupt!")
|
271
|
-
finally:
|
272
|
-
conn.close()"""
|
305
|
+
print("Server exited!")
|
306
|
+
self._wait_exit()
|
273
307
|
self._exited = True
|
274
308
|
|
275
|
-
def
|
276
|
-
"""
|
277
|
-
print("Stopping the server...")
|
278
|
-
self._background_app.stop()
|
279
|
-
self._server.close()
|
280
|
-
self._exited = True
|
281
|
-
print("Server stopped.")
|
309
|
+
def _stop_background_app(self):
|
310
|
+
"""Return immediately but will stop the server.
|
282
311
|
|
312
|
+
Mechanical is running on the background but
|
313
|
+
the rpyc server is running on the main thread
|
314
|
+
this signals for the server to stop, and the main
|
315
|
+
thread will wait until the server has stopped.
|
316
|
+
"""
|
283
317
|
|
284
|
-
|
285
|
-
|
318
|
+
def stop_f(): # wait for all connections to close
|
319
|
+
while len(self._server.clients) > 0:
|
320
|
+
time.sleep(0.005)
|
321
|
+
self._app_instance.stop()
|
322
|
+
self._app_instance = None
|
323
|
+
self._backend = None
|
324
|
+
self._server.close()
|
325
|
+
self._exited = True
|
326
|
+
|
327
|
+
self._exit_thread = threading.Thread(target=stop_f)
|
328
|
+
self._exit_thread.start()
|
329
|
+
|
330
|
+
def _start_foreground_app(self):
|
331
|
+
self._server_stopped = False
|
332
|
+
|
333
|
+
def start_f():
|
334
|
+
print("Server started!")
|
335
|
+
self._server.start()
|
336
|
+
print("Server exited!")
|
337
|
+
|
338
|
+
self._server_thread = threading.Thread(target=start_f)
|
339
|
+
self._server_thread.start()
|
340
|
+
while True:
|
341
|
+
if self._server_stopped:
|
342
|
+
break
|
343
|
+
try:
|
344
|
+
ansys.mechanical.core.embedding.utils.sleep(40)
|
345
|
+
except Exception as e:
|
346
|
+
print(f"An error occurred: {e}")
|
347
|
+
self._server_thread.join()
|
286
348
|
|
287
|
-
def
|
288
|
-
|
289
|
-
self.
|
349
|
+
def _stop_foreground_app(self):
|
350
|
+
self._server_stopped = True
|
351
|
+
self._server.close()
|
352
|
+
# self._server_thread.join()
|
290
353
|
|
291
|
-
def
|
292
|
-
|
293
|
-
|
354
|
+
def _get_app_repr(self) -> str:
|
355
|
+
def f():
|
356
|
+
return repr(self._backend.get_app())
|
294
357
|
|
295
|
-
|
296
|
-
def run_python_script(
|
297
|
-
self, script: str, enable_logging=False, log_level="WARNING", progress_interval=2000
|
298
|
-
):
|
299
|
-
"""Run scripts using Internal python engine."""
|
300
|
-
result = self._app.execute_script(script)
|
301
|
-
return result
|
358
|
+
return self._backend.try_post(f)
|
302
359
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
"""Run scripts using Internal python engine."""
|
312
|
-
return self._app.execute_script_from_file(file_path)
|
313
|
-
|
314
|
-
@remote_method
|
315
|
-
def clear(self):
|
316
|
-
"""Clear the current project."""
|
317
|
-
self._app.new()
|
318
|
-
|
319
|
-
@property
|
320
|
-
@remote_method
|
321
|
-
def project_directory(self):
|
322
|
-
"""Get the project directory."""
|
323
|
-
return self._app.execute_script("""ExtAPI.DataModel.Project.ProjectDirectory""")
|
324
|
-
|
325
|
-
@remote_method
|
326
|
-
def list_files(self):
|
327
|
-
"""List all files in the project directory."""
|
328
|
-
list = []
|
329
|
-
mechdbPath = self._app.execute_script("""ExtAPI.DataModel.Project.FilePath""")
|
330
|
-
if mechdbPath != "":
|
331
|
-
list.append(mechdbPath)
|
332
|
-
rootDir = self._app.execute_script("""ExtAPI.DataModel.Project.ProjectDirectory""")
|
333
|
-
|
334
|
-
for dirPath, dirNames, fileNames in os.walk(rootDir):
|
335
|
-
for fileName in fileNames:
|
336
|
-
list.append(os.path.join(dirPath, fileName))
|
337
|
-
files_out = "\n".join(list).splitlines()
|
338
|
-
if not files_out: # pragma: no cover
|
339
|
-
print("No files listed")
|
340
|
-
return files_out
|
341
|
-
|
342
|
-
@remote_method
|
343
|
-
def _get_files(self, files, recursive=False):
|
344
|
-
self_files = self.list_files() # to avoid calling it too much
|
345
|
-
|
346
|
-
if isinstance(files, str):
|
347
|
-
if files in self_files:
|
348
|
-
list_files = [files]
|
349
|
-
elif "*" in files:
|
350
|
-
list_files = fnmatch.filter(self_files, files)
|
351
|
-
if not list_files:
|
352
|
-
raise ValueError(
|
353
|
-
f"The `'files'` parameter ({files}) didn't match any file using "
|
354
|
-
f"glob expressions in the remote server."
|
355
|
-
)
|
356
|
-
else:
|
357
|
-
raise ValueError(
|
358
|
-
f"The `'files'` parameter ('{files}') does not match any file or pattern."
|
359
|
-
)
|
360
|
-
|
361
|
-
elif isinstance(files, (list, tuple)):
|
362
|
-
if not all([isinstance(each, str) for each in files]):
|
363
|
-
raise ValueError(
|
364
|
-
"The parameter `'files'` can be a list or tuple, but it "
|
365
|
-
"should only contain strings."
|
366
|
-
)
|
367
|
-
list_files = files
|
360
|
+
def start(self) -> None:
|
361
|
+
"""Start server on specified port."""
|
362
|
+
print(
|
363
|
+
f"Starting mechanical application in server.\n"
|
364
|
+
f"Listening on port {self._port}\n{self._get_app_repr()}"
|
365
|
+
)
|
366
|
+
if isinstance(self._app_instance, BackgroundApp):
|
367
|
+
self._start_background_app()
|
368
368
|
else:
|
369
|
-
|
370
|
-
f"The `file` parameter type ({type(files)}) is not supported."
|
371
|
-
"Only strings, tuple of strings, or list of strings are allowed."
|
372
|
-
)
|
373
|
-
|
374
|
-
return list_files
|
375
|
-
|
369
|
+
self._start_foreground_app()
|
376
370
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
371
|
+
def stop(self) -> None:
|
372
|
+
"""Stop the server."""
|
373
|
+
if self._is_stopped():
|
374
|
+
raise Exception("already stopped!")
|
375
|
+
if isinstance(self._app_instance, BackgroundApp):
|
376
|
+
self._stop_background_app()
|
377
|
+
else:
|
378
|
+
self._stop_foreground_app()
|
379
|
+
|
380
|
+
def _install_classes(self, impl: typing.Union[typing.Any, typing.List]) -> None:
|
381
|
+
app = self._backend.get_app()
|
382
|
+
if impl and not isinstance(impl, list):
|
383
|
+
impl = [impl]
|
384
|
+
self._impl = [i(app) for i in impl] if impl else []
|
385
|
+
|
386
|
+
def _install_methods(
|
387
|
+
self, methods: typing.Union[typing.Callable, typing.List[typing.Callable]]
|
388
|
+
) -> None:
|
389
|
+
if methods and not isinstance(methods, list):
|
390
|
+
methods = [methods]
|
391
|
+
self._methods = methods if methods is not None else []
|
@@ -22,6 +22,10 @@
|
|
22
22
|
"""Utilities necessary for remote calls."""
|
23
23
|
import typing
|
24
24
|
|
25
|
+
from ansys.mechanical.core.mechanical import port_in_use
|
26
|
+
|
27
|
+
PYMECHANICAL_DEFAULT_RPC_PORT = 20000
|
28
|
+
|
25
29
|
|
26
30
|
class remote_method:
|
27
31
|
"""Decorator for passing remote methods."""
|
@@ -102,12 +106,10 @@ def get_remote_methods(
|
|
102
106
|
A tuple containing the method name and the method itself
|
103
107
|
for each remote method found in the object
|
104
108
|
"""
|
105
|
-
print(f"Getting remote methods on {obj}")
|
106
109
|
objclass = obj.__class__
|
107
110
|
for attrname in dir(obj):
|
108
111
|
if attrname.startswith("__"):
|
109
112
|
continue
|
110
|
-
print(attrname)
|
111
113
|
if hasattr(objclass, attrname):
|
112
114
|
class_attribute = getattr(objclass, attrname)
|
113
115
|
if isinstance(class_attribute, property):
|
@@ -118,3 +120,17 @@ def get_remote_methods(
|
|
118
120
|
if result != None:
|
119
121
|
attrname, method = result
|
120
122
|
yield attrname, method, MethodType.METHOD
|
123
|
+
|
124
|
+
|
125
|
+
def get_free_port(port: int = None):
|
126
|
+
"""Get free port.
|
127
|
+
|
128
|
+
If port is not given, it will find a free port starting from PYMECHANICAL_DEFAULT_RPC_PORT.
|
129
|
+
"""
|
130
|
+
if port is None:
|
131
|
+
port = PYMECHANICAL_DEFAULT_RPC_PORT
|
132
|
+
|
133
|
+
while port_in_use(port):
|
134
|
+
port += 1
|
135
|
+
|
136
|
+
return port
|
@@ -50,13 +50,17 @@ def _bind_assembly_for_explicit_interface(assembly_name: str):
|
|
50
50
|
# if pythonnet is not installed, we can't bind the assembly
|
51
51
|
try:
|
52
52
|
distribution("pythonnet")
|
53
|
+
Logger.warning("Cannot bind for explicit interface because pythonnet is installed")
|
53
54
|
return
|
54
55
|
except ModuleNotFoundError:
|
55
56
|
pass
|
56
57
|
|
58
|
+
Logger.debug(f"Binding assembly for explicit interface {assembly_name}")
|
57
59
|
import clr
|
58
60
|
|
61
|
+
Logger.debug(f"Binding assembly for explicit interface, Loading {assembly_name}")
|
59
62
|
assembly = clr.AddReference(assembly_name)
|
63
|
+
Logger.debug(f"Binding assembly for explicit interface, Loaded {assembly_name}")
|
60
64
|
from Python.Runtime import BindingManager, BindingOptions
|
61
65
|
|
62
66
|
binding_options = BindingOptions()
|
@@ -78,6 +82,8 @@ def initialize(version: int) -> None:
|
|
78
82
|
if version >= 242 or os.name == "nt":
|
79
83
|
# function codec is distributed with pymechanical on linux only
|
80
84
|
# at version 242 or later
|
85
|
+
Logger.debug("Registering function codec")
|
81
86
|
__register_function_codec()
|
87
|
+
Logger.debug("Registered function codec")
|
82
88
|
|
83
89
|
_bind_assembly_for_explicit_interface("Ansys.ACT.WB1")
|
@@ -0,0 +1,51 @@
|
|
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
|
+
"""A class to speed up bulk user interactions using Ansys ACT Mechanical Transaction."""
|
23
|
+
|
24
|
+
|
25
|
+
class Transaction: # When ansys-pythonnet issue #14 is fixed, this class will be removed
|
26
|
+
"""
|
27
|
+
A class to speed up bulk user interactions using Ansys ACT Mechanical Transaction.
|
28
|
+
|
29
|
+
Example
|
30
|
+
-------
|
31
|
+
>>> with Transaction() as transaction:
|
32
|
+
... pass # Perform bulk user interactions here
|
33
|
+
...
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self):
|
37
|
+
"""Initialize the Transaction class."""
|
38
|
+
import clr
|
39
|
+
|
40
|
+
clr.AddReference("Ansys.ACT.WB1")
|
41
|
+
import Ansys
|
42
|
+
|
43
|
+
self._transaction = Ansys.ACT.Mechanical.Transaction()
|
44
|
+
|
45
|
+
def __enter__(self):
|
46
|
+
"""Enter the context of the transaction."""
|
47
|
+
return self
|
48
|
+
|
49
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
50
|
+
"""Exit the context of the transaction and disposes of resources."""
|
51
|
+
self._transaction.Dispose()
|
@@ -28,6 +28,7 @@ from pathlib import Path
|
|
28
28
|
import re
|
29
29
|
import site
|
30
30
|
import sys
|
31
|
+
import warnings
|
31
32
|
|
32
33
|
import ansys.tools.path as atp
|
33
34
|
import click
|
@@ -92,6 +93,7 @@ def _vscode_impl(
|
|
92
93
|
If unspecified, it finds the default Mechanical version from ansys-tools-path.
|
93
94
|
"""
|
94
95
|
# Update the user or workspace settings
|
96
|
+
settings_json = "the settings.json file"
|
95
97
|
if target == "user":
|
96
98
|
# Get the path to the user's settings.json file depending on the platform
|
97
99
|
if "win" in sys.platform:
|
@@ -151,13 +153,23 @@ def _cli_impl(
|
|
151
153
|
stubs_location = get_stubs_location()
|
152
154
|
# Get all revision numbers available in ansys-mechanical-stubs
|
153
155
|
revns = get_stubs_versions(stubs_location)
|
154
|
-
|
155
|
-
if revision
|
156
|
+
|
157
|
+
# Check if the user revision number is less or greater than the min and max revisions
|
158
|
+
# in the ansys-mechanical-stubs package location
|
159
|
+
if revision < min(revns):
|
156
160
|
raise Exception(f"PyMechanical Stubs are not available for {revision}")
|
157
|
-
elif
|
161
|
+
elif revision > max(revns):
|
162
|
+
warnings.warn(
|
163
|
+
f"PyMechanical Stubs are not available for {revision}. Using {max(revns)} instead.",
|
164
|
+
stacklevel=2,
|
165
|
+
)
|
166
|
+
revision = max(revns)
|
167
|
+
|
168
|
+
# Check the IDE and raise an exception if it's not VS Code
|
169
|
+
if ide != "vscode":
|
158
170
|
raise Exception(f"{ide} is not supported at the moment.")
|
159
|
-
|
160
|
-
|
171
|
+
|
172
|
+
return _vscode_impl(target, revision)
|
161
173
|
|
162
174
|
|
163
175
|
@click.command()
|
@@ -202,8 +214,11 @@ def cli(ide: str, target: str, revision: int) -> None:
|
|
202
214
|
$ ansys-mechanical-ideconfig --ide vscode --target user --revision 251
|
203
215
|
|
204
216
|
"""
|
205
|
-
|
206
|
-
|
217
|
+
if not revision:
|
218
|
+
exe = atp.get_mechanical_path(allow_input=False, version=revision)
|
219
|
+
version = atp.version_from_path("mechanical", exe)
|
220
|
+
else:
|
221
|
+
version = revision
|
207
222
|
|
208
223
|
return _cli_impl(
|
209
224
|
ide,
|