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.
Files changed (28) hide show
  1. ansys/mechanical/core/__init__.py +3 -4
  2. ansys/mechanical/core/embedding/app.py +97 -12
  3. ansys/mechanical/core/embedding/appdata.py +26 -22
  4. ansys/mechanical/core/embedding/enum_importer.py +5 -0
  5. ansys/mechanical/core/embedding/global_importer.py +50 -0
  6. ansys/mechanical/core/embedding/{viz → graphics}/embedding_plotter.py +1 -1
  7. ansys/mechanical/core/embedding/imports.py +30 -58
  8. ansys/mechanical/core/embedding/initializer.py +76 -4
  9. ansys/mechanical/core/embedding/messages.py +195 -0
  10. ansys/mechanical/core/embedding/resolver.py +1 -1
  11. ansys/mechanical/core/embedding/rpc/__init__.py +3 -7
  12. ansys/mechanical/core/embedding/rpc/client.py +55 -19
  13. ansys/mechanical/core/embedding/rpc/default_server.py +131 -0
  14. ansys/mechanical/core/embedding/rpc/server.py +171 -162
  15. ansys/mechanical/core/embedding/rpc/utils.py +18 -2
  16. ansys/mechanical/core/embedding/runtime.py +6 -0
  17. ansys/mechanical/core/embedding/transaction.py +51 -0
  18. ansys/mechanical/core/ide_config.py +22 -7
  19. ansys/mechanical/core/mechanical.py +86 -18
  20. {ansys_mechanical_core-0.11.13.dist-info → ansys_mechanical_core-0.11.15.dist-info}/METADATA +21 -17
  21. ansys_mechanical_core-0.11.15.dist-info/RECORD +53 -0
  22. {ansys_mechanical_core-0.11.13.dist-info → ansys_mechanical_core-0.11.15.dist-info}/WHEEL +1 -1
  23. ansys_mechanical_core-0.11.13.dist-info/RECORD +0 -49
  24. /ansys/mechanical/core/embedding/{viz → graphics}/__init__.py +0 -0
  25. /ansys/mechanical/core/embedding/{viz → graphics}/usd_converter.py +0 -0
  26. /ansys/mechanical/core/embedding/{viz → graphics}/utils.py +0 -0
  27. {ansys_mechanical_core-0.11.13.dist-info → ansys_mechanical_core-0.11.15.dist-info}/entry_points.txt +0 -0
  28. {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
- from ansys.mechanical.core.mechanical import port_in_use
35
+ import ansys.mechanical.core.embedding.utils
34
36
 
35
- from .utils import MethodType, get_remote_methods, remote_method
37
+ from .utils import MethodType, get_free_port, get_remote_methods
36
38
 
37
39
  # TODO : implement logging
38
40
 
39
- PYMECHANICAL_DEFAULT_RPC_PORT = 20000
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, backgroundapp, functions=[], impl=None):
78
+ def __init__(self, backend, functions=[], impl=[]):
46
79
  """Initialize the service."""
47
80
  super().__init__()
48
- self._backgroundapp = backgroundapp
81
+ self._backend = backend
49
82
  self._install_functions(functions)
50
- self._install_class(impl)
51
- self.EMBEDDED = True
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._backgroundapp.try_post(curried)
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._backgroundapp.try_post(curried)
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._app, *args, **kwargs)
155
+ return curried_method(self._get_app(), *args, **kwargs)
114
156
 
115
- return self._backgroundapp.try_post(curried)
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
- self._backgroundapp.stop()
215
- self._backgroundapp = None
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=None,
268
+ impl: typing.List = [],
228
269
  ):
229
270
  """Initialize the server."""
230
271
  self._exited = False
231
- self._background_app = BackgroundApp(version=version)
232
- self._service = service
233
- self._methods = methods
234
- print("Initializing Mechanical ...")
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._impl = impl(self._background_app.app)
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
- my_service = self._service(self._background_app, self._methods, self._impl)
243
- self._server = ThreadedServer(my_service, port=self._port)
284
+ def _create_service(self):
285
+ service = MechanicalService(self._backend, self._methods, self._impl)
244
286
 
245
- @staticmethod
246
- def get_free_port(port=None):
247
- """Get free port.
287
+ def exit_f():
288
+ self.stop()
248
289
 
249
- If port is not given, it will find a free port starting from PYMECHANICAL_DEFAULT_RPC_PORT.
250
- """
251
- if port is None:
252
- port = PYMECHANICAL_DEFAULT_RPC_PORT
290
+ service._exit_f = exit_f
291
+ return service
253
292
 
254
- while port_in_use(port):
255
- port += 1
293
+ def _is_stopped(self):
294
+ return self._app_instance is None
256
295
 
257
- return port
296
+ def _wait_exit(self) -> None:
297
+ if self._exit_thread is None:
298
+ return
299
+ self._exit_thread.join()
258
300
 
259
- def start(self) -> None:
301
+ def _start_background_app(self) -> None:
260
302
  """Start server on specified port."""
261
- print(
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
- """try:
267
- try:
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 stop(self) -> None:
276
- """Stop the server."""
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
- class DefaultServiceMethods:
285
- """Default service methods for MechanicalEmbeddedServer."""
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 __init__(self, app):
288
- """Initialize the DefaultServiceMethods."""
289
- self._app = app
349
+ def _stop_foreground_app(self):
350
+ self._server_stopped = True
351
+ self._server.close()
352
+ # self._server_thread.join()
290
353
 
291
- def __repr__(self):
292
- """Return the representation of the instance."""
293
- return '"ServiceMethods instance"'
354
+ def _get_app_repr(self) -> str:
355
+ def f():
356
+ return repr(self._backend.get_app())
294
357
 
295
- @remote_method
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
- @remote_method
304
- def run_python_script_from_file(
305
- self,
306
- file_path: str,
307
- enable_logging=False,
308
- log_level="WARNING",
309
- progress_interval=2000,
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
- raise ValueError(
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
- class MechanicalDefaultServer(MechanicalEmbeddedServer):
378
- """Default server with default service methods."""
379
-
380
- def __init__(self, **kwargs):
381
- """Initialize the MechanicalDefaultServer."""
382
- super().__init__(service=MechanicalService, impl=DefaultServiceMethods, **kwargs)
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
- # Check the IDE and raise an exception if it's not VS Code
155
- if revision < min(revns) or revision > max(revns):
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 ide != "vscode":
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
- else:
160
- return _vscode_impl(target, revision)
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
- exe = atp.get_mechanical_path(allow_input=False, version=revision)
206
- version = atp.version_from_path("mechanical", exe)
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,