ansys-pyensight-core 0.9.1__py3-none-any.whl → 0.9.3__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/__init__.py +1 -1
- ansys/pyensight/core/common.py +17 -0
- ansys/pyensight/core/dockerlauncher.py +4 -0
- ansys/pyensight/core/dvs.py +792 -0
- ansys/pyensight/core/ensight_grpc.py +302 -0
- ansys/pyensight/core/libuserd.py +200 -76
- ansys/pyensight/core/utils/omniverse.py +202 -77
- ansys/pyensight/core/utils/omniverse_cli.py +19 -11
- ansys/pyensight/core/utils/omniverse_dsg_server.py +113 -44
- ansys/pyensight/core/utils/omniverse_glb_server.py +32 -10
- {ansys_pyensight_core-0.9.1.dist-info → ansys_pyensight_core-0.9.3.dist-info}/METADATA +3 -2
- {ansys_pyensight_core-0.9.1.dist-info → ansys_pyensight_core-0.9.3.dist-info}/RECORD +14 -13
- {ansys_pyensight_core-0.9.1.dist-info → ansys_pyensight_core-0.9.3.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.9.1.dist-info → ansys_pyensight_core-0.9.3.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,198 @@ 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
|
+
|
|
33
|
+
def __del__(self) -> None:
|
|
34
|
+
"""Close down the instance on delete"""
|
|
35
|
+
self.close()
|
|
36
|
+
|
|
37
|
+
def close(self) -> None:
|
|
38
|
+
"""Shutdown the Omniverse instance
|
|
39
|
+
|
|
40
|
+
If the instance associated with this object is still running,
|
|
41
|
+
shut it down.
|
|
42
|
+
"""
|
|
43
|
+
if not self.is_running():
|
|
44
|
+
return
|
|
45
|
+
proc = psutil.Process(self._pid)
|
|
46
|
+
for child in proc.children(recursive=True):
|
|
47
|
+
if psutil.pid_exists(child.pid):
|
|
48
|
+
# This can be a race condition, so it is ok if the child is dead already
|
|
49
|
+
try:
|
|
50
|
+
child.kill()
|
|
51
|
+
except psutil.NoSuchProcess:
|
|
52
|
+
pass
|
|
53
|
+
# Same issue, this process might already be shutting down, so NoSuchProcess is ok.
|
|
54
|
+
try:
|
|
55
|
+
proc.kill()
|
|
56
|
+
except psutil.NoSuchProcess:
|
|
57
|
+
pass
|
|
58
|
+
self._pid = None
|
|
59
|
+
|
|
60
|
+
def is_running(self) -> bool:
|
|
61
|
+
"""Check if the instance is still running
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
bool
|
|
66
|
+
True if the instance is still running.
|
|
67
|
+
"""
|
|
68
|
+
if not self._pid:
|
|
69
|
+
return False
|
|
70
|
+
if psutil.pid_exists(self._pid):
|
|
71
|
+
return True
|
|
72
|
+
self._pid = None
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
|
|
77
|
+
"""
|
|
78
|
+
Use a combination of the current omniverse application and the information
|
|
79
|
+
in the local .nvidia-omniverse/config/omniverse.toml file to come up with
|
|
80
|
+
the pathname of a kit executable suitable for hosting another copy of the
|
|
81
|
+
ansys.geometry.server kit.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
Optional[str]
|
|
86
|
+
The pathname of a kit executable or None
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
# parse the toml config file for the location of the installed apps
|
|
90
|
+
try:
|
|
91
|
+
import tomllib
|
|
92
|
+
except ModuleNotFoundError:
|
|
93
|
+
import pip._vendor.tomli as tomllib
|
|
94
|
+
|
|
95
|
+
homedir = os.path.expanduser("~")
|
|
96
|
+
ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
|
|
97
|
+
if not os.path.exists(ov_config):
|
|
98
|
+
return None
|
|
99
|
+
# read the Omniverse configuration toml file
|
|
100
|
+
with open(ov_config, "r") as ov_file:
|
|
101
|
+
ov_data = ov_file.read()
|
|
102
|
+
config = tomllib.loads(ov_data)
|
|
103
|
+
appdir = config.get("paths", {}).get("library_root", fallback_directory)
|
|
104
|
+
|
|
105
|
+
# If we are running inside an Omniverse app, use that information
|
|
106
|
+
try:
|
|
107
|
+
import omni.kit.app
|
|
108
|
+
|
|
109
|
+
# get the current application
|
|
110
|
+
app = omni.kit.app.get_app()
|
|
111
|
+
app_name = app.get_app_filename().split(".")[-1]
|
|
112
|
+
app_version = app.get_app_version().split("-")[0]
|
|
113
|
+
# and where it is installed
|
|
114
|
+
appdir = os.path.join(appdir, f"{app_name}-{app_version}")
|
|
115
|
+
except ModuleNotFoundError:
|
|
116
|
+
# Names should be like: "C:\\Users\\foo\\AppData\\Local\\ov\\pkg\\create-2023.2.3\\launcher.toml"
|
|
117
|
+
target = None
|
|
118
|
+
target_version = None
|
|
119
|
+
for d in glob.glob(os.path.join(appdir, "*", "launcher.toml")):
|
|
120
|
+
test_dir = os.path.dirname(d)
|
|
121
|
+
# the name will be something like "create-2023.2.3"
|
|
122
|
+
name = os.path.basename(test_dir).split("-")
|
|
123
|
+
if len(name) != 2:
|
|
124
|
+
continue
|
|
125
|
+
if name[0] not in ("kit", "create", "view"):
|
|
126
|
+
continue
|
|
127
|
+
if (target_version is None) or (name[1] > target_version):
|
|
128
|
+
target = test_dir
|
|
129
|
+
target_version = name[1]
|
|
130
|
+
if target is None:
|
|
131
|
+
return None
|
|
132
|
+
appdir = target
|
|
133
|
+
|
|
134
|
+
# Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
|
|
135
|
+
# Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
|
|
136
|
+
exe_names = ["kit.sh", "kit"]
|
|
137
|
+
if sys.platform.startswith("win"):
|
|
138
|
+
exe_names = ["kit.bat", "kit.exe"]
|
|
139
|
+
|
|
140
|
+
# look in 4 places...
|
|
141
|
+
for dir_name in [appdir, os.path.join(appdir, "kit")]:
|
|
142
|
+
for exe_name in exe_names:
|
|
143
|
+
if os.path.exists(os.path.join(dir_name, exe_name)):
|
|
144
|
+
return os.path.join(dir_name, exe_name)
|
|
145
|
+
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def launch_kit_instance(
|
|
150
|
+
kit_path: Optional[str] = None,
|
|
151
|
+
extension_paths: Optional[List[str]] = None,
|
|
152
|
+
extensions: Optional[List[str]] = None,
|
|
153
|
+
cli_options: Optional[List[str]] = None,
|
|
154
|
+
log_file: Optional[str] = None,
|
|
155
|
+
log_level: str = "warn",
|
|
156
|
+
) -> "OmniverseKitInstance":
|
|
157
|
+
"""Launch an Omniverse application instance
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
kit_path : Optional[str]
|
|
162
|
+
The full pathname of to a binary capable of serving as a kit runner.
|
|
163
|
+
extension_paths : Optional[List[str]]
|
|
164
|
+
List of directory names to include the in search for kits.
|
|
165
|
+
extensions : Optional[List[str]]
|
|
166
|
+
List of kit extensions to be loaded into the launched kit instance.
|
|
167
|
+
log_file : Optional[str]
|
|
168
|
+
The name of a text file where the logging information for the instance will be saved.
|
|
169
|
+
log_level : str
|
|
170
|
+
The level of the logging information to record: "verbose", "info", "warn", "error", "fatal",
|
|
171
|
+
the default is "warn".
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
OmniverseKitInstance
|
|
176
|
+
The object interface for the launched instance
|
|
177
|
+
|
|
178
|
+
Examples
|
|
179
|
+
--------
|
|
180
|
+
Run a simple, empty GUI kit instance.
|
|
181
|
+
|
|
182
|
+
>>> from ansys.pyensight.core.utils import omniverse
|
|
183
|
+
>>> ov = omniverse.launch_kit_instance(extensions=['omni.kit.uiapp'])
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
# build the command line
|
|
187
|
+
if not kit_path:
|
|
188
|
+
kit_path = find_kit_filename()
|
|
189
|
+
if not kit_path:
|
|
190
|
+
raise RuntimeError("Unable to find a suitable Omniverse kit install")
|
|
191
|
+
cmd = [kit_path]
|
|
192
|
+
if extension_paths:
|
|
193
|
+
for path in extension_paths:
|
|
194
|
+
cmd.extend(["--ext-folder", path])
|
|
195
|
+
if extensions:
|
|
196
|
+
for ext in extensions:
|
|
197
|
+
cmd.extend(["--enable", ext])
|
|
198
|
+
if cli_options:
|
|
199
|
+
for opt in cli_options:
|
|
200
|
+
cmd.append(opt)
|
|
201
|
+
if log_level not in ("verbose", "info", "warn", "error", "fatal"):
|
|
202
|
+
raise RuntimeError(f"Invalid logging level: {log_level}")
|
|
203
|
+
cmd.append(f"--/log/level={log_level}")
|
|
204
|
+
if log_file:
|
|
205
|
+
cmd.append(f"--/log/file={log_file}")
|
|
206
|
+
cmd.append("--/log/enabled=true")
|
|
207
|
+
# Launch the process
|
|
208
|
+
env_vars = os.environ.copy()
|
|
209
|
+
p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=env_vars)
|
|
210
|
+
return OmniverseKitInstance(p.pid)
|
|
211
|
+
|
|
212
|
+
|
|
21
213
|
class Omniverse:
|
|
22
214
|
"""Provides the ``ensight.utils.omniverse`` interface.
|
|
23
215
|
|
|
@@ -56,79 +248,6 @@ class Omniverse:
|
|
|
56
248
|
self._interpreter: str = ""
|
|
57
249
|
self._status_filename: str = ""
|
|
58
250
|
|
|
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
251
|
def _check_modules(self) -> None:
|
|
133
252
|
"""Verify that the Python interpreter is correct
|
|
134
253
|
|
|
@@ -361,9 +480,15 @@ class Omniverse:
|
|
|
361
480
|
update_cmd += f"{prefix}timesteps=1"
|
|
362
481
|
prefix = "&"
|
|
363
482
|
if line_width != 0.0:
|
|
364
|
-
|
|
365
|
-
if self._ensight
|
|
366
|
-
|
|
483
|
+
add_linewidth = False
|
|
484
|
+
if isinstance(self._ensight, ModuleType):
|
|
485
|
+
add_linewidth = True
|
|
486
|
+
else:
|
|
487
|
+
# only in 2025 R2 and beyond
|
|
488
|
+
if self._ensight._session.ensight_version_check("2025 R2", exception=False):
|
|
489
|
+
add_linewidth = True
|
|
490
|
+
if add_linewidth:
|
|
491
|
+
update_cmd += f"{prefix}ANSYS_linewidth={line_width}"
|
|
367
492
|
prefix = "&"
|
|
368
493
|
self._check_modules()
|
|
369
494
|
if not self.is_running_omniverse():
|
|
@@ -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.
|
|
@@ -215,6 +209,11 @@ class OmniverseWrapper(object):
|
|
|
215
209
|
ord(","): "_",
|
|
216
210
|
ord(" "): "_",
|
|
217
211
|
ord("\\"): "_",
|
|
212
|
+
ord("^"): "_",
|
|
213
|
+
ord("!"): "_",
|
|
214
|
+
ord("#"): "_",
|
|
215
|
+
ord("%"): "_",
|
|
216
|
+
ord("&"): "_",
|
|
218
217
|
}
|
|
219
218
|
name = name.translate(replacements)
|
|
220
219
|
if name[0].isdigit():
|
|
@@ -582,7 +581,8 @@ class OmniverseWrapper(object):
|
|
|
582
581
|
pbrShader.CreateInput("specularColor", Sdf.ValueTypeNames.Color3f).Set(color)
|
|
583
582
|
|
|
584
583
|
material.CreateSurfaceOutput().ConnectToSource(pbrShader.ConnectableAPI(), "surface")
|
|
585
|
-
UsdShade.MaterialBindingAPI(mesh
|
|
584
|
+
mat_binding_api = UsdShade.MaterialBindingAPI.Apply(mesh.GetPrim())
|
|
585
|
+
mat_binding_api.Bind(material)
|
|
586
586
|
|
|
587
587
|
return material
|
|
588
588
|
|
|
@@ -639,7 +639,8 @@ class OmniverseWrapper(object):
|
|
|
639
639
|
cam = geom_cam.GetCamera()
|
|
640
640
|
# LOL, not sure why is might be correct, but so far it seems to work???
|
|
641
641
|
cam.focalLength = camera.fieldofview
|
|
642
|
-
|
|
642
|
+
dist = (target_pos - cam_pos).GetLength()
|
|
643
|
+
cam.clippingRange = Gf.Range1f(0.1 * dist, 10.0 * dist)
|
|
643
644
|
look_at = Gf.Matrix4d()
|
|
644
645
|
look_at.SetLookAt(cam_pos, target_pos, up_vec)
|
|
645
646
|
trans_row = look_at.GetRow(3)
|
|
@@ -723,14 +724,83 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
723
724
|
self._group_prims: Dict[int, Any] = dict()
|
|
724
725
|
self._root_prim = None
|
|
725
726
|
self._sent_textures = False
|
|
727
|
+
self._updated_camera = False
|
|
726
728
|
|
|
727
729
|
def add_group(self, id: int, view: bool = False) -> None:
|
|
728
730
|
super().add_group(id, view)
|
|
729
731
|
group = self.session.groups[id]
|
|
732
|
+
|
|
730
733
|
if not view:
|
|
734
|
+
# Capture changes in line/sphere sizes if it was not set from cli
|
|
735
|
+
width = self.get_dsg_cmd_attribute(group, "ANSYS_linewidth")
|
|
736
|
+
if width:
|
|
737
|
+
try:
|
|
738
|
+
self._omni.line_width = float(width)
|
|
739
|
+
except ValueError:
|
|
740
|
+
pass
|
|
741
|
+
|
|
731
742
|
parent_prim = self._group_prims[group.parent_id]
|
|
743
|
+
# get the EnSight object type and the transform matrix
|
|
732
744
|
obj_type = self.get_dsg_cmd_attribute(group, "ENS_OBJ_TYPE")
|
|
733
|
-
matrix =
|
|
745
|
+
matrix = group.matrix4x4
|
|
746
|
+
# Is this a "case" group (it will contain part of the camera view in the matrix)
|
|
747
|
+
if obj_type == "ENS_CASE":
|
|
748
|
+
if (not self.session.vrmode) and (not self._updated_camera):
|
|
749
|
+
# if in camera mode, we need to update the camera matrix so we can
|
|
750
|
+
# use the identity matrix on this group. The camera should have been
|
|
751
|
+
# created in the "view" handler
|
|
752
|
+
cam_name = "/Root/Cam"
|
|
753
|
+
cam_prim = self._omni._stage.GetPrimAtPath(cam_name) # type: ignore
|
|
754
|
+
geom_cam = UsdGeom.Camera(cam_prim)
|
|
755
|
+
# get the camera
|
|
756
|
+
cam = geom_cam.GetCamera()
|
|
757
|
+
c = cam.transform
|
|
758
|
+
m = Gf.Matrix4d(*matrix).GetTranspose()
|
|
759
|
+
# move the model transform to the camera transform
|
|
760
|
+
cam.transform = c * m.GetInverse()
|
|
761
|
+
# set the updated camera
|
|
762
|
+
geom_cam.SetFromCamera(cam)
|
|
763
|
+
# apply the inverse cam transform to move the center of interest
|
|
764
|
+
# from data space to camera space
|
|
765
|
+
coi_attr = cam_prim.GetAttribute("omni:kit:centerOfInterest")
|
|
766
|
+
if coi_attr.IsValid():
|
|
767
|
+
coi_data = coi_attr.Get()
|
|
768
|
+
coi_cam = (
|
|
769
|
+
Gf.Vec4d(coi_data[0], coi_data[1], coi_data[2], 1.0)
|
|
770
|
+
* cam.transform.GetInverse()
|
|
771
|
+
)
|
|
772
|
+
coi_attr.Set(
|
|
773
|
+
Gf.Vec3d(
|
|
774
|
+
coi_cam[0] / coi_cam[3],
|
|
775
|
+
coi_cam[1] / coi_cam[3],
|
|
776
|
+
coi_cam[2] / coi_cam[3],
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
# use the camera view by default
|
|
780
|
+
self._omni._stage.GetRootLayer().customLayerData = { # type: ignore
|
|
781
|
+
"cameraSettings": {"boundCamera": "/Root/Cam"}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
# We only want to do this once
|
|
785
|
+
self._updated_camera = True
|
|
786
|
+
matrix = [
|
|
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
|
+
0.0,
|
|
799
|
+
0.0,
|
|
800
|
+
0.0,
|
|
801
|
+
0.0,
|
|
802
|
+
1.0,
|
|
803
|
+
]
|
|
734
804
|
prim = self._omni.create_dsg_group(
|
|
735
805
|
group.name, parent_prim, matrix=matrix, obj_type=obj_type
|
|
736
806
|
)
|
|
@@ -801,40 +871,37 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
801
871
|
first_timestep=(self.session.cur_timeline[0] == self.session.time_limits[0]),
|
|
802
872
|
mat_info=mat_info,
|
|
803
873
|
)
|
|
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
|
-
)
|
|
874
|
+
command, verts, tcoords, var_cmd = part.line_rep()
|
|
875
|
+
if command is not None:
|
|
876
|
+
# If there are no triangle (ideally if these are not hidden line
|
|
877
|
+
# edges), then use the base color for the part. If there are
|
|
878
|
+
# triangles, then assume these are hidden line edges and use the
|
|
879
|
+
# line_color.
|
|
880
|
+
line_color = color
|
|
881
|
+
if has_triangles:
|
|
882
|
+
line_color = [
|
|
883
|
+
part.cmd.line_color[0] * part.cmd.diffuse,
|
|
884
|
+
part.cmd.line_color[1] * part.cmd.diffuse,
|
|
885
|
+
part.cmd.line_color[2] * part.cmd.diffuse,
|
|
886
|
+
part.cmd.line_color[3],
|
|
887
|
+
]
|
|
888
|
+
# TODO: texture coordinates on lines are current invalid in OV
|
|
889
|
+
var_cmd = None
|
|
890
|
+
tcoords = None
|
|
891
|
+
# Generate the lines
|
|
892
|
+
_ = self._omni.create_dsg_lines(
|
|
893
|
+
name,
|
|
894
|
+
obj_id,
|
|
895
|
+
part.hash,
|
|
896
|
+
parent_prim,
|
|
897
|
+
verts,
|
|
898
|
+
tcoords,
|
|
899
|
+
matrix=matrix,
|
|
900
|
+
diffuse=line_color,
|
|
901
|
+
variable=var_cmd,
|
|
902
|
+
timeline=self.session.cur_timeline,
|
|
903
|
+
first_timestep=(self.session.cur_timeline[0] == self.session.time_limits[0]),
|
|
904
|
+
)
|
|
838
905
|
|
|
839
906
|
elif part.cmd.render == part.cmd.NODES:
|
|
840
907
|
command, verts, sizes, colors, var_cmd = part.point_rep()
|
|
@@ -875,6 +942,8 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
875
942
|
# Upload a material to the Omniverse server
|
|
876
943
|
self._omni.uploadMaterial()
|
|
877
944
|
self._sent_textures = False
|
|
945
|
+
# We want to update the camera a single time within this update
|
|
946
|
+
self._updated_camera = False
|
|
878
947
|
|
|
879
948
|
def end_update(self) -> None:
|
|
880
949
|
super().end_update()
|