ansys-mechanical-core 0.11.14__py3-none-any.whl → 0.11.16__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 (24) hide show
  1. ansys/mechanical/core/__init__.py +3 -3
  2. ansys/mechanical/core/embedding/app.py +26 -12
  3. ansys/mechanical/core/embedding/appdata.py +26 -22
  4. ansys/mechanical/core/embedding/enum_importer.py +6 -0
  5. ansys/mechanical/core/embedding/{viz → graphics}/embedding_plotter.py +1 -1
  6. ansys/mechanical/core/embedding/initializer.py +70 -0
  7. ansys/mechanical/core/embedding/messages.py +27 -22
  8. ansys/mechanical/core/embedding/resolver.py +1 -1
  9. ansys/mechanical/core/embedding/rpc/__init__.py +3 -7
  10. ansys/mechanical/core/embedding/rpc/client.py +19 -5
  11. ansys/mechanical/core/embedding/rpc/default_server.py +131 -0
  12. ansys/mechanical/core/embedding/rpc/server.py +146 -158
  13. ansys/mechanical/core/embedding/rpc/utils.py +18 -0
  14. ansys/mechanical/core/embedding/runtime.py +6 -0
  15. ansys/mechanical/core/ide_config.py +1 -0
  16. ansys/mechanical/core/mechanical.py +24 -12
  17. {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/METADATA +15 -12
  18. {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/RECORD +24 -23
  19. {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/WHEEL +1 -1
  20. /ansys/mechanical/core/embedding/{viz → graphics}/__init__.py +0 -0
  21. /ansys/mechanical/core/embedding/{viz → graphics}/usd_converter.py +0 -0
  22. /ansys/mechanical/core/embedding/{viz → graphics}/utils.py +0 -0
  23. {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/entry_points.txt +0 -0
  24. {ansys_mechanical_core-0.11.14.dist-info → ansys_mechanical_core-0.11.16.dist-info}/licenses/LICENSE +0 -0
@@ -46,7 +46,10 @@ LOG = Logger(level=logging.ERROR, to_file=False, to_stdout=True)
46
46
 
47
47
 
48
48
  from ansys.mechanical.core._version import __version__
49
+
50
+ # import few classes / functions
49
51
  from ansys.mechanical.core.mechanical import (
52
+ Mechanical,
50
53
  change_default_mechanical_path,
51
54
  close_all_local_instances,
52
55
  connect_to_mechanical,
@@ -54,9 +57,6 @@ from ansys.mechanical.core.mechanical import (
54
57
  launch_mechanical,
55
58
  )
56
59
 
57
- # import few classes / functions
58
- from ansys.mechanical.core.mechanical import Mechanical as Mechanical
59
-
60
60
  try:
61
61
  from ansys.mechanical.core.embedding import App, global_variables
62
62
 
@@ -47,10 +47,10 @@ if typing.TYPE_CHECKING:
47
47
  try:
48
48
  import ansys.tools.visualization_interface # noqa: F401
49
49
 
50
- HAS_ANSYS_VIZ = True
50
+ HAS_ANSYS_GRAPHICS = True
51
51
  """Whether or not PyVista exists."""
52
52
  except ImportError:
53
- HAS_ANSYS_VIZ = False
53
+ HAS_ANSYS_GRAPHICS = False
54
54
 
55
55
 
56
56
  def _get_default_addin_configuration() -> AddinConfiguration:
@@ -186,10 +186,22 @@ class App:
186
186
 
187
187
  self.log_info("Starting Mechanical Application")
188
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
189
194
  if BUILDING_GALLERY:
190
195
  if len(INSTANCES) != 0:
196
+ # Get the first instance of the app
191
197
  instance: App = INSTANCES[0]
198
+ # Point to the same underlying application object
192
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
193
205
  if db_file is not None:
194
206
  self.open(db_file)
195
207
  return
@@ -212,7 +224,6 @@ class App:
212
224
  new_profile_name = f"PyMechanical-{os.getpid()}"
213
225
  profile = UniqueUserProfile(new_profile_name, copy_profile=copy_profile)
214
226
  profile.update_environment(os.environ)
215
- atexit.register(_cleanup_private_appdata, profile)
216
227
 
217
228
  runtime.initialize(self._version)
218
229
  self._app = _start_application(configuration, self._version, db_file)
@@ -222,11 +233,14 @@ class App:
222
233
  self._disposed = False
223
234
  atexit.register(_dispose_embedded_app, INSTANCES)
224
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
+
225
241
  self._updated_scopes: typing.List[typing.Dict[str, typing.Any]] = []
226
242
  self._subscribe()
227
243
  self._messages = None
228
-
229
- globals = kwargs.get("globals")
230
244
  if globals:
231
245
  self.update_globals(globals)
232
246
 
@@ -391,27 +405,27 @@ This may corrupt the project file.",
391
405
 
392
406
  def plotter(self) -> None:
393
407
  """Return ``ansys.tools.visualization_interface.Plotter`` object."""
394
- if not HAS_ANSYS_VIZ:
395
- warnings.warn(
396
- "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."
397
411
  )
398
412
  return
399
413
 
400
414
  if self.version < 242:
401
- warnings.warn("Plotting is only supported with version 2024R2 and later!")
415
+ LOG.warning("Plotting is only supported with version 2024R2 and later!")
402
416
  return
403
417
 
404
418
  # TODO Check if anything loaded inside app or else show warning and return
405
419
 
406
- from ansys.mechanical.core.embedding.viz.embedding_plotter import to_plotter
420
+ from ansys.mechanical.core.embedding.graphics.embedding_plotter import to_plotter
407
421
 
408
422
  return to_plotter(self)
409
423
 
410
424
  def plot(self) -> None:
411
425
  """Visualize the model in 3d.
412
426
 
413
- Requires installation using the viz option. E.g.
414
- pip install ansys-mechanical-core[viz]
427
+ Requires installation using the graphics option. E.g.
428
+ pip install ansys-mechanical-core[graphics]
415
429
 
416
430
  Examples
417
431
  --------
@@ -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)
@@ -25,6 +25,12 @@
25
25
  A useful subset of what is imported by
26
26
  Ansys Inc/v{NNN}/ACT/apis/Mechanical.py
27
27
  """
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
+
28
34
  import clr
29
35
 
30
36
  clr.AddReference("Ansys.Mechanical.DataModel")
@@ -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
@@ -112,6 +112,74 @@ def __check_python_interpreter_architecture() -> None:
112
112
  raise Exception("Mechanical Embedding requires a 64-bit Python environment.")
113
113
 
114
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
+
115
183
  def __set_environment(version: int) -> None:
116
184
  """Set environment variables to configure embedding."""
117
185
  if os.name == "nt": # pragma: no cover
@@ -188,6 +256,8 @@ def initialize(version: int = None):
188
256
 
189
257
  __set_environment(version)
190
258
 
259
+ __windows_store_workaround(version)
260
+
191
261
  __check_loaded_libs(version)
192
262
 
193
263
  __workaround_material_server(version)
@@ -142,7 +142,31 @@ class MessageManager:
142
142
  _msg = self._messages[index]
143
143
  self._messages.Remove(_msg)
144
144
 
145
- def show(self, filter="Severity;DisplayString"):
145
+ def _show_string(self, filter: str = "Severity;DisplayString") -> str:
146
+ if self._messages.Count == 0:
147
+ return "No messages to display."
148
+
149
+ if filter == "*":
150
+ selected_columns = [
151
+ "TimeStamp",
152
+ "Severity",
153
+ "DisplayString",
154
+ "Source",
155
+ "StringID",
156
+ "Location",
157
+ "RelatedObjects",
158
+ ]
159
+ else:
160
+ selected_columns = [col.strip() for col in filter.split(";")]
161
+
162
+ lines = []
163
+ for msg in self._messages:
164
+ for key in selected_columns:
165
+ line = f"{key}: {getattr(msg, key, 'Specified attribute not found.')}"
166
+ lines.append(line)
167
+ return "\n".join(lines)
168
+
169
+ def show(self, filter="Severity;DisplayString") -> None:
146
170
  """Print all messages with full details.
147
171
 
148
172
  Parameters
@@ -163,27 +187,8 @@ class MessageManager:
163
187
  ... severity: info
164
188
  ... message: Sample message.
165
189
  """
166
- if self._messages.Count == 0:
167
- print("No messages to display.")
168
- return
169
-
170
- if filter == "*":
171
- selected_columns = [
172
- "TimeStamp",
173
- "Severity",
174
- "DisplayString",
175
- "Source",
176
- "StringID",
177
- "Location",
178
- "RelatedObjects",
179
- ]
180
- else:
181
- selected_columns = [col.strip() for col in filter.split(";")]
182
-
183
- for msg in self._messages:
184
- for key in selected_columns:
185
- print(f"{key}: {getattr(msg, key, 'Specified attribute not found.')}")
186
- print()
190
+ show_string = self._show_string(filter)
191
+ print(show_string)
187
192
 
188
193
  def clear(self):
189
194
  """Clear all messages."""
@@ -41,7 +41,7 @@ def resolve(version):
41
41
  resolve_handler = assembly_resolver.MechanicalResolveEventHandler
42
42
  System.AppDomain.CurrentDomain.AssemblyResolve += resolve_handler
43
43
  except AttributeError:
44
- error_msg = f"""Unable to resolve Mechanical assemblies. Please ensure the following:
44
+ error_msg = """Unable to resolve Mechanical assemblies. Please ensure the following:
45
45
  1. Mechanical is installed.
46
46
  2. A folder with the name "Ansys" does not exist in the same directory as the script being run.
47
47
  """
@@ -22,15 +22,11 @@
22
22
 
23
23
  """RPC and Mechanical service implementation."""
24
24
  from .client import Client
25
-
26
- # todo - provide an implementation of Server (RemoteMechancial) that installs the below
27
- # from .default_server import RemoteMechanical
28
- # and remove them from this import statement
29
- # todo - combine Server and MechanicalService
25
+ from .default_server import DefaultServiceMethods, MechanicalDefaultServer
30
26
  from .server import (
31
- DefaultServiceMethods,
32
- MechanicalDefaultServer,
33
27
  MechanicalEmbeddedServer,
34
28
  MechanicalService,
35
29
  )
36
30
  from .utils import get_remote_methods, remote_method
31
+
32
+ # todo - combine Server and MechanicalService
@@ -27,14 +27,16 @@ import time
27
27
 
28
28
  import rpyc
29
29
 
30
- from ansys.mechanical.core.embedding.rpc.server import PYMECHANICAL_DEFAULT_RPC_PORT
30
+ from ansys.mechanical.core.embedding.rpc.utils import PYMECHANICAL_DEFAULT_RPC_PORT
31
31
  from ansys.mechanical.core.mechanical import DEFAULT_CHUNK_SIZE
32
32
 
33
33
 
34
34
  class Client:
35
35
  """Client for connecting to Mechanical services."""
36
36
 
37
- def __init__(self, host: str, port: int, timeout: float = 120.0, cleanup_on_exit=True):
37
+ def __init__(
38
+ self, host: str, port: int, timeout: float = 120.0, cleanup_on_exit=True, process=None
39
+ ):
38
40
  """Initialize the client.
39
41
 
40
42
  Parameters
@@ -48,6 +50,8 @@ class Client:
48
50
  timeout : float, optional
49
51
  Maximum allowable time for connecting to the Mechanical server.
50
52
  The default is ``60.0``.
53
+ process: subprocess.Popen, optional
54
+ The process object that was connected to
51
55
 
52
56
  """
53
57
  if host is None:
@@ -56,11 +60,14 @@ class Client:
56
60
  if port is None:
57
61
  port = PYMECHANICAL_DEFAULT_RPC_PORT
58
62
  self.port = port
63
+ self._process = process
59
64
  self.timeout = timeout
60
65
  self.connection = None
61
66
  self.root = None
62
67
  self._connect()
63
68
  self._cleanup_on_exit = cleanup_on_exit
69
+ self._error_type = Exception
70
+ self._has_exited = False
64
71
 
65
72
  def __getattr__(self, attr):
66
73
  """Get attribute from the root object."""
@@ -114,6 +121,7 @@ class Client:
114
121
 
115
122
  def close(self):
116
123
  """Close the connection."""
124
+ print("Closing the connection")
117
125
  self.connection.close()
118
126
  print(f"Connection to {self.host}:{self.port} closed")
119
127
 
@@ -234,6 +242,11 @@ class Client:
234
242
  list_of_files.extend(temp_files)
235
243
  return list_of_files
236
244
 
245
+ @property
246
+ def backend(self) -> str:
247
+ """Get the backend type."""
248
+ return "python"
249
+
237
250
  @property
238
251
  def is_alive(self):
239
252
  """Check if the Mechanical instance is alive."""
@@ -245,9 +258,10 @@ class Client:
245
258
 
246
259
  def exit(self):
247
260
  """Shuts down the Mechanical instance."""
248
- print("Requesting server shutdown ...")
249
- self.service_exit()
250
- self.connection.close()
261
+ if self._has_exited:
262
+ return
263
+ self.close()
264
+ self._has_exited = True
251
265
  print("Disconnected from server")
252
266
 
253
267
  def __del__(self): # pragma: no cover
@@ -0,0 +1,131 @@
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
+
27
+ from ansys.mechanical.core.embedding.app import App
28
+
29
+ from .server import MechanicalEmbeddedServer
30
+ from .utils import remote_method
31
+
32
+
33
+ class DefaultServiceMethods:
34
+ """Default service methods for MechanicalEmbeddedServer."""
35
+
36
+ def __init__(self, app: App):
37
+ """Initialize the DefaultServiceMethods."""
38
+ self._app = app
39
+
40
+ def __repr__(self):
41
+ """Return the representation of the instance."""
42
+ return '"ServiceMethods instance"'
43
+
44
+ @remote_method
45
+ def run_python_script(
46
+ self, script: str, enable_logging=False, log_level="WARNING", progress_interval=2000
47
+ ):
48
+ """Run scripts using Internal python engine."""
49
+ result = self._app.execute_script(script)
50
+ return result
51
+
52
+ @remote_method
53
+ def run_python_script_from_file(
54
+ self,
55
+ file_path: str,
56
+ enable_logging=False,
57
+ log_level="WARNING",
58
+ progress_interval=2000,
59
+ ):
60
+ """Run scripts using Internal python engine."""
61
+ return self._app.execute_script_from_file(file_path)
62
+
63
+ @remote_method
64
+ def clear(self):
65
+ """Clear the current project."""
66
+ self._app.new()
67
+
68
+ @property
69
+ @remote_method
70
+ def project_directory(self):
71
+ """Get the project directory."""
72
+ return self._app.ExtAPI.DataModel.Project.ProjectDirectory
73
+
74
+ @remote_method
75
+ def list_files(self):
76
+ """List all files in the project directory."""
77
+ list = []
78
+ mechdbPath = self._app.ExtAPI.DataModel.Project.FilePath
79
+ if mechdbPath != "":
80
+ list.append(mechdbPath)
81
+ rootDir = self._app.ExtAPI.DataModel.Project.ProjectDirectory
82
+
83
+ for dirPath, dirNames, fileNames in os.walk(rootDir):
84
+ for fileName in fileNames:
85
+ list.append(os.path.join(dirPath, fileName))
86
+ files_out = "\n".join(list).splitlines()
87
+ if not files_out: # pragma: no cover
88
+ print("No files listed")
89
+ return files_out
90
+
91
+ @remote_method
92
+ def _get_files(self, files, recursive=False):
93
+ self_files = self.list_files() # to avoid calling it too much
94
+
95
+ if isinstance(files, str):
96
+ if files in self_files:
97
+ list_files = [files]
98
+ elif "*" in files:
99
+ list_files = fnmatch.filter(self_files, files)
100
+ if not list_files:
101
+ raise ValueError(
102
+ f"The `'files'` parameter ({files}) didn't match any file using "
103
+ f"glob expressions in the remote server."
104
+ )
105
+ else:
106
+ raise ValueError(
107
+ f"The `'files'` parameter ('{files}') does not match any file or pattern."
108
+ )
109
+
110
+ elif isinstance(files, (list, tuple)):
111
+ if not all([isinstance(each, str) for each in files]):
112
+ raise ValueError(
113
+ "The parameter `'files'` can be a list or tuple, but it "
114
+ "should only contain strings."
115
+ )
116
+ list_files = files
117
+ else:
118
+ raise ValueError(
119
+ f"The `file` parameter type ({type(files)}) is not supported."
120
+ "Only strings, tuple of strings, or list of strings are allowed."
121
+ )
122
+
123
+ return list_files
124
+
125
+
126
+ class MechanicalDefaultServer(MechanicalEmbeddedServer):
127
+ """Default server with default service methods."""
128
+
129
+ def __init__(self, **kwargs):
130
+ """Initialize the MechanicalDefaultServer."""
131
+ super().__init__(impl=DefaultServiceMethods, **kwargs)