ansys-mechanical-core 0.11.11__py3-none-any.whl → 0.11.13__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/_version.py +1 -0
- ansys/mechanical/core/embedding/app.py +2 -3
- ansys/mechanical/core/embedding/background.py +11 -2
- ansys/mechanical/core/embedding/enum_importer.py +1 -1
- ansys/mechanical/core/embedding/imports.py +2 -0
- ansys/mechanical/core/embedding/initializer.py +6 -1
- ansys/mechanical/core/embedding/logger/__init__.py +1 -1
- ansys/mechanical/core/embedding/poster.py +33 -1
- ansys/mechanical/core/embedding/resolver.py +12 -5
- ansys/mechanical/core/embedding/rpc/__init__.py +36 -0
- ansys/mechanical/core/embedding/rpc/client.py +237 -0
- ansys/mechanical/core/embedding/rpc/server.py +382 -0
- ansys/mechanical/core/embedding/rpc/utils.py +120 -0
- ansys/mechanical/core/embedding/runtime.py +22 -0
- ansys/mechanical/core/feature_flags.py +1 -0
- ansys/mechanical/core/ide_config.py +5 -5
- ansys/mechanical/core/mechanical.py +34 -15
- ansys/mechanical/core/misc.py +2 -2
- ansys/mechanical/core/pool.py +8 -8
- ansys/mechanical/core/run.py +3 -3
- {ansys_mechanical_core-0.11.11.dist-info → ansys_mechanical_core-0.11.13.dist-info}/METADATA +42 -29
- {ansys_mechanical_core-0.11.11.dist-info → ansys_mechanical_core-0.11.13.dist-info}/RECORD +25 -21
- {ansys_mechanical_core-0.11.11.dist-info → ansys_mechanical_core-0.11.13.dist-info}/LICENSE +0 -0
- {ansys_mechanical_core-0.11.11.dist-info → ansys_mechanical_core-0.11.13.dist-info}/WHEEL +0 -0
- {ansys_mechanical_core-0.11.11.dist-info → ansys_mechanical_core-0.11.13.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,382 @@
|
|
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
|
+
import typing
|
27
|
+
|
28
|
+
import rpyc
|
29
|
+
from rpyc.utils.server import ThreadedServer
|
30
|
+
import toolz
|
31
|
+
|
32
|
+
from ansys.mechanical.core.embedding.background import BackgroundApp
|
33
|
+
from ansys.mechanical.core.mechanical import port_in_use
|
34
|
+
|
35
|
+
from .utils import MethodType, get_remote_methods, remote_method
|
36
|
+
|
37
|
+
# TODO : implement logging
|
38
|
+
|
39
|
+
PYMECHANICAL_DEFAULT_RPC_PORT = 20000
|
40
|
+
|
41
|
+
|
42
|
+
class MechanicalService(rpyc.Service):
|
43
|
+
"""Starts Mechanical app services."""
|
44
|
+
|
45
|
+
def __init__(self, backgroundapp, functions=[], impl=None):
|
46
|
+
"""Initialize the service."""
|
47
|
+
super().__init__()
|
48
|
+
self._backgroundapp = backgroundapp
|
49
|
+
self._install_functions(functions)
|
50
|
+
self._install_class(impl)
|
51
|
+
self.EMBEDDED = True
|
52
|
+
|
53
|
+
def _install_functions(self, methods):
|
54
|
+
"""Install the given list of methods."""
|
55
|
+
[self._install_function(method) for method in methods]
|
56
|
+
|
57
|
+
def _install_class(self, impl):
|
58
|
+
"""Install methods from the given implemented class."""
|
59
|
+
print("Install class")
|
60
|
+
if impl is None:
|
61
|
+
return
|
62
|
+
print("Installing methods from class")
|
63
|
+
for methodname, method, methodtype in get_remote_methods(impl):
|
64
|
+
print(f"installing {methodname} of {impl}")
|
65
|
+
if methodtype == MethodType.METHOD:
|
66
|
+
self._install_method(method)
|
67
|
+
elif methodtype == MethodType.PROP:
|
68
|
+
self._install_property(method, methodname)
|
69
|
+
|
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
|
+
def _curry_property(self, prop, propname, get: bool):
|
80
|
+
"""Curry the given property."""
|
81
|
+
|
82
|
+
def posted(*arg):
|
83
|
+
def curried():
|
84
|
+
if get:
|
85
|
+
return getattr(prop._owner, propname)
|
86
|
+
else:
|
87
|
+
setattr(prop._owner, propname, *arg)
|
88
|
+
|
89
|
+
return self._backgroundapp.try_post(curried)
|
90
|
+
|
91
|
+
return posted
|
92
|
+
|
93
|
+
def _curry_method(self, method, realmethodname):
|
94
|
+
"""Curry the given method."""
|
95
|
+
|
96
|
+
def posted(*args, **kwargs):
|
97
|
+
def curried():
|
98
|
+
original_method = getattr(method._owner, realmethodname)
|
99
|
+
result = original_method(*args, **kwargs)
|
100
|
+
return result
|
101
|
+
|
102
|
+
return self._backgroundapp.try_post(curried)
|
103
|
+
|
104
|
+
return posted
|
105
|
+
|
106
|
+
def _curry_function(self, methodname):
|
107
|
+
"""Curry the given function."""
|
108
|
+
wrapped = getattr(self, methodname)
|
109
|
+
curried_method = toolz.curry(wrapped)
|
110
|
+
|
111
|
+
def posted(*args, **kwargs):
|
112
|
+
def curried():
|
113
|
+
return curried_method(self._app, *args, **kwargs)
|
114
|
+
|
115
|
+
return self._backgroundapp.try_post(curried)
|
116
|
+
|
117
|
+
return posted
|
118
|
+
|
119
|
+
def _install_property(self, prop: property, propname: str):
|
120
|
+
"""Install property with inner and exposed pairs."""
|
121
|
+
if prop.fget:
|
122
|
+
exposed_get_name = f"exposed_propget_{propname}"
|
123
|
+
|
124
|
+
def exposed_propget():
|
125
|
+
"""Convert to exposed getter."""
|
126
|
+
f = self._curry_property(prop.fget, propname, True)
|
127
|
+
result = f()
|
128
|
+
return result
|
129
|
+
|
130
|
+
setattr(self, exposed_get_name, exposed_propget)
|
131
|
+
if prop.fset:
|
132
|
+
exposed_set_name = f"exposed_propset_{propname}"
|
133
|
+
|
134
|
+
def exposed_propset(arg):
|
135
|
+
"""Convert to exposed getter."""
|
136
|
+
f = self._curry_property(prop.fset, propname, True)
|
137
|
+
result = f(arg)
|
138
|
+
return result
|
139
|
+
|
140
|
+
setattr(self, exposed_set_name, exposed_propset)
|
141
|
+
|
142
|
+
def _install_method(self, method):
|
143
|
+
methodname = method.__name__
|
144
|
+
self._install_method_with_name(method, methodname, methodname)
|
145
|
+
|
146
|
+
def _install_method_with_name(self, method, methodname, innername):
|
147
|
+
"""Install methods of impl with inner and exposed pairs."""
|
148
|
+
exposed_name = f"exposed_{methodname}"
|
149
|
+
|
150
|
+
def exposed_method(*args, **kwargs):
|
151
|
+
"""Convert to exposed method."""
|
152
|
+
f = self._curry_method(method, innername)
|
153
|
+
result = f(*args, **kwargs)
|
154
|
+
return result
|
155
|
+
|
156
|
+
setattr(self, exposed_name, exposed_method)
|
157
|
+
|
158
|
+
def _install_function(self, function):
|
159
|
+
"""Install a functions with inner and exposed pairs."""
|
160
|
+
print(f"Installing {function}")
|
161
|
+
exposed_name = f"exposed_{function.__name__}"
|
162
|
+
inner_name = f"inner_{function.__name__}"
|
163
|
+
|
164
|
+
def inner_method(app, *args, **kwargs):
|
165
|
+
"""Convert to inner method."""
|
166
|
+
return function(app, *args, **kwargs)
|
167
|
+
|
168
|
+
def exposed_method(*args, **kwargs):
|
169
|
+
"""Convert to exposed method."""
|
170
|
+
f = self._curry_function(inner_name)
|
171
|
+
return f(*args, **kwargs)
|
172
|
+
|
173
|
+
setattr(self, inner_name, inner_method)
|
174
|
+
setattr(self, exposed_name, exposed_method)
|
175
|
+
|
176
|
+
def exposed_service_upload(self, remote_path, file_data):
|
177
|
+
"""Handle file upload request from client."""
|
178
|
+
if not remote_path:
|
179
|
+
raise ValueError("The remote file path is empty.")
|
180
|
+
|
181
|
+
remote_dir = os.path.dirname(remote_path)
|
182
|
+
|
183
|
+
if remote_dir:
|
184
|
+
os.makedirs(remote_dir, exist_ok=True)
|
185
|
+
|
186
|
+
with open(remote_path, "wb") as f:
|
187
|
+
f.write(file_data)
|
188
|
+
|
189
|
+
print(f"File {remote_path} uploaded successfully.")
|
190
|
+
|
191
|
+
def exposed_service_download(self, remote_path):
|
192
|
+
"""Handle file download request from client."""
|
193
|
+
# Check if the remote file exists
|
194
|
+
if not os.path.exists(remote_path):
|
195
|
+
raise FileNotFoundError(f"The file {remote_path} does not exist on the server.")
|
196
|
+
|
197
|
+
if os.path.isdir(remote_path):
|
198
|
+
files = []
|
199
|
+
for dirpath, _, filenames in os.walk(remote_path):
|
200
|
+
for filename in filenames:
|
201
|
+
full_path = os.path.join(dirpath, filename)
|
202
|
+
relative_path = os.path.relpath(full_path, remote_path)
|
203
|
+
files.append(relative_path)
|
204
|
+
return {"is_directory": True, "files": files}
|
205
|
+
|
206
|
+
with open(remote_path, "rb") as f:
|
207
|
+
file_data = f.read()
|
208
|
+
|
209
|
+
print(f"File {remote_path} downloaded successfully.")
|
210
|
+
return file_data
|
211
|
+
|
212
|
+
def exposed_service_exit(self):
|
213
|
+
"""Exit the server."""
|
214
|
+
self._backgroundapp.stop()
|
215
|
+
self._backgroundapp = None
|
216
|
+
|
217
|
+
|
218
|
+
class MechanicalEmbeddedServer:
|
219
|
+
"""Start rpc server."""
|
220
|
+
|
221
|
+
def __init__(
|
222
|
+
self,
|
223
|
+
service: typing.Type[rpyc.Service] = MechanicalService,
|
224
|
+
port: int = None,
|
225
|
+
version: int = None,
|
226
|
+
methods: typing.List[typing.Callable] = [],
|
227
|
+
impl=None,
|
228
|
+
):
|
229
|
+
"""Initialize the server."""
|
230
|
+
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
|
239
|
+
else:
|
240
|
+
self._impl = impl(self._background_app.app)
|
241
|
+
|
242
|
+
my_service = self._service(self._background_app, self._methods, self._impl)
|
243
|
+
self._server = ThreadedServer(my_service, port=self._port)
|
244
|
+
|
245
|
+
@staticmethod
|
246
|
+
def get_free_port(port=None):
|
247
|
+
"""Get free port.
|
248
|
+
|
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
|
253
|
+
|
254
|
+
while port_in_use(port):
|
255
|
+
port += 1
|
256
|
+
|
257
|
+
return port
|
258
|
+
|
259
|
+
def start(self) -> None:
|
260
|
+
"""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
|
+
)
|
265
|
+
self._server.start()
|
266
|
+
"""try:
|
267
|
+
try:
|
268
|
+
conn.serve_all()
|
269
|
+
except KeyboardInterrupt:
|
270
|
+
print("User interrupt!")
|
271
|
+
finally:
|
272
|
+
conn.close()"""
|
273
|
+
self._exited = True
|
274
|
+
|
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.")
|
282
|
+
|
283
|
+
|
284
|
+
class DefaultServiceMethods:
|
285
|
+
"""Default service methods for MechanicalEmbeddedServer."""
|
286
|
+
|
287
|
+
def __init__(self, app):
|
288
|
+
"""Initialize the DefaultServiceMethods."""
|
289
|
+
self._app = app
|
290
|
+
|
291
|
+
def __repr__(self):
|
292
|
+
"""Return the representation of the instance."""
|
293
|
+
return '"ServiceMethods instance"'
|
294
|
+
|
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
|
302
|
+
|
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
|
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
|
+
|
376
|
+
|
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)
|
@@ -0,0 +1,120 @@
|
|
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
|
+
"""Utilities necessary for remote calls."""
|
23
|
+
import typing
|
24
|
+
|
25
|
+
|
26
|
+
class remote_method:
|
27
|
+
"""Decorator for passing remote methods."""
|
28
|
+
|
29
|
+
def __init__(self, func):
|
30
|
+
"""Initialize with the given function."""
|
31
|
+
self._func = func
|
32
|
+
|
33
|
+
def __call__(self, *args, **kwargs):
|
34
|
+
"""Call the stored function with provided arguments."""
|
35
|
+
return self._func(*args, **kwargs)
|
36
|
+
|
37
|
+
def __call_method__(self, instance, *args, **kwargs):
|
38
|
+
"""Call the stored function with the instance and provided arguments."""
|
39
|
+
return self._func(instance, *args, **kwargs)
|
40
|
+
|
41
|
+
def __get__(self, obj, objtype):
|
42
|
+
"""Return a partially applied method."""
|
43
|
+
from functools import partial
|
44
|
+
|
45
|
+
func = partial(self.__call_method__, obj)
|
46
|
+
func._is_remote = True
|
47
|
+
func.__name__ = self._func.__name__
|
48
|
+
func._owner = obj
|
49
|
+
return func
|
50
|
+
|
51
|
+
|
52
|
+
class MethodType:
|
53
|
+
"""Enum for method or property types."""
|
54
|
+
|
55
|
+
METHOD = 0
|
56
|
+
PROP = 1
|
57
|
+
|
58
|
+
|
59
|
+
def try_get_remote_method(methodname: str, obj: typing.Any) -> typing.Tuple[str, typing.Callable]:
|
60
|
+
"""Try to get a remote method."""
|
61
|
+
method = getattr(obj, methodname)
|
62
|
+
if not callable(method):
|
63
|
+
return None
|
64
|
+
if hasattr(method, "_is_remote") and method._is_remote is True:
|
65
|
+
return (methodname, method)
|
66
|
+
|
67
|
+
|
68
|
+
def try_get_remote_property(attrname: str, obj: typing.Any) -> typing.Tuple[str, property]:
|
69
|
+
"""Try to get a remote property."""
|
70
|
+
objclass: typing.Type = obj.__class__
|
71
|
+
class_attribute = getattr(objclass, attrname)
|
72
|
+
getmethod = None
|
73
|
+
setmethod = None
|
74
|
+
|
75
|
+
if class_attribute.fget:
|
76
|
+
if isinstance(class_attribute.fget, remote_method):
|
77
|
+
getmethod = class_attribute.fget
|
78
|
+
getmethod._owner = obj
|
79
|
+
if class_attribute.fset:
|
80
|
+
if isinstance(class_attribute.fset, remote_method):
|
81
|
+
setmethod = class_attribute.fset
|
82
|
+
setmethod._owner = obj
|
83
|
+
|
84
|
+
return (attrname, property(getmethod, setmethod))
|
85
|
+
|
86
|
+
|
87
|
+
def get_remote_methods(
|
88
|
+
obj,
|
89
|
+
) -> typing.Generator[typing.Tuple[str, typing.Callable, MethodType], None, None]:
|
90
|
+
"""Yield names and methods of an object's remote methods.
|
91
|
+
|
92
|
+
A remote method is identified by the presence of an attribute `_is_remote` set to `True`.
|
93
|
+
|
94
|
+
Parameters
|
95
|
+
----------
|
96
|
+
obj: Any
|
97
|
+
The object to inspect for remote methods.
|
98
|
+
|
99
|
+
Yields
|
100
|
+
------
|
101
|
+
Generator[Tuple[str, Callable], None, None]
|
102
|
+
A tuple containing the method name and the method itself
|
103
|
+
for each remote method found in the object
|
104
|
+
"""
|
105
|
+
print(f"Getting remote methods on {obj}")
|
106
|
+
objclass = obj.__class__
|
107
|
+
for attrname in dir(obj):
|
108
|
+
if attrname.startswith("__"):
|
109
|
+
continue
|
110
|
+
print(attrname)
|
111
|
+
if hasattr(objclass, attrname):
|
112
|
+
class_attribute = getattr(objclass, attrname)
|
113
|
+
if isinstance(class_attribute, property):
|
114
|
+
attrname, prop = try_get_remote_property(attrname, obj)
|
115
|
+
yield attrname, prop, MethodType.PROP
|
116
|
+
continue
|
117
|
+
result = try_get_remote_method(attrname, obj)
|
118
|
+
if result != None:
|
119
|
+
attrname, method = result
|
120
|
+
yield attrname, method, MethodType.METHOD
|
@@ -22,6 +22,7 @@
|
|
22
22
|
|
23
23
|
"""Runtime initialize for pythonnet in embedding."""
|
24
24
|
|
25
|
+
from importlib.metadata import distribution
|
25
26
|
import os
|
26
27
|
|
27
28
|
from ansys.mechanical.core.embedding.logger import Logger
|
@@ -44,6 +45,25 @@ def __register_function_codec():
|
|
44
45
|
Ansys.Mechanical.CPython.Codecs.FunctionCodec.Register()
|
45
46
|
|
46
47
|
|
48
|
+
def _bind_assembly_for_explicit_interface(assembly_name: str):
|
49
|
+
"""Bind the assembly for explicit interface implementation."""
|
50
|
+
# if pythonnet is not installed, we can't bind the assembly
|
51
|
+
try:
|
52
|
+
distribution("pythonnet")
|
53
|
+
return
|
54
|
+
except ModuleNotFoundError:
|
55
|
+
pass
|
56
|
+
|
57
|
+
import clr
|
58
|
+
|
59
|
+
assembly = clr.AddReference(assembly_name)
|
60
|
+
from Python.Runtime import BindingManager, BindingOptions
|
61
|
+
|
62
|
+
binding_options = BindingOptions()
|
63
|
+
binding_options.AllowExplicitInterfaceImplementation = True
|
64
|
+
BindingManager.SetBindingOptions(assembly, binding_options)
|
65
|
+
|
66
|
+
|
47
67
|
def initialize(version: int) -> None:
|
48
68
|
"""Initialize the runtime.
|
49
69
|
|
@@ -59,3 +79,5 @@ def initialize(version: int) -> None:
|
|
59
79
|
# function codec is distributed with pymechanical on linux only
|
60
80
|
# at version 242 or later
|
61
81
|
__register_function_codec()
|
82
|
+
|
83
|
+
_bind_assembly_for_explicit_interface("Ansys.ACT.WB1")
|
@@ -88,7 +88,7 @@ def _vscode_impl(
|
|
88
88
|
The type of settings to update. Either "user" or "workspace" in VS Code.
|
89
89
|
By default, it's ``user``.
|
90
90
|
revision: int
|
91
|
-
The Mechanical revision number. For example, "
|
91
|
+
The Mechanical revision number. For example, "251".
|
92
92
|
If unspecified, it finds the default Mechanical version from ansys-tools-path.
|
93
93
|
"""
|
94
94
|
# Update the user or workspace settings
|
@@ -144,7 +144,7 @@ def _cli_impl(
|
|
144
144
|
The type of settings to update. Either "user" or "workspace" in VS Code.
|
145
145
|
By default, it's ``user``.
|
146
146
|
revision: int
|
147
|
-
The Mechanical revision number. For example, "
|
147
|
+
The Mechanical revision number. For example, "251".
|
148
148
|
If unspecified, it finds the default Mechanical version from ansys-tools-path.
|
149
149
|
"""
|
150
150
|
# Get the ansys-mechanical-stubs install location
|
@@ -178,7 +178,7 @@ def _cli_impl(
|
|
178
178
|
"--revision",
|
179
179
|
default=None,
|
180
180
|
type=int,
|
181
|
-
help='The Mechanical revision number, e.g. "
|
181
|
+
help='The Mechanical revision number, e.g. "251" or "242". If unspecified,\
|
182
182
|
it finds and uses the default version from ansys-tools-path.',
|
183
183
|
)
|
184
184
|
def cli(ide: str, target: str, revision: int) -> None:
|
@@ -192,14 +192,14 @@ def cli(ide: str, target: str, revision: int) -> None:
|
|
192
192
|
The type of settings to update. Either "user" or "workspace" in VS Code.
|
193
193
|
By default, it's ``user``.
|
194
194
|
revision: int
|
195
|
-
The Mechanical revision number. For example, "
|
195
|
+
The Mechanical revision number. For example, "251".
|
196
196
|
If unspecified, it finds the default Mechanical version from ansys-tools-path.
|
197
197
|
|
198
198
|
Usage
|
199
199
|
-----
|
200
200
|
The following example demonstrates the main use of this tool:
|
201
201
|
|
202
|
-
$ ansys-mechanical-ideconfig --ide vscode --target user --revision
|
202
|
+
$ ansys-mechanical-ideconfig --ide vscode --target user --revision 251
|
203
203
|
|
204
204
|
"""
|
205
205
|
exe = atp.get_mechanical_path(allow_input=False, version=revision)
|