ansys-pyensight-core 0.8.12__py3-none-any.whl → 0.8.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.

Potentially problematic release.


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

@@ -1,362 +1,362 @@
1
- import glob
2
- import json
3
- import os
4
- import platform
5
- import subprocess
6
- import sys
7
- import tempfile
8
- from types import ModuleType
9
- from typing import TYPE_CHECKING, Optional, Union
10
- import uuid
11
-
12
- import psutil
13
-
14
- if TYPE_CHECKING:
15
- try:
16
- import ensight
17
- except ImportError:
18
- from ansys.api.pyensight import ensight_api
19
-
20
-
21
- class Omniverse:
22
- """Provides the ``ensight.utils.omniverse`` interface.
23
-
24
- The omniverse class methods provide an interface between an EnSight session
25
- and an Omniverse instance. See :ref:`omniverse_info` for additional details.
26
-
27
- Parameters
28
- ----------
29
- interface: Union["ensight_api.ensight", "ensight"]
30
- Entity that provides the ``ensight`` namespace. In the case of
31
- EnSight Python, the ``ensight`` module is passed. In the case
32
- of PyEnSight, ``Session.ensight`` is passed.
33
-
34
- Notes
35
- -----
36
- This interface is only available when using pyensight (they do not work with
37
- the ensight Python interpreter) and the module must be used in an interpreter
38
- that includes the Omniverse Python modules (e.g. omni and pxr). Only a single
39
- Omniverse connection can be established within a single pyensight session.
40
-
41
- Examples
42
- --------
43
-
44
- >>> from ansys.pyensight.core import LocalLauncher
45
- >>> session = LocalLauncher().start()
46
- >>> ov = session.ensight.utils.omniverse
47
- >>> ov.create_connection(r"D:\Omniverse\Example")
48
- >>> ov.update()
49
- >>> ov.close_connection()
50
-
51
- """
52
-
53
- def __init__(self, interface: Union["ensight_api.ensight", "ensight"]):
54
- self._ensight = interface
55
- self._server_pid: Optional[int] = None
56
- self._interpreter: str = ""
57
- self._status_filename: str = ""
58
-
59
- @staticmethod
60
- def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
61
- """
62
- Use a combination of the current omniverse application and the information
63
- in the local .nvidia-omniverse/config/omniverse.toml file to come up with
64
- the pathname of a kit executable suitable for hosting another copy of the
65
- ansys.geometry.server kit.
66
-
67
- Returns
68
- -------
69
- Optional[str]
70
- The pathname of a kit executable or None
71
-
72
- """
73
- # parse the toml config file for the location of the installed apps
74
- try:
75
- import tomllib
76
- except ModuleNotFoundError:
77
- import pip._vendor.tomli as tomllib
78
-
79
- homedir = os.path.expanduser("~")
80
- ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
81
- if not os.path.exists(ov_config):
82
- return None
83
- # read the Omniverse configuration toml file
84
- with open(ov_config, "r") as ov_file:
85
- ov_data = ov_file.read()
86
- config = tomllib.loads(ov_data)
87
- appdir = config.get("paths", {}).get("library_root", fallback_directory)
88
-
89
- # If we are running inside an Omniverse app, use that information
90
- try:
91
- import omni.kit.app
92
-
93
- # get the current application
94
- app = omni.kit.app.get_app()
95
- app_name = app.get_app_filename().split(".")[-1]
96
- app_version = app.get_app_version().split("-")[0]
97
- # and where it is installed
98
- appdir = os.path.join(appdir, f"{app_name}-{app_version}")
99
- except ModuleNotFoundError:
100
- # Names should be like: "C:\\Users\\foo\\AppData\\Local\\ov\\pkg\\create-2023.2.3\\launcher.toml"
101
- target = None
102
- target_version = None
103
- for d in glob.glob(os.path.join(appdir, "*", "launcher.toml")):
104
- test_dir = os.path.dirname(d)
105
- # the name will be something like "create-2023.2.3"
106
- name = os.path.basename(test_dir).split("-")
107
- if len(name) != 2:
108
- continue
109
- if name[0] not in ("kit", "create", "view"):
110
- continue
111
- if (target_version is None) or (name[1] > target_version):
112
- target = test_dir
113
- target_version = name[1]
114
- if target is None:
115
- return None
116
- appdir = target
117
-
118
- # Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
119
- # Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
120
- exe_names = ["kit.sh", "kit"]
121
- if sys.platform.startswith("win"):
122
- exe_names = ["kit.bat", "kit.exe"]
123
-
124
- # look in 4 places...
125
- for dir_name in [appdir, os.path.join(appdir, "kit")]:
126
- for exe_name in exe_names:
127
- if os.path.exists(os.path.join(dir_name, exe_name)):
128
- return os.path.join(dir_name, exe_name)
129
-
130
- return None
131
-
132
- def _check_modules(self) -> None:
133
- """Verify that the Python interpreter is correct
134
-
135
- Check for module dependencies. If not present, raise an exception.
136
-
137
- Raises
138
- ------
139
- RuntimeError
140
- if the necessary modules are missing.
141
-
142
- """
143
- # One time check for this
144
- if len(self._interpreter):
145
- return
146
-
147
- # if a module, then we are inside EnSight
148
- if isinstance(self._ensight, ModuleType): # pragma: no cover
149
- # in this case, we can just use cpython
150
- import ceiversion
151
- import enve
152
-
153
- cei_home = os.environ.get("CEI_HOME", enve.home())
154
- self._interpreter = os.path.join(cei_home, "bin", f"cpython{ceiversion.apex_suffix}")
155
- if platform.system() == "Windows":
156
- self._interpreter += ".bat"
157
- return
158
- # Using the python interpreter running this code
159
- self._interpreter = sys.executable
160
-
161
- # in the future, these will be part of the pyensight wheel
162
- # dependencies, but for now we include this check.
163
- try:
164
- import pxr # noqa: F401
165
- import pygltflib # noqa: F401
166
- except Exception:
167
- raise RuntimeError("Unable to detect omniverse dependencies: usd-core, pygltflib.")
168
-
169
- def is_running_omniverse(self) -> bool:
170
- """Check that an Omniverse connection is active
171
-
172
- Returns
173
- -------
174
- bool
175
- True if the connection is active, False otherwise.
176
- """
177
- if self._server_pid is None:
178
- return False
179
- if psutil.pid_exists(self._server_pid):
180
- return True
181
- self._server_pid = None
182
- return False
183
-
184
- def create_connection(
185
- self,
186
- omniverse_path: str,
187
- include_camera: bool = False,
188
- normalize_geometry: bool = False,
189
- temporal: bool = False,
190
- live: bool = True,
191
- debug_filename: str = "",
192
- time_scale: float = 1.0,
193
- options: dict = {},
194
- ) -> None:
195
- """Ensure that an EnSight dsg -> omniverse server is running
196
-
197
- Connect the current EnSight session to an Omniverse server.
198
- This is done by launching a new service that makes a dynamic scene graph
199
- connection to the EnSight session and pushes updates to the Omniverse server.
200
- The initial EnSight scene will be pushed after the connection is established.
201
-
202
- Parameters
203
- ----------
204
- omniverse_path : str
205
- The directory name where the USD files should be saved. For example:
206
- "C:/Users/test/OV/usdfiles"
207
- include_camera : bool
208
- If True, apply the EnSight camera to the Omniverse scene. This option
209
- should be used if the target viewer is in AR/VR mode. Defaults to False.
210
- normalize_geometry : bool
211
- Omniverse units are in meters. If the source dataset is not in the correct
212
- unit system or is just too large/small, this option will remap the geometry
213
- to a unit cube. Defaults to False.
214
- temporal : bool
215
- If True, save all timesteps.
216
- live : bool
217
- If True, one can call 'update()' to send updated geometry to Omniverse.
218
- If False, the Omniverse connection will push a single update and then
219
- disconnect. Defaults to True.
220
- time_scale : float
221
- Multiply all EnSight time values by this factor before exporting to Omniverse.
222
- The default is 1.0.
223
- debug_filename : str
224
- If the name of a file is provided, it will be used to save logging information on
225
- the connection between EnSight and Omniverse. This option is no longer supported,
226
- but the API remains for backwards compatibility.
227
- options : dict
228
- Allows for a fallback for the grpc host/port and the security token.
229
- """
230
- if not isinstance(self._ensight, ModuleType):
231
- self._ensight._session.ensight_version_check("2023 R2")
232
- self._check_modules()
233
- if self.is_running_omniverse():
234
- raise RuntimeError("An Omniverse server connection is already active.")
235
- if not isinstance(self._ensight, ModuleType):
236
- # Make sure the internal ui module is loaded
237
- self._ensight._session.cmd("import enspyqtgui_int", do_eval=False)
238
- # Get the gRPC connection details and use them to launch the service
239
- port = self._ensight._session.grpc.port()
240
- hostname = self._ensight._session.grpc.host
241
- token = self._ensight._session.grpc.security_token
242
- else:
243
- hostname = options.get("host", "127.0.0.1")
244
- port = options.get("port", 12345)
245
- token = options.get("security", "")
246
-
247
- # Launch the server via the 'ansys.pyensight.core.utils.omniverse_cli' module
248
- dsg_uri = f"grpc://{hostname}:{port}"
249
- cmd = [self._interpreter]
250
- cmd.extend(["-m", "ansys.pyensight.core.utils.omniverse_cli"])
251
- cmd.append(omniverse_path)
252
- if token:
253
- cmd.extend(["--security_token", token])
254
- if temporal:
255
- cmd.extend(["--temporal", "true"])
256
- if not include_camera:
257
- cmd.extend(["--include_camera", "false"])
258
- if normalize_geometry:
259
- cmd.extend(["--normalize_geometry", "true"])
260
- if time_scale != 1.0:
261
- cmd.extend(["--time_scale", str(time_scale)])
262
- if not live:
263
- cmd.extend(["--oneshot", "1"])
264
- cmd.extend(["--dsg_uri", dsg_uri])
265
- env_vars = os.environ.copy()
266
- # we are launching the kit from EnSight or PyEnSight. In these cases, we
267
- # inform the kit instance of:
268
- # (1) the name of the "server status" file, if any
269
- self._new_status_file()
270
- env_vars["ANSYS_OV_SERVER_STATUS_FILENAME"] = self._status_filename
271
- process = subprocess.Popen(cmd, close_fds=True, env=env_vars)
272
- self._server_pid = process.pid
273
-
274
- def _new_status_file(self, new=True) -> None:
275
- """
276
- Remove any existing status file and create a new one if requested.
277
-
278
- Parameters
279
- ----------
280
- new : bool
281
- If True, create a new status file.
282
- """
283
- if self._status_filename:
284
- try:
285
- os.remove(self._status_filename)
286
- except OSError:
287
- pass
288
- self._status_filename = ""
289
- if new:
290
- self._status_filename = os.path.join(
291
- tempfile.gettempdir(), str(uuid.uuid1()) + "_gs_status.txt"
292
- )
293
-
294
- def read_status_file(self) -> dict:
295
- """Read the status file and return its contents as a dictionary.
296
-
297
- Note: this can fail if the file is being written to when this call is made, so expect
298
- failures.
299
-
300
- Returns
301
- -------
302
- Optional[dict]
303
- A dictionary with the fields 'status', 'start_time', 'processed_buffers', 'total_buffers' or empty
304
- """
305
- if not self._status_filename:
306
- return {}
307
- try:
308
- with open(self._status_filename, "r") as status_file:
309
- data = json.load(status_file)
310
- except Exception:
311
- return {}
312
- return data
313
-
314
- def close_connection(self) -> None:
315
- """Shut down the open EnSight dsg -> omniverse server
316
-
317
- Break the connection between the EnSight instance and Omniverse.
318
-
319
- """
320
- self._check_modules()
321
- if not self.is_running_omniverse():
322
- return
323
- proc = psutil.Process(self._server_pid)
324
- for child in proc.children(recursive=True):
325
- if psutil.pid_exists(child.pid):
326
- # This can be a race condition, so it is ok if the child is dead already
327
- try:
328
- child.kill()
329
- except psutil.NoSuchProcess:
330
- pass
331
- # Same issue, this process might already be shutting down, so NoSuchProcess is ok.
332
- try:
333
- proc.kill()
334
- except psutil.NoSuchProcess:
335
- pass
336
- self._server_pid = None
337
- self._new_status_file(new=False)
338
-
339
- def update(self, temporal: bool = False) -> None:
340
- """Update the geometry in Omniverse
341
-
342
- Export the current EnSight scene to the current Omniverse connection.
343
-
344
- Parameters
345
- ----------
346
- temporal : bool
347
- If True, export all timesteps.
348
- """
349
- update_cmd = "dynamicscenegraph://localhost/client/update"
350
- if temporal:
351
- update_cmd += "?timesteps=1"
352
- self._check_modules()
353
- if not self.is_running_omniverse():
354
- raise RuntimeError("No Omniverse server connection is currently active.")
355
- if not isinstance(self._ensight, ModuleType):
356
- self._ensight._session.ensight_version_check("2023 R2")
357
- cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
358
- self._ensight._session.cmd(cmd, do_eval=False)
359
- else:
360
- import enspyqtgui_int
361
-
362
- enspyqtgui_int.dynamic_scene_graph_command(f"{update_cmd}")
1
+ import glob
2
+ import json
3
+ import os
4
+ import platform
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ from types import ModuleType
9
+ from typing import TYPE_CHECKING, Optional, Union
10
+ import uuid
11
+
12
+ import psutil
13
+
14
+ if TYPE_CHECKING:
15
+ try:
16
+ import ensight
17
+ except ImportError:
18
+ from ansys.api.pyensight import ensight_api
19
+
20
+
21
+ class Omniverse:
22
+ """Provides the ``ensight.utils.omniverse`` interface.
23
+
24
+ The omniverse class methods provide an interface between an EnSight session
25
+ and an Omniverse instance. See :ref:`omniverse_info` for additional details.
26
+
27
+ Parameters
28
+ ----------
29
+ interface: Union["ensight_api.ensight", "ensight"]
30
+ Entity that provides the ``ensight`` namespace. In the case of
31
+ EnSight Python, the ``ensight`` module is passed. In the case
32
+ of PyEnSight, ``Session.ensight`` is passed.
33
+
34
+ Notes
35
+ -----
36
+ This interface is only available when using pyensight (they do not work with
37
+ the ensight Python interpreter) and the module must be used in an interpreter
38
+ that includes the Omniverse Python modules (e.g. omni and pxr). Only a single
39
+ Omniverse connection can be established within a single pyensight session.
40
+
41
+ Examples
42
+ --------
43
+
44
+ >>> from ansys.pyensight.core import LocalLauncher
45
+ >>> session = LocalLauncher().start()
46
+ >>> ov = session.ensight.utils.omniverse
47
+ >>> ov.create_connection(r"D:\Omniverse\Example")
48
+ >>> ov.update()
49
+ >>> ov.close_connection()
50
+
51
+ """
52
+
53
+ def __init__(self, interface: Union["ensight_api.ensight", "ensight"]):
54
+ self._ensight = interface
55
+ self._server_pid: Optional[int] = None
56
+ self._interpreter: str = ""
57
+ self._status_filename: str = ""
58
+
59
+ @staticmethod
60
+ def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
61
+ """
62
+ Use a combination of the current omniverse application and the information
63
+ in the local .nvidia-omniverse/config/omniverse.toml file to come up with
64
+ the pathname of a kit executable suitable for hosting another copy of the
65
+ ansys.geometry.server kit.
66
+
67
+ Returns
68
+ -------
69
+ Optional[str]
70
+ The pathname of a kit executable or None
71
+
72
+ """
73
+ # parse the toml config file for the location of the installed apps
74
+ try:
75
+ import tomllib
76
+ except ModuleNotFoundError:
77
+ import pip._vendor.tomli as tomllib
78
+
79
+ homedir = os.path.expanduser("~")
80
+ ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
81
+ if not os.path.exists(ov_config):
82
+ return None
83
+ # read the Omniverse configuration toml file
84
+ with open(ov_config, "r") as ov_file:
85
+ ov_data = ov_file.read()
86
+ config = tomllib.loads(ov_data)
87
+ appdir = config.get("paths", {}).get("library_root", fallback_directory)
88
+
89
+ # If we are running inside an Omniverse app, use that information
90
+ try:
91
+ import omni.kit.app
92
+
93
+ # get the current application
94
+ app = omni.kit.app.get_app()
95
+ app_name = app.get_app_filename().split(".")[-1]
96
+ app_version = app.get_app_version().split("-")[0]
97
+ # and where it is installed
98
+ appdir = os.path.join(appdir, f"{app_name}-{app_version}")
99
+ except ModuleNotFoundError:
100
+ # Names should be like: "C:\\Users\\foo\\AppData\\Local\\ov\\pkg\\create-2023.2.3\\launcher.toml"
101
+ target = None
102
+ target_version = None
103
+ for d in glob.glob(os.path.join(appdir, "*", "launcher.toml")):
104
+ test_dir = os.path.dirname(d)
105
+ # the name will be something like "create-2023.2.3"
106
+ name = os.path.basename(test_dir).split("-")
107
+ if len(name) != 2:
108
+ continue
109
+ if name[0] not in ("kit", "create", "view"):
110
+ continue
111
+ if (target_version is None) or (name[1] > target_version):
112
+ target = test_dir
113
+ target_version = name[1]
114
+ if target is None:
115
+ return None
116
+ appdir = target
117
+
118
+ # Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
119
+ # Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
120
+ exe_names = ["kit.sh", "kit"]
121
+ if sys.platform.startswith("win"):
122
+ exe_names = ["kit.bat", "kit.exe"]
123
+
124
+ # look in 4 places...
125
+ for dir_name in [appdir, os.path.join(appdir, "kit")]:
126
+ for exe_name in exe_names:
127
+ if os.path.exists(os.path.join(dir_name, exe_name)):
128
+ return os.path.join(dir_name, exe_name)
129
+
130
+ return None
131
+
132
+ def _check_modules(self) -> None:
133
+ """Verify that the Python interpreter is correct
134
+
135
+ Check for module dependencies. If not present, raise an exception.
136
+
137
+ Raises
138
+ ------
139
+ RuntimeError
140
+ if the necessary modules are missing.
141
+
142
+ """
143
+ # One time check for this
144
+ if len(self._interpreter):
145
+ return
146
+
147
+ # if a module, then we are inside EnSight
148
+ if isinstance(self._ensight, ModuleType): # pragma: no cover
149
+ # in this case, we can just use cpython
150
+ import ceiversion
151
+ import enve
152
+
153
+ cei_home = os.environ.get("CEI_HOME", enve.home())
154
+ self._interpreter = os.path.join(cei_home, "bin", f"cpython{ceiversion.apex_suffix}")
155
+ if platform.system() == "Windows":
156
+ self._interpreter += ".bat"
157
+ return
158
+ # Using the python interpreter running this code
159
+ self._interpreter = sys.executable
160
+
161
+ # in the future, these will be part of the pyensight wheel
162
+ # dependencies, but for now we include this check.
163
+ try:
164
+ import pxr # noqa: F401
165
+ import pygltflib # noqa: F401
166
+ except Exception:
167
+ raise RuntimeError("Unable to detect omniverse dependencies: usd-core, pygltflib.")
168
+
169
+ def is_running_omniverse(self) -> bool:
170
+ """Check that an Omniverse connection is active
171
+
172
+ Returns
173
+ -------
174
+ bool
175
+ True if the connection is active, False otherwise.
176
+ """
177
+ if self._server_pid is None:
178
+ return False
179
+ if psutil.pid_exists(self._server_pid):
180
+ return True
181
+ self._server_pid = None
182
+ return False
183
+
184
+ def create_connection(
185
+ self,
186
+ omniverse_path: str,
187
+ include_camera: bool = False,
188
+ normalize_geometry: bool = False,
189
+ temporal: bool = False,
190
+ live: bool = True,
191
+ debug_filename: str = "",
192
+ time_scale: float = 1.0,
193
+ options: dict = {},
194
+ ) -> None:
195
+ """Ensure that an EnSight dsg -> omniverse server is running
196
+
197
+ Connect the current EnSight session to an Omniverse server.
198
+ This is done by launching a new service that makes a dynamic scene graph
199
+ connection to the EnSight session and pushes updates to the Omniverse server.
200
+ The initial EnSight scene will be pushed after the connection is established.
201
+
202
+ Parameters
203
+ ----------
204
+ omniverse_path : str
205
+ The directory name where the USD files should be saved. For example:
206
+ "C:/Users/test/OV/usdfiles"
207
+ include_camera : bool
208
+ If True, apply the EnSight camera to the Omniverse scene. This option
209
+ should be used if the target viewer is in AR/VR mode. Defaults to False.
210
+ normalize_geometry : bool
211
+ Omniverse units are in meters. If the source dataset is not in the correct
212
+ unit system or is just too large/small, this option will remap the geometry
213
+ to a unit cube. Defaults to False.
214
+ temporal : bool
215
+ If True, save all timesteps.
216
+ live : bool
217
+ If True, one can call 'update()' to send updated geometry to Omniverse.
218
+ If False, the Omniverse connection will push a single update and then
219
+ disconnect. Defaults to True.
220
+ time_scale : float
221
+ Multiply all EnSight time values by this factor before exporting to Omniverse.
222
+ The default is 1.0.
223
+ debug_filename : str
224
+ If the name of a file is provided, it will be used to save logging information on
225
+ the connection between EnSight and Omniverse. This option is no longer supported,
226
+ but the API remains for backwards compatibility.
227
+ options : dict
228
+ Allows for a fallback for the grpc host/port and the security token.
229
+ """
230
+ if not isinstance(self._ensight, ModuleType):
231
+ self._ensight._session.ensight_version_check("2023 R2")
232
+ self._check_modules()
233
+ if self.is_running_omniverse():
234
+ raise RuntimeError("An Omniverse server connection is already active.")
235
+ if not isinstance(self._ensight, ModuleType):
236
+ # Make sure the internal ui module is loaded
237
+ self._ensight._session.cmd("import enspyqtgui_int", do_eval=False)
238
+ # Get the gRPC connection details and use them to launch the service
239
+ port = self._ensight._session.grpc.port()
240
+ hostname = self._ensight._session.grpc.host
241
+ token = self._ensight._session.grpc.security_token
242
+ else:
243
+ hostname = options.get("host", "127.0.0.1")
244
+ port = options.get("port", 12345)
245
+ token = options.get("security", "")
246
+
247
+ # Launch the server via the 'ansys.pyensight.core.utils.omniverse_cli' module
248
+ dsg_uri = f"grpc://{hostname}:{port}"
249
+ cmd = [self._interpreter]
250
+ cmd.extend(["-m", "ansys.pyensight.core.utils.omniverse_cli"])
251
+ cmd.append(omniverse_path)
252
+ if token:
253
+ cmd.extend(["--security_token", token])
254
+ if temporal:
255
+ cmd.extend(["--temporal", "true"])
256
+ if not include_camera:
257
+ cmd.extend(["--include_camera", "false"])
258
+ if normalize_geometry:
259
+ cmd.extend(["--normalize_geometry", "true"])
260
+ if time_scale != 1.0:
261
+ cmd.extend(["--time_scale", str(time_scale)])
262
+ if not live:
263
+ cmd.extend(["--oneshot", "1"])
264
+ cmd.extend(["--dsg_uri", dsg_uri])
265
+ env_vars = os.environ.copy()
266
+ # we are launching the kit from EnSight or PyEnSight. In these cases, we
267
+ # inform the kit instance of:
268
+ # (1) the name of the "server status" file, if any
269
+ self._new_status_file()
270
+ env_vars["ANSYS_OV_SERVER_STATUS_FILENAME"] = self._status_filename
271
+ process = subprocess.Popen(cmd, close_fds=True, env=env_vars)
272
+ self._server_pid = process.pid
273
+
274
+ def _new_status_file(self, new=True) -> None:
275
+ """
276
+ Remove any existing status file and create a new one if requested.
277
+
278
+ Parameters
279
+ ----------
280
+ new : bool
281
+ If True, create a new status file.
282
+ """
283
+ if self._status_filename:
284
+ try:
285
+ os.remove(self._status_filename)
286
+ except OSError:
287
+ pass
288
+ self._status_filename = ""
289
+ if new:
290
+ self._status_filename = os.path.join(
291
+ tempfile.gettempdir(), str(uuid.uuid1()) + "_gs_status.txt"
292
+ )
293
+
294
+ def read_status_file(self) -> dict:
295
+ """Read the status file and return its contents as a dictionary.
296
+
297
+ Note: this can fail if the file is being written to when this call is made, so expect
298
+ failures.
299
+
300
+ Returns
301
+ -------
302
+ Optional[dict]
303
+ A dictionary with the fields 'status', 'start_time', 'processed_buffers', 'total_buffers' or empty
304
+ """
305
+ if not self._status_filename:
306
+ return {}
307
+ try:
308
+ with open(self._status_filename, "r") as status_file:
309
+ data = json.load(status_file)
310
+ except Exception:
311
+ return {}
312
+ return data
313
+
314
+ def close_connection(self) -> None:
315
+ """Shut down the open EnSight dsg -> omniverse server
316
+
317
+ Break the connection between the EnSight instance and Omniverse.
318
+
319
+ """
320
+ self._check_modules()
321
+ if not self.is_running_omniverse():
322
+ return
323
+ proc = psutil.Process(self._server_pid)
324
+ for child in proc.children(recursive=True):
325
+ if psutil.pid_exists(child.pid):
326
+ # This can be a race condition, so it is ok if the child is dead already
327
+ try:
328
+ child.kill()
329
+ except psutil.NoSuchProcess:
330
+ pass
331
+ # Same issue, this process might already be shutting down, so NoSuchProcess is ok.
332
+ try:
333
+ proc.kill()
334
+ except psutil.NoSuchProcess:
335
+ pass
336
+ self._server_pid = None
337
+ self._new_status_file(new=False)
338
+
339
+ def update(self, temporal: bool = False) -> None:
340
+ """Update the geometry in Omniverse
341
+
342
+ Export the current EnSight scene to the current Omniverse connection.
343
+
344
+ Parameters
345
+ ----------
346
+ temporal : bool
347
+ If True, export all timesteps.
348
+ """
349
+ update_cmd = "dynamicscenegraph://localhost/client/update"
350
+ if temporal:
351
+ update_cmd += "?timesteps=1"
352
+ self._check_modules()
353
+ if not self.is_running_omniverse():
354
+ raise RuntimeError("No Omniverse server connection is currently active.")
355
+ if not isinstance(self._ensight, ModuleType):
356
+ self._ensight._session.ensight_version_check("2023 R2")
357
+ cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
358
+ self._ensight._session.cmd(cmd, do_eval=False)
359
+ else:
360
+ import enspyqtgui_int
361
+
362
+ enspyqtgui_int.dynamic_scene_graph_command(f"{update_cmd}")