ansys-pyensight-core 0.8.4__py3-none-any.whl → 0.8.5__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.
- ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/__init__.py +1 -0
- ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py +366 -0
- ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml +58 -0
- ansys/pyensight/core/exts/ansys.geometry.service/data/icon.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.service/data/preview.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/CHANGELOG.md +8 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/README.md +13 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/index.rst +18 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/__init__.py +1 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +170 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml +49 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/data/icon.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/data/preview.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/CHANGELOG.md +8 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/README.md +13 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/index.rst +18 -0
- ansys/pyensight/core/launcher.py +36 -1
- ansys/pyensight/core/locallauncher.py +3 -1
- ansys/pyensight/core/utils/dsg_server.py +29 -11
- ansys/pyensight/core/utils/omniverse.py +151 -95
- ansys/pyensight/core/utils/omniverse_dsg_server.py +85 -36
- {ansys_pyensight_core-0.8.4.dist-info → ansys_pyensight_core-0.8.5.dist-info}/METADATA +1 -1
- ansys_pyensight_core-0.8.5.dist-info/RECORD +52 -0
- ansys_pyensight_core-0.8.4.dist-info/RECORD +0 -36
- {ansys_pyensight_core-0.8.4.dist-info → ansys_pyensight_core-0.8.5.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.8.4.dist-info → ansys_pyensight_core-0.8.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
# Semantic Versioning is used: https://semver.org/
|
|
3
|
+
version = "0.8.5"
|
|
4
|
+
|
|
5
|
+
# Lists people or organizations that are considered the "authors" of the package.
|
|
6
|
+
authors = ["ANSYS"]
|
|
7
|
+
|
|
8
|
+
# The title and description fields are primarily for displaying extension info in UI
|
|
9
|
+
title = "ANSYS Omniverse Geometry Service GUI"
|
|
10
|
+
description = "A geometry synchronization service that enables export of geometry scenes from ANSYS products to Omniverse."
|
|
11
|
+
|
|
12
|
+
# Path (relative to the root) or content of readme markdown file for UI.
|
|
13
|
+
readme = "docs/README.md"
|
|
14
|
+
|
|
15
|
+
# URL of the extension source repository.
|
|
16
|
+
repository = "https://github.com/ansys/pyensight"
|
|
17
|
+
|
|
18
|
+
# One of categories for UI.
|
|
19
|
+
category = "simulation"
|
|
20
|
+
|
|
21
|
+
# Keywords for the extension
|
|
22
|
+
keywords = ["ANSYS", "EnSight", "PyEnSight", "Fluent", "kit"]
|
|
23
|
+
|
|
24
|
+
# Location of change log file in target (final) folder of extension, relative to the root.
|
|
25
|
+
# More info on writing changelog: https://keepachangelog.com/en/1.0.0/
|
|
26
|
+
changelog = "docs/CHANGELOG.md"
|
|
27
|
+
|
|
28
|
+
# Preview image and icon. Folder named "data" automatically goes in git lfs (see .gitattributes file).
|
|
29
|
+
# Preview image is shown in "Overview" of Extensions window. Screenshot of an extension might be a good preview image.
|
|
30
|
+
preview_image = "data/preview.png"
|
|
31
|
+
|
|
32
|
+
# Icon is shown in Extensions window, it is recommended to be square, of size 256x256.
|
|
33
|
+
icon = "data/icon.png"
|
|
34
|
+
|
|
35
|
+
# Use omni.ui to build simple UI
|
|
36
|
+
[dependencies]
|
|
37
|
+
"omni.kit.uiapp" = {}
|
|
38
|
+
"ansys.geometry.service" = {}
|
|
39
|
+
|
|
40
|
+
# Main python module this extension provides, it will be publicly available as "import ansys.geometry.serviceui".
|
|
41
|
+
[[python.module]]
|
|
42
|
+
name = "ansys.geometry.serviceui"
|
|
43
|
+
|
|
44
|
+
[[test]]
|
|
45
|
+
# Extra dependencies only to be used during test run
|
|
46
|
+
dependencies = [
|
|
47
|
+
"omni.kit.ui_test" # UI testing extension
|
|
48
|
+
]
|
|
49
|
+
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ANSYS Omniverse Geometry Service GUI [ansys.geometry.serviceui]
|
|
2
|
+
|
|
3
|
+
This Omniverse extension is a UI interface to the [ansys.geometry.service]
|
|
4
|
+
kit extension. It allows an Omniverse application user to connect to
|
|
5
|
+
a running copy of ANSYS EnSight or other application that supports the
|
|
6
|
+
Dynamic Scene Graph gRPC protocol. The GUI allows for the remote scene
|
|
7
|
+
to be pulled, on request, into a specified Omniverse location.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
For more details on this extension see:
|
|
11
|
+
https://ensight.docs.pyansys.com/version/dev/user_guide/omniverse_info.html
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
ansys.geometry.serviceui
|
|
2
|
+
########################
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
.. toctree::
|
|
6
|
+
:maxdepth: 1
|
|
7
|
+
|
|
8
|
+
README
|
|
9
|
+
CHANGELOG
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.. automodule::"ansys.geometry.serviceui"
|
|
13
|
+
:platform: Windows-x86_64, Linux-x86_64
|
|
14
|
+
:members:
|
|
15
|
+
:undoc-members:
|
|
16
|
+
:show-inheritance:
|
|
17
|
+
:imported-members:
|
|
18
|
+
:exclude-members: contextmanager
|
ansys/pyensight/core/launcher.py
CHANGED
|
@@ -13,10 +13,13 @@ Examples:
|
|
|
13
13
|
"""
|
|
14
14
|
import os.path
|
|
15
15
|
import platform
|
|
16
|
+
import random
|
|
17
|
+
import re
|
|
16
18
|
import socket
|
|
17
19
|
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
18
20
|
import warnings
|
|
19
21
|
|
|
22
|
+
import psutil
|
|
20
23
|
import requests
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
@@ -165,6 +168,38 @@ class Launcher:
|
|
|
165
168
|
"""
|
|
166
169
|
return
|
|
167
170
|
|
|
171
|
+
def _find_ports_used_by_other_pyensight_and_ensight(self):
|
|
172
|
+
"""Find ports to avoid when looking for empty ports.
|
|
173
|
+
|
|
174
|
+
The ports are found iterating the current processes and
|
|
175
|
+
looking for PyEnSight/EnSight sessions and their command
|
|
176
|
+
lines.
|
|
177
|
+
"""
|
|
178
|
+
pyensight_found = []
|
|
179
|
+
ensight_found = []
|
|
180
|
+
for process in psutil.process_iter():
|
|
181
|
+
try:
|
|
182
|
+
process_cmdline = process.cmdline()
|
|
183
|
+
except (psutil.AccessDenied, psutil.ZombieProcess):
|
|
184
|
+
continue
|
|
185
|
+
if not process_cmdline:
|
|
186
|
+
continue
|
|
187
|
+
if len(process_cmdline) > 1:
|
|
188
|
+
if "websocketserver.py" in os.path.basename(process_cmdline[1]):
|
|
189
|
+
pyensight_found.append(process_cmdline)
|
|
190
|
+
if any(["ensight" in os.path.basename(x) for x in process_cmdline]):
|
|
191
|
+
if any([x == "-ports" for x in process_cmdline]):
|
|
192
|
+
ensight_found.append(process_cmdline)
|
|
193
|
+
ports = []
|
|
194
|
+
for command_line in pyensight_found:
|
|
195
|
+
for command in command_line:
|
|
196
|
+
if re.match(r"^\d{4,5}$", command):
|
|
197
|
+
ports.append(int(command))
|
|
198
|
+
for command_line in ensight_found:
|
|
199
|
+
idx = command_line.index("-ports") + 1
|
|
200
|
+
ports.append(int(command_line[idx]))
|
|
201
|
+
return list(set(ports))
|
|
202
|
+
|
|
168
203
|
@staticmethod
|
|
169
204
|
def _find_unused_ports(count: int, avoid: Optional[List[int]] = None) -> Optional[List[int]]:
|
|
170
205
|
"""Find "count" unused ports on the host system
|
|
@@ -191,7 +226,7 @@ class Launcher:
|
|
|
191
226
|
ports = list()
|
|
192
227
|
|
|
193
228
|
# pick a starting port number
|
|
194
|
-
start =
|
|
229
|
+
start = random.randint(1024, 64000)
|
|
195
230
|
# We will scan for 65530 ports unless end is specified
|
|
196
231
|
port_mod = 65530
|
|
197
232
|
end = start + port_mod - 1
|
|
@@ -123,7 +123,8 @@ class LocalLauncher(Launcher):
|
|
|
123
123
|
self.session_directory = tempfile.mkdtemp(prefix="pyensight_")
|
|
124
124
|
|
|
125
125
|
# gRPC port, VNC port, websocketserver ws, websocketserver html
|
|
126
|
-
|
|
126
|
+
to_avoid = self._find_ports_used_by_other_pyensight_and_ensight()
|
|
127
|
+
self._ports = self._find_unused_ports(5, avoid=to_avoid)
|
|
127
128
|
if self._ports is None:
|
|
128
129
|
raise RuntimeError("Unable to allocate local ports for EnSight session")
|
|
129
130
|
is_windows = self._is_windows()
|
|
@@ -152,6 +153,7 @@ class LocalLauncher(Launcher):
|
|
|
152
153
|
cmd.extend(["-grpc_server", str(self._ports[0])])
|
|
153
154
|
vnc_url = f"vnc://%%3Frfb_port={self._ports[1]}%%26use_auth=0"
|
|
154
155
|
cmd.extend(["-vnc", vnc_url])
|
|
156
|
+
cmd.extend(["-ports", str(self._ports[4])])
|
|
155
157
|
|
|
156
158
|
use_egl = self._use_egl()
|
|
157
159
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import queue
|
|
4
|
+
import sys
|
|
4
5
|
import threading
|
|
5
6
|
from typing import Any, Dict, List, Optional
|
|
6
7
|
|
|
@@ -94,7 +95,7 @@ class Part(object):
|
|
|
94
95
|
else:
|
|
95
96
|
self.tcoords_var_id = None
|
|
96
97
|
|
|
97
|
-
def
|
|
98
|
+
def nodal_surface_rep(self):
|
|
98
99
|
"""
|
|
99
100
|
This function processes the geometry arrays and converts them into nodal representation.
|
|
100
101
|
It will duplicate triangles as needed (to preserve element normals) and will convert
|
|
@@ -115,10 +116,8 @@ class Part(object):
|
|
|
115
116
|
"""
|
|
116
117
|
if self.cmd is None:
|
|
117
118
|
return None, None, None, None, None, None
|
|
118
|
-
if self.
|
|
119
|
-
self.session.log(
|
|
120
|
-
f"Note: part '{self.cmd.name}' contains lines which are not currently supported."
|
|
121
|
-
)
|
|
119
|
+
if self.conn_tris.size == 0:
|
|
120
|
+
self.session.log(f"Note: part '{self.cmd.name}' contains no triangles.")
|
|
122
121
|
return None, None, None, None, None, None
|
|
123
122
|
verts = self.coords
|
|
124
123
|
if self.session.normalize_geometry and self.session.scene_bounds is not None:
|
|
@@ -237,7 +236,7 @@ class Part(object):
|
|
|
237
236
|
tcoords = tmp
|
|
238
237
|
|
|
239
238
|
self.session.log(
|
|
240
|
-
f"Part '{self.cmd.name}' defined: {self.coords.size/3} verts, {self.conn_tris.size/3} tris
|
|
239
|
+
f"Part '{self.cmd.name}' defined: {self.coords.size/3} verts, {self.conn_tris.size/3} tris."
|
|
241
240
|
)
|
|
242
241
|
command = self.cmd
|
|
243
242
|
|
|
@@ -395,11 +394,16 @@ class DSGSession(object):
|
|
|
395
394
|
self._dsg = None
|
|
396
395
|
self._normalize_geometry = normalize_geometry
|
|
397
396
|
self._vrmode = vrmode
|
|
397
|
+
self._time_limits = [
|
|
398
|
+
sys.float_info.max,
|
|
399
|
+
-sys.float_info.max,
|
|
400
|
+
] # Min/max across all time steps
|
|
398
401
|
self._mesh_block_count = 0
|
|
399
402
|
self._variables: Dict[int, Any] = dict()
|
|
400
403
|
self._groups: Dict[int, Any] = dict()
|
|
401
404
|
self._part: Part = Part(self)
|
|
402
405
|
self._scene_bounds: Optional[List] = None
|
|
406
|
+
self._cur_timeline: List = [0.0, 0.0] # Start/End time for current update
|
|
403
407
|
self._callback_handler.session = self
|
|
404
408
|
|
|
405
409
|
@property
|
|
@@ -438,6 +442,20 @@ class DSGSession(object):
|
|
|
438
442
|
def part(self) -> Part:
|
|
439
443
|
return self._part
|
|
440
444
|
|
|
445
|
+
@property
|
|
446
|
+
def time_limits(self) -> List:
|
|
447
|
+
return self._time_limits
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def cur_timeline(self) -> List:
|
|
451
|
+
return self._cur_timeline
|
|
452
|
+
|
|
453
|
+
@cur_timeline.setter
|
|
454
|
+
def cur_timeline(self, timeline: List) -> None:
|
|
455
|
+
self._cur_timeline = timeline
|
|
456
|
+
self._time_limits[0] = min(self._time_limits[0], self._cur_timeline[0])
|
|
457
|
+
self._time_limits[1] = max(self._time_limits[1], self._cur_timeline[1])
|
|
458
|
+
|
|
441
459
|
@property
|
|
442
460
|
def grpc(self) -> ensight_grpc.EnSightGRPC:
|
|
443
461
|
return self._grpc
|
|
@@ -483,7 +501,7 @@ class DSGSession(object):
|
|
|
483
501
|
def end(self):
|
|
484
502
|
"""Stop a gRPC connection to the EnSight instance"""
|
|
485
503
|
self._callback_handler.end_connection()
|
|
486
|
-
self._grpc.
|
|
504
|
+
self._grpc.shutdown()
|
|
487
505
|
self._shutdown = True
|
|
488
506
|
self._thread.join()
|
|
489
507
|
self._grpc.shutdown()
|
|
@@ -515,8 +533,6 @@ class DSGSession(object):
|
|
|
515
533
|
cmd.init.allow_incremental_updates = False
|
|
516
534
|
cmd.init.maximum_chunk_size = 1024 * 1024
|
|
517
535
|
self._dsg_queue.put(cmd) # type:ignore
|
|
518
|
-
# Handle the update messages
|
|
519
|
-
self.handle_one_update()
|
|
520
536
|
|
|
521
537
|
def _poll_messages(self) -> None:
|
|
522
538
|
"""Core interface to grab DSG events from gRPC and queue them for processing
|
|
@@ -626,7 +642,6 @@ class DSGSession(object):
|
|
|
626
642
|
There is always a part being modified. This method completes the current part, committing
|
|
627
643
|
it to the handler.
|
|
628
644
|
"""
|
|
629
|
-
self._part.build()
|
|
630
645
|
self._callback_handler.finalize_part(self.part)
|
|
631
646
|
self._mesh_block_count += 1
|
|
632
647
|
|
|
@@ -686,10 +701,13 @@ class DSGSession(object):
|
|
|
686
701
|
"""Handle a DSG UPDATE_VIEW command
|
|
687
702
|
|
|
688
703
|
Parameters
|
|
689
|
-
----------
|
|
704
|
+
----------
|
|
690
705
|
view:
|
|
691
706
|
The command coming from the EnSight stream.
|
|
692
707
|
"""
|
|
708
|
+
self._finish_part()
|
|
693
709
|
self._scene_bounds = None
|
|
694
710
|
self._groups[view.id] = view
|
|
711
|
+
if len(view.timeline) == 2:
|
|
712
|
+
self.cur_timeline = [view.timeline[0], view.timeline[1]]
|
|
695
713
|
self._callback_handler.add_group(view.id, view=True)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import glob
|
|
1
2
|
import os
|
|
2
3
|
import subprocess
|
|
3
4
|
import sys
|
|
4
5
|
from types import ModuleType
|
|
5
|
-
from typing import TYPE_CHECKING,
|
|
6
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
6
7
|
|
|
7
8
|
import psutil
|
|
8
9
|
|
|
@@ -12,6 +13,8 @@ if TYPE_CHECKING:
|
|
|
12
13
|
except ImportError:
|
|
13
14
|
from ansys.api.pyensight import ensight_api
|
|
14
15
|
|
|
16
|
+
import ansys.pyensight.core
|
|
17
|
+
|
|
15
18
|
|
|
16
19
|
class Omniverse:
|
|
17
20
|
"""Provides the ``ensight.utils.omniverse`` interface.
|
|
@@ -21,7 +24,7 @@ class Omniverse:
|
|
|
21
24
|
|
|
22
25
|
Parameters
|
|
23
26
|
----------
|
|
24
|
-
interface:
|
|
27
|
+
interface: Union["ensight_api.ensight", "ensight"]
|
|
25
28
|
Entity that provides the ``ensight`` namespace. In the case of
|
|
26
29
|
EnSight Python, the ``ensight`` module is passed. In the case
|
|
27
30
|
of PyEnSight, ``Session.ensight`` is passed.
|
|
@@ -35,20 +38,93 @@ class Omniverse:
|
|
|
35
38
|
|
|
36
39
|
Examples
|
|
37
40
|
--------
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
|
|
42
|
+
>>> from ansys.pyensight.core import LocalLauncher
|
|
43
|
+
>>> session = LocalLauncher().start()
|
|
44
|
+
>>> ov = session.ensight.utils.omniverse
|
|
45
|
+
>>> ov.create_connection()
|
|
46
|
+
>>> ov.update()
|
|
47
|
+
>>> ov.close_connection()
|
|
45
48
|
|
|
46
49
|
"""
|
|
47
50
|
|
|
48
51
|
def __init__(self, interface: Union["ensight_api.ensight", "ensight"]):
|
|
49
52
|
self._ensight = interface
|
|
50
53
|
self._server_pid: Optional[int] = None
|
|
51
|
-
self._interpreter:
|
|
54
|
+
self._interpreter: str = ""
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
|
|
58
|
+
"""
|
|
59
|
+
Use a combination of the current omniverse application and the information
|
|
60
|
+
in the local .nvidia-omniverse/config/omniverse.toml file to come up with
|
|
61
|
+
the pathname of a kit executable suitable for hosting another copy of the
|
|
62
|
+
ansys.geometry.server kit.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
Optional[str]
|
|
67
|
+
The pathname of a kit executable or None
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
# parse the toml config file for the location of the installed apps
|
|
71
|
+
try:
|
|
72
|
+
import tomllib
|
|
73
|
+
except ModuleNotFoundError:
|
|
74
|
+
import pip._vendor.tomli as tomllib
|
|
75
|
+
|
|
76
|
+
homedir = os.path.expanduser("~")
|
|
77
|
+
ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
|
|
78
|
+
if not os.path.exists(ov_config):
|
|
79
|
+
return None
|
|
80
|
+
# read the Omniverse configuration toml file
|
|
81
|
+
with open(ov_config, "r") as ov_file:
|
|
82
|
+
ov_data = ov_file.read()
|
|
83
|
+
config = tomllib.loads(ov_data)
|
|
84
|
+
appdir = config.get("paths", {}).get("library_root", fallback_directory)
|
|
85
|
+
|
|
86
|
+
# If we are running inside an Omniverse app, use that information
|
|
87
|
+
try:
|
|
88
|
+
import omni.kit.app
|
|
89
|
+
|
|
90
|
+
# get the current application
|
|
91
|
+
app = omni.kit.app.get_app()
|
|
92
|
+
app_name = app.get_app_filename().split(".")[-1]
|
|
93
|
+
app_version = app.get_app_version().split("-")[0]
|
|
94
|
+
# and where it is installed
|
|
95
|
+
appdir = os.path.join(appdir, f"{app_name}-{app_version}")
|
|
96
|
+
except ModuleNotFoundError:
|
|
97
|
+
# Names should be like: "C:\\Users\\foo\\AppData\\Local\\ov\\pkg\\create-2023.2.3\\launcher.toml"
|
|
98
|
+
target = None
|
|
99
|
+
target_version = None
|
|
100
|
+
for d in glob.glob(os.path.join(appdir, "*", "launcher.toml")):
|
|
101
|
+
test_dir = os.path.dirname(d)
|
|
102
|
+
# the name will be something like "create-2023.2.3"
|
|
103
|
+
name = os.path.basename(test_dir).split("-")
|
|
104
|
+
if len(name) != 2:
|
|
105
|
+
continue
|
|
106
|
+
if name[0] not in ("kit", "create", "view"):
|
|
107
|
+
continue
|
|
108
|
+
if (target_version is None) or (name[1] > target_version):
|
|
109
|
+
target = test_dir
|
|
110
|
+
target_version = name[1]
|
|
111
|
+
if target is None:
|
|
112
|
+
return None
|
|
113
|
+
appdir = target
|
|
114
|
+
|
|
115
|
+
# Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
|
|
116
|
+
# Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
|
|
117
|
+
exe_names = ["kit.sh", "kit"]
|
|
118
|
+
if sys.platform.startswith("win"):
|
|
119
|
+
exe_names = ["kit.bat", "kit.exe"]
|
|
120
|
+
|
|
121
|
+
# look in 4 places...
|
|
122
|
+
for dir_name in [appdir, os.path.join(appdir, "kit")]:
|
|
123
|
+
for exe_name in exe_names:
|
|
124
|
+
if os.path.exists(os.path.join(dir_name, exe_name)):
|
|
125
|
+
return os.path.join(dir_name, exe_name)
|
|
126
|
+
|
|
127
|
+
return None
|
|
52
128
|
|
|
53
129
|
def _check_modules(self) -> None:
|
|
54
130
|
"""Verify that the Python interpreter is correct
|
|
@@ -60,53 +136,26 @@ class Omniverse:
|
|
|
60
136
|
|
|
61
137
|
Raises
|
|
62
138
|
------
|
|
63
|
-
RuntimeError
|
|
139
|
+
RuntimeError
|
|
140
|
+
if the necessary modules are missing.
|
|
64
141
|
|
|
65
142
|
"""
|
|
66
143
|
# One time check for this
|
|
67
144
|
if len(self._interpreter):
|
|
68
145
|
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
146
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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]
|
|
147
|
+
kit_exe = self.find_kit_filename()
|
|
148
|
+
if kit_exe:
|
|
149
|
+
self._interpreter = kit_exe
|
|
84
150
|
return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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:
|
|
151
|
+
raise RuntimeError("Unable to detect a copy of the Omniverse kit executable.") from None
|
|
152
|
+
|
|
153
|
+
def is_running_omniverse(self) -> bool:
|
|
107
154
|
"""Check that an Omniverse connection is active
|
|
155
|
+
|
|
108
156
|
Returns
|
|
109
157
|
-------
|
|
158
|
+
bool
|
|
110
159
|
True if the connection is active, False otherwise.
|
|
111
160
|
"""
|
|
112
161
|
if self._server_pid is None:
|
|
@@ -121,8 +170,10 @@ class Omniverse:
|
|
|
121
170
|
omniverse_path: str,
|
|
122
171
|
include_camera: bool = False,
|
|
123
172
|
normalize_geometry: bool = False,
|
|
173
|
+
temporal: bool = False,
|
|
124
174
|
live: bool = True,
|
|
125
175
|
debug_filename: str = "",
|
|
176
|
+
options: dict = {},
|
|
126
177
|
) -> None:
|
|
127
178
|
"""Ensure that an EnSight dsg -> omniverse server is running
|
|
128
179
|
|
|
@@ -143,6 +194,8 @@ class Omniverse:
|
|
|
143
194
|
Omniverse units are in meters. If the source dataset is not in the correct
|
|
144
195
|
unit system or is just too large/small, this option will remap the geometry
|
|
145
196
|
to a unit cube. Defaults to False.
|
|
197
|
+
temporal : bool
|
|
198
|
+
If True, save all timesteps.
|
|
146
199
|
live : bool
|
|
147
200
|
If True, one can call 'update()' to send updated geometry to Omniverse.
|
|
148
201
|
If False, the Omniverse connection will push a single update and then
|
|
@@ -150,53 +203,46 @@ class Omniverse:
|
|
|
150
203
|
debug_filename : str
|
|
151
204
|
If the name of a file is provided, it will be used to save logging information on
|
|
152
205
|
the connection between EnSight and Omniverse.
|
|
153
|
-
|
|
206
|
+
options : dict
|
|
207
|
+
Allows for a fallback for the grpc host/port and the security token.
|
|
154
208
|
"""
|
|
155
209
|
if not isinstance(self._ensight, ModuleType):
|
|
156
210
|
self._ensight._session.ensight_version_check("2023 R2")
|
|
157
211
|
self._check_modules()
|
|
158
|
-
if self.
|
|
212
|
+
if self.is_running_omniverse():
|
|
159
213
|
raise RuntimeError("An Omniverse server connection is already active.")
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
]
|
|
177
|
-
|
|
178
|
-
cmd.extend(["--live"])
|
|
179
|
-
if include_camera:
|
|
180
|
-
cmd.extend(["--vrmode"])
|
|
214
|
+
if not isinstance(self._ensight, ModuleType):
|
|
215
|
+
# Make sure the internal ui module is loaded
|
|
216
|
+
self._ensight._session.cmd("import enspyqtgui_int", do_eval=False)
|
|
217
|
+
# Get the gRPC connection details and use them to launch the service
|
|
218
|
+
port = self._ensight._session.grpc.port()
|
|
219
|
+
hostname = self._ensight._session.grpc.host
|
|
220
|
+
token = self._ensight._session.grpc.security_token
|
|
221
|
+
else:
|
|
222
|
+
hostname = options.get("host", "127.0.0.1")
|
|
223
|
+
port = options.get("port", 12345)
|
|
224
|
+
token = options.get("security", "")
|
|
225
|
+
|
|
226
|
+
# Launch the server via the 'ansys.geometry.service' kit
|
|
227
|
+
dsg_uri = f"grpc://{hostname}:{port}"
|
|
228
|
+
kit_dir = os.path.join(os.path.dirname(ansys.pyensight.core.__file__), "exts")
|
|
229
|
+
cmd = [self._interpreter]
|
|
230
|
+
cmd.extend(["--ext-folder", kit_dir])
|
|
231
|
+
cmd.extend(["--enable", "ansys.geometry.service"])
|
|
181
232
|
if token:
|
|
182
|
-
cmd.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if debug_filename:
|
|
188
|
-
cmd.extend(["--log_file", debug_filename])
|
|
189
|
-
cmd.extend(["--verbose", "1"])
|
|
233
|
+
cmd.append(f"--/exts/ansys.geometry.service/securityCode={token}")
|
|
234
|
+
if temporal:
|
|
235
|
+
cmd.append("--/exts/ansys.geometry.service/temporal=1")
|
|
236
|
+
if not include_camera:
|
|
237
|
+
cmd.append("--/exts/ansys.geometry.service/vrmode=1")
|
|
190
238
|
if normalize_geometry:
|
|
191
|
-
cmd.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if len(self._interpreter) > 1:
|
|
196
|
-
cmd = [" ".join(cmd)]
|
|
197
|
-
cmdline.extend(cmd)
|
|
239
|
+
cmd.append("--/exts/ansys.geometry.service/normalizeGeometry=1")
|
|
240
|
+
cmd.append(f"--/exts/ansys.geometry.service/omniUrl={omniverse_path}")
|
|
241
|
+
cmd.append(f"--/exts/ansys.geometry.service/dsgUrl={dsg_uri}")
|
|
242
|
+
cmd.append("--/exts/ansys.geometry.service/run=1")
|
|
198
243
|
env_vars = os.environ.copy()
|
|
199
|
-
|
|
244
|
+
working_dir = os.path.join(os.path.dirname(ansys.pyensight.core.__file__), "utils")
|
|
245
|
+
process = subprocess.Popen(cmd, close_fds=True, env=env_vars, cwd=working_dir)
|
|
200
246
|
self._server_pid = process.pid
|
|
201
247
|
|
|
202
248
|
def close_connection(self) -> None:
|
|
@@ -206,7 +252,7 @@ class Omniverse:
|
|
|
206
252
|
|
|
207
253
|
"""
|
|
208
254
|
self._check_modules()
|
|
209
|
-
if not self.
|
|
255
|
+
if not self.is_running_omniverse():
|
|
210
256
|
return
|
|
211
257
|
proc = psutil.Process(self._server_pid)
|
|
212
258
|
for child in proc.children(recursive=True):
|
|
@@ -223,17 +269,27 @@ class Omniverse:
|
|
|
223
269
|
pass
|
|
224
270
|
self._server_pid = None
|
|
225
271
|
|
|
226
|
-
def update(self) -> None:
|
|
272
|
+
def update(self, temporal: bool = False) -> None:
|
|
227
273
|
"""Update the geometry in Omniverse
|
|
228
274
|
|
|
229
|
-
|
|
275
|
+
Export the current EnSight scene to the current Omniverse connection.
|
|
230
276
|
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
temporal : bool
|
|
280
|
+
If True, export all timesteps.
|
|
231
281
|
"""
|
|
232
|
-
|
|
233
|
-
|
|
282
|
+
update_cmd = "dynamicscenegraph://localhost/client/update"
|
|
283
|
+
if temporal:
|
|
284
|
+
update_cmd += "?timesteps=1"
|
|
234
285
|
self._check_modules()
|
|
235
|
-
if not self.
|
|
286
|
+
if not self.is_running_omniverse():
|
|
236
287
|
raise RuntimeError("No Omniverse server connection is currently active.")
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
288
|
+
if not isinstance(self._ensight, ModuleType):
|
|
289
|
+
self._ensight._session.ensight_version_check("2023 R2")
|
|
290
|
+
cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
|
|
291
|
+
self._ensight._session.cmd(cmd, do_eval=False)
|
|
292
|
+
else:
|
|
293
|
+
import enspyqtgui_int
|
|
294
|
+
|
|
295
|
+
enspyqtgui_int.dynamic_scene_graph_command(f"{update_cmd}")
|