ansys-pyensight-core 0.7.6__py3-none-any.whl → 0.7.8__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.

Potentially problematic release.


This version of ansys-pyensight-core might be problematic. Click here for more details.

@@ -540,6 +540,20 @@ class RenderableVNC(Renderable):
540
540
  self._rendertype = "remote"
541
541
  self.update()
542
542
 
543
+ def _update_2023R2_or_less(self):
544
+ """Update the remote rendering widget and display it for
545
+ backend EnSight of version earlier than 2024R1
546
+ """
547
+ query_params = {
548
+ "autoconnect": "true",
549
+ "host": self._session.html_hostname,
550
+ "port": self._session.ws_port,
551
+ }
552
+ url = f"{self._http_protocol}://{self._session.html_hostname}:{self._session.html_port}"
553
+ url += "/ansys/nexus/novnc/vnc_envision.html"
554
+ url += self._get_query_parameters_str(query_params)
555
+ self._url = url
556
+
543
557
  def update(self):
544
558
  """Update the remote rendering widget and display it.
545
559
 
@@ -549,29 +563,36 @@ class RenderableVNC(Renderable):
549
563
  """
550
564
  optional_query = self._get_query_parameters_str()
551
565
  version = _get_ansysnexus_version(self._session._cei_suffix)
552
- html = f"<script src='/ansys{version}/nexus/viewer-loader.js{optional_query}'></script>\n"
553
- rest_uri = (
554
- f"{self._http_protocol}://{self._session.html_hostname}:{self._session.html_port}"
555
- )
556
- ws_uri = f"{self._http_protocol}://{self._session.html_hostname}:{self._session.ws_port}"
557
-
558
- query_args = ""
559
- if self._using_proxy and optional_query:
560
- query_args = f', "extra_query_args":"{optional_query[1:]}"'
561
-
562
- attributes = ' renderer="envnc"'
563
- attributes += ' ui="simple"'
564
- attributes += ' active="true"'
565
- attributes += (
566
- " renderer_options='"
567
- + f'{{ "ws":"{ws_uri}", "http":"{rest_uri}", "security_token":"{self._session.secret_key}", "connect_to_running_ens":true {query_args} }}'
568
- + "'"
569
- )
566
+ if int(self._session._cei_suffix) < 241:
567
+ self._update_2023R2_or_less()
568
+ else:
569
+ html = (
570
+ f"<script src='/ansys{version}/nexus/viewer-loader.js{optional_query}'></script>\n"
571
+ )
572
+ rest_uri = (
573
+ f"{self._http_protocol}://{self._session.html_hostname}:{self._session.html_port}"
574
+ )
575
+ ws_uri = (
576
+ f"{self._http_protocol}://{self._session.html_hostname}:{self._session.ws_port}"
577
+ )
570
578
 
571
- html += f"<ansys-nexus-viewer {attributes}></ansys-nexus-viewer>\n"
579
+ query_args = ""
580
+ if self._using_proxy and optional_query:
581
+ query_args = f', "extra_query_args":"{optional_query[1:]}"'
582
+
583
+ attributes = ' renderer="envnc"'
584
+ attributes += ' ui="simple"'
585
+ attributes += ' active="true"'
586
+ attributes += (
587
+ " renderer_options='"
588
+ + f'{{ "ws":"{ws_uri}", "http":"{rest_uri}", "security_token":"{self._session.secret_key}", "connect_to_running_ens":true {query_args} }}'
589
+ + "'"
590
+ )
572
591
 
573
- # refresh the remote HTML
574
- self._save_remote_html_page(html)
592
+ html += f"<ansys-nexus-viewer {attributes}></ansys-nexus-viewer>\n"
593
+
594
+ # refresh the remote HTML
595
+ self._save_remote_html_page(html)
575
596
  super().update()
576
597
 
577
598
 
@@ -1058,8 +1058,11 @@ class Session:
1058
1058
  if _utils_dir not in sys.path:
1059
1059
  sys.path.insert(0, _utils_dir)
1060
1060
  onlyfiles = [f for f in listdir(_utils_dir) if os.path.isfile(os.path.join(_utils_dir, f))]
1061
- for _filename in onlyfiles:
1062
- _filename = os.path.join(_utils_dir, _filename)
1061
+ for _basename in onlyfiles:
1062
+ # skip over any files with the "_server" in their names
1063
+ if "_server" in _basename:
1064
+ continue
1065
+ _filename = os.path.join(_utils_dir, _basename)
1063
1066
  try:
1064
1067
  # get the module and class names
1065
1068
  _name = os.path.splitext(os.path.basename(_filename))[0]
