ansys-pyensight-core 0.9.0__py3-none-any.whl → 0.9.2__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/ensight_grpc.py +302 -0
- ansys/pyensight/core/renderable.py +6 -1
- ansys/pyensight/core/session.py +3 -1
- ansys/pyensight/core/utils/dsg_server.py +16 -0
- ansys/pyensight/core/utils/omniverse.py +218 -76
- ansys/pyensight/core/utils/omniverse_cli.py +19 -11
- ansys/pyensight/core/utils/omniverse_dsg_server.py +108 -44
- ansys/pyensight/core/utils/omniverse_glb_server.py +172 -24
- {ansys_pyensight_core-0.9.0.dist-info → ansys_pyensight_core-0.9.2.dist-info}/METADATA +2 -2
- {ansys_pyensight_core-0.9.0.dist-info → ansys_pyensight_core-0.9.2.dist-info}/RECORD +12 -12
- {ansys_pyensight_core-0.9.0.dist-info → ansys_pyensight_core-0.9.2.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.9.0.dist-info → ansys_pyensight_core-0.9.2.dist-info}/WHEEL +0 -0
|
@@ -6,7 +6,7 @@ import subprocess
|
|
|
6
6
|
import sys
|
|
7
7
|
import tempfile
|
|
8
8
|
from types import ModuleType
|
|
9
|
-
from typing import TYPE_CHECKING, Optional, Union
|
|
9
|
+
from typing import TYPE_CHECKING, List, Optional, Union
|
|
10
10
|
import uuid
|
|
11
11
|
|
|
12
12
|
import psutil
|
|
@@ -18,6 +18,199 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from ansys.api.pyensight import ensight_api
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
class OmniverseKitInstance:
|
|
22
|
+
"""Interface to an Omniverse application instance
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
pid : int
|
|
27
|
+
The process id of the launched instance
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, pid: int) -> None:
|
|
31
|
+
self._pid: Optional[int] = pid
|
|
32
|
+
print("CTOR:", pid)
|
|
33
|
+
|
|
34
|
+
def __del__(self) -> None:
|
|
35
|
+
"""Close down the instance on delete"""
|
|
36
|
+
self.close()
|
|
37
|
+
|
|
38
|
+
def close(self) -> None:
|
|
39
|
+
"""Shutdown the Omniverse instance
|
|
40
|
+
|
|
41
|
+
If the instance associated with this object is still running,
|
|
42
|
+
shut it down.
|
|
43
|
+
"""
|
|
44
|
+
if not self.is_running():
|
|
45
|
+
return
|
|
46
|
+
proc = psutil.Process(self._pid)
|
|
47
|
+
for child in proc.children(recursive=True):
|
|
48
|
+
if psutil.pid_exists(child.pid):
|
|
49
|
+
# This can be a race condition, so it is ok if the child is dead already
|
|
50
|
+
try:
|
|
51
|
+
child.kill()
|
|
52
|
+
except psutil.NoSuchProcess:
|
|
53
|
+
pass
|
|
54
|
+
# Same issue, this process might already be shutting down, so NoSuchProcess is ok.
|
|
55
|
+
try:
|
|
56
|
+
proc.kill()
|
|
57
|
+
except psutil.NoSuchProcess:
|
|
58
|
+
pass
|
|
59
|
+
self._pid = None
|
|
60
|
+
|
|
61
|
+
def is_running(self) -> bool:
|
|
62
|
+
"""Check if the instance is still running
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
bool
|
|
67
|
+
True if the instance is still running.
|
|
68
|
+
"""
|
|
69
|
+
if not self._pid:
|
|
70
|
+
return False
|
|
71
|
+
if psutil.pid_exists(self._pid):
|
|
72
|
+
return True
|
|
73
|
+
self._pid = None
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
|
|
78
|
+
"""
|
|
79
|
+
Use a combination of the current omniverse application and the information
|
|
80
|
+
in the local .nvidia-omniverse/config/omniverse.toml file to come up with
|
|
81
|
+
the pathname of a kit executable suitable for hosting another copy of the
|
|
82
|
+
ansys.geometry.server kit.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
Optional[str]
|
|
87
|
+
The pathname of a kit executable or None
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
# parse the toml config file for the location of the installed apps
|
|
91
|
+
try:
|
|
92
|
+
import tomllib
|
|
93
|
+
except ModuleNotFoundError:
|
|
94
|
+
import pip._vendor.tomli as tomllib
|
|
95
|
+
|
|
96
|
+
homedir = os.path.expanduser("~")
|
|
97
|
+
ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
|
|
98
|
+
if not os.path.exists(ov_config):
|
|
99
|
+
return None
|
|
100
|
+
# read the Omniverse configuration toml file
|
|
101
|
+
with open(ov_config, "r") as ov_file:
|
|
102
|
+
ov_data = ov_file.read()
|
|
103
|
+
config = tomllib.loads(ov_data)
|
|
104
|
+
appdir = config.get("paths", {}).get("library_root", fallback_directory)
|
|
105
|
+
|
|
106
|
+
# If we are running inside an Omniverse app, use that information
|
|
107
|
+
try:
|
|
108
|
+
import omni.kit.app
|
|
109
|
+
|
|
110
|
+
# get the current application
|
|
111
|
+
app = omni.kit.app.get_app()
|
|
112
|
+
app_name = app.get_app_filename().split(".")[-1]
|
|
113
|
+
app_version = app.get_app_version().split("-")[0]
|
|
114
|
+
# and where it is installed
|
|
115
|
+
appdir = os.path.join(appdir, f"{app_name}-{app_version}")
|
|
116
|
+
except ModuleNotFoundError:
|
|
117
|
+
# Names should be like: "C:\\Users\\foo\\AppData\\Local\\ov\\pkg\\create-2023.2.3\\launcher.toml"
|
|
118
|
+
target = None
|
|
119
|
+
target_version = None
|
|
120
|
+
for d in glob.glob(os.path.join(appdir, "*", "launcher.toml")):
|
|
121
|
+
test_dir = os.path.dirname(d)
|
|
122
|
+
# the name will be something like "create-2023.2.3"
|
|
123
|
+
name = os.path.basename(test_dir).split("-")
|
|
124
|
+
if len(name) != 2:
|
|
125
|
+
continue
|
|
126
|
+
if name[0] not in ("kit", "create", "view"):
|
|
127
|
+
continue
|
|
128
|
+
if (target_version is None) or (name[1] > target_version):
|
|
129
|
+
target = test_dir
|
|
130
|
+
target_version = name[1]
|
|
131
|
+
if target is None:
|
|
132
|
+
return None
|
|
133
|
+
appdir = target
|
|
134
|
+
|
|
135
|
+
# Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
|
|
136
|
+
# Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
|
|
137
|
+
exe_names = ["kit.sh", "kit"]
|
|
138
|
+
if sys.platform.startswith("win"):
|
|
139
|
+
exe_names = ["kit.bat", "kit.exe"]
|
|
140
|
+
|
|
141
|
+
# look in 4 places...
|
|
142
|
+
for dir_name in [appdir, os.path.join(appdir, "kit")]:
|
|
143
|
+
for exe_name in exe_names:
|
|
144
|
+
if os.path.exists(os.path.join(dir_name, exe_name)):
|
|
145
|
+
return os.path.join(dir_name, exe_name)
|
|
146
|
+
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def launch_kit_instance(
|
|
151
|
+
kit_path: Optional[str] = None,
|
|
152
|
+
extension_paths: Optional[List[str]] = None,
|
|
153
|
+
extensions: Optional[List[str]] = None,
|
|
154
|
+
cli_options: Optional[List[str]] = None,
|
|
155
|
+
log_file: Optional[str] = None,
|
|
156
|
+
log_level: str = "warn",
|
|
157
|
+
) -> "OmniverseKitInstance":
|
|
158
|
+
"""Launch an Omniverse application instance
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
kit_path : Optional[str]
|
|
163
|
+
The full pathname of to a binary capable of serving as a kit runner.
|
|
164
|
+
extension_paths : Optional[List[str]]
|
|
165
|
+
List of directory names to include the in search for kits.
|
|
166
|
+
extensions : Optional[List[str]]
|
|
167
|
+
List of kit extensions to be loaded into the launched kit instance.
|
|
168
|
+
log_file : Optional[str]
|
|
169
|
+
The name of a text file where the logging information for the instance will be saved.
|
|
170
|
+
log_level : str
|
|
171
|
+
The level of the logging information to record: "verbose", "info", "warn", "error", "fatal",
|
|
172
|
+
the default is "warn".
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
OmniverseKitInstance
|
|
177
|
+
The object interface for the launched instance
|
|
178
|
+
|
|
179
|
+
Examples
|
|
180
|
+
--------
|
|
181
|
+
Run a simple, empty GUI kit instance.
|
|
182
|
+
|
|
183
|
+
>>> from ansys.pyensight.core.utils import omniverse
|
|
184
|
+
>>> ov = omniverse.launch_kit_instance(extensions=['omni.kit.uiapp'])
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
# build the command line
|
|
188
|
+
if not kit_path:
|
|
189
|
+
kit_path = find_kit_filename()
|
|
190
|
+
if not kit_path:
|
|
191
|
+
raise RuntimeError("Unable to find a suitable Omniverse kit install")
|
|
192
|
+
cmd = [kit_path]
|
|
193
|
+
if extension_paths:
|
|
194
|
+
for path in extension_paths:
|
|
195
|
+
cmd.extend(["--ext-folder", path])
|
|
196
|
+
if extensions:
|
|
197
|
+
for ext in extensions:
|
|
198
|
+
cmd.extend(["--enable", ext])
|
|
199
|
+
if cli_options:
|
|
200
|
+
for opt in cli_options:
|
|
201
|
+
cmd.append(opt)
|
|
202
|
+
if log_level not in ("verbose", "info", "warn", "error", "fatal"):
|
|
203
|
+
raise RuntimeError(f"Invalid logging level: {log_level}")
|
|
204
|
+
cmd.append(f"--/log/level={log_level}")
|
|
205
|
+
if log_file:
|
|
206
|
+
cmd.append(f"--/log/file={log_file}")
|
|
207
|
+
cmd.append("--/log/enabled=true")
|
|
208
|
+
# Launch the process
|
|
209
|
+
env_vars = os.environ.copy()
|
|
210
|
+
p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=env_vars)
|
|
211
|
+
return OmniverseKitInstance(p.pid)
|
|
212
|
+
|
|
213
|
+
|
|
21
214
|
class Omniverse:
|
|
22
215
|
"""Provides the ``ensight.utils.omniverse`` interface.
|
|
23
216
|
|
|
@@ -56,79 +249,6 @@ class Omniverse:
|
|
|
56
249
|
self._interpreter: str = ""
|
|
57
250
|
self._status_filename: str = ""
|
|
58
251
|
|
|
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
252
|
def _check_modules(self) -> None:
|
|
133
253
|
"""Verify that the Python interpreter is correct
|
|
134
254
|
|
|
@@ -190,6 +310,7 @@ class Omniverse:
|
|
|
190
310
|
live: bool = True,
|
|
191
311
|
debug_filename: str = "",
|
|
192
312
|
time_scale: float = 1.0,
|
|
313
|
+
line_width: float = 0.0,
|
|
193
314
|
options: dict = {},
|
|
194
315
|
) -> None:
|
|
195
316
|
"""Ensure that an EnSight dsg -> omniverse server is running
|
|
@@ -224,6 +345,9 @@ class Omniverse:
|
|
|
224
345
|
If the name of a file is provided, it will be used to save logging information on
|
|
225
346
|
the connection between EnSight and Omniverse. This option is no longer supported,
|
|
226
347
|
but the API remains for backwards compatibility.
|
|
348
|
+
line_width : float
|
|
349
|
+
If set, line objects will be represented as "tubes" of the size specified by
|
|
350
|
+
this factor. The default is 0.0 and causes lines not to be exported.
|
|
227
351
|
options : dict
|
|
228
352
|
Allows for a fallback for the grpc host/port and the security token.
|
|
229
353
|
"""
|
|
@@ -259,6 +383,8 @@ class Omniverse:
|
|
|
259
383
|
cmd.extend(["--normalize_geometry", "true"])
|
|
260
384
|
if time_scale != 1.0:
|
|
261
385
|
cmd.extend(["--time_scale", str(time_scale)])
|
|
386
|
+
if line_width != 0.0:
|
|
387
|
+
cmd.extend(["--line_width", str(line_width)])
|
|
262
388
|
if not live:
|
|
263
389
|
cmd.extend(["--oneshot", "1"])
|
|
264
390
|
cmd.extend(["--dsg_uri", dsg_uri])
|
|
@@ -336,7 +462,7 @@ class Omniverse:
|
|
|
336
462
|
self._server_pid = None
|
|
337
463
|
self._new_status_file(new=False)
|
|
338
464
|
|
|
339
|
-
def update(self, temporal: bool = False) -> None:
|
|
465
|
+
def update(self, temporal: bool = False, line_width: float = 0.0) -> None:
|
|
340
466
|
"""Update the geometry in Omniverse
|
|
341
467
|
|
|
342
468
|
Export the current EnSight scene to the current Omniverse connection.
|
|
@@ -345,10 +471,26 @@ class Omniverse:
|
|
|
345
471
|
----------
|
|
346
472
|
temporal : bool
|
|
347
473
|
If True, export all timesteps.
|
|
474
|
+
line_width : float
|
|
475
|
+
If set to a non-zero value, lines will be exported with this thickness.
|
|
476
|
+
This feature is only available in 2025 R2 and later.
|
|
348
477
|
"""
|
|
349
478
|
update_cmd = "dynamicscenegraph://localhost/client/update"
|
|
479
|
+
prefix = "?"
|
|
350
480
|
if temporal:
|
|
351
|
-
update_cmd += "
|
|
481
|
+
update_cmd += f"{prefix}timesteps=1"
|
|
482
|
+
prefix = "&"
|
|
483
|
+
if line_width != 0.0:
|
|
484
|
+
add_linewidth = False
|
|
485
|
+
if isinstance(self._ensight, ModuleType):
|
|
486
|
+
add_linewidth = True
|
|
487
|
+
else:
|
|
488
|
+
# only in 2025 R2 and beyond
|
|
489
|
+
if self._ensight._session.ensight_version_check("2025 R2", exception=False):
|
|
490
|
+
add_linewidth = True
|
|
491
|
+
if add_linewidth:
|
|
492
|
+
update_cmd += f"{prefix}ANSYS_linewidth={line_width}"
|
|
493
|
+
prefix = "&"
|
|
352
494
|
self._check_modules()
|
|
353
495
|
if not self.is_running_omniverse():
|
|
354
496
|
raise RuntimeError("No Omniverse server connection is currently active.")
|
|
@@ -78,8 +78,7 @@ class OmniverseGeometryServer(object):
|
|
|
78
78
|
normalize_geometry: bool = False,
|
|
79
79
|
dsg_uri: str = "",
|
|
80
80
|
monitor_directory: str = "",
|
|
81
|
-
line_width: float =
|
|
82
|
-
use_lines: bool = False,
|
|
81
|
+
line_width: float = 0.0,
|
|
83
82
|
) -> None:
|
|
84
83
|
self._dsg_uri = dsg_uri
|
|
85
84
|
self._destination = destination
|
|
@@ -96,7 +95,6 @@ class OmniverseGeometryServer(object):
|
|
|
96
95
|
self._status_filename: str = ""
|
|
97
96
|
self._monitor_directory: str = monitor_directory
|
|
98
97
|
self._line_width = line_width
|
|
99
|
-
self._use_lines = use_lines
|
|
100
98
|
|
|
101
99
|
@property
|
|
102
100
|
def monitor_directory(self) -> Optional[str]:
|
|
@@ -173,6 +171,14 @@ class OmniverseGeometryServer(object):
|
|
|
173
171
|
def time_scale(self, value: float) -> None:
|
|
174
172
|
self._time_scale = value
|
|
175
173
|
|
|
174
|
+
@property
|
|
175
|
+
def line_width(self) -> float:
|
|
176
|
+
return self._line_width
|
|
177
|
+
|
|
178
|
+
@line_width.setter
|
|
179
|
+
def line_width(self, line_width: float) -> None:
|
|
180
|
+
self._line_width = line_width
|
|
181
|
+
|
|
176
182
|
def run_server(self, one_shot: bool = False) -> None:
|
|
177
183
|
"""
|
|
178
184
|
Run a DSG to Omniverse server in process.
|
|
@@ -189,11 +195,11 @@ class OmniverseGeometryServer(object):
|
|
|
189
195
|
|
|
190
196
|
# Build the Omniverse connection
|
|
191
197
|
omni_link = ov_dsg_server.OmniverseWrapper(
|
|
192
|
-
destination=self._destination, line_width=self.
|
|
198
|
+
destination=self._destination, line_width=self.line_width
|
|
193
199
|
)
|
|
194
200
|
logging.info("Omniverse connection established.")
|
|
195
201
|
|
|
196
|
-
# parse the DSG
|
|
202
|
+
# parse the DSG URI
|
|
197
203
|
parsed = urlparse(self.dsg_uri)
|
|
198
204
|
port = parsed.port
|
|
199
205
|
host = parsed.hostname
|
|
@@ -223,7 +229,10 @@ class OmniverseGeometryServer(object):
|
|
|
223
229
|
|
|
224
230
|
# until the link is dropped, continue
|
|
225
231
|
while not dsg_link.is_shutdown() and not self._shutdown:
|
|
232
|
+
# Reset the line width to the CLI default before each update
|
|
233
|
+
omni_link.line_width = self.line_width
|
|
226
234
|
dsg_link.handle_one_update()
|
|
235
|
+
|
|
227
236
|
if one_shot:
|
|
228
237
|
break
|
|
229
238
|
|
|
@@ -276,7 +285,7 @@ class OmniverseGeometryServer(object):
|
|
|
276
285
|
|
|
277
286
|
# Build the Omniverse connection
|
|
278
287
|
omni_link = ov_dsg_server.OmniverseWrapper(
|
|
279
|
-
destination=self._destination, line_width=self.
|
|
288
|
+
destination=self._destination, line_width=self.line_width
|
|
280
289
|
)
|
|
281
290
|
logging.info("Omniverse connection established.")
|
|
282
291
|
|
|
@@ -347,6 +356,8 @@ class OmniverseGeometryServer(object):
|
|
|
347
356
|
logging.warning("Time values not currently supported.")
|
|
348
357
|
if len(files_to_process) > 1:
|
|
349
358
|
logging.warning("Multiple glb files not currently fully supported.")
|
|
359
|
+
# Reset the line width to the CLI default before each update
|
|
360
|
+
omni_link.line_width = self.line_width
|
|
350
361
|
# Upload the files
|
|
351
362
|
glb_link.start_uploads([timeline[0], timeline[-1]])
|
|
352
363
|
for glb_file, timestamp in zip(files_to_process, file_timestamps):
|
|
@@ -463,13 +474,12 @@ if __name__ == "__main__":
|
|
|
463
474
|
line_default = float(line_default)
|
|
464
475
|
except ValueError:
|
|
465
476
|
line_default = None
|
|
466
|
-
# Potential future default: -0.0001
|
|
467
477
|
parser.add_argument(
|
|
468
478
|
"--line_width",
|
|
469
479
|
metavar="line_width",
|
|
470
480
|
default=line_default,
|
|
471
481
|
type=float,
|
|
472
|
-
help=f"Width of lines: >0=absolute size. <0=fraction of diagonal. 0=
|
|
482
|
+
help=f"Width of lines: >0=absolute size. <0=fraction of diagonal. 0=none. Default: {line_default}",
|
|
473
483
|
)
|
|
474
484
|
|
|
475
485
|
# parse the command line
|
|
@@ -492,8 +502,7 @@ if __name__ == "__main__":
|
|
|
492
502
|
logging.basicConfig(**log_args) # type: ignore
|
|
493
503
|
|
|
494
504
|
# size of lines in data units or fraction of bounding box diagonal
|
|
495
|
-
|
|
496
|
-
line_width = -0.0001
|
|
505
|
+
line_width = 0.0
|
|
497
506
|
if args.line_width is not None:
|
|
498
507
|
line_width = args.line_width
|
|
499
508
|
|
|
@@ -508,7 +517,6 @@ if __name__ == "__main__":
|
|
|
508
517
|
vrmode=not args.include_camera,
|
|
509
518
|
temporal=args.temporal,
|
|
510
519
|
line_width=line_width,
|
|
511
|
-
use_lines=use_lines,
|
|
512
520
|
)
|
|
513
521
|
|
|
514
522
|
# run the server
|
|
@@ -41,8 +41,7 @@ class OmniverseWrapper(object):
|
|
|
41
41
|
self,
|
|
42
42
|
live_edit: bool = False,
|
|
43
43
|
destination: str = "",
|
|
44
|
-
line_width: float =
|
|
45
|
-
use_lines: bool = False,
|
|
44
|
+
line_width: float = 0.0,
|
|
46
45
|
) -> None:
|
|
47
46
|
self._cleaned_index = 0
|
|
48
47
|
self._cleaned_names: dict = {}
|
|
@@ -61,7 +60,6 @@ class OmniverseWrapper(object):
|
|
|
61
60
|
self.destination = destination
|
|
62
61
|
|
|
63
62
|
self._line_width = line_width
|
|
64
|
-
self._use_lines = use_lines
|
|
65
63
|
|
|
66
64
|
@property
|
|
67
65
|
def destination(self) -> str:
|
|
@@ -82,10 +80,6 @@ class OmniverseWrapper(object):
|
|
|
82
80
|
def line_width(self, line_width: float) -> None:
|
|
83
81
|
self._line_width = line_width
|
|
84
82
|
|
|
85
|
-
@property
|
|
86
|
-
def use_lines(self) -> bool:
|
|
87
|
-
return self._use_lines
|
|
88
|
-
|
|
89
83
|
def shutdown(self) -> None:
|
|
90
84
|
"""
|
|
91
85
|
Shutdown the connection to Omniverse cleanly.
|
|
@@ -582,7 +576,8 @@ class OmniverseWrapper(object):
|
|
|
582
576
|
pbrShader.CreateInput("specularColor", Sdf.ValueTypeNames.Color3f).Set(color)
|
|
583
577
|
|
|
584
578
|
material.CreateSurfaceOutput().ConnectToSource(pbrShader.ConnectableAPI(), "surface")
|
|
585
|
-
UsdShade.MaterialBindingAPI(mesh
|
|
579
|
+
mat_binding_api = UsdShade.MaterialBindingAPI.Apply(mesh.GetPrim())
|
|
580
|
+
mat_binding_api.Bind(material)
|
|
586
581
|
|
|
587
582
|
return material
|
|
588
583
|
|
|
@@ -639,7 +634,8 @@ class OmniverseWrapper(object):
|
|
|
639
634
|
cam = geom_cam.GetCamera()
|
|
640
635
|
# LOL, not sure why is might be correct, but so far it seems to work???
|
|
641
636
|
cam.focalLength = camera.fieldofview
|
|
642
|
-
|
|
637
|
+
dist = (target_pos - cam_pos).GetLength()
|
|
638
|
+
cam.clippingRange = Gf.Range1f(0.1 * dist, 10.0 * dist)
|
|
643
639
|
look_at = Gf.Matrix4d()
|
|
644
640
|
look_at.SetLookAt(cam_pos, target_pos, up_vec)
|
|
645
641
|
trans_row = look_at.GetRow(3)
|
|
@@ -723,14 +719,83 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
723
719
|
self._group_prims: Dict[int, Any] = dict()
|
|
724
720
|
self._root_prim = None
|
|
725
721
|
self._sent_textures = False
|
|
722
|
+
self._updated_camera = False
|
|
726
723
|
|
|
727
724
|
def add_group(self, id: int, view: bool = False) -> None:
|
|
728
725
|
super().add_group(id, view)
|
|
729
726
|
group = self.session.groups[id]
|
|
727
|
+
|
|
730
728
|
if not view:
|
|
729
|
+
# Capture changes in line/sphere sizes if it was not set from cli
|
|
730
|
+
width = self.get_dsg_cmd_attribute(group, "ANSYS_linewidth")
|
|
731
|
+
if width:
|
|
732
|
+
try:
|
|
733
|
+
self._omni.line_width = float(width)
|
|
734
|
+
except ValueError:
|
|
735
|
+
pass
|
|
736
|
+
|
|
731
737
|
parent_prim = self._group_prims[group.parent_id]
|
|
738
|
+
# get the EnSight object type and the transform matrix
|
|
732
739
|
obj_type = self.get_dsg_cmd_attribute(group, "ENS_OBJ_TYPE")
|
|
733
|
-
matrix =
|
|
740
|
+
matrix = group.matrix4x4
|
|
741
|
+
# Is this a "case" group (it will contain part of the camera view in the matrix)
|
|
742
|
+
if obj_type == "ENS_CASE":
|
|
743
|
+
if (not self.session.vrmode) and (not self._updated_camera):
|
|
744
|
+
# if in camera mode, we need to update the camera matrix so we can
|
|
745
|
+
# use the identity matrix on this group. The camera should have been
|
|
746
|
+
# created in the "view" handler
|
|
747
|
+
cam_name = "/Root/Cam"
|
|
748
|
+
cam_prim = self._omni._stage.GetPrimAtPath(cam_name) # type: ignore
|
|
749
|
+
geom_cam = UsdGeom.Camera(cam_prim)
|
|
750
|
+
# get the camera
|
|
751
|
+
cam = geom_cam.GetCamera()
|
|
752
|
+
c = cam.transform
|
|
753
|
+
m = Gf.Matrix4d(*matrix).GetTranspose()
|
|
754
|
+
# move the model transform to the camera transform
|
|
755
|
+
cam.transform = c * m.GetInverse()
|
|
756
|
+
# set the updated camera
|
|
757
|
+
geom_cam.SetFromCamera(cam)
|
|
758
|
+
# apply the inverse cam transform to move the center of interest
|
|
759
|
+
# from data space to camera space
|
|
760
|
+
coi_attr = cam_prim.GetAttribute("omni:kit:centerOfInterest")
|
|
761
|
+
if coi_attr.IsValid():
|
|
762
|
+
coi_data = coi_attr.Get()
|
|
763
|
+
coi_cam = (
|
|
764
|
+
Gf.Vec4d(coi_data[0], coi_data[1], coi_data[2], 1.0)
|
|
765
|
+
* cam.transform.GetInverse()
|
|
766
|
+
)
|
|
767
|
+
coi_attr.Set(
|
|
768
|
+
Gf.Vec3d(
|
|
769
|
+
coi_cam[0] / coi_cam[3],
|
|
770
|
+
coi_cam[1] / coi_cam[3],
|
|
771
|
+
coi_cam[2] / coi_cam[3],
|
|
772
|
+
)
|
|
773
|
+
)
|
|
774
|
+
# use the camera view by default
|
|
775
|
+
self._omni._stage.GetRootLayer().customLayerData = { # type: ignore
|
|
776
|
+
"cameraSettings": {"boundCamera": "/Root/Cam"}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
# We only want to do this once
|
|
780
|
+
self._updated_camera = True
|
|
781
|
+
matrix = [
|
|
782
|
+
1.0,
|
|
783
|
+
0.0,
|
|
784
|
+
0.0,
|
|
785
|
+
0.0,
|
|
786
|
+
0.0,
|
|
787
|
+
1.0,
|
|
788
|
+
0.0,
|
|
789
|
+
0.0,
|
|
790
|
+
0.0,
|
|
791
|
+
0.0,
|
|
792
|
+
1.0,
|
|
793
|
+
0.0,
|
|
794
|
+
0.0,
|
|
795
|
+
0.0,
|
|
796
|
+
0.0,
|
|
797
|
+
1.0,
|
|
798
|
+
]
|
|
734
799
|
prim = self._omni.create_dsg_group(
|
|
735
800
|
group.name, parent_prim, matrix=matrix, obj_type=obj_type
|
|
736
801
|
)
|
|
@@ -801,40 +866,37 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
801
866
|
first_timestep=(self.session.cur_timeline[0] == self.session.time_limits[0]),
|
|
802
867
|
mat_info=mat_info,
|
|
803
868
|
)
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
if
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
line_color
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
self.session.cur_timeline[0] == self.session.time_limits[0]
|
|
836
|
-
),
|
|
837
|
-
)
|
|
869
|
+
command, verts, tcoords, var_cmd = part.line_rep()
|
|
870
|
+
if command is not None:
|
|
871
|
+
# If there are no triangle (ideally if these are not hidden line
|
|
872
|
+
# edges), then use the base color for the part. If there are
|
|
873
|
+
# triangles, then assume these are hidden line edges and use the
|
|
874
|
+
# line_color.
|
|
875
|
+
line_color = color
|
|
876
|
+
if has_triangles:
|
|
877
|
+
line_color = [
|
|
878
|
+
part.cmd.line_color[0] * part.cmd.diffuse,
|
|
879
|
+
part.cmd.line_color[1] * part.cmd.diffuse,
|
|
880
|
+
part.cmd.line_color[2] * part.cmd.diffuse,
|
|
881
|
+
part.cmd.line_color[3],
|
|
882
|
+
]
|
|
883
|
+
# TODO: texture coordinates on lines are current invalid in OV
|
|
884
|
+
var_cmd = None
|
|
885
|
+
tcoords = None
|
|
886
|
+
# Generate the lines
|
|
887
|
+
_ = self._omni.create_dsg_lines(
|
|
888
|
+
name,
|
|
889
|
+
obj_id,
|
|
890
|
+
part.hash,
|
|
891
|
+
parent_prim,
|
|
892
|
+
verts,
|
|
893
|
+
tcoords,
|
|
894
|
+
matrix=matrix,
|
|
895
|
+
diffuse=line_color,
|
|
896
|
+
variable=var_cmd,
|
|
897
|
+
timeline=self.session.cur_timeline,
|
|
898
|
+
first_timestep=(self.session.cur_timeline[0] == self.session.time_limits[0]),
|
|
899
|
+
)
|
|
838
900
|
|
|
839
901
|
elif part.cmd.render == part.cmd.NODES:
|
|
840
902
|
command, verts, sizes, colors, var_cmd = part.point_rep()
|
|
@@ -875,6 +937,8 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
875
937
|
# Upload a material to the Omniverse server
|
|
876
938
|
self._omni.uploadMaterial()
|
|
877
939
|
self._sent_textures = False
|
|
940
|
+
# We want to update the camera a single time within this update
|
|
941
|
+
self._updated_camera = False
|
|
878
942
|
|
|
879
943
|
def end_update(self) -> None:
|
|
880
944
|
super().end_update()
|