ansys-pyensight-core 0.11.0__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.
- ansys/pyensight/core/__init__.py +41 -0
- ansys/pyensight/core/common.py +341 -0
- ansys/pyensight/core/deep_pixel_view.html +98 -0
- ansys/pyensight/core/dockerlauncher.py +1124 -0
- ansys/pyensight/core/dvs.py +872 -0
- ansys/pyensight/core/enscontext.py +345 -0
- ansys/pyensight/core/enshell_grpc.py +641 -0
- ansys/pyensight/core/ensight_grpc.py +874 -0
- ansys/pyensight/core/ensobj.py +515 -0
- ansys/pyensight/core/launch_ensight.py +296 -0
- ansys/pyensight/core/launcher.py +388 -0
- ansys/pyensight/core/libuserd.py +2110 -0
- ansys/pyensight/core/listobj.py +280 -0
- ansys/pyensight/core/locallauncher.py +579 -0
- ansys/pyensight/core/py.typed +0 -0
- ansys/pyensight/core/renderable.py +880 -0
- ansys/pyensight/core/session.py +1923 -0
- ansys/pyensight/core/sgeo_poll.html +24 -0
- ansys/pyensight/core/utils/__init__.py +21 -0
- ansys/pyensight/core/utils/adr.py +111 -0
- ansys/pyensight/core/utils/dsg_server.py +1220 -0
- ansys/pyensight/core/utils/export.py +606 -0
- ansys/pyensight/core/utils/omniverse.py +769 -0
- ansys/pyensight/core/utils/omniverse_cli.py +614 -0
- ansys/pyensight/core/utils/omniverse_dsg_server.py +1196 -0
- ansys/pyensight/core/utils/omniverse_glb_server.py +848 -0
- ansys/pyensight/core/utils/parts.py +1221 -0
- ansys/pyensight/core/utils/query.py +487 -0
- ansys/pyensight/core/utils/readers.py +300 -0
- ansys/pyensight/core/utils/resources/Materials/000_sky.exr +0 -0
- ansys/pyensight/core/utils/support.py +128 -0
- ansys/pyensight/core/utils/variables.py +2019 -0
- ansys/pyensight/core/utils/views.py +674 -0
- ansys_pyensight_core-0.11.0.dist-info/METADATA +309 -0
- ansys_pyensight_core-0.11.0.dist-info/RECORD +37 -0
- ansys_pyensight_core-0.11.0.dist-info/WHEEL +4 -0
- ansys_pyensight_core-0.11.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
# Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
from functools import partial
|
|
25
|
+
import glob
|
|
26
|
+
import json
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
import pathlib
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
from typing import Any, List, Optional
|
|
33
|
+
from urllib.parse import urlparse
|
|
34
|
+
|
|
35
|
+
import ansys.pyensight.core
|
|
36
|
+
|
|
37
|
+
original_stderr = sys.stderr
|
|
38
|
+
original_stdout = sys.stdout
|
|
39
|
+
sys.stderr = open(os.devnull, "w")
|
|
40
|
+
sys.stdout = open(os.devnull, "w")
|
|
41
|
+
try:
|
|
42
|
+
import ansys.pyensight.core.utils.dsg_server as dsg_server
|
|
43
|
+
import ansys.pyensight.core.utils.omniverse_dsg_server as ov_dsg_server
|
|
44
|
+
import ansys.pyensight.core.utils.omniverse_glb_server as ov_glb_server
|
|
45
|
+
except AttributeError as exc:
|
|
46
|
+
if "_ARRAY_API" not in str(exc):
|
|
47
|
+
raise exc
|
|
48
|
+
finally:
|
|
49
|
+
sys.stderr = original_stderr
|
|
50
|
+
sys.stdout = original_stdout
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def str2bool_type(v: Any) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
This function is designed to be a 'type=' filter for an argparse entry returning a boolean.
|
|
56
|
+
It allows for additional, common alternative strings as booleans. These include 'yes','no',
|
|
57
|
+
'true','false','t','f','y','n','1' and '0'. If the value does not meet the requirements,
|
|
58
|
+
the function will raise the argparse.ArgumentTypeError exception.
|
|
59
|
+
:param v: The (potential) boolean argument.
|
|
60
|
+
:return: The actual boolean value.
|
|
61
|
+
:raises: argparse.ArgumentTypeError
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(v, bool):
|
|
64
|
+
return v
|
|
65
|
+
if v.lower() in ("yes", "true", "t", "y", "1"):
|
|
66
|
+
return True
|
|
67
|
+
elif v.lower() in ("no", "false", "f", "n", "0"):
|
|
68
|
+
return False
|
|
69
|
+
else:
|
|
70
|
+
raise argparse.ArgumentTypeError("Boolean value expected.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def int_range_type(
|
|
74
|
+
v: Any, min_value: int = 0, max_value: int = 100, allow: Optional[List[int]] = None
|
|
75
|
+
) -> int:
|
|
76
|
+
"""
|
|
77
|
+
This function is designed to be a 'type=' filter for an argparse entry returning an integer value within
|
|
78
|
+
a specified range. If the value does not meet the requirements, the function will raise the
|
|
79
|
+
argparse.ArgumentTypeError exception. This function is normally used with functools.partial to bind
|
|
80
|
+
the minimum and maximum values. For example: type=partial(int_range_type, min_value=0, max_value=65535)
|
|
81
|
+
:param v: The (potential) integer argument.
|
|
82
|
+
:param min_value: The minimum legal integer value.
|
|
83
|
+
:param max_value: The maximum legal integer value.
|
|
84
|
+
:param allow:A list of additional, legal values
|
|
85
|
+
:return: The validated integer value.
|
|
86
|
+
:raises: argparse.ArgumentTypeError
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
value = int(v)
|
|
90
|
+
except ValueError:
|
|
91
|
+
raise argparse.ArgumentTypeError("Integer value expected.")
|
|
92
|
+
if allow is None:
|
|
93
|
+
allow = []
|
|
94
|
+
if (value >= min_value) and (value <= max_value):
|
|
95
|
+
return value
|
|
96
|
+
elif value in allow:
|
|
97
|
+
return value
|
|
98
|
+
else:
|
|
99
|
+
msg = f"Integer value is not in the range [{min_value},{max_value}]"
|
|
100
|
+
if allow:
|
|
101
|
+
msg += f" or in the list {allow}"
|
|
102
|
+
raise argparse.ArgumentTypeError(msg + ".")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class OmniverseGeometryServer(object):
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
security_token: str = "",
|
|
109
|
+
destination: str = "",
|
|
110
|
+
temporal: bool = False,
|
|
111
|
+
vrmode: bool = False,
|
|
112
|
+
time_scale: float = 1.0,
|
|
113
|
+
normalize_geometry: bool = False,
|
|
114
|
+
dsg_uri: str = "",
|
|
115
|
+
monitor_directory: str = "",
|
|
116
|
+
line_width: float = 0.0,
|
|
117
|
+
grpc_use_tcp_sockets: bool = False,
|
|
118
|
+
grpc_allow_network_connections: bool = False,
|
|
119
|
+
grpc_disable_tls: bool = False,
|
|
120
|
+
disable_grpc_options: bool = False,
|
|
121
|
+
) -> None:
|
|
122
|
+
self._dsg_uri = dsg_uri
|
|
123
|
+
self._destination = destination
|
|
124
|
+
self._security_token = security_token
|
|
125
|
+
if not self._security_token:
|
|
126
|
+
self._security_token = os.environ.get("ENSIGHT_SECURITY_TOKEN", "")
|
|
127
|
+
self._temporal = temporal
|
|
128
|
+
self._vrmode = vrmode
|
|
129
|
+
self._time_scale = time_scale
|
|
130
|
+
self._normalize_geometry = normalize_geometry
|
|
131
|
+
self._version = "unknown"
|
|
132
|
+
self._shutdown = False
|
|
133
|
+
self._server_process = None
|
|
134
|
+
self._status_filename: str = ""
|
|
135
|
+
self._monitor_directory: str = monitor_directory
|
|
136
|
+
self._line_width = line_width
|
|
137
|
+
self._grpc_allow_network_connections = grpc_allow_network_connections
|
|
138
|
+
self._grpc_disable_tls = grpc_disable_tls
|
|
139
|
+
self._grpc_use_tcp_sockets = grpc_use_tcp_sockets
|
|
140
|
+
self._disable_grpc_options = disable_grpc_options
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def monitor_directory(self) -> Optional[str]:
|
|
144
|
+
if self._monitor_directory:
|
|
145
|
+
return self._monitor_directory
|
|
146
|
+
# converts "" -> None
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def pyensight_version(self) -> str:
|
|
151
|
+
"""The ansys.pyensight.core version"""
|
|
152
|
+
return ansys.pyensight.core.VERSION
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def dsg_uri(self) -> str:
|
|
156
|
+
"""The endpoint of a Dynamic Scene Graph service: grpc://{hostname}:{port}"""
|
|
157
|
+
return self._dsg_uri
|
|
158
|
+
|
|
159
|
+
@dsg_uri.setter
|
|
160
|
+
def dsg_uri(self, uri: str) -> None:
|
|
161
|
+
self._dsg_uri = uri
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def destination(self) -> str:
|
|
165
|
+
"""The endpoint of an Omniverse Nucleus service: omniverse://{hostname}/{path}"""
|
|
166
|
+
return self._destination
|
|
167
|
+
|
|
168
|
+
@destination.setter
|
|
169
|
+
def destination(self, value: str) -> None:
|
|
170
|
+
self._destination = value
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def security_token(self) -> str:
|
|
174
|
+
"""The security token of the DSG service instance."""
|
|
175
|
+
return self._security_token
|
|
176
|
+
|
|
177
|
+
@security_token.setter
|
|
178
|
+
def security_token(self, value: str) -> None:
|
|
179
|
+
self._security_token = value
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def temporal(self) -> bool:
|
|
183
|
+
"""If True, the DSG update should include all timesteps."""
|
|
184
|
+
return self._temporal
|
|
185
|
+
|
|
186
|
+
@temporal.setter
|
|
187
|
+
def temporal(self, value: bool) -> None:
|
|
188
|
+
self._temporal = bool(value)
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def vrmode(self) -> bool:
|
|
192
|
+
"""If True, the DSG update should not include camera transforms."""
|
|
193
|
+
return self._vrmode
|
|
194
|
+
|
|
195
|
+
@vrmode.setter
|
|
196
|
+
def vrmode(self, value: bool) -> None:
|
|
197
|
+
self._vrmode = bool(value)
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def normalize_geometry(self) -> bool:
|
|
201
|
+
"""If True, the DSG geometry should be remapped into normalized space."""
|
|
202
|
+
return self._normalize_geometry
|
|
203
|
+
|
|
204
|
+
@normalize_geometry.setter
|
|
205
|
+
def normalize_geometry(self, val: bool) -> None:
|
|
206
|
+
self._normalize_geometry = val
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def time_scale(self) -> float:
|
|
210
|
+
"""Value to multiply DSG time values by before passing to Omniverse"""
|
|
211
|
+
return self._time_scale
|
|
212
|
+
|
|
213
|
+
@time_scale.setter
|
|
214
|
+
def time_scale(self, value: float) -> None:
|
|
215
|
+
self._time_scale = value
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def line_width(self) -> float:
|
|
219
|
+
return self._line_width
|
|
220
|
+
|
|
221
|
+
@line_width.setter
|
|
222
|
+
def line_width(self, line_width: float) -> None:
|
|
223
|
+
self._line_width = line_width
|
|
224
|
+
|
|
225
|
+
def run_server(self, one_shot: bool = False) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Run a DSG to Omniverse server in process.
|
|
228
|
+
|
|
229
|
+
Note: this method does not return until the DSG connection is dropped or
|
|
230
|
+
self.stop_server() has been called.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
one_shot : bool
|
|
235
|
+
If True, only run the server to transfer a single scene and
|
|
236
|
+
then return.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
# Build the Omniverse connection
|
|
240
|
+
omni_link = ov_dsg_server.OmniverseWrapper(
|
|
241
|
+
destination=self._destination, line_width=self.line_width
|
|
242
|
+
)
|
|
243
|
+
logging.info("Omniverse connection established.")
|
|
244
|
+
|
|
245
|
+
# parse the DSG URI
|
|
246
|
+
uds_path = None
|
|
247
|
+
port = None
|
|
248
|
+
host = None
|
|
249
|
+
if self.dsg_uri.endswith(".sock"):
|
|
250
|
+
# The path is a uds path
|
|
251
|
+
uds_path = self.dsg_uri.replace("unix:", "").replace(".sock", "")
|
|
252
|
+
else:
|
|
253
|
+
parsed = urlparse(self.dsg_uri)
|
|
254
|
+
port = parsed.port
|
|
255
|
+
host = parsed.hostname
|
|
256
|
+
|
|
257
|
+
# link it to a DSG session
|
|
258
|
+
update_handler = ov_dsg_server.OmniverseUpdateHandler(omni_link)
|
|
259
|
+
dsg_link = dsg_server.DSGSession(
|
|
260
|
+
port=port,
|
|
261
|
+
host=host,
|
|
262
|
+
vrmode=self.vrmode,
|
|
263
|
+
security_code=self.security_token,
|
|
264
|
+
verbose=1,
|
|
265
|
+
normalize_geometry=self.normalize_geometry,
|
|
266
|
+
time_scale=self.time_scale,
|
|
267
|
+
handler=update_handler,
|
|
268
|
+
uds_path=uds_path,
|
|
269
|
+
grpc_disable_tls=self._grpc_disable_tls,
|
|
270
|
+
grpc_allow_network_connections=self._grpc_allow_network_connections,
|
|
271
|
+
grpc_use_tcp_sockets=self._grpc_use_tcp_sockets,
|
|
272
|
+
disable_grpc_options=self._disable_grpc_options,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Start the DSG link
|
|
276
|
+
logging.info(f"Making DSG connection to: {self.dsg_uri}")
|
|
277
|
+
err = dsg_link.start()
|
|
278
|
+
if err < 0:
|
|
279
|
+
logging.error("Omniverse connection failed.")
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
# Initial pull request
|
|
283
|
+
dsg_link.request_an_update(animation=self.temporal)
|
|
284
|
+
|
|
285
|
+
# until the link is dropped, continue
|
|
286
|
+
while not dsg_link.is_shutdown() and not self._shutdown:
|
|
287
|
+
# Reset the line width to the CLI default before each update
|
|
288
|
+
omni_link.line_width = self.line_width
|
|
289
|
+
dsg_link.handle_one_update()
|
|
290
|
+
|
|
291
|
+
if one_shot:
|
|
292
|
+
break
|
|
293
|
+
|
|
294
|
+
logging.info("Shutting down DSG connection")
|
|
295
|
+
dsg_link.end()
|
|
296
|
+
omni_link.shutdown()
|
|
297
|
+
|
|
298
|
+
def run_monitor(self):
|
|
299
|
+
"""
|
|
300
|
+
Run monitor and upload GLB files to Omniverse in process. There are two cases:
|
|
301
|
+
|
|
302
|
+
1) the "directory name" is actually a .glb file. In this case, simply push
|
|
303
|
+
the glb file contents to Omniverse.
|
|
304
|
+
|
|
305
|
+
2) If a directory, then we periodically scan the directory for files named "*.upload".
|
|
306
|
+
If this file is found, there are two cases:
|
|
307
|
+
|
|
308
|
+
a) The file is empty. In this case, for a file named ABC.upload, the file
|
|
309
|
+
ABC.glb will be read and uploaded before both files are deleted.
|
|
310
|
+
|
|
311
|
+
b) The file contains valid json. In this case, the json object is parsed with
|
|
312
|
+
the following format (two glb files for the first timestep and one for the second):
|
|
313
|
+
|
|
314
|
+
{
|
|
315
|
+
"version": 1,
|
|
316
|
+
"destination": "",
|
|
317
|
+
"files": ["a.glb", "b.glb", "c.glb"],
|
|
318
|
+
"times": [0.0, 0.0, 1.0]
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
"times" is optional and defaults to [0*len("files")]. Once processed,
|
|
322
|
+
all the files referenced in the json and the json file itself are deleted.
|
|
323
|
+
"omniuri" is optional and defaults to the passed Omniverse path.
|
|
324
|
+
|
|
325
|
+
Note: In this mode, the method does not return until a "shutdown" file or
|
|
326
|
+
an error is encountered.
|
|
327
|
+
|
|
328
|
+
TODO: add "push" mechanism to trigger a DSG push from the connected session. This
|
|
329
|
+
can be done via the monitor mechanism and used by the Omniverse kit to implement
|
|
330
|
+
a "pull".
|
|
331
|
+
"""
|
|
332
|
+
the_dir = self.monitor_directory
|
|
333
|
+
single_file_upload = False
|
|
334
|
+
if os.path.isfile(the_dir) and the_dir.lower().endswith(".glb"):
|
|
335
|
+
single_file_upload = True
|
|
336
|
+
else:
|
|
337
|
+
if not os.path.isdir(the_dir):
|
|
338
|
+
logging.error(f"The monitor directory {the_dir} does not exist.")
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
# Build the Omniverse connection
|
|
342
|
+
omni_link = ov_dsg_server.OmniverseWrapper(
|
|
343
|
+
destination=self._destination, line_width=self.line_width
|
|
344
|
+
)
|
|
345
|
+
logging.info("Omniverse connection established.")
|
|
346
|
+
|
|
347
|
+
# use an OmniverseUpdateHandler
|
|
348
|
+
update_handler = ov_dsg_server.OmniverseUpdateHandler(omni_link)
|
|
349
|
+
|
|
350
|
+
# Link it to the GLB file monitoring service
|
|
351
|
+
glb_link = ov_glb_server.GLBSession(verbose=1, handler=update_handler, vrmode=self.vrmode)
|
|
352
|
+
if single_file_upload:
|
|
353
|
+
start_time = time.time()
|
|
354
|
+
logging.info(f"Uploading file: {the_dir}.")
|
|
355
|
+
try:
|
|
356
|
+
glb_link.start_uploads([0.0, 0.0])
|
|
357
|
+
glb_link.upload_file(the_dir)
|
|
358
|
+
glb_link.end_uploads()
|
|
359
|
+
except Exception as error:
|
|
360
|
+
logging.error(f"Unable to upload file: {the_dir}: {error}")
|
|
361
|
+
logging.info(f"Uploaded in {(time.time() - start_time):.2f}")
|
|
362
|
+
else:
|
|
363
|
+
logging.info(f"Starting file monitoring for {the_dir}.")
|
|
364
|
+
the_dir_path = pathlib.Path(the_dir)
|
|
365
|
+
try:
|
|
366
|
+
stop_file = os.path.join(the_dir, "shutdown")
|
|
367
|
+
orig_destination = omni_link.destination
|
|
368
|
+
while not os.path.exists(stop_file):
|
|
369
|
+
loop_time = time.time()
|
|
370
|
+
files_to_remove = []
|
|
371
|
+
for filename in glob.glob(os.path.join(the_dir, "*.upload")):
|
|
372
|
+
# reset to the launch URI/directory
|
|
373
|
+
omni_link.destination = orig_destination
|
|
374
|
+
# Keep track of the files and time values
|
|
375
|
+
files_to_remove.append(filename)
|
|
376
|
+
files_to_process = []
|
|
377
|
+
file_timestamps = []
|
|
378
|
+
if os.path.getsize(filename) == 0:
|
|
379
|
+
# replace the ".upload" extension with ".glb"
|
|
380
|
+
glb_file = os.path.splitext(filename)[0] + ".glb"
|
|
381
|
+
if os.path.exists(glb_file):
|
|
382
|
+
files_to_process.append(glb_file)
|
|
383
|
+
file_timestamps.append(0.0)
|
|
384
|
+
files_to_remove.append(glb_file)
|
|
385
|
+
else:
|
|
386
|
+
# read the .upload file json content
|
|
387
|
+
try:
|
|
388
|
+
with open(filename, "r") as fp:
|
|
389
|
+
glb_info = json.load(fp)
|
|
390
|
+
except Exception:
|
|
391
|
+
logging.error(f"Unable to read file: {filename}")
|
|
392
|
+
continue
|
|
393
|
+
# if specified, set the URI/directory target
|
|
394
|
+
omni_link.destination = glb_info.get("destination", orig_destination)
|
|
395
|
+
# Get the GLB files to process
|
|
396
|
+
the_files = glb_info.get("files", [])
|
|
397
|
+
files_to_remove.extend(the_files)
|
|
398
|
+
# Times not used for now, but parse them anyway
|
|
399
|
+
the_times = glb_info.get("times", [0.0] * len(the_files))
|
|
400
|
+
file_timestamps.extend(the_times)
|
|
401
|
+
# Validate a few things
|
|
402
|
+
if len(the_files) != len(the_times):
|
|
403
|
+
logging.error(
|
|
404
|
+
f"Number of times and files are not the same in: {filename}"
|
|
405
|
+
)
|
|
406
|
+
continue
|
|
407
|
+
files_to_process.extend(the_files)
|
|
408
|
+
# manage time
|
|
409
|
+
timeline = sorted(set(file_timestamps))
|
|
410
|
+
if len(timeline) != 1:
|
|
411
|
+
logging.warning("Time values not currently supported.")
|
|
412
|
+
if len(files_to_process) > 1:
|
|
413
|
+
logging.warning("Multiple glb files not currently fully supported.")
|
|
414
|
+
# Reset the line width to the CLI default before each update
|
|
415
|
+
omni_link.line_width = self.line_width
|
|
416
|
+
# Upload the files
|
|
417
|
+
glb_link.start_uploads([timeline[0], timeline[-1]])
|
|
418
|
+
for glb_file, timestamp in zip(files_to_process, file_timestamps):
|
|
419
|
+
start_time = time.time()
|
|
420
|
+
logging.info(f"Uploading file: {glb_file} to {omni_link.destination}.")
|
|
421
|
+
try:
|
|
422
|
+
time_idx = timeline.index(timestamp) + 1
|
|
423
|
+
if time_idx == len(timeline):
|
|
424
|
+
time_idx -= 1
|
|
425
|
+
limits = [timestamp, timeline[time_idx]]
|
|
426
|
+
glb_link.upload_file(glb_file, timeline=limits)
|
|
427
|
+
except Exception as error:
|
|
428
|
+
logging.error(f"Unable to upload file: {glb_file}: {error}")
|
|
429
|
+
logging.info(f"Uploaded in {(time.time() - start_time):.2f}s")
|
|
430
|
+
glb_link.end_uploads()
|
|
431
|
+
for filename in files_to_remove:
|
|
432
|
+
try:
|
|
433
|
+
# Only delete the file if it is in the_dir_path
|
|
434
|
+
filename_path = pathlib.Path(filename)
|
|
435
|
+
if filename_path.is_relative_to(the_dir_path):
|
|
436
|
+
os.remove(filename)
|
|
437
|
+
except IOError:
|
|
438
|
+
pass
|
|
439
|
+
if time.time() - loop_time < 0.1:
|
|
440
|
+
time.sleep(0.25)
|
|
441
|
+
except Exception as error:
|
|
442
|
+
logging.error(f"Error encountered while monitoring: {error}")
|
|
443
|
+
logging.info("Stopping file monitoring.")
|
|
444
|
+
try:
|
|
445
|
+
os.remove(stop_file)
|
|
446
|
+
except IOError:
|
|
447
|
+
logging.error("Unable to remove 'shutdown' file.")
|
|
448
|
+
|
|
449
|
+
omni_link.shutdown()
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
if __name__ == "__main__":
|
|
453
|
+
parser = argparse.ArgumentParser(description="PyEnSight Omniverse Geometry Service")
|
|
454
|
+
parser.add_argument(
|
|
455
|
+
"destination", default="", type=str, help="The directory to save the USD scene graph into."
|
|
456
|
+
)
|
|
457
|
+
parser.add_argument(
|
|
458
|
+
"--verbose",
|
|
459
|
+
metavar="verbose_level",
|
|
460
|
+
default=0,
|
|
461
|
+
type=partial(int_range_type, min_value=0, max_value=3),
|
|
462
|
+
help="Enable logging information (0-3). Default: 0",
|
|
463
|
+
)
|
|
464
|
+
parser.add_argument(
|
|
465
|
+
"--log_file",
|
|
466
|
+
metavar="log_filename",
|
|
467
|
+
default="",
|
|
468
|
+
type=str,
|
|
469
|
+
help="Save logging output to the named log file instead of stdout.",
|
|
470
|
+
)
|
|
471
|
+
parser.add_argument(
|
|
472
|
+
"--dsg_uri",
|
|
473
|
+
default="grpc://127.0.0.1:5234",
|
|
474
|
+
type=str,
|
|
475
|
+
help="The URI of the EnSight Dynamic Scene Graph server. Default: grpc://127.0.0.1:5234",
|
|
476
|
+
)
|
|
477
|
+
parser.add_argument(
|
|
478
|
+
"--security_token",
|
|
479
|
+
metavar="token",
|
|
480
|
+
default="",
|
|
481
|
+
type=str,
|
|
482
|
+
help="Dynamic scene graph API security token. Default: none",
|
|
483
|
+
)
|
|
484
|
+
parser.add_argument(
|
|
485
|
+
"--monitor_directory",
|
|
486
|
+
metavar="glb_directory",
|
|
487
|
+
default="",
|
|
488
|
+
type=str,
|
|
489
|
+
help="Monitor specified directory for GLB files to be exported. Default: none",
|
|
490
|
+
)
|
|
491
|
+
parser.add_argument(
|
|
492
|
+
"--time_scale",
|
|
493
|
+
metavar="time_scale",
|
|
494
|
+
default=1.0,
|
|
495
|
+
type=float,
|
|
496
|
+
help="Scaling factor to be applied to input time values. Default: 1.0",
|
|
497
|
+
)
|
|
498
|
+
parser.add_argument(
|
|
499
|
+
"--normalize_geometry",
|
|
500
|
+
metavar="yes|no|true|false|1|0",
|
|
501
|
+
default=False,
|
|
502
|
+
type=str2bool_type,
|
|
503
|
+
help="Enable mapping of geometry to a normalized Cartesian space. Default: false",
|
|
504
|
+
)
|
|
505
|
+
parser.add_argument(
|
|
506
|
+
"--include_camera",
|
|
507
|
+
metavar="yes|no|true|false|1|0",
|
|
508
|
+
default=True,
|
|
509
|
+
type=str2bool_type,
|
|
510
|
+
help="Include the camera in the output USD scene graph. Default: true",
|
|
511
|
+
)
|
|
512
|
+
parser.add_argument(
|
|
513
|
+
"--temporal",
|
|
514
|
+
metavar="yes|no|true|false|1|0",
|
|
515
|
+
default=False,
|
|
516
|
+
type=str2bool_type,
|
|
517
|
+
help="Export a temporal scene graph. Default: false",
|
|
518
|
+
)
|
|
519
|
+
parser.add_argument(
|
|
520
|
+
"--oneshot",
|
|
521
|
+
metavar="yes|no|true|false|1|0",
|
|
522
|
+
default=False,
|
|
523
|
+
type=str2bool_type,
|
|
524
|
+
help="Convert a single geometry into USD and exit. Default: false",
|
|
525
|
+
)
|
|
526
|
+
line_default: Any = os.environ.get("ANSYS_OV_LINE_WIDTH", None)
|
|
527
|
+
if line_default is not None:
|
|
528
|
+
try:
|
|
529
|
+
line_default = float(line_default)
|
|
530
|
+
except ValueError:
|
|
531
|
+
line_default = None
|
|
532
|
+
parser.add_argument(
|
|
533
|
+
"--line_width",
|
|
534
|
+
metavar="line_width",
|
|
535
|
+
default=line_default,
|
|
536
|
+
type=float,
|
|
537
|
+
help=f"Width of lines: >0=absolute size. <0=fraction of diagonal. 0=none. Default: {line_default}",
|
|
538
|
+
)
|
|
539
|
+
parser.add_argument(
|
|
540
|
+
"--grpc_use_tcp_sockets",
|
|
541
|
+
metavar="yes|no|true|false|1|0",
|
|
542
|
+
default=False,
|
|
543
|
+
type=str2bool_type,
|
|
544
|
+
help="If using gRPC, and if True, then allow TCP Socket based connections instead of only local connections.",
|
|
545
|
+
)
|
|
546
|
+
parser.add_argument(
|
|
547
|
+
"--grpc_allow_network_connections",
|
|
548
|
+
metavar="yes|no|true|false|1|0",
|
|
549
|
+
default=False,
|
|
550
|
+
type=str2bool_type,
|
|
551
|
+
help="If using gRPC and using TCP Socket based connections, listen on all networks.",
|
|
552
|
+
)
|
|
553
|
+
parser.add_argument(
|
|
554
|
+
"--grpc_disable_tls",
|
|
555
|
+
metavar="yes|no|true|false|1|0",
|
|
556
|
+
default=False,
|
|
557
|
+
type=str2bool_type,
|
|
558
|
+
help="If using gRPC and using TCP Socket based connections, disable TLS.",
|
|
559
|
+
)
|
|
560
|
+
parser.add_argument(
|
|
561
|
+
"--disable_grpc_options",
|
|
562
|
+
metavar="yes|no|true|false|1|0",
|
|
563
|
+
default=False,
|
|
564
|
+
type=str2bool_type,
|
|
565
|
+
help="Disable all the gRPC options check and allow to run older versions of EnSight.",
|
|
566
|
+
)
|
|
567
|
+
# parse the command line
|
|
568
|
+
args = parser.parse_args()
|
|
569
|
+
|
|
570
|
+
# set up logging
|
|
571
|
+
level = logging.ERROR
|
|
572
|
+
if args.verbose == 1:
|
|
573
|
+
level = logging.WARN
|
|
574
|
+
elif args.verbose == 2:
|
|
575
|
+
level = logging.INFO
|
|
576
|
+
elif args.verbose == 3:
|
|
577
|
+
level = logging.DEBUG
|
|
578
|
+
log_args = dict(format="GeometryService:%(levelname)s:%(message)s", level=level)
|
|
579
|
+
if args.log_file:
|
|
580
|
+
log_args["filename"] = args.log_file
|
|
581
|
+
# start with a clean logging instance
|
|
582
|
+
while logging.root.hasHandlers():
|
|
583
|
+
logging.root.removeHandler(logging.root.handlers[0])
|
|
584
|
+
logging.basicConfig(**log_args) # type: ignore
|
|
585
|
+
|
|
586
|
+
# size of lines in data units or fraction of bounding box diagonal
|
|
587
|
+
line_width = 0.0
|
|
588
|
+
if args.line_width is not None:
|
|
589
|
+
line_width = args.line_width
|
|
590
|
+
|
|
591
|
+
# Build the server object
|
|
592
|
+
server = OmniverseGeometryServer(
|
|
593
|
+
destination=args.destination,
|
|
594
|
+
dsg_uri=args.dsg_uri,
|
|
595
|
+
security_token=args.security_token,
|
|
596
|
+
monitor_directory=args.monitor_directory,
|
|
597
|
+
time_scale=args.time_scale,
|
|
598
|
+
normalize_geometry=args.normalize_geometry,
|
|
599
|
+
vrmode=not args.include_camera,
|
|
600
|
+
temporal=args.temporal,
|
|
601
|
+
line_width=line_width,
|
|
602
|
+
grpc_disable_tls=args.grpc_disable_tls,
|
|
603
|
+
grpc_allow_network_connections=args.grpc_allow_network_connections,
|
|
604
|
+
grpc_use_tcp_sockets=args.grpc_use_tcp_sockets,
|
|
605
|
+
disable_grpc_options=args.disable_grpc_options,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# run the server
|
|
609
|
+
logging.info("Server startup.")
|
|
610
|
+
if server.monitor_directory:
|
|
611
|
+
server.run_monitor()
|
|
612
|
+
else:
|
|
613
|
+
server.run_server(one_shot=args.oneshot)
|
|
614
|
+
logging.info("Server shutdown.")
|