@@ -0,0 +1,239 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ from types import ModuleType
5
+ from typing import TYPE_CHECKING, List, Optional, Union
6
+
7
+ import psutil
8
+
9
+ if TYPE_CHECKING:
10
+ try:
11
+ import ensight
12
+ except ImportError:
13
+ from ansys.api.pyensight import ensight_api
14
+
15
+
16
+ class Omniverse:
17
+ """Provides the ``ensight.utils.omniverse`` interface.
18
+
19
+ The omniverse class methods provide an interface between an EnSight session
20
+ and an Omniverse instance. See :ref:`omniverse_info` for additional details.
21
+
22
+ Note
23
+ ----
24
+ This interface is only available when using pyensight (they do not work with
25
+ the ensight Python interpreter) and the module must be used in an interpreter
26
+ that includes the Omniverse Python modules (e.g. omni and pxr). Only a single
27
+ Omniverse connection can be established within a single pyensight session.
28
+
29
+ Parameters
30
+ ----------
31
+ interface:
32
+ Entity that provides the ``ensight`` namespace. In the case of
33
+ EnSight Python, the ``ensight`` module is passed. In the case
34
+ of PyEnSight, ``Session.ensight`` is passed.
35
+
36
+ Example
37
+ -------
38
+ ::
39
+ from ansys.pyensight.core import LocalLauncher
40
+ session = LocalLauncher().start()
41
+ ov = session.ensight.utils.omniverse
42
+ ov.create_connection()
43
+ ov.update()
44
+ ov.close_connection()
45
+
46
+ """
47
+
48
+ def __init__(self, interface: Union["ensight_api.ensight", "ensight"]):
49
+ self._ensight = interface
50
+ self._server_pid: Optional[int] = None
51
+ self._interpreter: List[str] = []
52
+
53
+ def _check_modules(self) -> None:
54
+ """Verify that the Python interpreter is correct
55
+
56
+ Check for omni module. If not present, raise an exception.
57
+ If pxr is there as well, then we can just use sys.executable.
58
+ If not, check to see if 'kit.bat' or 'kit.sh' can be found and
59
+ arrange to use those instead.
60
+
61
+ Raises
62
+ ------
63
+ RuntimeError if the necessary modules are missing.
64
+
65
+ """
66
+ # One time check for this
67
+ if len(self._interpreter):
68
+ return
69
+ try:
70
+ # Note: the EnSight embedded interpreter will not have these
71
+ import omni.client # noqa: F401
72
+ except ImportError:
73
+ raise RuntimeError("The module requires the omni module to be installed.") from None
74
+
75
+ try:
76
+ # if we can import pxr, then we can just use sys.executable
77
+ from pxr import Gf, Sdf, Usd, UsdGeom, UsdLux, UsdShade # noqa: F401
78
+
79
+ if os.path.basename(sys.executable).startswith("kit"):
80
+ # we are running inside of an Omniverse app like Create, use the 'kit' script
81
+ raise ImportError("Internal retry")
82
+
83
+ self._interpreter = [sys.executable]
84
+ return
85
+ except ImportError:
86
+ # Can we find 'kit.bat' or 'kit.sh' (we may be running in it)?
87
+ # Interesting cases: something/kit/python/python.exe,
88
+ # something/kit/kit.exe. All mapped to something/kit.{bat,sh} if found.
89
+ ov_dir = os.path.dirname(sys.executable)
90
+ for _ in range(3):
91
+ for name in ("kit.bat", "kit.sh"):
92
+ exe_name = os.path.join(ov_dir, name)
93
+ if os.path.exists(exe_name):
94
+ self._interpreter = [
95
+ exe_name,
96
+ "--enable",
97
+ "omni.client",
98
+ "--enable",
99
+ "omni.usd",
100
+ "--exec",
101
+ ]
102
+ return
103
+ ov_dir = os.path.dirname(ov_dir)
104
+ raise RuntimeError("Unable to detect a copy of the Omniverse kit executable.") from None
105
+
106
+ def _is_running_omniverse(self) -> bool:
107
+ """Check that an Omniverse connection is active
108
+ Returns
109
+ -------
110
+ True if the connection is active, False otherwise.
111
+ """
112
+ if self._server_pid is None:
113
+ return False
114
+ if psutil.pid_exists(self._server_pid):
115
+ return True
116
+ self._server_pid = None
117
+ return False
118
+
119
+ def create_connection(
120
+ self,
121
+ omniverse_path: str,
122
+ include_camera: bool = False,
123
+ normalize_geometry: bool = False,
124
+ live: bool = True,
125
+ debug_filename: str = "",
126
+ ) -> None:
127
+ """Ensure that an EnSight dsg -> omniverse server is running
128
+
129
+ Connect the current EnSight session to an Omniverse server.
130
+ This is done by launching a new service that makes a dynamic scene graph
131
+ connection to the EnSight session and pushes updates to the Omniverse server.
132
+ The initial EnSight scene will be pushed after the connection is established.
133
+
134
+ Parameters
135
+ ----------
136
+ omniverse_path : str
137
+ The URI to the Omniverse server. It will look like this:
138
+ "omniverse://localhost/Users/test"
139
+ include_camera : bool
140
+ If True, apply the EnSight camera to the Omniverse scene. This option
141
+ should be used if the target viewer is in AR/VR mode. Defaults to False.
142
+ normalize_geometry : bool
143
+ Omniverse units are in meters. If the source dataset is not in the correct
144
+ unit system or is just too large/small, this option will remap the geometry
145
+ to a unit cube. Defaults to False.
146
+ live : bool
147
+ If True, one can call 'update()' to send updated geometry to Omniverse.
148
+ If False, the Omniverse connection will push a single update and then
149
+ disconnect. Defaults to True.
150
+ debug_filename : str
151
+ If the name of a file is provided, it will be used to save logging information on
152
+ the connection between EnSight and Omniverse.
153
+
154
+ """
155
+ if not isinstance(self._ensight, ModuleType):
156
+ self._ensight._session.ensight_version_check("2023 R2")
157
+ self._check_modules()
158
+ if self._is_running_omniverse():
159
+ raise RuntimeError("An Omniverse server connection is already active.")
160
+ # Make sure the internal ui module is loaded
161
+ self._ensight._session.cmd("import enspyqtgui_int", do_eval=False)
162
+ # Get the gRPC connection details and use them to launch the service
163
+ port = self._ensight._session.grpc.port()
164
+ hostname = self._ensight._session.grpc.host
165
+ token = self._ensight._session.grpc.security_token
166
+ script_name = "omniverse_dsg_server.py"
167
+ working_dir = os.path.dirname(__file__)
168
+ cmd = [
169
+ script_name,
170
+ "--host",
171
+ hostname,
172
+ "--port",
173
+ str(port),
174
+ "--path",
175
+ omniverse_path,
176
+ ]
177
+ if live:
178
+ cmd.extend(["--live"])
179
+ if include_camera:
180
+ cmd.extend(["--vrmode"])
181
+ if token:
182
+ cmd.extend(["--security", token])
183
+ # if temporal:
184
+ # cmd.extend(["--animation"])
185
+ # else:
186
+ # cmd.extend(["--no-animation"])
187
+ if debug_filename:
188
+ cmd.extend(["--log_file", debug_filename])
189
+ cmd.extend(["--verbose", "1"])
190
+ if normalize_geometry:
191
+ cmd.extend(["--normalize_geometry"])
192
+ # if using kit.bat, convert args into a string, otherwise, just use them
193
+ cmdline = []
194
+ cmdline.extend(self._interpreter)
195
+ if len(self._interpreter) > 1:
196
+ cmd = [" ".join(cmd)]
197
+ cmdline.extend(cmd)
198
+ env_vars = os.environ.copy()
199
+ process = subprocess.Popen(cmdline, close_fds=True, env=env_vars, cwd=working_dir)
200
+ self._server_pid = process.pid
201
+
202
+ def close_connection(self) -> None:
203
+ """Shut down the open EnSight dsg -> omniverse server
204
+
205
+ Break the connection between the EnSight instance and Omniverse.
206
+
207
+ """
208
+ self._check_modules()
209
+ if not self._is_running_omniverse():
210
+ return
211
+ proc = psutil.Process(self._server_pid)
212
+ for child in proc.children(recursive=True):
213
+ if psutil.pid_exists(child.pid):
214
+ # This can be a race condition, so it is ok if the child is dead already
215
+ try:
216
+ child.kill()
217
+ except psutil.NoSuchProcess:
218
+ pass
219
+ # Same issue, this process might already be shutting down, so NoSuchProcess is ok.
220
+ try:
221
+ proc.kill()
222
+ except psutil.NoSuchProcess:
223
+ pass
224
+ self._server_pid = None
225
+
226
+ def update(self) -> None:
227
+ """Update the geometry in Omniverse
228
+
229
+ Push the current EnSight scene to the current Omniverse connection.
230
+
231
+ """
232
+ if not isinstance(self._ensight, ModuleType):
233
+ self._ensight._session.ensight_version_check("2023 R2")
234
+ self._check_modules()
235
+ if not self._is_running_omniverse():
236
+ raise RuntimeError("No Omniverse server connection is currently active.")
237
+ update_cmd = "dynamicscenegraph://localhost/client/update"
238
+ cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
239
+ self._ensight._session.cmd(cmd, do_eval=False)