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
@@ -44,10 +44,12 @@ from ansys.mechanical.core.logging import Logger
44
44
  LOG = Logger(level=logging.ERROR, to_file=False, to_stdout=True)
45
45
  """Create logger for package level use."""
46
46
 
47
- LOG.debug("Loaded logging module as LOG")
48
47
 
49
48
  from ansys.mechanical.core._version import __version__
49
+
50
+ # import few classes / functions
50
51
  from ansys.mechanical.core.mechanical import (
52
+ Mechanical,
51
53
  change_default_mechanical_path,
52
54
  close_all_local_instances,
53
55
  connect_to_mechanical,
@@ -55,9 +57,6 @@ from ansys.mechanical.core.mechanical import (
55
57
  launch_mechanical,
56
58
  )
57
59
 
58
- # import few classes / functions
59
- from ansys.mechanical.core.mechanical import Mechanical as Mechanical
60
-
61
60
  try:
62
61
  from ansys.mechanical.core.embedding import App, global_variables
63
62
 
@@ -30,6 +30,7 @@ import shutil
30
30
  import typing
31
31
  import warnings
32
32
 
33
+ from ansys.mechanical.core import LOG
33
34
  from ansys.mechanical.core.embedding import initializer, runtime
34
35
  from ansys.mechanical.core.embedding.addins import AddinConfiguration
35
36
  from ansys.mechanical.core.embedding.appdata import UniqueUserProfile
@@ -46,10 +47,10 @@ if typing.TYPE_CHECKING:
46
47
  try:
47
48
  import ansys.tools.visualization_interface # noqa: F401
48
49
 
49
- HAS_ANSYS_VIZ = True
50
+ HAS_ANSYS_GRAPHICS = True
50
51
  """Whether or not PyVista exists."""
51
52
  except ImportError:
52
- HAS_ANSYS_VIZ = False
53
+ HAS_ANSYS_GRAPHICS = False
53
54
 
54
55
 
55
56
  def _get_default_addin_configuration() -> AddinConfiguration:
@@ -89,6 +90,11 @@ def _start_application(configuration: AddinConfiguration, version, db_file) -> "
89
90
  return Ansys.Mechanical.Embedding.Application(db_file)
90
91
 
91
92
 
93
+ def is_initialized():
94
+ """Check if the app has been initialized."""
95
+ return len(INSTANCES) != 0
96
+
97
+
92
98
  class GetterWrapper(object):
93
99
  """Wrapper class around an attribute of an object."""
94
100
 
@@ -124,10 +130,17 @@ class App:
124
130
  private_appdata : bool, optional
125
131
  Setting for a temporary AppData directory. Default is False.
126
132
  Enables running parallel instances of Mechanical.
133
+ globals : dict, optional
134
+ Global variables to be updated. For example, globals().
135
+ Replaces "app.update_globals(globals())".
127
136
  config : AddinConfiguration, optional
128
137
  Configuration for addins. By default "Mechanical" is used and ACT Addins are disabled.
129
138
  copy_profile : bool, optional
130
139
  Whether to copy the user profile when private_appdata is True. Default is True.
140
+ enable_logging : bool, optional
141
+ Whether to enable logging. Default is True.
142
+ log_level : str, optional
143
+ The logging level for the application. Default is "WARNING".
131
144
 
132
145
  Examples
133
146
  --------
@@ -140,6 +153,10 @@ class App:
140
153
 
141
154
  >>> app = App(private_appdata=True, copy_profile=False)
142
155
 
156
+ Update the global variables with globals
157
+
158
+ >>> app = App(globals=globals())
159
+
143
160
  Create App with "Mechanical" configuration and no ACT Addins
144
161
 
145
162
  >>> from ansys.mechanical.core.embedding import AddinConfiguration
@@ -147,6 +164,13 @@ class App:
147
164
  >>> config = AddinConfiguration("Mechanical")
148
165
  >>> config.no_act_addins = True
149
166
  >>> app = App(config=config)
167
+
168
+ Set log level
169
+
170
+ >>> app = App(log_level='INFO')
171
+
172
+ ... INFO - - app - log_info - Starting Mechanical Application
173
+
150
174
  """
151
175
 
152
176
  def __init__(self, db_file=None, private_appdata=False, **kwargs):
@@ -154,15 +178,36 @@ class App:
154
178
  global INSTANCES
155
179
  from ansys.mechanical.core import BUILDING_GALLERY
156
180
 
181
+ self._enable_logging = kwargs.get("enable_logging", True)
182
+ if self._enable_logging:
183
+ self._log = LOG
184
+ self._log_level = kwargs.get("log_level", "WARNING")
185
+ self._log.setLevel(self._log_level)
186
+
187
+ self.log_info("Starting Mechanical Application")
188
+
189
+ # Get the globals dictionary from kwargs
190
+ globals = kwargs.get("globals")
191
+
192
+ # If the building gallery flag is set, we need to share the instance
193
+ # This can apply to running the `make -C doc html` command
157
194
  if BUILDING_GALLERY:
158
195
  if len(INSTANCES) != 0:
196
+ # Get the first instance of the app
159
197
  instance: App = INSTANCES[0]
198
+ # Point to the same underlying application object
160
199
  instance._share(self)
200
+ # Update the globals if provided in kwargs
201
+ if globals:
202
+ # The next line is covered by test_globals_kwarg_building_gallery
203
+ instance.update_globals(globals) # pragma: nocover
204
+ # Open the mechdb file if provided
161
205
  if db_file is not None:
162
206
  self.open(db_file)
163
207
  return
164
208
  if len(INSTANCES) > 0:
165
209
  raise Exception("Cannot have more than one embedded mechanical instance!")
210
+
166
211
  version = kwargs.get("version")
167
212
  if version is not None:
168
213
  try:
@@ -179,7 +224,6 @@ class App:
179
224
  new_profile_name = f"PyMechanical-{os.getpid()}"
180
225
  profile = UniqueUserProfile(new_profile_name, copy_profile=copy_profile)
181
226
  profile.update_environment(os.environ)
182
- atexit.register(_cleanup_private_appdata, profile)
183
227
 
184
228
  runtime.initialize(self._version)
185
229
  self._app = _start_application(configuration, self._version, db_file)
@@ -189,8 +233,16 @@ class App:
189
233
  self._disposed = False
190
234
  atexit.register(_dispose_embedded_app, INSTANCES)
191
235
  INSTANCES.append(self)
236
+
237
+ # Clean up the private appdata directory on exit if private_appdata is True
238
+ if private_appdata:
239
+ atexit.register(_cleanup_private_appdata, profile)
240
+
192
241
  self._updated_scopes: typing.List[typing.Dict[str, typing.Any]] = []
193
242
  self._subscribe()
243
+ self._messages = None
244
+ if globals:
245
+ self.update_globals(globals)
194
246
 
195
247
  def __repr__(self):
196
248
  """Get the product info."""
@@ -227,6 +279,7 @@ class App:
227
279
  remove_lock : bool, optional
228
280
  Whether or not to remove the lock file if it exists before opening the project file.
229
281
  """
282
+ self.log_info(f"Opening {db_file} ...")
230
283
  if remove_lock:
231
284
  lock_file = Path(self.DataModel.Project.ProjectDirectory) / ".mech_lock"
232
285
  # Remove the lock file if it exists before opening the project file
@@ -352,27 +405,27 @@ This may corrupt the project file.",
352
405
 
353
406
  def plotter(self) -> None:
354
407
  """Return ``ansys.tools.visualization_interface.Plotter`` object."""
355
- if not HAS_ANSYS_VIZ:
356
- warnings.warn(
357
- "Installation of viz option required! Use pip install ansys-mechanical-core[viz]"
408
+ if not HAS_ANSYS_GRAPHICS:
409
+ LOG.warning(
410
+ "Use ``pip install ansys-mechanical-core[graphics]`` to enable this option."
358
411
  )
359
412
  return
360
413
 
361
414
  if self.version < 242:
362
- warnings.warn("Plotting is only supported with version 2024R2 and later!")
415
+ LOG.warning("Plotting is only supported with version 2024R2 and later!")
363
416
  return
364
417
 
365
418
  # TODO Check if anything loaded inside app or else show warning and return
366
419
 
367
- from ansys.mechanical.core.embedding.viz.embedding_plotter import to_plotter
420
+ from ansys.mechanical.core.embedding.graphics.embedding_plotter import to_plotter
368
421
 
369
422
  return to_plotter(self)
370
423
 
371
424
  def plot(self) -> None:
372
425
  """Visualize the model in 3d.
373
426
 
374
- Requires installation using the viz option. E.g.
375
- pip install ansys-mechanical-core[viz]
427
+ Requires installation using the graphics option. E.g.
428
+ pip install ansys-mechanical-core[graphics]
376
429
 
377
430
  Examples
378
431
  --------
@@ -437,6 +490,15 @@ This may corrupt the project file.",
437
490
  """Returns the current project directory."""
438
491
  return self.DataModel.Project.ProjectDirectory
439
492
 
493
+ @property
494
+ def messages(self):
495
+ """Lazy-load the MessageManager."""
496
+ if self._messages is None:
497
+ from ansys.mechanical.core.embedding.messages import MessageManager
498
+
499
+ self._messages = MessageManager(self._app)
500
+ return self._messages
501
+
440
502
  def _share(self, other) -> None:
441
503
  """Shares the state of self with other.
442
504
 
@@ -580,8 +642,7 @@ This may corrupt the project file.",
580
642
  Examples
581
643
  --------
582
644
  >>> from ansys.mechanical.core import App
583
- >>> app = App()
584
- >>> app.update_globals(globals())
645
+ >>> app = App(globals=globals())
585
646
  >>> app.print_tree()
586
647
  ... ├── Project
587
648
  ... | ├── Model
@@ -608,3 +669,27 @@ This may corrupt the project file.",
608
669
  node = self.DataModel.Project
609
670
 
610
671
  self._print_tree(node, max_lines, lines_count, indentation)
672
+
673
+ def log_debug(self, message):
674
+ """Log the debug message."""
675
+ if not self._enable_logging:
676
+ return
677
+ self._log.debug(message)
678
+
679
+ def log_info(self, message):
680
+ """Log the info message."""
681
+ if not self._enable_logging:
682
+ return
683
+ self._log.info(message)
684
+
685
+ def log_warning(self, message):
686
+ """Log the warning message."""
687
+ if not self._enable_logging:
688
+ return
689
+ self._log.warning(message)
690
+
691
+ def log_error(self, message):
692
+ """Log the error message."""
693
+ if not self._enable_logging:
694
+ return
695
+ self._log.error(message)
@@ -22,7 +22,7 @@
22
22
 
23
23
  """Temporary Appdata for Ansys Mechanical."""
24
24
 
25
- import os
25
+ from pathlib import Path
26
26
  import shutil
27
27
  import sys
28
28
  import warnings
@@ -33,8 +33,8 @@ class UniqueUserProfile:
33
33
 
34
34
  def __init__(self, profile_name: str, copy_profile: bool = True, dry_run: bool = False):
35
35
  """Initialize UniqueUserProfile class."""
36
- self._default_profile = os.path.expanduser("~")
37
- self._location = os.path.join(self._default_profile, "PyMechanical-AppData", profile_name)
36
+ self._default_profile = Path("~").expanduser()
37
+ self._location = self._default_profile / "PyMechanical-AppData" / profile_name
38
38
  self._dry_run = dry_run
39
39
  self.copy_profile = copy_profile
40
40
  self.initialize()
@@ -58,15 +58,14 @@ class UniqueUserProfile:
58
58
  """Cleanup unique user profile."""
59
59
  if self._dry_run:
60
60
  return
61
- text = "The `private_appdata` option was used, but the following files were not removed: "
62
- message = []
63
61
 
64
- def onerror(function, path, excinfo):
65
- if len(message) == 0:
66
- message.append(f"{text}{path}")
67
- warnings.warn(message[0])
62
+ # Remove the appdata directory if it exists
63
+ shutil.rmtree(self.location, ignore_errors=True)
68
64
 
69
- shutil.rmtree(self.location, onerror=onerror)
65
+ if self.location.is_dir():
66
+ warnings.warn(
67
+ f"The `private appdata` option was used, but {self.location} was not removed"
68
+ )
70
69
 
71
70
  @property
72
71
  def location(self) -> str:
@@ -77,28 +76,32 @@ class UniqueUserProfile:
77
76
  """Set environment variables for new user profile."""
78
77
  home = self.location
79
78
  if "win" in sys.platform:
80
- env["USERPROFILE"] = home
81
- env["APPDATA"] = os.path.join(home, "AppData/Roaming")
82
- env["LOCALAPPDATA"] = os.path.join(home, "AppData/Local")
83
- env["TMP"] = os.path.join(home, "AppData/Local/Temp")
84
- env["TEMP"] = os.path.join(home, "AppData/Local/Temp")
79
+ appdata_dir = home / "AppData"
80
+ appdata_local_temp = str(appdata_dir / "Local" / "Temp")
81
+
82
+ env["USERPROFILE"] = str(home)
83
+ env["APPDATA"] = str(appdata_dir / "Roaming")
84
+ env["LOCALAPPDATA"] = str(appdata_dir / "Local")
85
+ env["TMP"] = appdata_local_temp
86
+ env["TEMP"] = appdata_local_temp
85
87
  elif "lin" in sys.platform:
86
- env["HOME"] = home
88
+ env["HOME"] = str(home)
87
89
 
88
90
  def exists(self) -> bool:
89
91
  """Check if unique profile name already exists."""
90
- return os.path.exists(self.location)
92
+ return self.location.exists()
91
93
 
92
94
  def mkdirs(self) -> None:
93
95
  """Create a unique user profile & set up the directory tree."""
94
- os.makedirs(self.location, exist_ok=True)
96
+ self.location.mkdir(parents=True, exist_ok=True)
95
97
  if "win" in sys.platform:
96
98
  locs = ["AppData/Roaming", "AppData/Local", "Documents"]
97
99
  elif "lin" in sys.platform:
98
100
  locs = [".config", "temp/reports"]
99
101
 
100
102
  for loc in locs:
101
- os.makedirs(os.path.join(self.location, loc))
103
+ dir_name = self.location / loc
104
+ dir_name.mkdir(parents=True, exist_ok=True)
102
105
 
103
106
  def copy_profiles(self) -> None:
104
107
  """Copy current user directories into a new user profile."""
@@ -107,6 +110,7 @@ class UniqueUserProfile:
107
110
  elif "lin" in sys.platform:
108
111
  locs = [".mw/Application Data/Ansys", ".config/Ansys"]
109
112
  for loc in locs:
110
- shutil.copytree(
111
- os.path.join(self._default_profile, loc), os.path.join(self.location, loc)
112
- )
113
+ default_profile_loc = self._default_profile / loc
114
+ temp_appdata_loc = self.location / loc
115
+ if default_profile_loc.exists():
116
+ shutil.copytree(default_profile_loc, temp_appdata_loc)
@@ -26,6 +26,11 @@ A useful subset of what is imported by
26
26
  Ansys Inc/v{NNN}/ACT/apis/Mechanical.py
27
27
  """
28
28
 
29
+ from ansys.mechanical.core.embedding.app import is_initialized
30
+
31
+ if not is_initialized():
32
+ raise Exception("Enums cannot be imported until the embedded app is initialized.")
33
+
29
34
  import clr
30
35
 
31
36
  clr.AddReference("Ansys.Mechanical.DataModel")
@@ -0,0 +1,50 @@
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
+ """Import Mechanical globals."""
23
+
24
+ from ansys.mechanical.core.embedding.app import is_initialized
25
+
26
+ if not is_initialized():
27
+ raise Exception("Globals cannot be imported until the embedded app is initialized.")
28
+
29
+ import clr
30
+
31
+ clr.AddReference("Ansys.Mechanical.DataModel")
32
+ clr.AddReference("Ansys.ACT.Interfaces")
33
+
34
+
35
+ clr.AddReference("System.Collections")
36
+ clr.AddReference("Ansys.ACT.WB1")
37
+ clr.AddReference("Ansys.Mechanical.DataModel")
38
+
39
+ # from Ansys.ACT.Mechanical import Transaction
40
+ # When ansys-pythonnet issue #14 is fixed, uncomment above
41
+ from Ansys.ACT.Core.Math import Point2D, Point3D # noqa isort: skip
42
+ from Ansys.ACT.Math import Vector3D # noqa isort: skip
43
+ from Ansys.Core.Units import Quantity # noqa isort: skip
44
+ from Ansys.Mechanical.DataModel import MechanicalEnums # noqa isort: skip
45
+ from Ansys.Mechanical.Graphics import Point, SectionPlane # noqa isort: skip
46
+
47
+ from ansys.mechanical.core.embedding.transaction import Transaction # noqa isort: skip
48
+
49
+ import System # noqa isort: skip
50
+ import Ansys # noqa isort: skip
@@ -82,7 +82,7 @@ def to_plotter(app: "ansys.mechanical.core.embedding.App"):
82
82
  if np_coordinates is None or np_indices is None:
83
83
  continue
84
84
  pv_transform = _transform_to_pyvista(scenegraph_node.Transform)
85
- polydata = pv.PolyData(np_coordinates, np_indices).transform(pv_transform)
85
+ polydata = pv.PolyData(np_coordinates, np_indices).transform(pv_transform, inplace=True)
86
86
  color = pv.Color(bgr_to_rgb_tuple(body.Color))
87
87
  plotter.plot(polydata, color=color, smooth_shading=True)
88
88
  return plotter
@@ -46,36 +46,37 @@ def global_variables(app: "ansys.mechanical.core.App", enums: bool = False) -> t
46
46
  To also import all the enums, set the parameter enums to true.
47
47
  """
48
48
  vars = global_entry_points(app)
49
- import clr # isort: skip
50
-
51
- clr.AddReference("System.Collections")
52
- clr.AddReference("Ansys.ACT.WB1")
53
- clr.AddReference("Ansys.Mechanical.DataModel")
54
- # from Ansys.ACT.Mechanical import Transaction
55
- # When ansys-pythonnet issue #14 is fixed, uncomment above
56
- from Ansys.ACT.Core.Math import Point2D, Point3D
57
- from Ansys.ACT.Math import Vector3D
58
- from Ansys.ACT.Mechanical.Fields import VariableDefinitionType
59
- from Ansys.Core.Units import Quantity
60
- from Ansys.Mechanical.DataModel import MechanicalEnums
61
- from Ansys.Mechanical.Graphics import Point, SectionPlane
62
-
63
- import System # isort: skip
64
- import Ansys # isort: skip
65
-
66
- vars["Quantity"] = Quantity
67
- vars["System"] = System
68
- vars["Ansys"] = Ansys
49
+
50
+ from ansys.mechanical.core.embedding.app import is_initialized
51
+ from ansys.mechanical.core.embedding.transaction import Transaction
52
+
69
53
  vars["Transaction"] = Transaction
70
- vars["MechanicalEnums"] = MechanicalEnums
71
- # Graphics
72
- vars["Point"] = Point
73
- vars["SectionPlane"] = SectionPlane
74
- # Math
75
- vars["Point2D"] = Point2D
76
- vars["Point3D"] = Point3D
77
- vars["Vector3D"] = Vector3D
78
- vars["VariableDefinitionType"] = VariableDefinitionType
54
+
55
+ # Import modules if the app is initialized
56
+ if is_initialized():
57
+ from ansys.mechanical.core.embedding.global_importer import (
58
+ Ansys,
59
+ MechanicalEnums,
60
+ Point,
61
+ Point2D,
62
+ Point3D,
63
+ Quantity,
64
+ SectionPlane,
65
+ System,
66
+ Vector3D,
67
+ )
68
+
69
+ vars["Quantity"] = Quantity
70
+ vars["System"] = System
71
+ vars["Ansys"] = Ansys
72
+ vars["MechanicalEnums"] = MechanicalEnums
73
+ # Graphics
74
+ vars["Point"] = Point
75
+ vars["SectionPlane"] = SectionPlane
76
+ # Math
77
+ vars["Point2D"] = Point2D
78
+ vars["Point3D"] = Point3D
79
+ vars["Vector3D"] = Vector3D
79
80
 
80
81
  if enums:
81
82
  vars.update(get_all_enums())
@@ -95,32 +96,3 @@ def get_all_enums() -> typing.Dict[str, typing.Any]:
95
96
  if type(the_enum).__name__ == "CLRMetatype":
96
97
  enums[attr] = the_enum
97
98
  return enums
98
-
99
-
100
- class Transaction: # When ansys-pythonnet issue #14 is fixed, this class will be removed
101
- """
102
- A class to speed up bulk user interactions using Ansys ACT Mechanical Transaction.
103
-
104
- Example
105
- -------
106
- >>> with Transaction() as transaction:
107
- ... pass # Perform bulk user interactions here
108
- ...
109
- """
110
-
111
- def __init__(self):
112
- """Initialize the Transaction class."""
113
- import clr
114
-
115
- clr.AddReference("Ansys.ACT.WB1")
116
- import Ansys
117
-
118
- self._transaction = Ansys.ACT.Mechanical.Transaction()
119
-
120
- def __enter__(self):
121
- """Enter the context of the transaction."""
122
- return self
123
-
124
- def __exit__(self, exc_type, exc_val, exc_tb):
125
- """Exit the context of the transaction and disposes of resources."""
126
- self._transaction.Dispose()
@@ -30,6 +30,7 @@ import warnings
30
30
 
31
31
  from ansys.mechanical.core.embedding.loader import load_clr
32
32
  from ansys.mechanical.core.embedding.resolver import resolve
33
+ from ansys.mechanical.core.mechanical import LOG
33
34
 
34
35
  INITIALIZED_VERSION = None
35
36
  """Constant for the initialized version."""
@@ -85,7 +86,6 @@ def _get_latest_default_version() -> int:
85
86
  If multiple versions are detected, select the latest one, as no specific version is provided.
86
87
  """
87
88
  awp_roots = [value for key, value in os.environ.items() if key.startswith("AWP_ROOT")]
88
-
89
89
  if not awp_roots:
90
90
  raise Exception("No Mechanical installations found.")
91
91
 
@@ -94,13 +94,15 @@ def _get_latest_default_version() -> int:
94
94
  folder = os.path.basename(os.path.normpath(path))
95
95
  version = folder.split("v")[-1]
96
96
  versions_found.append(int(version))
97
+
98
+ LOG.info(f"Available versions of Mechanical: {versions_found}")
99
+
97
100
  latest_version = max(versions_found)
98
101
 
99
102
  if len(awp_roots) > 1:
100
- warnings.warn(
103
+ LOG.warning(
101
104
  f"Multiple versions of Mechanical found! Using latest version {latest_version} ..."
102
105
  )
103
-
104
106
  return latest_version
105
107
 
106
108
 
@@ -110,6 +112,74 @@ def __check_python_interpreter_architecture() -> None:
110
112
  raise Exception("Mechanical Embedding requires a 64-bit Python environment.")
111
113
 
112
114
 
115
+ def __windows_store_workaround(version: int) -> None:
116
+ """Workaround for Windows store.
117
+
118
+ See https://github.com/ansys/pymechanical/issues/1136
119
+
120
+ Windows store Python uses the Win32 API SetDefaultDllDirectories
121
+ so that the PATH environment variable isn't scanned for any DLL
122
+ dependency.
123
+
124
+ PyMechanical loads the embedding library which automatically sets
125
+ these Paths, but this uses the PATH environment variable which doesn't
126
+ work for Windows store Python.
127
+
128
+ We provide a workaround for versions 2024R2 and 2025R1 that sets
129
+ these paths using `os.add_dll_directory`.
130
+
131
+ Note: This workaround does not include DLL paths used in FSI
132
+ mapping workflows.
133
+
134
+ Parameters
135
+ ----------
136
+ version : int
137
+ The version of Mechanical to set the DLL paths for.
138
+ """
139
+ # Nothing to do on Linux
140
+ if os.name != "nt":
141
+ return
142
+
143
+ # Nothing to do if it isn't a Windows store application
144
+ if r"Microsoft\WindowsApps" not in sys.executable:
145
+ return
146
+
147
+ # Get the AWP_ROOT environment variable for the specified version
148
+ awp_root = Path(os.environ[f"AWP_ROOT{version}"])
149
+ # Set paths to the aisol and framework DLLs
150
+ paths = [
151
+ awp_root / "aisol" / "bin" / "winx64",
152
+ awp_root / "Framework" / "bin" / "Win64",
153
+ ]
154
+ # Set the path to the tp directory within the AWP_ROOTXYZ directory
155
+ awp_root_tp = awp_root / "tp"
156
+ # Add paths to the IntelCompiler, IntelMKL, HDF5, and Qt DLLs for 2024R2 and 2025R1
157
+ if version == 242:
158
+ paths.extend(
159
+ [
160
+ awp_root_tp / "IntelCompiler" / "2023.1.0" / "winx64",
161
+ awp_root_tp / "IntelMKL" / "2023.1.0" / "winx64",
162
+ awp_root_tp / "hdf5" / "1.12.2" / "winx64",
163
+ awp_root_tp / "qt" / "5.15.16" / "winx64" / "bin",
164
+ ]
165
+ )
166
+ elif version == 251:
167
+ paths.extend(
168
+ [
169
+ awp_root_tp / "IntelCompiler" / "2023.1.0" / "winx64",
170
+ awp_root_tp / "IntelMKL" / "2023.1.0" / "winx64",
171
+ awp_root_tp / "hdf5" / "1.12.2" / "winx64",
172
+ awp_root_tp / "qt" / "5.15.17" / "winx64" / "bin",
173
+ ]
174
+ )
175
+ else:
176
+ return
177
+
178
+ # Add each path to the DLL search path
179
+ for path in paths:
180
+ os.add_dll_directory(str(path))
181
+
182
+
113
183
  def __set_environment(version: int) -> None:
114
184
  """Set environment variables to configure embedding."""
115
185
  if os.name == "nt": # pragma: no cover
@@ -159,7 +229,7 @@ def __check_loaded_libs(version: int = None): # pragma: no cover
159
229
  # For 2025 R1, PyMechanical will crash on shutdown if libX11.so is already loaded
160
230
  # before starting Mechanical
161
231
  if __is_lib_loaded("libX11.so"):
162
- warnings.warn(
232
+ LOG.warning(
163
233
  "libX11.so is loaded prior to initializing the Embedded Instance of Mechanical.\
164
234
  Python will crash on shutdown..."
165
235
  )
@@ -186,6 +256,8 @@ def initialize(version: int = None):
186
256
 
187
257
  __set_environment(version)
188
258
 
259
+ __windows_store_workaround(version)
260
+
189
261
  __check_loaded_libs(version)
190
262
 
191
263
  __workaround_material_server(version)