ansys-mechanical-core 0.11.12__py3-none-any.whl → 0.11.14__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 (29) hide show
  1. ansys/mechanical/core/__init__.py +0 -1
  2. ansys/mechanical/core/_version.py +48 -48
  3. ansys/mechanical/core/embedding/app.py +681 -610
  4. ansys/mechanical/core/embedding/background.py +11 -2
  5. ansys/mechanical/core/embedding/enum_importer.py +0 -1
  6. ansys/mechanical/core/embedding/global_importer.py +50 -0
  7. ansys/mechanical/core/embedding/imports.py +30 -58
  8. ansys/mechanical/core/embedding/initializer.py +6 -4
  9. ansys/mechanical/core/embedding/logger/__init__.py +219 -219
  10. ansys/mechanical/core/embedding/messages.py +190 -0
  11. ansys/mechanical/core/embedding/resolver.py +48 -41
  12. ansys/mechanical/core/embedding/rpc/__init__.py +36 -0
  13. ansys/mechanical/core/embedding/rpc/client.py +259 -0
  14. ansys/mechanical/core/embedding/rpc/server.py +403 -0
  15. ansys/mechanical/core/embedding/rpc/utils.py +118 -0
  16. ansys/mechanical/core/embedding/runtime.py +22 -0
  17. ansys/mechanical/core/embedding/transaction.py +51 -0
  18. ansys/mechanical/core/feature_flags.py +1 -0
  19. ansys/mechanical/core/ide_config.py +226 -212
  20. ansys/mechanical/core/mechanical.py +2399 -2324
  21. ansys/mechanical/core/misc.py +176 -176
  22. ansys/mechanical/core/pool.py +712 -712
  23. ansys/mechanical/core/run.py +321 -321
  24. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/METADATA +40 -27
  25. ansys_mechanical_core-0.11.14.dist-info/RECORD +52 -0
  26. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/WHEEL +1 -1
  27. ansys_mechanical_core-0.11.12.dist-info/RECORD +0 -45
  28. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/entry_points.txt +0 -0
  29. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,403 @@
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 threading
27
+ import time
28
+ import typing
29
+
30
+ import rpyc
31
+ from rpyc.utils.server import ThreadedServer
32
+ import toolz
33
+
34
+ from ansys.mechanical.core.embedding.background import BackgroundApp
35
+ from ansys.mechanical.core.mechanical import port_in_use
36
+
37
+ from .utils import MethodType, get_remote_methods, remote_method
38
+
39
+ # TODO : implement logging
40
+
41
+ PYMECHANICAL_DEFAULT_RPC_PORT = 20000
42
+
43
+
44
+ class MechanicalService(rpyc.Service):
45
+ """Starts Mechanical app services."""
46
+
47
+ def __init__(self, backgroundapp, functions=[], impl=None):
48
+ """Initialize the service."""
49
+ super().__init__()
50
+ self._backgroundapp = backgroundapp
51
+ self._install_functions(functions)
52
+ self._install_class(impl)
53
+ self.EMBEDDED = True
54
+
55
+ def on_connect(self, conn):
56
+ """Handle client connection."""
57
+ print("Client connected")
58
+
59
+ def on_disconnect(self, conn):
60
+ """Handle client disconnection."""
61
+ print("Client disconnected")
62
+
63
+ def _install_functions(self, methods):
64
+ """Install the given list of methods."""
65
+ [self._install_function(method) for method in methods]
66
+
67
+ def _install_class(self, impl):
68
+ """Install methods from the given implemented class."""
69
+ if impl is None:
70
+ return
71
+ for methodname, method, methodtype in get_remote_methods(impl):
72
+ if methodtype == MethodType.METHOD:
73
+ self._install_method(method)
74
+ elif methodtype == MethodType.PROP:
75
+ self._install_property(method, methodname)
76
+
77
+ def _curry_property(self, prop, propname, get: bool):
78
+ """Curry the given property."""
79
+
80
+ def posted(*arg):
81
+ def curried():
82
+ if get:
83
+ return getattr(prop._owner, propname)
84
+ else:
85
+ setattr(prop._owner, propname, *arg)
86
+
87
+ return self._backgroundapp.try_post(curried)
88
+
89
+ return posted
90
+
91
+ def _curry_method(self, method, realmethodname):
92
+ """Curry the given method."""
93
+
94
+ def posted(*args, **kwargs):
95
+ def curried():
96
+ original_method = getattr(method._owner, realmethodname)
97
+ result = original_method(*args, **kwargs)
98
+ return result
99
+
100
+ return self._backgroundapp.try_post(curried)
101
+
102
+ return posted
103
+
104
+ def _curry_function(self, methodname):
105
+ """Curry the given function."""
106
+ wrapped = getattr(self, methodname)
107
+ curried_method = toolz.curry(wrapped)
108
+
109
+ def posted(*args, **kwargs):
110
+ def curried():
111
+ return curried_method(self._app, *args, **kwargs)
112
+
113
+ return self._backgroundapp.try_post(curried)
114
+
115
+ return posted
116
+
117
+ def _install_property(self, prop: property, propname: str):
118
+ """Install property with inner and exposed pairs."""
119
+ if prop.fget:
120
+ exposed_get_name = f"exposed_propget_{propname}"
121
+
122
+ def exposed_propget():
123
+ """Convert to exposed getter."""
124
+ f = self._curry_property(prop.fget, propname, True)
125
+ result = f()
126
+ return result
127
+
128
+ setattr(self, exposed_get_name, exposed_propget)
129
+ if prop.fset:
130
+ exposed_set_name = f"exposed_propset_{propname}"
131
+
132
+ def exposed_propset(arg):
133
+ """Convert to exposed getter."""
134
+ f = self._curry_property(prop.fset, propname, True)
135
+ result = f(arg)
136
+ return result
137
+
138
+ setattr(self, exposed_set_name, exposed_propset)
139
+
140
+ def _install_method(self, method):
141
+ methodname = method.__name__
142
+ self._install_method_with_name(method, methodname, methodname)
143
+
144
+ def _install_method_with_name(self, method, methodname, innername):
145
+ """Install methods of impl with inner and exposed pairs."""
146
+ exposed_name = f"exposed_{methodname}"
147
+
148
+ def exposed_method(*args, **kwargs):
149
+ """Convert to exposed method."""
150
+ f = self._curry_method(method, innername)
151
+ result = f(*args, **kwargs)
152
+ return result
153
+
154
+ setattr(self, exposed_name, exposed_method)
155
+
156
+ def _install_function(self, function):
157
+ """Install a functions with inner and exposed pairs."""
158
+ exposed_name = f"exposed_{function.__name__}"
159
+ inner_name = f"inner_{function.__name__}"
160
+
161
+ def inner_method(app, *args, **kwargs):
162
+ """Convert to inner method."""
163
+ return function(app, *args, **kwargs)
164
+
165
+ def exposed_method(*args, **kwargs):
166
+ """Convert to exposed method."""
167
+ f = self._curry_function(inner_name)
168
+ return f(*args, **kwargs)
169
+
170
+ setattr(self, inner_name, inner_method)
171
+ setattr(self, exposed_name, exposed_method)
172
+
173
+ def exposed_service_upload(self, remote_path, file_data):
174
+ """Handle file upload request from client."""
175
+ if not remote_path:
176
+ raise ValueError("The remote file path is empty.")
177
+
178
+ remote_dir = os.path.dirname(remote_path)
179
+
180
+ if remote_dir:
181
+ os.makedirs(remote_dir, exist_ok=True)
182
+
183
+ with open(remote_path, "wb") as f:
184
+ f.write(file_data)
185
+
186
+ print(f"File {remote_path} uploaded successfully.")
187
+
188
+ def exposed_service_download(self, remote_path):
189
+ """Handle file download request from client."""
190
+ # Check if the remote file exists
191
+ if not os.path.exists(remote_path):
192
+ raise FileNotFoundError(f"The file {remote_path} does not exist on the server.")
193
+
194
+ if os.path.isdir(remote_path):
195
+ files = []
196
+ for dirpath, _, filenames in os.walk(remote_path):
197
+ for filename in filenames:
198
+ full_path = os.path.join(dirpath, filename)
199
+ relative_path = os.path.relpath(full_path, remote_path)
200
+ files.append(relative_path)
201
+ return {"is_directory": True, "files": files}
202
+
203
+ with open(remote_path, "rb") as f:
204
+ file_data = f.read()
205
+
206
+ print(f"File {remote_path} downloaded successfully.")
207
+ return file_data
208
+
209
+ def exposed_service_exit(self):
210
+ """Exit the server."""
211
+ print("Shutting down server ...")
212
+ self._backgroundapp.stop()
213
+ self._backgroundapp = None
214
+ self._server.stop_async()
215
+ print("Server stopped")
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
+ self._exit_thread: threading.Thread = None
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
+ my_service._server = self
245
+
246
+ @staticmethod
247
+ def get_free_port(port=None):
248
+ """Get free port.
249
+
250
+ If port is not given, it will find a free port starting from PYMECHANICAL_DEFAULT_RPC_PORT.
251
+ """
252
+ if port is None:
253
+ port = PYMECHANICAL_DEFAULT_RPC_PORT
254
+
255
+ while port_in_use(port):
256
+ port += 1
257
+
258
+ return port
259
+
260
+ def start(self) -> None:
261
+ """Start server on specified port."""
262
+ print(
263
+ f"Starting mechanical application in server.\n"
264
+ f"Listening on port {self._port}\n{self._background_app.app}"
265
+ )
266
+ self._server.start()
267
+ """try:
268
+ try:
269
+ conn.serve_all()
270
+ except KeyboardInterrupt:
271
+ print("User interrupt!")
272
+ finally:
273
+ conn.close()"""
274
+ print("Server exited!")
275
+ self._wait_exit()
276
+ self._exited = True
277
+
278
+ def _wait_exit(self) -> None:
279
+ if self._exit_thread is None:
280
+ return
281
+ self._exit_thread.join()
282
+
283
+ def stop_async(self):
284
+ """Return immediately but will stop the server."""
285
+
286
+ def stop_f(): # wait for all connections to close
287
+ while len(self._server.clients) > 0:
288
+ time.sleep(0.005)
289
+ self._background_app.stop()
290
+ self._server.close()
291
+ self._exited = True
292
+
293
+ self._exit_thread = threading.Thread(target=stop_f)
294
+ self._exit_thread.start()
295
+
296
+ def stop(self) -> None:
297
+ """Stop the server."""
298
+ print("Stopping the server...")
299
+ self._background_app.stop()
300
+ self._server.close()
301
+ self._exited = True
302
+ print("Server stopped.")
303
+
304
+
305
+ class DefaultServiceMethods:
306
+ """Default service methods for MechanicalEmbeddedServer."""
307
+
308
+ def __init__(self, app):
309
+ """Initialize the DefaultServiceMethods."""
310
+ self._app = app
311
+
312
+ def __repr__(self):
313
+ """Return the representation of the instance."""
314
+ return '"ServiceMethods instance"'
315
+
316
+ @remote_method
317
+ def run_python_script(
318
+ self, script: str, enable_logging=False, log_level="WARNING", progress_interval=2000
319
+ ):
320
+ """Run scripts using Internal python engine."""
321
+ result = self._app.execute_script(script)
322
+ return result
323
+
324
+ @remote_method
325
+ def run_python_script_from_file(
326
+ self,
327
+ file_path: str,
328
+ enable_logging=False,
329
+ log_level="WARNING",
330
+ progress_interval=2000,
331
+ ):
332
+ """Run scripts using Internal python engine."""
333
+ return self._app.execute_script_from_file(file_path)
334
+
335
+ @remote_method
336
+ def clear(self):
337
+ """Clear the current project."""
338
+ self._app.new()
339
+
340
+ @property
341
+ @remote_method
342
+ def project_directory(self):
343
+ """Get the project directory."""
344
+ return self._app.execute_script("""ExtAPI.DataModel.Project.ProjectDirectory""")
345
+
346
+ @remote_method
347
+ def list_files(self):
348
+ """List all files in the project directory."""
349
+ list = []
350
+ mechdbPath = self._app.execute_script("""ExtAPI.DataModel.Project.FilePath""")
351
+ if mechdbPath != "":
352
+ list.append(mechdbPath)
353
+ rootDir = self._app.execute_script("""ExtAPI.DataModel.Project.ProjectDirectory""")
354
+
355
+ for dirPath, dirNames, fileNames in os.walk(rootDir):
356
+ for fileName in fileNames:
357
+ list.append(os.path.join(dirPath, fileName))
358
+ files_out = "\n".join(list).splitlines()
359
+ if not files_out: # pragma: no cover
360
+ print("No files listed")
361
+ return files_out
362
+
363
+ @remote_method
364
+ def _get_files(self, files, recursive=False):
365
+ self_files = self.list_files() # to avoid calling it too much
366
+
367
+ if isinstance(files, str):
368
+ if files in self_files:
369
+ list_files = [files]
370
+ elif "*" in files:
371
+ list_files = fnmatch.filter(self_files, files)
372
+ if not list_files:
373
+ raise ValueError(
374
+ f"The `'files'` parameter ({files}) didn't match any file using "
375
+ f"glob expressions in the remote server."
376
+ )
377
+ else:
378
+ raise ValueError(
379
+ f"The `'files'` parameter ('{files}') does not match any file or pattern."
380
+ )
381
+
382
+ elif isinstance(files, (list, tuple)):
383
+ if not all([isinstance(each, str) for each in files]):
384
+ raise ValueError(
385
+ "The parameter `'files'` can be a list or tuple, but it "
386
+ "should only contain strings."
387
+ )
388
+ list_files = files
389
+ else:
390
+ raise ValueError(
391
+ f"The `file` parameter type ({type(files)}) is not supported."
392
+ "Only strings, tuple of strings, or list of strings are allowed."
393
+ )
394
+
395
+ return list_files
396
+
397
+
398
+ class MechanicalDefaultServer(MechanicalEmbeddedServer):
399
+ """Default server with default service methods."""
400
+
401
+ def __init__(self, **kwargs):
402
+ """Initialize the MechanicalDefaultServer."""
403
+ super().__init__(service=MechanicalService, impl=DefaultServiceMethods, **kwargs)
@@ -0,0 +1,118 @@
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
+ objclass = obj.__class__
106
+ for attrname in dir(obj):
107
+ if attrname.startswith("__"):
108
+ continue
109
+ if hasattr(objclass, attrname):
110
+ class_attribute = getattr(objclass, attrname)
111
+ if isinstance(class_attribute, property):
112
+ attrname, prop = try_get_remote_property(attrname, obj)
113
+ yield attrname, prop, MethodType.PROP
114
+ continue
115
+ result = try_get_remote_method(attrname, obj)
116
+ if result != None:
117
+ attrname, method = result
118
+ 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")
@@ -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()
@@ -31,6 +31,7 @@ class FeatureFlags:
31
31
 
32
32
  ThermalShells = "Mechanical.ThermalShells"
33
33
  MultistageHarmonic = "Mechanical.MultistageHarmonic"
34
+ CPython = "Mechanical.CPython.Capability"
34
35
 
35
36
 
36
37
  def get_feature_flag_names() -> typing.List[str]: