ansys-pyensight-core 0.8.1__py3-none-any.whl → 0.8.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/renderable.py +14 -3
- ansys/pyensight/core/utils/dsg_server.py +698 -0
- ansys/pyensight/core/utils/omniverse_dsg_server.py +163 -692
- ansys/pyensight/core/utils/readers.py +1 -1
- {ansys_pyensight_core-0.8.1.dist-info → ansys_pyensight_core-0.8.3.dist-info}/METADATA +5 -5
- {ansys_pyensight_core-0.8.1.dist-info → ansys_pyensight_core-0.8.3.dist-info}/RECORD +8 -7
- {ansys_pyensight_core-0.8.1.dist-info → ansys_pyensight_core-0.8.3.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.8.1.dist-info → ansys_pyensight_core-0.8.3.dist-info}/WHEEL +0 -0
|
@@ -28,25 +28,28 @@ import argparse
|
|
|
28
28
|
import logging
|
|
29
29
|
import math
|
|
30
30
|
import os
|
|
31
|
-
import queue
|
|
32
31
|
import shutil
|
|
33
32
|
import sys
|
|
34
|
-
import
|
|
35
|
-
from typing import Any, List, Optional
|
|
33
|
+
from typing import Any, Dict, List, Optional
|
|
36
34
|
|
|
37
|
-
from ansys.api.pyensight.v0 import dynamic_scene_graph_pb2
|
|
38
|
-
from ansys.pyensight.core import ensight_grpc
|
|
39
|
-
import numpy
|
|
40
35
|
import omni.client
|
|
41
36
|
import png
|
|
42
37
|
from pxr import Gf, Sdf, Usd, UsdGeom, UsdLux, UsdShade
|
|
43
38
|
|
|
39
|
+
sys.path.append(os.path.dirname(__file__))
|
|
40
|
+
from dsg_server import DSGSession, Part, UpdateHandler # noqa: E402
|
|
41
|
+
|
|
44
42
|
|
|
45
43
|
class OmniverseWrapper:
|
|
46
44
|
verbose = 0
|
|
47
45
|
|
|
48
46
|
@staticmethod
|
|
49
47
|
def logCallback(threadName: None, component: Any, level: Any, message: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
The logger method registered to handle async messages from Omniverse
|
|
50
|
+
|
|
51
|
+
If running in verbose mode, reroute the messages to Python Logging.
|
|
52
|
+
"""
|
|
50
53
|
if OmniverseWrapper.verbose:
|
|
51
54
|
logging.info(message)
|
|
52
55
|
|
|
@@ -54,6 +57,9 @@ class OmniverseWrapper:
|
|
|
54
57
|
def connectionStatusCallback(
|
|
55
58
|
url: Any, connectionStatus: "omni.client.ConnectionStatus"
|
|
56
59
|
) -> None:
|
|
60
|
+
"""
|
|
61
|
+
If no connection to Omniverse can be made, shut down the service.
|
|
62
|
+
"""
|
|
57
63
|
if connectionStatus is omni.client.ConnectionStatus.CONNECT_ERROR:
|
|
58
64
|
sys.exit("[ERROR] Failed connection, exiting.")
|
|
59
65
|
|
|
@@ -62,15 +68,15 @@ class OmniverseWrapper:
|
|
|
62
68
|
live_edit: bool = False,
|
|
63
69
|
path: str = "omniverse://localhost/Users/test",
|
|
64
70
|
verbose: int = 0,
|
|
65
|
-
):
|
|
71
|
+
) -> None:
|
|
66
72
|
self._cleaned_index = 0
|
|
67
73
|
self._cleaned_names: dict = {}
|
|
68
74
|
self._connectionStatusSubscription = None
|
|
69
75
|
self._stage = None
|
|
70
|
-
self._destinationPath = path
|
|
76
|
+
self._destinationPath: str = path
|
|
71
77
|
self._old_stages: list = []
|
|
72
78
|
self._stagename = "dsg_scene.usd"
|
|
73
|
-
self._live_edit = live_edit
|
|
79
|
+
self._live_edit: bool = live_edit
|
|
74
80
|
if self._live_edit:
|
|
75
81
|
self._stagename = "dsg_scene.live"
|
|
76
82
|
OmniverseWrapper.verbose = verbose
|
|
@@ -90,10 +96,16 @@ class OmniverseWrapper:
|
|
|
90
96
|
self.log("Note technically the Omniverse URL {self._destinationPath} is not valid")
|
|
91
97
|
|
|
92
98
|
def log(self, msg: str) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Local method to dispatch to whatever logging system has been enabled.
|
|
101
|
+
"""
|
|
93
102
|
if OmniverseWrapper.verbose:
|
|
94
103
|
logging.info(msg)
|
|
95
104
|
|
|
96
105
|
def shutdown(self) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Shutdown the connection to Omniverse cleanly.
|
|
108
|
+
"""
|
|
97
109
|
omni.client.live_wait_for_pending_updates()
|
|
98
110
|
self._connectionStatusSubscription = None
|
|
99
111
|
omni.client.shutdown()
|
|
@@ -106,37 +118,67 @@ class OmniverseWrapper:
|
|
|
106
118
|
return False
|
|
107
119
|
|
|
108
120
|
def stage_url(self, name: Optional[str] = None) -> str:
|
|
121
|
+
"""
|
|
122
|
+
For a given object name, create the URL for the item.
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
name: the name of the object to generate the URL for. If None, it will be the URL for the
|
|
126
|
+
stage name.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
The URL for the object.
|
|
131
|
+
"""
|
|
109
132
|
if name is None:
|
|
110
133
|
name = self._stagename
|
|
111
134
|
return self._destinationPath + "/" + name
|
|
112
135
|
|
|
113
136
|
def delete_old_stages(self) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Remove all the stages included in the "_old_stages" list.
|
|
139
|
+
"""
|
|
114
140
|
while self._old_stages:
|
|
115
141
|
stage = self._old_stages.pop()
|
|
116
142
|
omni.client.delete(stage)
|
|
117
143
|
|
|
118
144
|
def create_new_stage(self) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Create a new stage. using the current stage name.
|
|
147
|
+
"""
|
|
119
148
|
self.log(f"Creating Omniverse stage: {self.stage_url()}")
|
|
120
149
|
if self._stage:
|
|
121
150
|
self._stage.Unload()
|
|
122
151
|
self._stage = None
|
|
123
152
|
self.delete_old_stages()
|
|
124
153
|
self._stage = Usd.Stage.CreateNew(self.stage_url())
|
|
154
|
+
# record the stage in the "_old_stages" list.
|
|
125
155
|
self._old_stages.append(self.stage_url())
|
|
126
156
|
UsdGeom.SetStageUpAxis(self._stage, UsdGeom.Tokens.y)
|
|
127
157
|
# in M
|
|
128
158
|
UsdGeom.SetStageMetersPerUnit(self._stage, 1.0)
|
|
129
159
|
self.log(f"Created stage: {self.stage_url()}")
|
|
130
160
|
|
|
131
|
-
def save_stage(self) -> None:
|
|
161
|
+
def save_stage(self, comment: str = "") -> None:
|
|
162
|
+
"""
|
|
163
|
+
For live connections, save the current edit and allow live processing.
|
|
164
|
+
|
|
165
|
+
Presently, live connections are disabled.
|
|
166
|
+
"""
|
|
132
167
|
self._stage.GetRootLayer().Save() # type:ignore
|
|
133
168
|
omni.client.live_process()
|
|
134
169
|
|
|
135
|
-
# This function will add a commented checkpoint to a file on Nucleus if
|
|
136
|
-
#
|
|
137
|
-
# The Nucleus server supports checkpoints
|
|
170
|
+
# This function will add a commented checkpoint to a file on Nucleus if
|
|
171
|
+
# the Nucleus server supports checkpoints
|
|
138
172
|
def checkpoint(self, comment: str = "") -> None:
|
|
139
|
-
|
|
173
|
+
"""
|
|
174
|
+
Add a checkpoint to the current stage.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
comment: str
|
|
179
|
+
If not empty, the comment to be added to the stage
|
|
180
|
+
"""
|
|
181
|
+
if not comment:
|
|
140
182
|
return
|
|
141
183
|
result, serverInfo = omni.client.get_server_info(self.stage_url())
|
|
142
184
|
if result and serverInfo and serverInfo.checkpoints_enabled:
|
|
@@ -145,6 +187,17 @@ class OmniverseWrapper:
|
|
|
145
187
|
omni.client.create_checkpoint(self.stage_url(), comment, bForceCheckpoint)
|
|
146
188
|
|
|
147
189
|
def username(self, display: bool = True) -> Optional[str]:
|
|
190
|
+
"""
|
|
191
|
+
Get the username of the current user.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
display : bool, optional if True, send the username to the logging system.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
The username or None.
|
|
200
|
+
"""
|
|
148
201
|
result, serverInfo = omni.client.get_server_info(self.stage_url())
|
|
149
202
|
if serverInfo:
|
|
150
203
|
if display:
|
|
@@ -152,162 +205,15 @@ class OmniverseWrapper:
|
|
|
152
205
|
return serverInfo.username
|
|
153
206
|
return None
|
|
154
207
|
|
|
155
|
-
h = 50.0
|
|
156
|
-
boxVertexIndices = [
|
|
157
|
-
0,
|
|
158
|
-
1,
|
|
159
|
-
2,
|
|
160
|
-
1,
|
|
161
|
-
3,
|
|
162
|
-
2,
|
|
163
|
-
4,
|
|
164
|
-
5,
|
|
165
|
-
6,
|
|
166
|
-
4,
|
|
167
|
-
6,
|
|
168
|
-
7,
|
|
169
|
-
8,
|
|
170
|
-
9,
|
|
171
|
-
10,
|
|
172
|
-
8,
|
|
173
|
-
10,
|
|
174
|
-
11,
|
|
175
|
-
12,
|
|
176
|
-
13,
|
|
177
|
-
14,
|
|
178
|
-
12,
|
|
179
|
-
14,
|
|
180
|
-
15,
|
|
181
|
-
16,
|
|
182
|
-
17,
|
|
183
|
-
18,
|
|
184
|
-
16,
|
|
185
|
-
18,
|
|
186
|
-
19,
|
|
187
|
-
20,
|
|
188
|
-
21,
|
|
189
|
-
22,
|
|
190
|
-
20,
|
|
191
|
-
22,
|
|
192
|
-
23,
|
|
193
|
-
]
|
|
194
|
-
boxVertexCounts = [3] * 12
|
|
195
|
-
boxNormals = [
|
|
196
|
-
(0, 0, -1),
|
|
197
|
-
(0, 0, -1),
|
|
198
|
-
(0, 0, -1),
|
|
199
|
-
(0, 0, -1),
|
|
200
|
-
(0, 0, 1),
|
|
201
|
-
(0, 0, 1),
|
|
202
|
-
(0, 0, 1),
|
|
203
|
-
(0, 0, 1),
|
|
204
|
-
(0, -1, 0),
|
|
205
|
-
(0, -1, 0),
|
|
206
|
-
(0, -1, 0),
|
|
207
|
-
(0, -1, 0),
|
|
208
|
-
(1, 0, 0),
|
|
209
|
-
(1, 0, 0),
|
|
210
|
-
(1, 0, 0),
|
|
211
|
-
(1, 0, 0),
|
|
212
|
-
(0, 1, 0),
|
|
213
|
-
(0, 1, 0),
|
|
214
|
-
(0, 1, 0),
|
|
215
|
-
(0, 1, 0),
|
|
216
|
-
(-1, 0, 0),
|
|
217
|
-
(-1, 0, 0),
|
|
218
|
-
(-1, 0, 0),
|
|
219
|
-
(-1, 0, 0),
|
|
220
|
-
]
|
|
221
|
-
boxPoints = [
|
|
222
|
-
(h, -h, -h),
|
|
223
|
-
(-h, -h, -h),
|
|
224
|
-
(h, h, -h),
|
|
225
|
-
(-h, h, -h),
|
|
226
|
-
(h, h, h),
|
|
227
|
-
(-h, h, h),
|
|
228
|
-
(-h, -h, h),
|
|
229
|
-
(h, -h, h),
|
|
230
|
-
(h, -h, h),
|
|
231
|
-
(-h, -h, h),
|
|
232
|
-
(-h, -h, -h),
|
|
233
|
-
(h, -h, -h),
|
|
234
|
-
(h, h, h),
|
|
235
|
-
(h, -h, h),
|
|
236
|
-
(h, -h, -h),
|
|
237
|
-
(h, h, -h),
|
|
238
|
-
(-h, h, h),
|
|
239
|
-
(h, h, h),
|
|
240
|
-
(h, h, -h),
|
|
241
|
-
(-h, h, -h),
|
|
242
|
-
(-h, -h, h),
|
|
243
|
-
(-h, h, h),
|
|
244
|
-
(-h, h, -h),
|
|
245
|
-
(-h, -h, -h),
|
|
246
|
-
]
|
|
247
|
-
boxUVs = [
|
|
248
|
-
(0, 0),
|
|
249
|
-
(0, 1),
|
|
250
|
-
(1, 1),
|
|
251
|
-
(1, 0),
|
|
252
|
-
(0, 0),
|
|
253
|
-
(0, 1),
|
|
254
|
-
(1, 1),
|
|
255
|
-
(1, 0),
|
|
256
|
-
(0, 0),
|
|
257
|
-
(0, 1),
|
|
258
|
-
(1, 1),
|
|
259
|
-
(1, 0),
|
|
260
|
-
(0, 0),
|
|
261
|
-
(0, 1),
|
|
262
|
-
(1, 1),
|
|
263
|
-
(1, 0),
|
|
264
|
-
(0, 0),
|
|
265
|
-
(0, 1),
|
|
266
|
-
(1, 1),
|
|
267
|
-
(1, 0),
|
|
268
|
-
(0, 0),
|
|
269
|
-
(0, 1),
|
|
270
|
-
(1, 1),
|
|
271
|
-
(1, 0),
|
|
272
|
-
]
|
|
273
|
-
|
|
274
|
-
def createBox(self, box_number: int = 0) -> "UsdGeom.Mesh":
|
|
275
|
-
rootUrl = "/Root"
|
|
276
|
-
boxUrl = rootUrl + "/Boxes/box_%d" % box_number
|
|
277
|
-
xformPrim = UsdGeom.Xform.Define(self._stage, rootUrl) # noqa: F841
|
|
278
|
-
# Define the defaultPrim as the /Root prim
|
|
279
|
-
rootPrim = self._stage.GetPrimAtPath(rootUrl) # type:ignore
|
|
280
|
-
self._stage.SetDefaultPrim(rootPrim) # type:ignore
|
|
281
|
-
boxPrim = UsdGeom.Mesh.Define(self._stage, boxUrl)
|
|
282
|
-
boxPrim.CreateDisplayColorAttr([(0.463, 0.725, 0.0)])
|
|
283
|
-
boxPrim.CreatePointsAttr(OmniverseWrapper.boxPoints)
|
|
284
|
-
boxPrim.CreateNormalsAttr(OmniverseWrapper.boxNormals)
|
|
285
|
-
boxPrim.CreateFaceVertexCountsAttr(OmniverseWrapper.boxVertexCounts)
|
|
286
|
-
boxPrim.CreateFaceVertexIndicesAttr(OmniverseWrapper.boxVertexIndices)
|
|
287
|
-
# USD 22.08 changed the primvar API
|
|
288
|
-
if hasattr(boxPrim, "CreatePrimvar"):
|
|
289
|
-
texCoords = boxPrim.CreatePrimvar(
|
|
290
|
-
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
291
|
-
)
|
|
292
|
-
else:
|
|
293
|
-
primvarsAPI = UsdGeom.PrimvarsAPI(boxPrim)
|
|
294
|
-
texCoords = primvarsAPI.CreatePrimvar(
|
|
295
|
-
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
296
|
-
)
|
|
297
|
-
texCoords.Set(OmniverseWrapper.boxUVs)
|
|
298
|
-
texCoords.SetInterpolation("vertex")
|
|
299
|
-
if not boxPrim:
|
|
300
|
-
sys.exit("[ERROR] Failure to create box")
|
|
301
|
-
self.save_stage()
|
|
302
|
-
return boxPrim
|
|
303
|
-
|
|
304
208
|
def clear_cleaned_names(self) -> None:
|
|
305
|
-
"""
|
|
209
|
+
"""
|
|
210
|
+
Clear the list of cleaned names
|
|
211
|
+
"""
|
|
306
212
|
self._cleaned_names = {}
|
|
307
213
|
self._cleaned_index = 0
|
|
308
214
|
|
|
309
215
|
def clean_name(self, name: str, id_name: Any = None) -> str:
|
|
310
|
-
"""Generate a
|
|
216
|
+
"""Generate a valid USD name
|
|
311
217
|
|
|
312
218
|
From a base (EnSight) varname, partname, etc. and the DSG id, generate
|
|
313
219
|
a unique, valid USD name. Save the names so that if the same name
|
|
@@ -329,7 +235,7 @@ class OmniverseWrapper:
|
|
|
329
235
|
# return any previously generated name
|
|
330
236
|
if (name, id_name) in self._cleaned_names:
|
|
331
237
|
return self._cleaned_names[(name, id_name)]
|
|
332
|
-
# replace invalid characters
|
|
238
|
+
# replace invalid characters. EnSight uses a number of characters that are illegal in USD names.
|
|
333
239
|
name = name.replace("+", "_").replace("-", "_")
|
|
334
240
|
name = name.replace(".", "_").replace(":", "_")
|
|
335
241
|
name = name.replace("[", "_").replace("]", "_")
|
|
@@ -351,6 +257,17 @@ class OmniverseWrapper:
|
|
|
351
257
|
|
|
352
258
|
@staticmethod
|
|
353
259
|
def decompose_matrix(values: Any) -> Any:
|
|
260
|
+
"""
|
|
261
|
+
Decompose an array of floats (representing a 4x4 matrix) into scale, rotation and translation.
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
values:
|
|
265
|
+
16 values (input to Gf.Matrix4f CTOR)
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
(scale, rotation, translation)
|
|
270
|
+
"""
|
|
354
271
|
# ang_convert = 180.0/math.pi
|
|
355
272
|
ang_convert = 1.0
|
|
356
273
|
trans_convert = 1.0
|
|
@@ -656,8 +573,6 @@ class OmniverseWrapper:
|
|
|
656
573
|
|
|
657
574
|
UsdShade.MaterialBindingAPI.Apply(mesh.GetPrim()).Bind(newMat)
|
|
658
575
|
|
|
659
|
-
# self.save_stage()
|
|
660
|
-
|
|
661
576
|
# Create a distant light in the scene.
|
|
662
577
|
def createDistantLight(self):
|
|
663
578
|
newLight = UsdLux.DistantLight.Define(self._stage, "/Root/DistantLight")
|
|
@@ -665,8 +580,6 @@ class OmniverseWrapper:
|
|
|
665
580
|
newLight.CreateColorAttr(Gf.Vec3f(1.0, 1.0, 0.745))
|
|
666
581
|
newLight.CreateIntensityAttr(500.0)
|
|
667
582
|
|
|
668
|
-
# self.save_stage()
|
|
669
|
-
|
|
670
583
|
# Create a dome light in the scene.
|
|
671
584
|
def createDomeLight(self, texturePath):
|
|
672
585
|
newLight = UsdLux.DomeLight.Define(self._stage, "/Root/DomeLight")
|
|
@@ -679,8 +592,6 @@ class OmniverseWrapper:
|
|
|
679
592
|
rotateOp = xForm.AddXformOp(UsdGeom.XformOp.TypeRotateZYX, UsdGeom.XformOp.PrecisionFloat)
|
|
680
593
|
rotateOp.Set(Gf.Vec3f(270, 0, 0))
|
|
681
594
|
|
|
682
|
-
# self.save_stage()
|
|
683
|
-
|
|
684
595
|
def createEmptyFolder(self, emptyFolderPath):
|
|
685
596
|
folder = self._destinationPath + emptyFolderPath
|
|
686
597
|
self.log(f"Creating new folder: {folder}")
|
|
@@ -689,531 +600,95 @@ class OmniverseWrapper:
|
|
|
689
600
|
return result.name
|
|
690
601
|
|
|
691
602
|
|
|
692
|
-
class
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
self.reset()
|
|
697
|
-
|
|
698
|
-
def reset(self, cmd: Any = None) -> None:
|
|
699
|
-
self.conn_tris = numpy.array([], dtype="int32")
|
|
700
|
-
self.conn_lines = numpy.array([], dtype="int32")
|
|
701
|
-
self.coords = numpy.array([], dtype="float32")
|
|
702
|
-
self.normals = numpy.array([], dtype="float32")
|
|
703
|
-
self.normals_elem = False
|
|
704
|
-
self.tcoords = numpy.array([], dtype="float32")
|
|
705
|
-
self.tcoords_var = None
|
|
706
|
-
self.tcoords_elem = False
|
|
707
|
-
self.cmd = cmd
|
|
708
|
-
|
|
709
|
-
def update_geom(self, cmd: dynamic_scene_graph_pb2.UpdateGeom) -> None:
|
|
710
|
-
if cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.COORDINATES:
|
|
711
|
-
if self.coords.size != cmd.total_array_size:
|
|
712
|
-
self.coords = numpy.resize(self.coords, cmd.total_array_size)
|
|
713
|
-
self.coords[cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)] = cmd.flt_array
|
|
714
|
-
elif cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.TRIANGLES:
|
|
715
|
-
if self.conn_tris.size != cmd.total_array_size:
|
|
716
|
-
self.conn_tris = numpy.resize(self.conn_tris, cmd.total_array_size)
|
|
717
|
-
self.conn_tris[cmd.chunk_offset : cmd.chunk_offset + len(cmd.int_array)] = cmd.int_array
|
|
718
|
-
elif cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.LINES:
|
|
719
|
-
if self.conn_lines.size != cmd.total_array_size:
|
|
720
|
-
self.conn_lines = numpy.resize(self.conn_lines, cmd.total_array_size)
|
|
721
|
-
self.conn_lines[
|
|
722
|
-
cmd.chunk_offset : cmd.chunk_offset + len(cmd.int_array)
|
|
723
|
-
] = cmd.int_array
|
|
724
|
-
elif (cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.ELEM_NORMALS) or (
|
|
725
|
-
cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.NODE_NORMALS
|
|
726
|
-
):
|
|
727
|
-
self.normals_elem = cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.ELEM_NORMALS
|
|
728
|
-
if self.normals.size != cmd.total_array_size:
|
|
729
|
-
self.normals = numpy.resize(self.normals, cmd.total_array_size)
|
|
730
|
-
self.normals[cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)] = cmd.flt_array
|
|
731
|
-
elif (cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.ELEM_VARIABLE) or (
|
|
732
|
-
cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.NODE_VARIABLE
|
|
733
|
-
):
|
|
734
|
-
# Get the variable definition
|
|
735
|
-
if cmd.variable_id in self._link._variables:
|
|
736
|
-
self.tcoords_var = cmd.variable_id
|
|
737
|
-
self.tcoords_elem = (
|
|
738
|
-
cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.ELEM_VARIABLE
|
|
739
|
-
)
|
|
740
|
-
if self.tcoords.size != cmd.total_array_size:
|
|
741
|
-
self.tcoords = numpy.resize(self.tcoords, cmd.total_array_size)
|
|
742
|
-
self.tcoords[
|
|
743
|
-
cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)
|
|
744
|
-
] = cmd.flt_array
|
|
745
|
-
else:
|
|
746
|
-
self.tcoords_var = None
|
|
603
|
+
class OmniverseUpdateHandler(UpdateHandler):
|
|
604
|
+
"""
|
|
605
|
+
Implement the Omniverse glue to a DSGSession instance
|
|
606
|
+
"""
|
|
747
607
|
|
|
748
|
-
def
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
608
|
+
def __init__(self, omni: OmniverseWrapper):
|
|
609
|
+
super().__init__()
|
|
610
|
+
self._omni = omni
|
|
611
|
+
self._group_prims: Dict[int, Any] = dict()
|
|
612
|
+
|
|
613
|
+
def add_group(self, id: int, view: bool = False) -> None:
|
|
614
|
+
super().add_group(id, view)
|
|
615
|
+
group = self.session.groups[id]
|
|
616
|
+
if not view:
|
|
617
|
+
parent_prim = self._group_prims[group.parent_id]
|
|
618
|
+
obj_type = self.get_dsg_cmd_attribute(group, "ENS_OBJ_TYPE")
|
|
619
|
+
matrix = self.group_matrix(group)
|
|
620
|
+
prim = self._omni.create_dsg_group(
|
|
621
|
+
group.name, parent_prim, matrix=matrix, obj_type=obj_type
|
|
754
622
|
)
|
|
755
|
-
self.
|
|
623
|
+
self._group_prims[id] = prim
|
|
624
|
+
else:
|
|
625
|
+
# Map a view command into a new Omniverse stage and populate it with materials/lights.
|
|
626
|
+
# Create a new root stage in Omniverse
|
|
627
|
+
self._omni.create_new_stage()
|
|
628
|
+
# Create the root group/camera
|
|
629
|
+
camera_info = group
|
|
630
|
+
if self.session.vrmode:
|
|
631
|
+
camera_info = None
|
|
632
|
+
prim = self._omni.create_dsg_root(camera=camera_info)
|
|
633
|
+
# Create a distance and dome light in the scene
|
|
634
|
+
self._omni.createDomeLight("./Materials/000_sky.exr")
|
|
635
|
+
# Upload a material and textures to the Omniverse server
|
|
636
|
+
self._omni.uploadMaterial()
|
|
637
|
+
self._omni.create_dsg_variable_textures(self.session.variables)
|
|
638
|
+
# record
|
|
639
|
+
self._group_prims[id] = prim
|
|
640
|
+
|
|
641
|
+
def add_variable(self, id: int) -> None:
|
|
642
|
+
super().add_variable(id)
|
|
643
|
+
|
|
644
|
+
def finalize_part(self, part: Part) -> None:
|
|
645
|
+
# generate an Omniverse compliant mesh from the Part
|
|
646
|
+
command, verts, conn, normals, tcoords, var_cmd = part.build()
|
|
647
|
+
if command is None:
|
|
756
648
|
return
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
midz = (self._link._scene_bounds[5] + self._link._scene_bounds[2]) * 0.5
|
|
762
|
-
dx = self._link._scene_bounds[3] - self._link._scene_bounds[0]
|
|
763
|
-
dy = self._link._scene_bounds[4] - self._link._scene_bounds[1]
|
|
764
|
-
dz = self._link._scene_bounds[5] - self._link._scene_bounds[2]
|
|
765
|
-
s = dx
|
|
766
|
-
if dy > s:
|
|
767
|
-
s = dy
|
|
768
|
-
if dz > s:
|
|
769
|
-
s = dz
|
|
770
|
-
if s == 0:
|
|
771
|
-
s = 1.0
|
|
772
|
-
num_verts = int(verts.size / 3)
|
|
773
|
-
for i in range(num_verts):
|
|
774
|
-
j = i * 3
|
|
775
|
-
verts[j + 0] = (verts[j + 0] - midx) / s
|
|
776
|
-
verts[j + 1] = (verts[j + 1] - midy) / s
|
|
777
|
-
verts[j + 2] = (verts[j + 2] - midz) / s
|
|
778
|
-
|
|
779
|
-
conn = self.conn_tris
|
|
780
|
-
normals = self.normals
|
|
781
|
-
tcoords = None
|
|
782
|
-
if self.tcoords.size:
|
|
783
|
-
tcoords = self.tcoords
|
|
784
|
-
if self.tcoords_elem or self.normals_elem:
|
|
785
|
-
verts_per_prim = 3
|
|
786
|
-
num_prims = int(conn.size / verts_per_prim)
|
|
787
|
-
# "flatten" the triangles to move values from elements to nodes
|
|
788
|
-
new_verts = numpy.ndarray((num_prims * verts_per_prim * 3,), dtype="float32")
|
|
789
|
-
new_conn = numpy.ndarray((num_prims * verts_per_prim,), dtype="int32")
|
|
790
|
-
new_tcoords = None
|
|
791
|
-
if tcoords is not None:
|
|
792
|
-
# remember that the input values are 1D at this point, we will expand to 2D later
|
|
793
|
-
new_tcoords = numpy.ndarray((num_prims * verts_per_prim,), dtype="float32")
|
|
794
|
-
new_normals = None
|
|
795
|
-
if normals is not None:
|
|
796
|
-
if normals.size == 0:
|
|
797
|
-
print("Warning: zero length normals!")
|
|
798
|
-
else:
|
|
799
|
-
new_normals = numpy.ndarray((num_prims * verts_per_prim * 3,), dtype="float32")
|
|
800
|
-
j = 0
|
|
801
|
-
for i0 in range(num_prims):
|
|
802
|
-
for i1 in range(verts_per_prim):
|
|
803
|
-
idx = conn[i0 * verts_per_prim + i1]
|
|
804
|
-
# new connectivity (identity)
|
|
805
|
-
new_conn[j] = j
|
|
806
|
-
# copy the vertex
|
|
807
|
-
new_verts[j * 3 + 0] = verts[idx * 3 + 0]
|
|
808
|
-
new_verts[j * 3 + 1] = verts[idx * 3 + 1]
|
|
809
|
-
new_verts[j * 3 + 2] = verts[idx * 3 + 2]
|
|
810
|
-
if new_normals is not None:
|
|
811
|
-
if self.normals_elem:
|
|
812
|
-
# copy the normal associated with the face
|
|
813
|
-
new_normals[j * 3 + 0] = normals[i0 * 3 + 0]
|
|
814
|
-
new_normals[j * 3 + 1] = normals[i0 * 3 + 1]
|
|
815
|
-
new_normals[j * 3 + 2] = normals[i0 * 3 + 2]
|
|
816
|
-
else:
|
|
817
|
-
# copy the same normal as the vertex
|
|
818
|
-
new_normals[j * 3 + 0] = normals[idx * 3 + 0]
|
|
819
|
-
new_normals[j * 3 + 1] = normals[idx * 3 + 1]
|
|
820
|
-
new_normals[j * 3 + 2] = normals[idx * 3 + 2]
|
|
821
|
-
if new_tcoords is not None:
|
|
822
|
-
# remember, 1D texture coords at this point
|
|
823
|
-
if self.tcoords_elem:
|
|
824
|
-
# copy the texture coord associated with the face
|
|
825
|
-
new_tcoords[j] = tcoords[i0]
|
|
826
|
-
else:
|
|
827
|
-
# copy the same texture coord as the vertex
|
|
828
|
-
new_tcoords[j] = tcoords[idx]
|
|
829
|
-
j += 1
|
|
830
|
-
# new arrays.
|
|
831
|
-
verts = new_verts
|
|
832
|
-
conn = new_conn
|
|
833
|
-
normals = new_normals
|
|
834
|
-
if tcoords is not None:
|
|
835
|
-
tcoords = new_tcoords
|
|
836
|
-
|
|
837
|
-
var = None
|
|
838
|
-
# texture coords need transformation from variable value to [ST]
|
|
839
|
-
if tcoords is not None:
|
|
840
|
-
var_id = self.cmd.color_variableid
|
|
841
|
-
var = self._link._variables[var_id]
|
|
842
|
-
v_min = None
|
|
843
|
-
v_max = None
|
|
844
|
-
for lvl in var.levels:
|
|
845
|
-
if (v_min is None) or (v_min > lvl.value):
|
|
846
|
-
v_min = lvl.value
|
|
847
|
-
if (v_max is None) or (v_max < lvl.value):
|
|
848
|
-
v_max = lvl.value
|
|
849
|
-
var_minmax = [v_min, v_max]
|
|
850
|
-
# build a power of two x 1 texture
|
|
851
|
-
num_texels = int(len(var.texture) / 4)
|
|
852
|
-
half_texel = 1 / (num_texels * 2.0)
|
|
853
|
-
num_verts = int(verts.size / 3)
|
|
854
|
-
tmp = numpy.ndarray((num_verts * 2,), dtype="float32")
|
|
855
|
-
tmp.fill(0.5) # fill in the T coordinate...
|
|
856
|
-
tex_width = half_texel * 2 * (num_texels - 1) # center to center of num_texels
|
|
857
|
-
# if the range is 0, adjust the min by -1. The result is that the texture
|
|
858
|
-
# coords will get mapped to S=1.0 which is what EnSight does in this situation
|
|
859
|
-
if (var_minmax[1] - var_minmax[0]) == 0.0:
|
|
860
|
-
var_minmax[0] = var_minmax[0] - 1.0
|
|
861
|
-
var_width = var_minmax[1] - var_minmax[0]
|
|
862
|
-
for idx in range(num_verts):
|
|
863
|
-
# normalized S coord value (clamp)
|
|
864
|
-
s = (tcoords[idx] - var_minmax[0]) / var_width
|
|
865
|
-
if s < 0.0:
|
|
866
|
-
s = 0.0
|
|
867
|
-
if s > 1.0:
|
|
868
|
-
s = 1.0
|
|
869
|
-
# map to the texture range and set the S value
|
|
870
|
-
tmp[idx * 2] = s * tex_width + half_texel
|
|
871
|
-
tcoords = tmp
|
|
872
|
-
|
|
873
|
-
parent = self._link._groups[self.cmd.parent_id]
|
|
649
|
+
parent_prim = self._group_prims[command.parent_id]
|
|
650
|
+
obj_id = self._session.mesh_block_count
|
|
651
|
+
matrix = command.matrix4x4
|
|
652
|
+
name = command.name
|
|
874
653
|
color = [
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
654
|
+
command.fill_color[0] * command.diffuse,
|
|
655
|
+
command.fill_color[1] * command.diffuse,
|
|
656
|
+
command.fill_color[2] * command.diffuse,
|
|
657
|
+
command.fill_color[3],
|
|
879
658
|
]
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
self.cmd.name,
|
|
659
|
+
# Generate the mesh block
|
|
660
|
+
_ = self._omni.create_dsg_mesh_block(
|
|
661
|
+
name,
|
|
884
662
|
obj_id,
|
|
885
|
-
|
|
663
|
+
parent_prim,
|
|
886
664
|
verts,
|
|
887
665
|
conn,
|
|
888
666
|
normals,
|
|
889
667
|
tcoords,
|
|
890
|
-
matrix=
|
|
668
|
+
matrix=matrix,
|
|
891
669
|
diffuse=color,
|
|
892
|
-
variable=
|
|
893
|
-
)
|
|
894
|
-
self._link.log(
|
|
895
|
-
f"Part '{self.cmd.name}' defined: {self.coords.size/3} verts, {self.conn_tris.size/3} tris, {self.conn_lines.size/2} lines."
|
|
670
|
+
variable=var_cmd,
|
|
896
671
|
)
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
class DSGOmniverseLink(object):
|
|
901
|
-
def __init__(
|
|
902
|
-
self,
|
|
903
|
-
omni: OmniverseWrapper,
|
|
904
|
-
port: int = 12345,
|
|
905
|
-
host: str = "127.0.0.1",
|
|
906
|
-
security_code: str = "",
|
|
907
|
-
verbose: int = 0,
|
|
908
|
-
normalize_geometry: bool = False,
|
|
909
|
-
vrmode: bool = False,
|
|
910
|
-
):
|
|
911
|
-
super().__init__()
|
|
912
|
-
self._grpc = ensight_grpc.EnSightGRPC(port=port, host=host, secret_key=security_code)
|
|
913
|
-
self._verbose = verbose
|
|
914
|
-
self._thread: Optional[threading.Thread] = None
|
|
915
|
-
self._message_queue: queue.Queue = queue.Queue() # Messages coming from EnSight
|
|
916
|
-
self._dsg_queue: Optional[queue.SimpleQueue] = None # Outgoing messages to EnSight
|
|
917
|
-
self._shutdown = False
|
|
918
|
-
self._dsg = None
|
|
919
|
-
self._omni = omni
|
|
920
|
-
self._normalize_geometry = normalize_geometry
|
|
921
|
-
self._vrmode = vrmode
|
|
922
|
-
self._mesh_block_count = 0
|
|
923
|
-
self._variables: dict = {}
|
|
924
|
-
self._groups: dict = {}
|
|
925
|
-
self._part: Part = Part(self)
|
|
926
|
-
self._scene_bounds: Optional[List] = None
|
|
927
|
-
|
|
928
|
-
def log(self, s: str) -> None:
|
|
929
|
-
"""Log a string to the logging system
|
|
930
|
-
|
|
931
|
-
If verbosity is set, log the string.
|
|
932
|
-
"""
|
|
933
|
-
if self._verbose > 0:
|
|
934
|
-
logging.info(s)
|
|
672
|
+
super().finalize_part(part)
|
|
935
673
|
|
|
936
|
-
def
|
|
937
|
-
|
|
674
|
+
def start_connection(self) -> None:
|
|
675
|
+
super().start_connection()
|
|
938
676
|
|
|
939
|
-
|
|
677
|
+
def end_connection(self) -> None:
|
|
678
|
+
super().end_connection()
|
|
940
679
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
"""
|
|
945
|
-
# Start by setting up and verifying the connection
|
|
946
|
-
self._grpc.connect()
|
|
947
|
-
if not self._grpc.is_connected():
|
|
948
|
-
logging.info(
|
|
949
|
-
f"Unable to establish gRPC connection to: {self._grpc.host()}:{self._grpc.port()}"
|
|
950
|
-
)
|
|
951
|
-
return -1
|
|
952
|
-
# Streaming API requires an iterator, so we make one from a queue
|
|
953
|
-
# it also returns an iterator. self._dsg_queue is the input stream interface
|
|
954
|
-
# self._dsg is the returned stream iterator.
|
|
955
|
-
if self._dsg is not None:
|
|
956
|
-
return 0
|
|
957
|
-
self._dsg_queue = queue.SimpleQueue()
|
|
958
|
-
self._dsg = self._grpc.dynamic_scene_graph_stream(
|
|
959
|
-
iter(self._dsg_queue.get, None) # type:ignore
|
|
960
|
-
)
|
|
961
|
-
self._thread = threading.Thread(target=self.poll_messages)
|
|
962
|
-
if self._thread is not None:
|
|
963
|
-
self._thread.start()
|
|
964
|
-
return 0
|
|
965
|
-
|
|
966
|
-
def end(self):
|
|
967
|
-
"""Stop a gRPC connection to the EnSight instance"""
|
|
968
|
-
self._grpc.stop_server()
|
|
969
|
-
self._shutdown = True
|
|
970
|
-
self._thread.join()
|
|
971
|
-
self._grpc.shutdown()
|
|
972
|
-
self._dsg = None
|
|
973
|
-
self._thread = None
|
|
974
|
-
self._dsg_queue = None
|
|
975
|
-
|
|
976
|
-
def is_shutdown(self):
|
|
977
|
-
"""Check the service shutdown request status"""
|
|
978
|
-
return self._shutdown
|
|
979
|
-
|
|
980
|
-
def request_an_update(self, animation: bool = False) -> None:
|
|
981
|
-
"""Start a DSG update
|
|
982
|
-
Send a command to the DSG protocol to "init" an update.
|
|
983
|
-
|
|
984
|
-
Parameters
|
|
985
|
-
----------
|
|
986
|
-
animation:
|
|
987
|
-
if True, export all EnSight timesteps.
|
|
988
|
-
"""
|
|
989
|
-
# Send an INIT command to trigger a stream of update packets
|
|
990
|
-
cmd = dynamic_scene_graph_pb2.SceneClientCommand()
|
|
991
|
-
cmd.command_type = dynamic_scene_graph_pb2.SceneClientCommand.INIT
|
|
992
|
-
# Allow EnSight push commands, but full scene only for now...
|
|
993
|
-
cmd.init.allow_spontaneous = True
|
|
994
|
-
cmd.init.include_temporal_geometry = animation
|
|
995
|
-
cmd.init.allow_incremental_updates = False
|
|
996
|
-
cmd.init.maximum_chunk_size = 1024 * 1024
|
|
997
|
-
self._dsg_queue.put(cmd) # type:ignore
|
|
998
|
-
# Handle the update messages
|
|
999
|
-
self.handle_one_update()
|
|
1000
|
-
|
|
1001
|
-
def poll_messages(self) -> None:
|
|
1002
|
-
"""Core interface to grab DSG events from gRPC and queue them for processing
|
|
1003
|
-
|
|
1004
|
-
This is run by a thread that is monitoring the dsg RPC call for update messages
|
|
1005
|
-
it places them in _message_queue as it finds them. They are picked up by the
|
|
1006
|
-
main thread via get_next_message()
|
|
1007
|
-
"""
|
|
1008
|
-
while not self._shutdown:
|
|
1009
|
-
try:
|
|
1010
|
-
self._message_queue.put(next(self._dsg)) # type:ignore
|
|
1011
|
-
except Exception:
|
|
1012
|
-
self._shutdown = True
|
|
1013
|
-
logging.info("DSG connection broken, calling exit")
|
|
1014
|
-
os._exit(0)
|
|
1015
|
-
|
|
1016
|
-
def get_next_message(self, wait: bool = True) -> Any:
|
|
1017
|
-
"""Get the next queued up protobuffer message
|
|
1018
|
-
|
|
1019
|
-
Called by the main thread to get any messages that were pulled in from the
|
|
1020
|
-
dsg stream and placed here by poll_messages()
|
|
1021
|
-
"""
|
|
1022
|
-
try:
|
|
1023
|
-
return self._message_queue.get(block=wait)
|
|
1024
|
-
except queue.Empty:
|
|
1025
|
-
return None
|
|
1026
|
-
|
|
1027
|
-
def handle_one_update(self) -> None:
|
|
1028
|
-
"""Monitor the DSG stream and handle a single update operation
|
|
1029
|
-
|
|
1030
|
-
Wait until we get the scene update begin message. From there, reset the current
|
|
1031
|
-
scene buckets and then parse all the incoming commands until we get the scene
|
|
1032
|
-
update end command. At which point, save the generated stage (started in the
|
|
1033
|
-
view command handler). Note: Parts are handled with an available bucket at all times.
|
|
1034
|
-
When a new part update comes in or the scene update end happens, the part is "finished".
|
|
1035
|
-
"""
|
|
1036
|
-
# An update starts with a UPDATE_SCENE_BEGIN command
|
|
1037
|
-
cmd = self.get_next_message()
|
|
1038
|
-
while (cmd is not None) and (
|
|
1039
|
-
cmd.command_type != dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_SCENE_BEGIN
|
|
1040
|
-
):
|
|
1041
|
-
# Look for a begin command
|
|
1042
|
-
cmd = self.get_next_message()
|
|
1043
|
-
self.log("Begin update ------------------------")
|
|
1044
|
-
|
|
1045
|
-
# Start anew
|
|
1046
|
-
self._variables = {}
|
|
1047
|
-
self._groups = {}
|
|
1048
|
-
self._part = Part(self)
|
|
1049
|
-
self._scene_bounds = None
|
|
1050
|
-
self._mesh_block_count = 0 # reset when a new group shows up
|
|
680
|
+
def begin_update(self) -> None:
|
|
681
|
+
super().begin_update()
|
|
682
|
+
# restart the name tables
|
|
1051
683
|
self._omni.clear_cleaned_names()
|
|
684
|
+
# clear the group Omni prims list
|
|
685
|
+
self._group_prims = dict()
|
|
1052
686
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
while (cmd is not None) and (
|
|
1056
|
-
cmd.command_type != dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_SCENE_END
|
|
1057
|
-
):
|
|
1058
|
-
self.handle_update_command(cmd)
|
|
1059
|
-
cmd = self.get_next_message()
|
|
1060
|
-
|
|
1061
|
-
# Flush the last part
|
|
1062
|
-
self.finish_part()
|
|
1063
|
-
|
|
687
|
+
def end_update(self) -> None:
|
|
688
|
+
super().end_update()
|
|
1064
689
|
# Stage update complete
|
|
1065
690
|
self._omni.save_stage()
|
|
1066
691
|
|
|
1067
|
-
self.log("End update --------------------------")
|
|
1068
|
-
|
|
1069
|
-
# handle an incoming gRPC update command
|
|
1070
|
-
def handle_update_command(self, cmd: dynamic_scene_graph_pb2.SceneUpdateCommand) -> None:
|
|
1071
|
-
"""Dispatch out a scene update command to the proper handler
|
|
1072
|
-
|
|
1073
|
-
Given a command object, pull out the correct portion of the protobuffer union and
|
|
1074
|
-
pass it to the appropriate handler.
|
|
1075
|
-
|
|
1076
|
-
Parameters
|
|
1077
|
-
----------
|
|
1078
|
-
cmd:
|
|
1079
|
-
The command to be dispatched.
|
|
1080
|
-
"""
|
|
1081
|
-
name = "Unknown"
|
|
1082
|
-
if cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.DELETE_ID:
|
|
1083
|
-
name = "Delete IDs"
|
|
1084
|
-
elif cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_PART:
|
|
1085
|
-
name = "Part update"
|
|
1086
|
-
tmp = cmd.update_part
|
|
1087
|
-
self.handle_part(tmp)
|
|
1088
|
-
elif cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_GROUP:
|
|
1089
|
-
name = "Group update"
|
|
1090
|
-
tmp = cmd.update_group
|
|
1091
|
-
self.handle_group(tmp)
|
|
1092
|
-
elif cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_GEOM:
|
|
1093
|
-
name = "Geom update"
|
|
1094
|
-
tmp = cmd.update_geom
|
|
1095
|
-
self._part.update_geom(tmp)
|
|
1096
|
-
elif cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_VARIABLE:
|
|
1097
|
-
name = "Variable update"
|
|
1098
|
-
tmp = cmd.update_variable
|
|
1099
|
-
self.handle_variable(tmp)
|
|
1100
|
-
elif cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_VIEW:
|
|
1101
|
-
name = "View update"
|
|
1102
|
-
tmp = cmd.update_view
|
|
1103
|
-
self.handle_view(tmp)
|
|
1104
|
-
elif cmd.command_type == dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_TEXTURE:
|
|
1105
|
-
name = "Texture update"
|
|
1106
|
-
self.log(f"{name} --------------------------")
|
|
1107
|
-
|
|
1108
|
-
def finish_part(self) -> None:
|
|
1109
|
-
"""Complete the current part
|
|
1110
|
-
|
|
1111
|
-
There is always a part being modified. This method completes the current part, commits
|
|
1112
|
-
it to the Omniverse USD, and sets up the next part.
|
|
1113
|
-
"""
|
|
1114
|
-
self._part.build()
|
|
1115
|
-
self._mesh_block_count += 1
|
|
1116
|
-
|
|
1117
|
-
def handle_part(self, part: Any) -> None:
|
|
1118
|
-
"""Handle a DSG UPDATE_GROUP command
|
|
1119
|
-
Parameters
|
|
1120
|
-
----------
|
|
1121
|
-
part:
|
|
1122
|
-
The command coming from the EnSight stream.
|
|
1123
|
-
"""
|
|
1124
|
-
self.finish_part()
|
|
1125
|
-
self._part.reset(part)
|
|
1126
|
-
|
|
1127
|
-
def handle_group(self, group: Any) -> None:
|
|
1128
|
-
"""Handle a DSG UPDATE_GROUP command
|
|
1129
|
-
Parameters
|
|
1130
|
-
----------
|
|
1131
|
-
group:
|
|
1132
|
-
The command coming from the EnSight stream.
|
|
1133
|
-
"""
|
|
1134
|
-
# reset current mesh (part) count for unique "part" naming in USD
|
|
1135
|
-
self._mesh_block_count = 0
|
|
1136
|
-
# get the parent group or view
|
|
1137
|
-
parent = self._groups[group.parent_id]
|
|
1138
|
-
obj_type = group.attributes.get("ENS_OBJ_TYPE", None)
|
|
1139
|
-
matrix = group.matrix4x4
|
|
1140
|
-
# The Case matrix is basically the camera transform. In vrmode, we only want
|
|
1141
|
-
# the raw geometry, so use the identity matrix.
|
|
1142
|
-
if (obj_type == "ENS_CASE") and self._vrmode:
|
|
1143
|
-
matrix = [
|
|
1144
|
-
1.0,
|
|
1145
|
-
0.0,
|
|
1146
|
-
0.0,
|
|
1147
|
-
0.0,
|
|
1148
|
-
0.0,
|
|
1149
|
-
1.0,
|
|
1150
|
-
0.0,
|
|
1151
|
-
0.0,
|
|
1152
|
-
0.0,
|
|
1153
|
-
0.0,
|
|
1154
|
-
1.0,
|
|
1155
|
-
0.0,
|
|
1156
|
-
0.0,
|
|
1157
|
-
0.0,
|
|
1158
|
-
0.0,
|
|
1159
|
-
1.0,
|
|
1160
|
-
]
|
|
1161
|
-
prim = self._omni.create_dsg_group(group.name, parent[1], matrix=matrix, obj_type=obj_type)
|
|
1162
|
-
# record the scene bounds in case they are needed later
|
|
1163
|
-
self._groups[group.id] = [group, prim]
|
|
1164
|
-
bounds = group.attributes.get("ENS_SCENE_BOUNDS", None)
|
|
1165
|
-
if bounds:
|
|
1166
|
-
minmax = []
|
|
1167
|
-
for v in bounds.split(","):
|
|
1168
|
-
try:
|
|
1169
|
-
minmax.append(float(v))
|
|
1170
|
-
except Exception:
|
|
1171
|
-
pass
|
|
1172
|
-
if len(minmax) == 6:
|
|
1173
|
-
self._scene_bounds = minmax
|
|
1174
|
-
|
|
1175
|
-
def handle_variable(self, var: Any) -> None:
|
|
1176
|
-
"""Handle a DSG UPDATE_VARIABLE command
|
|
1177
|
-
|
|
1178
|
-
Save off the EnSight variable DSG command object.
|
|
1179
|
-
|
|
1180
|
-
Parameters
|
|
1181
|
-
----------
|
|
1182
|
-
var:
|
|
1183
|
-
The command coming from the EnSight stream.
|
|
1184
|
-
"""
|
|
1185
|
-
self._variables[var.id] = var
|
|
1186
|
-
|
|
1187
|
-
def handle_view(self, view: Any) -> None:
|
|
1188
|
-
"""Handle a DSG UPDATE_VIEW command
|
|
1189
|
-
|
|
1190
|
-
Map a view command into a new Omniverse stage and populate it with materials/lights.
|
|
1191
|
-
|
|
1192
|
-
Parameters
|
|
1193
|
-
----------
|
|
1194
|
-
view:
|
|
1195
|
-
The command coming from the EnSight stream.
|
|
1196
|
-
"""
|
|
1197
|
-
self._scene_bounds = None
|
|
1198
|
-
# Create a new root stage in Omniverse
|
|
1199
|
-
self._omni.create_new_stage()
|
|
1200
|
-
# Create the root group/camera
|
|
1201
|
-
camera_info = view
|
|
1202
|
-
if self._vrmode:
|
|
1203
|
-
camera_info = None
|
|
1204
|
-
root = self._omni.create_dsg_root(camera=camera_info)
|
|
1205
|
-
self._omni.checkpoint("Created base scene")
|
|
1206
|
-
# Create a distance and dome light in the scene
|
|
1207
|
-
# self._omni.createDistantLight()
|
|
1208
|
-
# self._omni.createDomeLight("./Materials/kloofendal_48d_partly_cloudy.hdr")
|
|
1209
|
-
self._omni.createDomeLight("./Materials/000_sky.exr")
|
|
1210
|
-
self._omni.checkpoint("Added lights to stage")
|
|
1211
|
-
# Upload a material and textures to the Omniverse server
|
|
1212
|
-
self._omni.uploadMaterial()
|
|
1213
|
-
self._omni.create_dsg_variable_textures(self._variables)
|
|
1214
|
-
# record
|
|
1215
|
-
self._groups[view.id] = [view, root]
|
|
1216
|
-
|
|
1217
692
|
|
|
1218
693
|
if __name__ == "__main__":
|
|
1219
694
|
parser = argparse.ArgumentParser(
|
|
@@ -1305,26 +780,28 @@ if __name__ == "__main__":
|
|
|
1305
780
|
destinationPath = args.path
|
|
1306
781
|
loggingEnabled = args.verbose
|
|
1307
782
|
|
|
1308
|
-
#
|
|
1309
|
-
target = OmniverseWrapper(path=destinationPath, verbose=loggingEnabled)
|
|
1310
|
-
|
|
783
|
+
# Build the OmniVerse connection
|
|
784
|
+
target = OmniverseWrapper(path=destinationPath, verbose=loggingEnabled, live_edit=args.live)
|
|
1311
785
|
# Print the username for the server
|
|
1312
786
|
target.username()
|
|
1313
787
|
|
|
1314
788
|
if loggingEnabled:
|
|
1315
|
-
logging.info("
|
|
789
|
+
logging.info("Omniverse connection established.")
|
|
1316
790
|
|
|
1317
|
-
|
|
1318
|
-
|
|
791
|
+
# link it to a DSG session
|
|
792
|
+
update_handler = OmniverseUpdateHandler(target)
|
|
793
|
+
dsg_link = DSGSession(
|
|
1319
794
|
port=args.port,
|
|
1320
795
|
host=args.host,
|
|
1321
796
|
vrmode=args.vrmode,
|
|
1322
797
|
security_code=args.security,
|
|
1323
798
|
verbose=loggingEnabled,
|
|
1324
799
|
normalize_geometry=args.normalize,
|
|
800
|
+
handler=update_handler,
|
|
1325
801
|
)
|
|
802
|
+
|
|
1326
803
|
if loggingEnabled:
|
|
1327
|
-
|
|
804
|
+
dsg_link.log(f"Making DSG connection to: {args.host}:{args.port}")
|
|
1328
805
|
|
|
1329
806
|
# Start the DSG link
|
|
1330
807
|
err = dsg_link.start()
|
|
@@ -1337,19 +814,13 @@ if __name__ == "__main__":
|
|
|
1337
814
|
# Live operation
|
|
1338
815
|
if args.live:
|
|
1339
816
|
if loggingEnabled:
|
|
1340
|
-
|
|
817
|
+
dsg_link.log("Waiting for remote push operations")
|
|
1341
818
|
while not dsg_link.is_shutdown():
|
|
1342
819
|
dsg_link.handle_one_update()
|
|
1343
820
|
|
|
1344
821
|
# Done...
|
|
1345
822
|
if loggingEnabled:
|
|
1346
|
-
|
|
823
|
+
dsg_link.log("Shutting down DSG connection")
|
|
1347
824
|
dsg_link.end()
|
|
1348
825
|
|
|
1349
|
-
# Add a material to the box
|
|
1350
|
-
# target.createMaterial(boxMesh)
|
|
1351
|
-
|
|
1352
|
-
# Add a Nucleus Checkpoint to the stage
|
|
1353
|
-
# target.checkpoint("Add material to the box")
|
|
1354
|
-
|
|
1355
826
|
target.shutdown()
|