ansys-pyensight-core 0.8.3__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 +32 -17
- ansys/pyensight/core/utils/omniverse.py +160 -104
- ansys/pyensight/core/utils/omniverse_dsg_server.py +85 -36
- ansys/pyensight/core/utils/parts.py +5 -5
- ansys/pyensight/core/utils/variables.py +36 -1
- {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/METADATA +4 -4
- ansys_pyensight_core-0.8.5.dist-info/RECORD +52 -0
- ansys_pyensight_core-0.8.3.dist-info/RECORD +0 -36
- {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.8.3.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,15 +95,12 @@ 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
|
|
101
102
|
variable data into texture coordinates.
|
|
102
103
|
|
|
103
|
-
Note: this call can only be made once as the internal structures are released when this
|
|
104
|
-
call is made.
|
|
105
|
-
|
|
106
104
|
Returns
|
|
107
105
|
-------
|
|
108
106
|
On failure, the method returns None for the first return value. The returned tuple is:
|
|
@@ -118,11 +116,8 @@ class Part(object):
|
|
|
118
116
|
"""
|
|
119
117
|
if self.cmd is None:
|
|
120
118
|
return None, None, None, None, None, None
|
|
121
|
-
if self.
|
|
122
|
-
self.session.log(
|
|
123
|
-
f"Note: part '{self.cmd.name}' contains lines which are not currently supported."
|
|
124
|
-
)
|
|
125
|
-
self.cmd = None
|
|
119
|
+
if self.conn_tris.size == 0:
|
|
120
|
+
self.session.log(f"Note: part '{self.cmd.name}' contains no triangles.")
|
|
126
121
|
return None, None, None, None, None, None
|
|
127
122
|
verts = self.coords
|
|
128
123
|
if self.session.normalize_geometry and self.session.scene_bounds is not None:
|
|
@@ -241,7 +236,7 @@ class Part(object):
|
|
|
241
236
|
tcoords = tmp
|
|
242
237
|
|
|
243
238
|
self.session.log(
|
|
244
|
-
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."
|
|
245
240
|
)
|
|
246
241
|
command = self.cmd
|
|
247
242
|
|
|
@@ -285,8 +280,9 @@ class UpdateHandler(object):
|
|
|
285
280
|
|
|
286
281
|
def finalize_part(self, part: Part) -> None:
|
|
287
282
|
"""Called when all the updates on a Part object have been completed.
|
|
288
|
-
|
|
289
|
-
|
|
283
|
+
|
|
284
|
+
Note: this superclass method should be called after the subclass has processed
|
|
285
|
+
the part geometry as the saved part command will be destroyed by this call.
|
|
290
286
|
"""
|
|
291
287
|
if part.cmd:
|
|
292
288
|
self.session.log(f"Part finalized: {part.cmd.name}")
|
|
@@ -398,11 +394,16 @@ class DSGSession(object):
|
|
|
398
394
|
self._dsg = None
|
|
399
395
|
self._normalize_geometry = normalize_geometry
|
|
400
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
|
|
401
401
|
self._mesh_block_count = 0
|
|
402
402
|
self._variables: Dict[int, Any] = dict()
|
|
403
403
|
self._groups: Dict[int, Any] = dict()
|
|
404
404
|
self._part: Part = Part(self)
|
|
405
405
|
self._scene_bounds: Optional[List] = None
|
|
406
|
+
self._cur_timeline: List = [0.0, 0.0] # Start/End time for current update
|
|
406
407
|
self._callback_handler.session = self
|
|
407
408
|
|
|
408
409
|
@property
|
|
@@ -441,6 +442,20 @@ class DSGSession(object):
|
|
|
441
442
|
def part(self) -> Part:
|
|
442
443
|
return self._part
|
|
443
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
|
+
|
|
444
459
|
@property
|
|
445
460
|
def grpc(self) -> ensight_grpc.EnSightGRPC:
|
|
446
461
|
return self._grpc
|
|
@@ -486,7 +501,7 @@ class DSGSession(object):
|
|
|
486
501
|
def end(self):
|
|
487
502
|
"""Stop a gRPC connection to the EnSight instance"""
|
|
488
503
|
self._callback_handler.end_connection()
|
|
489
|
-
self._grpc.
|
|
504
|
+
self._grpc.shutdown()
|
|
490
505
|
self._shutdown = True
|
|
491
506
|
self._thread.join()
|
|
492
507
|
self._grpc.shutdown()
|
|
@@ -518,8 +533,6 @@ class DSGSession(object):
|
|
|
518
533
|
cmd.init.allow_incremental_updates = False
|
|
519
534
|
cmd.init.maximum_chunk_size = 1024 * 1024
|
|
520
535
|
self._dsg_queue.put(cmd) # type:ignore
|
|
521
|
-
# Handle the update messages
|
|
522
|
-
self.handle_one_update()
|
|
523
536
|
|
|
524
537
|
def _poll_messages(self) -> None:
|
|
525
538
|
"""Core interface to grab DSG events from gRPC and queue them for processing
|
|
@@ -629,7 +642,6 @@ class DSGSession(object):
|
|
|
629
642
|
There is always a part being modified. This method completes the current part, committing
|
|
630
643
|
it to the handler.
|
|
631
644
|
"""
|
|
632
|
-
self._part.build()
|
|
633
645
|
self._callback_handler.finalize_part(self.part)
|
|
634
646
|
self._mesh_block_count += 1
|
|
635
647
|
|
|
@@ -689,10 +701,13 @@ class DSGSession(object):
|
|
|
689
701
|
"""Handle a DSG UPDATE_VIEW command
|
|
690
702
|
|
|
691
703
|
Parameters
|
|
692
|
-
----------
|
|
704
|
+
----------
|
|
693
705
|
view:
|
|
694
706
|
The command coming from the EnSight stream.
|
|
695
707
|
"""
|
|
708
|
+
self._finish_part()
|
|
696
709
|
self._scene_bounds = None
|
|
697
710
|
self._groups[view.id] = view
|
|
711
|
+
if len(view.timeline) == 2:
|
|
712
|
+
self.cur_timeline = [view.timeline[0], view.timeline[1]]
|
|
698
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.
|
|
@@ -19,36 +22,109 @@ class Omniverse:
|
|
|
19
22
|
The omniverse class methods provide an interface between an EnSight session
|
|
20
23
|
and an Omniverse instance. See :ref:`omniverse_info` for additional details.
|
|
21
24
|
|
|
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
25
|
Parameters
|
|
30
26
|
----------
|
|
31
|
-
interface:
|
|
27
|
+
interface: Union["ensight_api.ensight", "ensight"]
|
|
32
28
|
Entity that provides the ``ensight`` namespace. In the case of
|
|
33
29
|
EnSight Python, the ``ensight`` module is passed. In the case
|
|
34
30
|
of PyEnSight, ``Session.ensight`` is passed.
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
Notes
|
|
33
|
+
-----
|
|
34
|
+
This interface is only available when using pyensight (they do not work with
|
|
35
|
+
the ensight Python interpreter) and the module must be used in an interpreter
|
|
36
|
+
that includes the Omniverse Python modules (e.g. omni and pxr). Only a single
|
|
37
|
+
Omniverse connection can be established within a single pyensight session.
|
|
38
|
+
|
|
39
|
+
Examples
|
|
40
|
+
--------
|
|
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}")
|