ansys-pyensight-core 0.8.8__py3-none-any.whl → 0.8.9__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/dockerlauncher.py +6 -0
- ansys/pyensight/core/launcher.py +6 -1
- ansys/pyensight/core/locallauncher.py +4 -0
- ansys/pyensight/core/session.py +1 -1
- ansys/pyensight/core/utils/dsg_server.py +227 -35
- ansys/pyensight/core/utils/omniverse.py +84 -24
- ansys/pyensight/core/utils/omniverse_cli.py +481 -0
- ansys/pyensight/core/utils/omniverse_dsg_server.py +236 -426
- ansys/pyensight/core/utils/omniverse_glb_server.py +279 -0
- {ansys_pyensight_core-0.8.8.dist-info → ansys_pyensight_core-0.8.9.dist-info}/METADATA +11 -5
- ansys_pyensight_core-0.8.9.dist-info/RECORD +34 -0
- ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/__init__.py +0 -1
- ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py +0 -407
- ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml +0 -59
- ansys/pyensight/core/exts/ansys.geometry.service/data/icon.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.service/data/preview.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/CHANGELOG.md +0 -11
- ansys/pyensight/core/exts/ansys.geometry.service/docs/README.md +0 -13
- ansys/pyensight/core/exts/ansys.geometry.service/docs/index.rst +0 -18
- ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/__init__.py +0 -1
- ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +0 -193
- ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml +0 -49
- ansys/pyensight/core/exts/ansys.geometry.serviceui/data/icon.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/data/preview.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/CHANGELOG.md +0 -11
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/README.md +0 -13
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/index.rst +0 -18
- ansys/pyensight/core/utils/resources/Materials/Fieldstone/Fieldstone_BaseColor.png +0 -0
- ansys/pyensight/core/utils/resources/Materials/Fieldstone/Fieldstone_N.png +0 -0
- ansys/pyensight/core/utils/resources/Materials/Fieldstone/Fieldstone_ORM.png +0 -0
- ansys/pyensight/core/utils/resources/Materials/Fieldstone.mdl +0 -54
- ansys_pyensight_core-0.8.8.dist-info/RECORD +0 -52
- {ansys_pyensight_core-0.8.8.dist-info → ansys_pyensight_core-0.8.9.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.8.8.dist-info → ansys_pyensight_core-0.8.9.dist-info}/WHEEL +0 -0
|
@@ -24,98 +24,68 @@
|
|
|
24
24
|
#
|
|
25
25
|
###############################################################################
|
|
26
26
|
|
|
27
|
-
import argparse
|
|
28
27
|
import logging
|
|
29
28
|
import math
|
|
30
29
|
import os
|
|
31
30
|
import shutil
|
|
32
|
-
import
|
|
31
|
+
import tempfile
|
|
33
32
|
from typing import Any, Dict, List, Optional
|
|
34
33
|
|
|
35
|
-
import
|
|
34
|
+
from ansys.pyensight.core.utils.dsg_server import Part, UpdateHandler
|
|
36
35
|
import png
|
|
37
36
|
from pxr import Gf, Sdf, Usd, UsdGeom, UsdLux, UsdShade
|
|
38
37
|
|
|
39
|
-
sys.path.append(os.path.dirname(__file__))
|
|
40
|
-
from dsg_server import DSGSession, Part, UpdateHandler # noqa: E402
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
verbose = 0
|
|
45
|
-
|
|
46
|
-
@staticmethod
|
|
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
|
-
"""
|
|
53
|
-
if OmniverseWrapper.verbose:
|
|
54
|
-
logging.info(message)
|
|
55
|
-
|
|
56
|
-
@staticmethod
|
|
57
|
-
def connectionStatusCallback(
|
|
58
|
-
url: Any, connectionStatus: "omni.client.ConnectionStatus"
|
|
59
|
-
) -> None:
|
|
60
|
-
"""
|
|
61
|
-
If no connection to Omniverse can be made, shut down the service.
|
|
62
|
-
"""
|
|
63
|
-
if connectionStatus is omni.client.ConnectionStatus.CONNECT_ERROR:
|
|
64
|
-
sys.exit("[ERROR] Failed connection, exiting.")
|
|
65
|
-
|
|
66
|
-
def __init__(
|
|
67
|
-
self,
|
|
68
|
-
live_edit: bool = False,
|
|
69
|
-
path: str = "omniverse://localhost/Users/test",
|
|
70
|
-
verbose: int = 0,
|
|
71
|
-
) -> None:
|
|
39
|
+
class OmniverseWrapper(object):
|
|
40
|
+
def __init__(self, live_edit: bool = False, destination: str = "") -> None:
|
|
72
41
|
self._cleaned_index = 0
|
|
73
42
|
self._cleaned_names: dict = {}
|
|
74
43
|
self._connectionStatusSubscription = None
|
|
75
44
|
self._stage = None
|
|
76
|
-
self._destinationPath: str =
|
|
45
|
+
self._destinationPath: str = ""
|
|
77
46
|
self._old_stages: list = []
|
|
78
47
|
self._stagename = "dsg_scene.usd"
|
|
79
48
|
self._live_edit: bool = live_edit
|
|
80
49
|
if self._live_edit:
|
|
81
50
|
self._stagename = "dsg_scene.live"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
omni.client.set_log_callback(OmniverseWrapper.logCallback)
|
|
85
|
-
if verbose > 1:
|
|
86
|
-
omni.client.set_log_level(omni.client.LogLevel.DEBUG)
|
|
51
|
+
# USD time slider will have 120 tick marks per second of animation time
|
|
52
|
+
self._time_codes_per_second = 120.0
|
|
87
53
|
|
|
88
|
-
if
|
|
89
|
-
|
|
54
|
+
if destination:
|
|
55
|
+
self.destination = destination
|
|
90
56
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if not self.isValidOmniUrl(self._destinationPath):
|
|
96
|
-
self.log("Note technically the Omniverse URL {self._destinationPath} is not valid")
|
|
57
|
+
@property
|
|
58
|
+
def destination(self) -> str:
|
|
59
|
+
"""The current output directory."""
|
|
60
|
+
return self._destinationPath
|
|
97
61
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
logging.info(msg)
|
|
62
|
+
@destination.setter
|
|
63
|
+
def destination(self, directory: str) -> None:
|
|
64
|
+
self._destinationPath = directory
|
|
65
|
+
if not self.is_valid_destination(directory):
|
|
66
|
+
logging.warning(f"Invalid destination path: {directory}")
|
|
104
67
|
|
|
105
68
|
def shutdown(self) -> None:
|
|
106
69
|
"""
|
|
107
70
|
Shutdown the connection to Omniverse cleanly.
|
|
108
71
|
"""
|
|
109
|
-
omni.client.live_wait_for_pending_updates()
|
|
110
72
|
self._connectionStatusSubscription = None
|
|
111
|
-
omni.client.shutdown()
|
|
112
73
|
|
|
113
74
|
@staticmethod
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
75
|
+
def is_valid_destination(path: str) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Verify that the target path is a writeable directory.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
path
|
|
82
|
+
The path to check
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
True if the path is a writeable directory, False otherwise.
|
|
87
|
+
"""
|
|
88
|
+
return os.access(path, os.W_OK)
|
|
119
89
|
|
|
120
90
|
def stage_url(self, name: Optional[str] = None) -> str:
|
|
121
91
|
"""
|
|
@@ -131,21 +101,31 @@ class OmniverseWrapper:
|
|
|
131
101
|
"""
|
|
132
102
|
if name is None:
|
|
133
103
|
name = self._stagename
|
|
134
|
-
return self._destinationPath
|
|
104
|
+
return os.path.join(self._destinationPath, name)
|
|
135
105
|
|
|
136
106
|
def delete_old_stages(self) -> None:
|
|
137
107
|
"""
|
|
138
108
|
Remove all the stages included in the "_old_stages" list.
|
|
109
|
+
If a stage is in use and cannot be removed, keep its name in _old_stages
|
|
110
|
+
to retry later.
|
|
139
111
|
"""
|
|
112
|
+
stages_unremoved = list()
|
|
140
113
|
while self._old_stages:
|
|
141
114
|
stage = self._old_stages.pop()
|
|
142
|
-
|
|
115
|
+
try:
|
|
116
|
+
if os.path.isfile(stage):
|
|
117
|
+
os.remove(stage)
|
|
118
|
+
else:
|
|
119
|
+
shutil.rmtree(stage, ignore_errors=True, onerror=None)
|
|
120
|
+
except OSError:
|
|
121
|
+
stages_unremoved.append(stage)
|
|
122
|
+
self._old_stages = stages_unremoved
|
|
143
123
|
|
|
144
124
|
def create_new_stage(self) -> None:
|
|
145
125
|
"""
|
|
146
126
|
Create a new stage. using the current stage name.
|
|
147
127
|
"""
|
|
148
|
-
|
|
128
|
+
logging.info(f"Creating Omniverse stage: {self.stage_url()}")
|
|
149
129
|
if self._stage:
|
|
150
130
|
self._stage.Unload()
|
|
151
131
|
self._stage = None
|
|
@@ -156,7 +136,7 @@ class OmniverseWrapper:
|
|
|
156
136
|
UsdGeom.SetStageUpAxis(self._stage, UsdGeom.Tokens.y)
|
|
157
137
|
# in M
|
|
158
138
|
UsdGeom.SetStageMetersPerUnit(self._stage, 1.0)
|
|
159
|
-
|
|
139
|
+
logging.info(f"Created stage: {self.stage_url()}")
|
|
160
140
|
|
|
161
141
|
def save_stage(self, comment: str = "") -> None:
|
|
162
142
|
"""
|
|
@@ -165,45 +145,6 @@ class OmniverseWrapper:
|
|
|
165
145
|
Presently, live connections are disabled.
|
|
166
146
|
"""
|
|
167
147
|
self._stage.GetRootLayer().Save() # type:ignore
|
|
168
|
-
omni.client.live_process()
|
|
169
|
-
|
|
170
|
-
# This function will add a commented checkpoint to a file on Nucleus if
|
|
171
|
-
# the Nucleus server supports checkpoints
|
|
172
|
-
def checkpoint(self, comment: str = "") -> None:
|
|
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:
|
|
182
|
-
return
|
|
183
|
-
result, serverInfo = omni.client.get_server_info(self.stage_url())
|
|
184
|
-
if result and serverInfo and serverInfo.checkpoints_enabled:
|
|
185
|
-
bForceCheckpoint = True
|
|
186
|
-
self.log(f"Adding checkpoint comment <{comment}> to stage <{self.stage_url()}>")
|
|
187
|
-
omni.client.create_checkpoint(self.stage_url(), comment, bForceCheckpoint)
|
|
188
|
-
|
|
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
|
-
"""
|
|
201
|
-
result, serverInfo = omni.client.get_server_info(self.stage_url())
|
|
202
|
-
if serverInfo:
|
|
203
|
-
if display:
|
|
204
|
-
self.log(f"Connected username:{serverInfo.username}")
|
|
205
|
-
return serverInfo.username
|
|
206
|
-
return None
|
|
207
148
|
|
|
208
149
|
def clear_cleaned_names(self) -> None:
|
|
209
150
|
"""
|
|
@@ -305,6 +246,7 @@ class OmniverseWrapper:
|
|
|
305
246
|
self,
|
|
306
247
|
name,
|
|
307
248
|
id,
|
|
249
|
+
part_hash,
|
|
308
250
|
parent_prim,
|
|
309
251
|
verts,
|
|
310
252
|
conn,
|
|
@@ -318,46 +260,64 @@ class OmniverseWrapper:
|
|
|
318
260
|
):
|
|
319
261
|
# 1D texture map for variables https://graphics.pixar.com/usd/release/tut_simple_shading.html
|
|
320
262
|
# create the part usd object
|
|
321
|
-
partname = self.clean_name(name +
|
|
263
|
+
partname = self.clean_name(name + part_hash.hexdigest())
|
|
322
264
|
stage_name = "/Parts/" + partname + ".usd"
|
|
323
|
-
part_stage_url = self.stage_url(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
265
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + ".usd"))
|
|
266
|
+
part_stage = None
|
|
267
|
+
|
|
268
|
+
if not os.path.exists(part_stage_url):
|
|
269
|
+
part_stage = Usd.Stage.CreateNew(part_stage_url)
|
|
270
|
+
self._old_stages.append(part_stage_url)
|
|
271
|
+
xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
|
|
272
|
+
mesh = UsdGeom.Mesh.Define(part_stage, "/" + partname + "/Mesh")
|
|
273
|
+
# mesh.CreateDisplayColorAttr()
|
|
274
|
+
mesh.CreateDoubleSidedAttr().Set(True)
|
|
275
|
+
mesh.CreatePointsAttr(verts)
|
|
276
|
+
mesh.CreateNormalsAttr(normals)
|
|
277
|
+
mesh.CreateFaceVertexCountsAttr([3] * int(conn.size / 3))
|
|
278
|
+
mesh.CreateFaceVertexIndicesAttr(conn)
|
|
279
|
+
if (tcoords is not None) and variable:
|
|
280
|
+
# USD 22.08 changed the primvar API
|
|
281
|
+
if hasattr(mesh, "CreatePrimvar"):
|
|
282
|
+
texCoords = mesh.CreatePrimvar(
|
|
283
|
+
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
primvarsAPI = UsdGeom.PrimvarsAPI(mesh)
|
|
287
|
+
texCoords = primvarsAPI.CreatePrimvar(
|
|
288
|
+
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
289
|
+
)
|
|
290
|
+
texCoords.Set(tcoords)
|
|
291
|
+
texCoords.SetInterpolation("vertex")
|
|
292
|
+
part_prim = part_stage.GetPrimAtPath("/" + partname)
|
|
293
|
+
part_stage.SetDefaultPrim(part_prim)
|
|
294
|
+
|
|
295
|
+
# Currently, this will never happen, but it is a setup for rigid body transforms
|
|
296
|
+
# At present, the group transforms have been cooked into the vertices so this is not needed
|
|
297
|
+
matrixOp = xform.AddXformOp(
|
|
298
|
+
UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
|
|
299
|
+
)
|
|
300
|
+
matrixOp.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
301
|
+
|
|
302
|
+
self.create_dsg_material(
|
|
303
|
+
part_stage, mesh, "/" + partname, diffuse=diffuse, variable=variable
|
|
304
|
+
)
|
|
360
305
|
|
|
306
|
+
timestep_prim = self.add_timestep_group(parent_prim, timeline, first_timestep)
|
|
307
|
+
|
|
308
|
+
# glue it into our stage
|
|
309
|
+
path = timestep_prim.GetPath().AppendChild("part_ref_" + partname)
|
|
310
|
+
part_ref = self._stage.OverridePrim(path)
|
|
311
|
+
part_ref.GetReferences().AddReference("." + stage_name)
|
|
312
|
+
|
|
313
|
+
if part_stage is not None:
|
|
314
|
+
part_stage.GetRootLayer().Save()
|
|
315
|
+
|
|
316
|
+
return part_stage_url
|
|
317
|
+
|
|
318
|
+
def add_timestep_group(
|
|
319
|
+
self, parent_prim: UsdGeom.Xform, timeline: List[float], first_timestep: bool
|
|
320
|
+
) -> UsdGeom.Xform:
|
|
361
321
|
# add a layer in the group hierarchy for the timestep
|
|
362
322
|
timestep_group_path = parent_prim.GetPath().AppendChild(
|
|
363
323
|
self.clean_name("t" + str(timeline[0]), None)
|
|
@@ -368,17 +328,72 @@ class OmniverseWrapper:
|
|
|
368
328
|
visibility_attr.Set("inherited", Usd.TimeCode.EarliestTime())
|
|
369
329
|
else:
|
|
370
330
|
visibility_attr.Set("invisible", Usd.TimeCode.EarliestTime())
|
|
371
|
-
visibility_attr.Set("inherited", timeline[0])
|
|
331
|
+
visibility_attr.Set("inherited", timeline[0] * self._time_codes_per_second)
|
|
372
332
|
# Final timestep has timeline[0]==timeline[1]. Leave final timestep visible.
|
|
373
333
|
if timeline[0] < timeline[1]:
|
|
374
|
-
visibility_attr.Set("invisible", timeline[1])
|
|
334
|
+
visibility_attr.Set("invisible", timeline[1] * self._time_codes_per_second)
|
|
335
|
+
return timestep_prim
|
|
336
|
+
|
|
337
|
+
def create_dsg_points(
|
|
338
|
+
self,
|
|
339
|
+
name,
|
|
340
|
+
id,
|
|
341
|
+
part_hash,
|
|
342
|
+
parent_prim,
|
|
343
|
+
verts,
|
|
344
|
+
sizes,
|
|
345
|
+
colors,
|
|
346
|
+
matrix=[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0],
|
|
347
|
+
default_size=1.0,
|
|
348
|
+
default_color=[1.0, 1.0, 1.0, 1.0],
|
|
349
|
+
timeline=[0.0, 0.0],
|
|
350
|
+
first_timestep=False,
|
|
351
|
+
):
|
|
352
|
+
# create the part usd object
|
|
353
|
+
partname = self.clean_name(name + part_hash.hexdigest())
|
|
354
|
+
stage_name = "/Parts/" + partname + ".usd"
|
|
355
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + ".usd"))
|
|
356
|
+
part_stage = None
|
|
357
|
+
|
|
358
|
+
if not os.path.exists(part_stage_url):
|
|
359
|
+
part_stage = Usd.Stage.CreateNew(part_stage_url)
|
|
360
|
+
self._old_stages.append(part_stage_url)
|
|
361
|
+
xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
|
|
362
|
+
|
|
363
|
+
points = UsdGeom.Points.Define(part_stage, "/" + partname + "/Points")
|
|
364
|
+
# points.GetPointsAttr().Set(Vt.Vec3fArray(verts.tolist()))
|
|
365
|
+
points.GetPointsAttr().Set(verts)
|
|
366
|
+
if sizes is not None and sizes.size == (verts.size // 3):
|
|
367
|
+
points.GetWidthsAttr().Set(sizes)
|
|
368
|
+
else:
|
|
369
|
+
points.GetWidthsAttr().Set([default_size] * (verts.size // 3))
|
|
370
|
+
|
|
371
|
+
colorAttr = points.GetPrim().GetAttribute("primvars:displayColor")
|
|
372
|
+
colorAttr.SetMetadata("interpolation", "vertex")
|
|
373
|
+
if colors is not None and colors.size == verts.size:
|
|
374
|
+
colorAttr.Set(colors)
|
|
375
|
+
else:
|
|
376
|
+
colorAttr.Set([default_color[0:3]] * (verts.size // 3))
|
|
377
|
+
|
|
378
|
+
part_prim = part_stage.GetPrimAtPath("/" + partname)
|
|
379
|
+
part_stage.SetDefaultPrim(part_prim)
|
|
380
|
+
|
|
381
|
+
# Currently, this will never happen, but it is a setup for rigid body transforms
|
|
382
|
+
# At present, the group transforms have been cooked into the vertices so this is not needed
|
|
383
|
+
matrixOp = xform.AddXformOp(
|
|
384
|
+
UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
|
|
385
|
+
)
|
|
386
|
+
matrixOp.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
387
|
+
|
|
388
|
+
timestep_prim = self.add_timestep_group(parent_prim, timeline, first_timestep)
|
|
375
389
|
|
|
376
390
|
# glue it into our stage
|
|
377
391
|
path = timestep_prim.GetPath().AppendChild("part_ref_" + partname)
|
|
378
392
|
part_ref = self._stage.OverridePrim(path)
|
|
379
393
|
part_ref.GetReferences().AddReference("." + stage_name)
|
|
380
394
|
|
|
381
|
-
part_stage
|
|
395
|
+
if part_stage is not None:
|
|
396
|
+
part_stage.GetRootLayer().Save()
|
|
382
397
|
|
|
383
398
|
return part_stage_url
|
|
384
399
|
|
|
@@ -424,25 +439,25 @@ class OmniverseWrapper:
|
|
|
424
439
|
return material
|
|
425
440
|
|
|
426
441
|
def create_dsg_variable_textures(self, variables):
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
442
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
443
|
+
# make folder: {tempdir}/scratch/Textures/{palette_*.png}
|
|
444
|
+
os.makedirs(f"{tempdir}/scratch/Textures", exist_ok=True)
|
|
445
|
+
for var in variables.values():
|
|
446
|
+
data = bytearray(var.texture)
|
|
447
|
+
n_pixels = int(len(data) / 4)
|
|
448
|
+
row = []
|
|
449
|
+
for i in range(n_pixels):
|
|
450
|
+
row.append(data[i * 4 + 0])
|
|
451
|
+
row.append(data[i * 4 + 1])
|
|
452
|
+
row.append(data[i * 4 + 2])
|
|
453
|
+
io = png.Writer(width=n_pixels, height=2, bitdepth=8, greyscale=False)
|
|
454
|
+
rows = [row, row]
|
|
455
|
+
name = self.clean_name(var.name)
|
|
456
|
+
with open(f"{tempdir}/scratch/Textures/palette_{name}.png", "wb") as fp:
|
|
457
|
+
io.write(fp, rows)
|
|
458
|
+
uriPath = self._destinationPath + "/Parts/Textures"
|
|
459
|
+
shutil.rmtree(uriPath, ignore_errors=True, onerror=None)
|
|
460
|
+
shutil.copytree(f"{tempdir}/scratch/Textures", uriPath)
|
|
446
461
|
|
|
447
462
|
def create_dsg_root(self):
|
|
448
463
|
root_name = "/Root"
|
|
@@ -521,99 +536,14 @@ class OmniverseWrapper:
|
|
|
521
536
|
UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
|
|
522
537
|
)
|
|
523
538
|
matrixOp.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
524
|
-
|
|
539
|
+
logging.info(f"Created group:'{name}' {str(obj_type)}")
|
|
525
540
|
return group_prim
|
|
526
541
|
|
|
527
542
|
def uploadMaterial(self):
|
|
528
543
|
uriPath = self._destinationPath + "/Materials"
|
|
529
|
-
|
|
544
|
+
shutil.rmtree(uriPath, ignore_errors=True, onerror=None)
|
|
530
545
|
fullpath = os.path.join(os.path.dirname(__file__), "resources", "Materials")
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
def createMaterial(self, mesh):
|
|
534
|
-
# Create a material instance for this in USD
|
|
535
|
-
materialName = "Fieldstone"
|
|
536
|
-
newMat = UsdShade.Material.Define(self._stage, "/Root/Looks/Fieldstone")
|
|
537
|
-
|
|
538
|
-
matPath = "/Root/Looks/Fieldstone"
|
|
539
|
-
|
|
540
|
-
# MDL Shader
|
|
541
|
-
# Create the MDL shader
|
|
542
|
-
mdlShader = UsdShade.Shader.Define(self._stage, matPath + "/Fieldstone")
|
|
543
|
-
mdlShader.CreateIdAttr("mdlMaterial")
|
|
544
|
-
|
|
545
|
-
mdlShaderModule = "./Materials/Fieldstone.mdl"
|
|
546
|
-
mdlShader.SetSourceAsset(mdlShaderModule, "mdl")
|
|
547
|
-
# mdlShader.GetPrim().CreateAttribute("info:mdl:sourceAsset:subIdentifier",
|
|
548
|
-
# Sdf.ValueTypeNames.Token, True).Set(materialName)
|
|
549
|
-
# mdlOutput = newMat.CreateSurfaceOutput("mdl")
|
|
550
|
-
# mdlOutput.ConnectToSource(mdlShader, "out")
|
|
551
|
-
mdlShader.SetSourceAssetSubIdentifier(materialName, "mdl")
|
|
552
|
-
shaderOutput = mdlShader.CreateOutput("out", Sdf.ValueTypeNames.Token)
|
|
553
|
-
shaderOutput.SetRenderType("material")
|
|
554
|
-
newMat.CreateSurfaceOutput("mdl").ConnectToSource(shaderOutput)
|
|
555
|
-
newMat.CreateDisplacementOutput("mdl").ConnectToSource(shaderOutput)
|
|
556
|
-
newMat.CreateVolumeOutput("mdl").ConnectToSource(shaderOutput)
|
|
557
|
-
|
|
558
|
-
# USD Preview Surface Shaders
|
|
559
|
-
|
|
560
|
-
# Create the "USD Primvar reader for float2" shader
|
|
561
|
-
primStShader = UsdShade.Shader.Define(self._stage, matPath + "/PrimST")
|
|
562
|
-
primStShader.CreateIdAttr("UsdPrimvarReader_float2")
|
|
563
|
-
primStShader.CreateOutput("result", Sdf.ValueTypeNames.Float2)
|
|
564
|
-
primStShader.CreateInput("varname", Sdf.ValueTypeNames.Token).Set("st")
|
|
565
|
-
|
|
566
|
-
# Create the "Diffuse Color Tex" shader
|
|
567
|
-
diffuseColorShader = UsdShade.Shader.Define(self._stage, matPath + "/DiffuseColorTex")
|
|
568
|
-
diffuseColorShader.CreateIdAttr("UsdUVTexture")
|
|
569
|
-
texInput = diffuseColorShader.CreateInput("file", Sdf.ValueTypeNames.Asset)
|
|
570
|
-
texInput.Set("./Materials/Fieldstone/Fieldstone_BaseColor.png")
|
|
571
|
-
texInput.GetAttr().SetColorSpace("RGB")
|
|
572
|
-
diffuseColorShader.CreateInput("st", Sdf.ValueTypeNames.Float2).ConnectToSource(
|
|
573
|
-
primStShader.CreateOutput("result", Sdf.ValueTypeNames.Float2)
|
|
574
|
-
)
|
|
575
|
-
diffuseColorShaderOutput = diffuseColorShader.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
|
|
576
|
-
|
|
577
|
-
# Create the "Normal Tex" shader
|
|
578
|
-
normalShader = UsdShade.Shader.Define(self._stage, matPath + "/NormalTex")
|
|
579
|
-
normalShader.CreateIdAttr("UsdUVTexture")
|
|
580
|
-
normalTexInput = normalShader.CreateInput("file", Sdf.ValueTypeNames.Asset)
|
|
581
|
-
normalTexInput.Set("./Materials/Fieldstone/Fieldstone_N.png")
|
|
582
|
-
normalTexInput.GetAttr().SetColorSpace("RAW")
|
|
583
|
-
normalShader.CreateInput("st", Sdf.ValueTypeNames.Float2).ConnectToSource(
|
|
584
|
-
primStShader.CreateOutput("result", Sdf.ValueTypeNames.Float2)
|
|
585
|
-
)
|
|
586
|
-
normalShaderOutput = normalShader.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
|
|
587
|
-
|
|
588
|
-
# Create the USD Preview Surface shader
|
|
589
|
-
usdPreviewSurfaceShader = UsdShade.Shader.Define(self._stage, matPath + "/PreviewSurface")
|
|
590
|
-
usdPreviewSurfaceShader.CreateIdAttr("UsdPreviewSurface")
|
|
591
|
-
diffuseColorInput = usdPreviewSurfaceShader.CreateInput(
|
|
592
|
-
"diffuseColor", Sdf.ValueTypeNames.Color3f
|
|
593
|
-
)
|
|
594
|
-
diffuseColorInput.ConnectToSource(diffuseColorShaderOutput)
|
|
595
|
-
normalInput = usdPreviewSurfaceShader.CreateInput("normal", Sdf.ValueTypeNames.Normal3f)
|
|
596
|
-
normalInput.ConnectToSource(normalShaderOutput)
|
|
597
|
-
|
|
598
|
-
# Set the linkage between material and USD Preview surface shader
|
|
599
|
-
# usdPreviewSurfaceOutput = newMat.CreateSurfaceOutput()
|
|
600
|
-
# usdPreviewSurfaceOutput.ConnectToSource(usdPreviewSurfaceShader, "surface")
|
|
601
|
-
# UsdShade.MaterialBindingAPI(mesh).Bind(newMat)
|
|
602
|
-
|
|
603
|
-
usdPreviewSurfaceShaderOutput = usdPreviewSurfaceShader.CreateOutput(
|
|
604
|
-
"surface", Sdf.ValueTypeNames.Token
|
|
605
|
-
)
|
|
606
|
-
usdPreviewSurfaceShaderOutput.SetRenderType("material")
|
|
607
|
-
newMat.CreateSurfaceOutput().ConnectToSource(usdPreviewSurfaceShaderOutput)
|
|
608
|
-
|
|
609
|
-
UsdShade.MaterialBindingAPI.Apply(mesh.GetPrim()).Bind(newMat)
|
|
610
|
-
|
|
611
|
-
# Create a distant light in the scene.
|
|
612
|
-
def createDistantLight(self):
|
|
613
|
-
newLight = UsdLux.DistantLight.Define(self._stage, "/Root/DistantLight")
|
|
614
|
-
newLight.CreateAngleAttr(0.53)
|
|
615
|
-
newLight.CreateColorAttr(Gf.Vec3f(1.0, 1.0, 0.745))
|
|
616
|
-
newLight.CreateIntensityAttr(500.0)
|
|
546
|
+
shutil.copytree(fullpath, uriPath)
|
|
617
547
|
|
|
618
548
|
# Create a dome light in the scene.
|
|
619
549
|
def createDomeLight(self, texturePath):
|
|
@@ -627,13 +557,6 @@ class OmniverseWrapper:
|
|
|
627
557
|
rotateOp = xForm.AddXformOp(UsdGeom.XformOp.TypeRotateZYX, UsdGeom.XformOp.PrecisionFloat)
|
|
628
558
|
rotateOp.Set(Gf.Vec3f(270, 0, 0))
|
|
629
559
|
|
|
630
|
-
def createEmptyFolder(self, emptyFolderPath):
|
|
631
|
-
folder = self._destinationPath + emptyFolderPath
|
|
632
|
-
self.log(f"Creating new folder: {folder}")
|
|
633
|
-
result = omni.client.create_folder(folder)
|
|
634
|
-
self.log(f"Finished creating: {result.name}")
|
|
635
|
-
return result.name
|
|
636
|
-
|
|
637
560
|
|
|
638
561
|
class OmniverseUpdateHandler(UpdateHandler):
|
|
639
562
|
"""
|
|
@@ -670,10 +593,13 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
670
593
|
self._group_prims[id] = self._root_prim
|
|
671
594
|
|
|
672
595
|
if self._omni._stage is not None:
|
|
673
|
-
self._omni._stage.SetStartTimeCode(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
self._omni._stage.
|
|
596
|
+
self._omni._stage.SetStartTimeCode(
|
|
597
|
+
self.session.time_limits[0] * self._omni._time_codes_per_second
|
|
598
|
+
)
|
|
599
|
+
self._omni._stage.SetEndTimeCode(
|
|
600
|
+
self.session.time_limits[1] * self._omni._time_codes_per_second
|
|
601
|
+
)
|
|
602
|
+
self._omni._stage.SetTimeCodesPerSecond(self._omni._time_codes_per_second)
|
|
677
603
|
|
|
678
604
|
# Send the variable textures. Safe to do so once the first view is processed.
|
|
679
605
|
if not self._sent_textures:
|
|
@@ -685,34 +611,56 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
685
611
|
|
|
686
612
|
def finalize_part(self, part: Part) -> None:
|
|
687
613
|
# generate an Omniverse compliant mesh from the Part
|
|
688
|
-
|
|
689
|
-
if command is None:
|
|
614
|
+
if part is None or part.cmd is None:
|
|
690
615
|
return
|
|
691
|
-
parent_prim = self._group_prims[
|
|
616
|
+
parent_prim = self._group_prims[part.cmd.parent_id]
|
|
692
617
|
obj_id = self.session.mesh_block_count
|
|
693
|
-
matrix =
|
|
694
|
-
name =
|
|
618
|
+
matrix = part.cmd.matrix4x4
|
|
619
|
+
name = part.cmd.name
|
|
695
620
|
color = [
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
621
|
+
part.cmd.fill_color[0] * part.cmd.diffuse,
|
|
622
|
+
part.cmd.fill_color[1] * part.cmd.diffuse,
|
|
623
|
+
part.cmd.fill_color[2] * part.cmd.diffuse,
|
|
624
|
+
part.cmd.fill_color[3],
|
|
700
625
|
]
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
626
|
+
|
|
627
|
+
if part.cmd.render == part.cmd.CONNECTIVITY:
|
|
628
|
+
command, verts, conn, normals, tcoords, var_cmd = part.nodal_surface_rep()
|
|
629
|
+
if command is not None:
|
|
630
|
+
# Generate the mesh block
|
|
631
|
+
_ = self._omni.create_dsg_mesh_block(
|
|
632
|
+
name,
|
|
633
|
+
obj_id,
|
|
634
|
+
part.hash,
|
|
635
|
+
parent_prim,
|
|
636
|
+
verts,
|
|
637
|
+
conn,
|
|
638
|
+
normals,
|
|
639
|
+
tcoords,
|
|
640
|
+
matrix=matrix,
|
|
641
|
+
diffuse=color,
|
|
642
|
+
variable=var_cmd,
|
|
643
|
+
timeline=self.session.cur_timeline,
|
|
644
|
+
first_timestep=(self.session.cur_timeline[0] == self.session.time_limits[0]),
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
elif part.cmd.render == part.cmd.NODES:
|
|
648
|
+
command, verts, sizes, colors, var_cmd = part.point_rep()
|
|
649
|
+
if command is not None:
|
|
650
|
+
_ = self._omni.create_dsg_points(
|
|
651
|
+
name,
|
|
652
|
+
obj_id,
|
|
653
|
+
part.hash,
|
|
654
|
+
parent_prim,
|
|
655
|
+
verts,
|
|
656
|
+
sizes,
|
|
657
|
+
colors,
|
|
658
|
+
matrix=matrix,
|
|
659
|
+
default_size=part.cmd.node_size_default,
|
|
660
|
+
default_color=color,
|
|
661
|
+
timeline=self.session.cur_timeline,
|
|
662
|
+
first_timestep=(self.session.cur_timeline[0] == self.session.time_limits[0]),
|
|
663
|
+
)
|
|
716
664
|
super().finalize_part(part)
|
|
717
665
|
|
|
718
666
|
def start_connection(self) -> None:
|
|
@@ -740,141 +688,3 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
740
688
|
super().end_update()
|
|
741
689
|
# Stage update complete
|
|
742
690
|
self._omni.save_stage()
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if __name__ == "__main__":
|
|
746
|
-
parser = argparse.ArgumentParser(
|
|
747
|
-
description="Python Omniverse EnSight Dynamic Scene Graph Client",
|
|
748
|
-
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
749
|
-
)
|
|
750
|
-
parser.add_argument(
|
|
751
|
-
"--path",
|
|
752
|
-
action="store",
|
|
753
|
-
default="omniverse://localhost/Users/test",
|
|
754
|
-
help="Omniverse pathname. Default=omniverse://localhost/Users/test",
|
|
755
|
-
)
|
|
756
|
-
parser.add_argument(
|
|
757
|
-
"--port",
|
|
758
|
-
metavar="ensight_grpc_port",
|
|
759
|
-
nargs="?",
|
|
760
|
-
default=12345,
|
|
761
|
-
type=int,
|
|
762
|
-
help="EnSight gRPC port number",
|
|
763
|
-
)
|
|
764
|
-
parser.add_argument(
|
|
765
|
-
"--host",
|
|
766
|
-
metavar="ensight_grpc_host",
|
|
767
|
-
nargs="?",
|
|
768
|
-
default="127.0.0.1",
|
|
769
|
-
type=str,
|
|
770
|
-
help="EnSight gRPC hostname",
|
|
771
|
-
)
|
|
772
|
-
parser.add_argument(
|
|
773
|
-
"--security",
|
|
774
|
-
metavar="ensight_grpc_security_code",
|
|
775
|
-
nargs="?",
|
|
776
|
-
default="",
|
|
777
|
-
type=str,
|
|
778
|
-
help="EnSight gRPC security code",
|
|
779
|
-
)
|
|
780
|
-
parser.add_argument(
|
|
781
|
-
"--verbose",
|
|
782
|
-
metavar="verbose_level",
|
|
783
|
-
default=0,
|
|
784
|
-
type=int,
|
|
785
|
-
help="Enable debugging information",
|
|
786
|
-
)
|
|
787
|
-
parser.add_argument(
|
|
788
|
-
"--animation", dest="animation", action="store_true", help="Save all timesteps (default)"
|
|
789
|
-
)
|
|
790
|
-
parser.add_argument(
|
|
791
|
-
"--no-animation",
|
|
792
|
-
dest="animation",
|
|
793
|
-
action="store_false",
|
|
794
|
-
help="Save only the current timestep",
|
|
795
|
-
)
|
|
796
|
-
parser.set_defaults(animation=False)
|
|
797
|
-
parser.add_argument(
|
|
798
|
-
"--log_file",
|
|
799
|
-
metavar="log_filename",
|
|
800
|
-
default="",
|
|
801
|
-
type=str,
|
|
802
|
-
help="Save program output to the named log file instead of stdout",
|
|
803
|
-
)
|
|
804
|
-
parser.add_argument(
|
|
805
|
-
"--live",
|
|
806
|
-
dest="live",
|
|
807
|
-
action="store_true",
|
|
808
|
-
default=False,
|
|
809
|
-
help="Enable continuous operation",
|
|
810
|
-
)
|
|
811
|
-
parser.add_argument(
|
|
812
|
-
"--normalize_geometry",
|
|
813
|
-
dest="normalize",
|
|
814
|
-
action="store_true",
|
|
815
|
-
default=False,
|
|
816
|
-
help="Spatially normalize incoming geometry",
|
|
817
|
-
)
|
|
818
|
-
parser.add_argument(
|
|
819
|
-
"--vrmode",
|
|
820
|
-
dest="vrmode",
|
|
821
|
-
action="store_true",
|
|
822
|
-
default=False,
|
|
823
|
-
help="In this mode do not include a camera or the case level matrix. Geometry only.",
|
|
824
|
-
)
|
|
825
|
-
args = parser.parse_args()
|
|
826
|
-
|
|
827
|
-
log_args = dict(format="DSG/Omniverse: %(message)s", level=logging.INFO)
|
|
828
|
-
if args.log_file:
|
|
829
|
-
log_args["filename"] = args.log_file
|
|
830
|
-
logging.basicConfig(**log_args) # type: ignore
|
|
831
|
-
|
|
832
|
-
destinationPath = args.path
|
|
833
|
-
loggingEnabled = args.verbose
|
|
834
|
-
|
|
835
|
-
# Build the OmniVerse connection
|
|
836
|
-
target = OmniverseWrapper(path=destinationPath, verbose=loggingEnabled, live_edit=args.live)
|
|
837
|
-
# Print the username for the server
|
|
838
|
-
target.username()
|
|
839
|
-
|
|
840
|
-
if loggingEnabled:
|
|
841
|
-
logging.info("Omniverse connection established.")
|
|
842
|
-
|
|
843
|
-
# link it to a DSG session
|
|
844
|
-
update_handler = OmniverseUpdateHandler(target)
|
|
845
|
-
dsg_link = DSGSession(
|
|
846
|
-
port=args.port,
|
|
847
|
-
host=args.host,
|
|
848
|
-
vrmode=args.vrmode,
|
|
849
|
-
security_code=args.security,
|
|
850
|
-
verbose=loggingEnabled,
|
|
851
|
-
normalize_geometry=args.normalize,
|
|
852
|
-
handler=update_handler,
|
|
853
|
-
)
|
|
854
|
-
|
|
855
|
-
if loggingEnabled:
|
|
856
|
-
dsg_link.log(f"Making DSG connection to: {args.host}:{args.port}")
|
|
857
|
-
|
|
858
|
-
# Start the DSG link
|
|
859
|
-
err = dsg_link.start()
|
|
860
|
-
if err < 0:
|
|
861
|
-
sys.exit(err)
|
|
862
|
-
|
|
863
|
-
# Simple pull request
|
|
864
|
-
dsg_link.request_an_update(animation=args.animation)
|
|
865
|
-
# Handle the update block
|
|
866
|
-
dsg_link.handle_one_update()
|
|
867
|
-
|
|
868
|
-
# Live operation
|
|
869
|
-
if args.live:
|
|
870
|
-
if loggingEnabled:
|
|
871
|
-
dsg_link.log("Waiting for remote push operations")
|
|
872
|
-
while not dsg_link.is_shutdown():
|
|
873
|
-
dsg_link.handle_one_update()
|
|
874
|
-
|
|
875
|
-
# Done...
|
|
876
|
-
if loggingEnabled:
|
|
877
|
-
dsg_link.log("Shutting down DSG connection")
|
|
878
|
-
dsg_link.end()
|
|
879
|
-
|
|
880
|
-
target.shutdown()
|