ansys-pyensight-core 0.10.10__py3-none-any.whl → 0.10.13__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 +50 -0
- ansys/pyensight/core/launcher.py +4 -1
- ansys/pyensight/core/locallauncher.py +56 -0
- ansys/pyensight/core/utils/omniverse.py +14 -4
- ansys/pyensight/core/utils/omniverse_dsg_server.py +397 -199
- ansys/pyensight/core/utils/views.py +27 -98
- {ansys_pyensight_core-0.10.10.dist-info → ansys_pyensight_core-0.10.13.dist-info}/METADATA +3 -3
- {ansys_pyensight_core-0.10.10.dist-info → ansys_pyensight_core-0.10.13.dist-info}/RECORD +10 -10
- {ansys_pyensight_core-0.10.10.dist-info → ansys_pyensight_core-0.10.13.dist-info}/WHEEL +0 -0
- {ansys_pyensight_core-0.10.10.dist-info → ansys_pyensight_core-0.10.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -539,6 +539,9 @@ class DockerLauncher(Launcher):
|
|
|
539
539
|
# http port
|
|
540
540
|
wss_cmd += " --http_port " + str(self._service_host_port["http"][1])
|
|
541
541
|
# vnc port
|
|
542
|
+
if int(self._ansys_version) > 252:
|
|
543
|
+
wss_cmd += " --separate_loops"
|
|
544
|
+
wss_cmd += f" --security_token {self._secret_key}"
|
|
542
545
|
wss_cmd += " --client_port 1999"
|
|
543
546
|
# optional PIM instance header
|
|
544
547
|
if self._pim_instance is not None:
|
|
@@ -598,11 +601,58 @@ class DockerLauncher(Launcher):
|
|
|
598
601
|
|
|
599
602
|
return session
|
|
600
603
|
|
|
604
|
+
def close(self, session):
|
|
605
|
+
"""Shut down the launched EnSight session.
|
|
606
|
+
|
|
607
|
+
This method closes all associated sessions and then stops the
|
|
608
|
+
launched EnSight instance.
|
|
609
|
+
|
|
610
|
+
Parameters
|
|
611
|
+
----------
|
|
612
|
+
session : ``pyensight.Session``
|
|
613
|
+
Session to close.
|
|
614
|
+
|
|
615
|
+
Raises
|
|
616
|
+
------
|
|
617
|
+
RuntimeError
|
|
618
|
+
If the session was not launched by this launcher.
|
|
619
|
+
|
|
620
|
+
"""
|
|
621
|
+
if self._enshell:
|
|
622
|
+
if self._enshell.is_connected(): # pragma: no cover
|
|
623
|
+
logging.debug("Killing WSS\n")
|
|
624
|
+
command = 'pkill -f "websocketserver.py"'
|
|
625
|
+
kill_env_vars = None
|
|
626
|
+
container_env_str = ""
|
|
627
|
+
if self._pim_instance is not None:
|
|
628
|
+
container_env = self._get_container_env()
|
|
629
|
+
for i in container_env.items():
|
|
630
|
+
container_env_str += f"{i[0]}={i[1]}\n"
|
|
631
|
+
if container_env_str: # pragma: no cover
|
|
632
|
+
kill_env_vars = container_env_str # pragma: no cover
|
|
633
|
+
ret = self._enshell.start_other(command, extra_env=kill_env_vars)
|
|
634
|
+
if ret[0] != 0: # pragma: no cover
|
|
635
|
+
pass
|
|
636
|
+
return super().close(session)
|
|
637
|
+
|
|
601
638
|
def stop(self) -> None:
|
|
602
639
|
"""Release any additional resources allocated during launching."""
|
|
603
640
|
if self._enshell:
|
|
604
641
|
if self._enshell.is_connected(): # pragma: no cover
|
|
605
642
|
try:
|
|
643
|
+
logging.debug("Killing WSS\n")
|
|
644
|
+
command = 'pkill -f "websocketserver.py"'
|
|
645
|
+
kill_env_vars = None
|
|
646
|
+
container_env_str = ""
|
|
647
|
+
if self._pim_instance is not None:
|
|
648
|
+
container_env = self._get_container_env()
|
|
649
|
+
for i in container_env.items():
|
|
650
|
+
container_env_str += f"{i[0]}={i[1]}\n"
|
|
651
|
+
if container_env_str: # pragma: no cover
|
|
652
|
+
kill_env_vars = container_env_str # pragma: no cover
|
|
653
|
+
ret = self._enshell.start_other(command, extra_env=kill_env_vars)
|
|
654
|
+
if ret[0] != 0: # pragma: no cover
|
|
655
|
+
pass
|
|
606
656
|
logging.debug("Stopping EnShell.\n")
|
|
607
657
|
self._enshell.stop_server()
|
|
608
658
|
except Exception: # pragma: no cover
|
ansys/pyensight/core/launcher.py
CHANGED
|
@@ -186,7 +186,10 @@ class Launcher:
|
|
|
186
186
|
url = f"http://{session.hostname}:{session.html_port}/v1/stop"
|
|
187
187
|
if session.secret_key: # pragma: no cover
|
|
188
188
|
url += f"?security_token={session.secret_key}"
|
|
189
|
-
|
|
189
|
+
try:
|
|
190
|
+
_ = requests.get(url)
|
|
191
|
+
except requests.exceptions.ConnectionError:
|
|
192
|
+
pass
|
|
190
193
|
|
|
191
194
|
# Stop the launcher instance
|
|
192
195
|
self.stop()
|
|
@@ -24,6 +24,7 @@ import ansys.pyensight.core as pyensight
|
|
|
24
24
|
from ansys.pyensight.core.common import find_unused_ports
|
|
25
25
|
from ansys.pyensight.core.launcher import Launcher
|
|
26
26
|
import ansys.pyensight.core.session
|
|
27
|
+
import psutil
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class LocalLauncher(Launcher):
|
|
@@ -283,6 +284,9 @@ class LocalLauncher(Launcher):
|
|
|
283
284
|
cmd.extend(["--grpc_port", str(self._ports[0])])
|
|
284
285
|
# EnVision sessions
|
|
285
286
|
cmd.extend(["--local_session", "envision", "5"])
|
|
287
|
+
if int(version) > 252:
|
|
288
|
+
cmd.append("--separate_loops")
|
|
289
|
+
cmd.append(["--security_token", self._secret_key])
|
|
286
290
|
# websocket port
|
|
287
291
|
cmd.append(str(self._ports[3]))
|
|
288
292
|
logging.debug(f"Starting WSS: {cmd}\n")
|
|
@@ -322,6 +326,38 @@ class LocalLauncher(Launcher):
|
|
|
322
326
|
self.launch_webui(version, popen_common)
|
|
323
327
|
return session
|
|
324
328
|
|
|
329
|
+
@staticmethod
|
|
330
|
+
def _kill_process_unix(pid):
|
|
331
|
+
external_kill = ["kill", "-9", str(pid)]
|
|
332
|
+
process = psutil.Popen(external_kill, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
|
333
|
+
process.wait()
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def _kill_process_windows(pid):
|
|
337
|
+
external_kill = ["taskkill", "/F", "/PID", str(pid)]
|
|
338
|
+
process = psutil.Popen(external_kill, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
|
339
|
+
process.wait()
|
|
340
|
+
|
|
341
|
+
def _kill_process_by_pid(self, pid):
|
|
342
|
+
if self._is_windows():
|
|
343
|
+
self._kill_process_windows(pid)
|
|
344
|
+
else:
|
|
345
|
+
self._kill_process_unix(pid)
|
|
346
|
+
|
|
347
|
+
def _kill_process_tree(self, pid):
|
|
348
|
+
try:
|
|
349
|
+
parent = psutil.Process(pid)
|
|
350
|
+
for child in parent.children(recursive=True):
|
|
351
|
+
try:
|
|
352
|
+
self._kill_process_by_pid(child.pid)
|
|
353
|
+
child.kill()
|
|
354
|
+
except (psutil.AccessDenied, psutil.ZombieProcess, OSError, psutil.NoSuchProcess):
|
|
355
|
+
continue
|
|
356
|
+
self._kill_process_by_pid(parent.pid)
|
|
357
|
+
parent.kill()
|
|
358
|
+
except (psutil.AccessDenied, psutil.ZombieProcess, OSError, psutil.NoSuchProcess):
|
|
359
|
+
pass
|
|
360
|
+
|
|
325
361
|
def stop(self) -> None:
|
|
326
362
|
"""Release any additional resources allocated during launching."""
|
|
327
363
|
maximum_wait_secs = 120.0
|
|
@@ -340,6 +376,26 @@ class LocalLauncher(Launcher):
|
|
|
340
376
|
raise
|
|
341
377
|
raise RuntimeError(f"Unable to remove {self.session_directory} in {maximum_wait_secs}s")
|
|
342
378
|
|
|
379
|
+
def close(self, session):
|
|
380
|
+
"""Shut down the launched EnSight session.
|
|
381
|
+
|
|
382
|
+
This method closes all associated sessions and then stops the
|
|
383
|
+
launched EnSight instance.
|
|
384
|
+
|
|
385
|
+
Parameters
|
|
386
|
+
----------
|
|
387
|
+
session : ``pyensight.Session``
|
|
388
|
+
Session to close.
|
|
389
|
+
|
|
390
|
+
Raises
|
|
391
|
+
------
|
|
392
|
+
RuntimeError
|
|
393
|
+
If the session was not launched by this launcher.
|
|
394
|
+
|
|
395
|
+
"""
|
|
396
|
+
self._kill_process_tree(self._websocketserver_pid)
|
|
397
|
+
return super().close(session)
|
|
398
|
+
|
|
343
399
|
@staticmethod
|
|
344
400
|
def get_cei_install_directory(ansys_installation: Optional[str]) -> str:
|
|
345
401
|
"""Get the Ansys distribution CEI directory to use.
|
|
@@ -268,16 +268,26 @@ def find_app(ansys_installation: Optional[str] = None) -> Optional[str]:
|
|
|
268
268
|
dirs_to_check = []
|
|
269
269
|
if ansys_installation:
|
|
270
270
|
# Given a different Ansys install
|
|
271
|
-
local_tp = os.path.join(os.path.join(ansys_installation, "tp", "
|
|
271
|
+
local_tp = os.path.join(os.path.join(ansys_installation, "tp", "showcase"))
|
|
272
272
|
if os.path.exists(local_tp):
|
|
273
273
|
dirs_to_check.append(local_tp)
|
|
274
274
|
# Dev Folder
|
|
275
|
-
|
|
275
|
+
omni_platform_dir = "linux-x86_64"
|
|
276
|
+
if sys.platform.startswith("win"):
|
|
277
|
+
omni_platform_dir = "windows-x86_64"
|
|
278
|
+
local_dev_omni = os.path.join(
|
|
279
|
+
ansys_installation,
|
|
280
|
+
"omni_build",
|
|
281
|
+
"kit-app-template",
|
|
282
|
+
"_build",
|
|
283
|
+
omni_platform_dir,
|
|
284
|
+
"release",
|
|
285
|
+
)
|
|
276
286
|
if os.path.exists(local_dev_omni):
|
|
277
287
|
dirs_to_check.append(local_dev_omni)
|
|
278
288
|
if "PYENSIGHT_ANSYS_INSTALLATION" in os.environ:
|
|
279
289
|
env_inst = os.environ["PYENSIGHT_ANSYS_INSTALLATION"]
|
|
280
|
-
dirs_to_check.append(os.path.join(env_inst, "tp", "
|
|
290
|
+
dirs_to_check.append(os.path.join(env_inst, "tp", "showcase"))
|
|
281
291
|
|
|
282
292
|
# Look for most recent Ansys install, 25.2 or later
|
|
283
293
|
awp_roots = []
|
|
@@ -286,7 +296,7 @@ def find_app(ansys_installation: Optional[str] = None) -> Optional[str]:
|
|
|
286
296
|
awp_roots.append(env_name)
|
|
287
297
|
awp_roots.sort(reverse=True)
|
|
288
298
|
for env_name in awp_roots:
|
|
289
|
-
dirs_to_check.append(os.path.join(os.environ[env_name], "tp", "
|
|
299
|
+
dirs_to_check.append(os.path.join(os.environ[env_name], "tp", "showcase"))
|
|
290
300
|
|
|
291
301
|
# check all the collected locations in order
|
|
292
302
|
for install_dir in dirs_to_check:
|
|
@@ -40,8 +40,8 @@ import png
|
|
|
40
40
|
try:
|
|
41
41
|
from pxr import Gf, Kind, Sdf, Usd, UsdGeom, UsdLux, UsdShade
|
|
42
42
|
except ModuleNotFoundError:
|
|
43
|
-
if sys.version_info.minor >=
|
|
44
|
-
warnings.warn("USD Export not supported for Python >= 3.
|
|
43
|
+
if sys.version_info.minor >= 14:
|
|
44
|
+
warnings.warn("USD Export not supported for Python >= 3.14")
|
|
45
45
|
sys.exit(1)
|
|
46
46
|
is_linux_arm64 = platform.system() == "Linux" and platform.machine() == "aarch64"
|
|
47
47
|
if is_linux_arm64:
|
|
@@ -56,13 +56,15 @@ class OmniverseWrapper(object):
|
|
|
56
56
|
destination: str = "",
|
|
57
57
|
line_width: float = 0.0,
|
|
58
58
|
) -> None:
|
|
59
|
+
# File extension. For debugging, .usda is sometimes helpful.
|
|
60
|
+
self._ext = ".usd"
|
|
59
61
|
self._cleaned_index = 0
|
|
60
62
|
self._cleaned_names: dict = {}
|
|
61
63
|
self._connectionStatusSubscription = None
|
|
62
64
|
self._stage = None
|
|
63
65
|
self._destinationPath: str = ""
|
|
64
66
|
self._old_stages: list = []
|
|
65
|
-
self._stagename: str = "dsg_scene.
|
|
67
|
+
self._stagename: str = "dsg_scene" + self._ext
|
|
66
68
|
self._live_edit: bool = live_edit
|
|
67
69
|
if self._live_edit:
|
|
68
70
|
self._stagename = "dsg_scene.live"
|
|
@@ -75,6 +77,9 @@ class OmniverseWrapper(object):
|
|
|
75
77
|
self.destination = destination
|
|
76
78
|
|
|
77
79
|
self._line_width = line_width
|
|
80
|
+
self._centroid: Optional[list] = None
|
|
81
|
+
# Record the files per timestep, per mesh type. {part_name: {"surfaces": [], "lines": [], "points": []} }
|
|
82
|
+
self._time_files: dict = {}
|
|
78
83
|
|
|
79
84
|
@property
|
|
80
85
|
def destination(self) -> str:
|
|
@@ -148,7 +153,8 @@ class OmniverseWrapper(object):
|
|
|
148
153
|
else:
|
|
149
154
|
shutil.rmtree(stage, ignore_errors=True, onerror=None)
|
|
150
155
|
except OSError:
|
|
151
|
-
|
|
156
|
+
if not stage.endswith("_manifest" + self._ext):
|
|
157
|
+
stages_unremoved.append(stage)
|
|
152
158
|
self._old_stages = stages_unremoved
|
|
153
159
|
|
|
154
160
|
def create_new_stage(self) -> None:
|
|
@@ -276,8 +282,74 @@ class OmniverseWrapper(object):
|
|
|
276
282
|
t = [t[0] * trans_convert, t[1] * trans_convert, t[2] * trans_convert]
|
|
277
283
|
return s, r, t
|
|
278
284
|
|
|
285
|
+
# Common code to create the part manifest file and the file per timestep
|
|
286
|
+
def create_dsg_surfaces_file(
|
|
287
|
+
self,
|
|
288
|
+
file_url, # SdfPath, location on disk
|
|
289
|
+
part_path: str, # base path name, such as "/Root/Case_1/Isosurface_part"
|
|
290
|
+
verts,
|
|
291
|
+
normals,
|
|
292
|
+
conn,
|
|
293
|
+
tcoords,
|
|
294
|
+
diffuse,
|
|
295
|
+
variable,
|
|
296
|
+
mat_info,
|
|
297
|
+
is_manifest: bool,
|
|
298
|
+
):
|
|
299
|
+
if is_manifest and file_url in self._old_stages:
|
|
300
|
+
return False
|
|
301
|
+
if not is_manifest and os.path.exists(file_url):
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
stage = Usd.Stage.CreateNew(file_url)
|
|
305
|
+
UsdGeom.SetStageUpAxis(stage, self._up_axis)
|
|
306
|
+
UsdGeom.SetStageMetersPerUnit(stage, 1.0 / self._units_per_meter)
|
|
307
|
+
self._old_stages.append(file_url)
|
|
308
|
+
|
|
309
|
+
part_prim = stage.OverridePrim(part_path)
|
|
310
|
+
|
|
311
|
+
surfaces_prim = self.create_xform_node(stage, part_path + "/surfaces")
|
|
312
|
+
mesh = UsdGeom.Mesh.Define(stage, str(surfaces_prim.GetPath()) + "/Mesh")
|
|
313
|
+
mesh.CreateDoubleSidedAttr().Set(True)
|
|
314
|
+
pt_attr = mesh.CreatePointsAttr()
|
|
315
|
+
if verts is not None:
|
|
316
|
+
pt_attr.Set(verts, 0)
|
|
317
|
+
norm_attr = mesh.CreateNormalsAttr()
|
|
318
|
+
if normals is not None:
|
|
319
|
+
norm_attr.Set(normals, 0)
|
|
320
|
+
fvc_attr = mesh.CreateFaceVertexCountsAttr()
|
|
321
|
+
fvi_attr = mesh.CreateFaceVertexIndicesAttr()
|
|
322
|
+
if conn is not None:
|
|
323
|
+
fvc_attr.Set([3] * (conn.size // 3), 0)
|
|
324
|
+
fvi_attr.Set(conn, 0)
|
|
325
|
+
|
|
326
|
+
primvarsAPI = UsdGeom.PrimvarsAPI(mesh)
|
|
327
|
+
texCoords = primvarsAPI.CreatePrimvar(
|
|
328
|
+
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
329
|
+
)
|
|
330
|
+
texCoords.SetInterpolation("vertex")
|
|
331
|
+
if tcoords is not None and variable is not None:
|
|
332
|
+
texCoords.Set(tcoords, 0)
|
|
333
|
+
|
|
334
|
+
stage.SetDefaultPrim(part_prim)
|
|
335
|
+
stage.SetStartTimeCode(0)
|
|
336
|
+
stage.SetEndTimeCode(0)
|
|
337
|
+
|
|
338
|
+
self.create_dsg_material(
|
|
339
|
+
stage,
|
|
340
|
+
mesh,
|
|
341
|
+
str(surfaces_prim.GetPath()),
|
|
342
|
+
diffuse=diffuse,
|
|
343
|
+
variable=variable,
|
|
344
|
+
mat_info=mat_info,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
stage.Save()
|
|
348
|
+
return True
|
|
349
|
+
|
|
279
350
|
def create_dsg_mesh_block(
|
|
280
351
|
self,
|
|
352
|
+
part: Part,
|
|
281
353
|
name,
|
|
282
354
|
id,
|
|
283
355
|
part_hash,
|
|
@@ -293,82 +365,173 @@ class OmniverseWrapper(object):
|
|
|
293
365
|
first_timestep=False,
|
|
294
366
|
mat_info={},
|
|
295
367
|
):
|
|
368
|
+
if self._stage is None:
|
|
369
|
+
return
|
|
370
|
+
|
|
296
371
|
# 1D texture map for variables https://graphics.pixar.com/usd/release/tut_simple_shading.html
|
|
297
372
|
# create the part usd object
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
part_stage,
|
|
335
|
-
mesh,
|
|
336
|
-
"/" + partname,
|
|
337
|
-
diffuse=diffuse,
|
|
338
|
-
variable=variable,
|
|
339
|
-
mat_info=mat_info,
|
|
340
|
-
)
|
|
373
|
+
part_base_name = self.clean_name(name)
|
|
374
|
+
partname = part_base_name + part_hash.hexdigest()
|
|
375
|
+
stage_name = "/Parts/" + partname + self._ext
|
|
376
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + self._ext))
|
|
377
|
+
|
|
378
|
+
# Make the manifest file - once for all timesteps
|
|
379
|
+
part_manifest_url_relative = "./Parts/" + part_base_name + "_manifest" + self._ext
|
|
380
|
+
part_manifest_url = self.stage_url(part_manifest_url_relative)
|
|
381
|
+
created_file = self.create_dsg_surfaces_file(
|
|
382
|
+
part_manifest_url,
|
|
383
|
+
str(parent_prim.GetPath()),
|
|
384
|
+
None,
|
|
385
|
+
None,
|
|
386
|
+
None,
|
|
387
|
+
None,
|
|
388
|
+
diffuse,
|
|
389
|
+
variable,
|
|
390
|
+
mat_info,
|
|
391
|
+
True,
|
|
392
|
+
)
|
|
393
|
+
if created_file:
|
|
394
|
+
self._stage.GetRootLayer().subLayerPaths.append(part_manifest_url_relative)
|
|
395
|
+
|
|
396
|
+
# Make the per-timestep file
|
|
397
|
+
created_file = self.create_dsg_surfaces_file(
|
|
398
|
+
part_stage_url,
|
|
399
|
+
str(parent_prim.GetPath()),
|
|
400
|
+
verts,
|
|
401
|
+
normals,
|
|
402
|
+
conn,
|
|
403
|
+
tcoords,
|
|
404
|
+
diffuse,
|
|
405
|
+
variable,
|
|
406
|
+
mat_info,
|
|
407
|
+
False,
|
|
408
|
+
)
|
|
341
409
|
|
|
342
|
-
|
|
410
|
+
# Glue the file into the main stage
|
|
411
|
+
path = parent_prim.GetPath().AppendChild("surfaces")
|
|
412
|
+
surfaces_prim = self._stage.OverridePrim(path)
|
|
413
|
+
self.add_timestep_valueclip(
|
|
414
|
+
part_base_name,
|
|
415
|
+
"surfaces",
|
|
416
|
+
surfaces_prim,
|
|
417
|
+
part_manifest_url_relative,
|
|
418
|
+
timeline,
|
|
419
|
+
stage_name,
|
|
420
|
+
)
|
|
343
421
|
|
|
344
|
-
|
|
345
|
-
path = timestep_prim.GetPath().AppendChild("part_ref_" + partname)
|
|
346
|
-
part_ref = self._stage.OverridePrim(path)
|
|
347
|
-
part_ref.GetReferences().AddReference("." + stage_name)
|
|
422
|
+
return part_stage_url
|
|
348
423
|
|
|
349
|
-
|
|
350
|
-
|
|
424
|
+
def get_time_files(self, part_name: str, mesh_type: str):
|
|
425
|
+
if part_name not in self._time_files:
|
|
426
|
+
self._time_files[part_name] = {"surfaces": [], "lines": [], "points": []}
|
|
427
|
+
return self._time_files[part_name][mesh_type]
|
|
351
428
|
|
|
352
|
-
|
|
429
|
+
def add_timestep_valueclip(
|
|
430
|
+
self,
|
|
431
|
+
part_name: str,
|
|
432
|
+
mesh_type: str,
|
|
433
|
+
part_prim: UsdGeom.Xform,
|
|
434
|
+
manifest_path: str,
|
|
435
|
+
timeline: List[float],
|
|
436
|
+
stage_name: str,
|
|
437
|
+
) -> None:
|
|
438
|
+
clips_api = Usd.ClipsAPI(part_prim)
|
|
439
|
+
asset_path = "." + stage_name
|
|
440
|
+
|
|
441
|
+
time_files = self.get_time_files(part_name, mesh_type)
|
|
442
|
+
|
|
443
|
+
if len(time_files) == 0 or time_files[-1][0] != asset_path:
|
|
444
|
+
time_files.append((asset_path, timeline[0]))
|
|
445
|
+
clips_api.SetClipAssetPaths([time_file[0] for time_file in time_files])
|
|
446
|
+
clips_api.SetClipActive(
|
|
447
|
+
[
|
|
448
|
+
(time_file[1] * self._time_codes_per_second, ii)
|
|
449
|
+
for ii, time_file in enumerate(time_files)
|
|
450
|
+
]
|
|
451
|
+
)
|
|
452
|
+
clips_api.SetClipTimes(
|
|
453
|
+
[
|
|
454
|
+
(time_file[1] * self._time_codes_per_second, 0)
|
|
455
|
+
for ii, time_file in enumerate(time_files)
|
|
456
|
+
]
|
|
457
|
+
)
|
|
458
|
+
clips_api.SetClipPrimPath(str(part_prim.GetPath()))
|
|
459
|
+
clips_api.SetClipManifestAssetPath(Sdf.AssetPath(manifest_path))
|
|
353
460
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
#
|
|
358
|
-
|
|
359
|
-
|
|
461
|
+
# Common code to create the part manifest file and the file per timestep
|
|
462
|
+
def create_dsg_lines_file(
|
|
463
|
+
self,
|
|
464
|
+
file_url, # SdfPath, location on disk
|
|
465
|
+
part_path: str, # base path name, such as "/Root/Case_1/Isosurface_part"
|
|
466
|
+
verts,
|
|
467
|
+
width: float,
|
|
468
|
+
tcoords,
|
|
469
|
+
diffuse,
|
|
470
|
+
var_cmd,
|
|
471
|
+
mat_info,
|
|
472
|
+
is_manifest: bool,
|
|
473
|
+
):
|
|
474
|
+
if is_manifest and file_url in self._old_stages:
|
|
475
|
+
return False
|
|
476
|
+
if not is_manifest and os.path.exists(file_url):
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
stage = Usd.Stage.CreateNew(file_url)
|
|
480
|
+
UsdGeom.SetStageUpAxis(stage, self._up_axis)
|
|
481
|
+
UsdGeom.SetStageMetersPerUnit(stage, 1.0 / self._units_per_meter)
|
|
482
|
+
self._old_stages.append(file_url)
|
|
483
|
+
|
|
484
|
+
part_prim = stage.OverridePrim(part_path)
|
|
485
|
+
|
|
486
|
+
lines_prim = self.create_xform_node(stage, part_path + "/lines")
|
|
487
|
+
|
|
488
|
+
lines = UsdGeom.BasisCurves.Define(stage, str(lines_prim.GetPath()) + "/Lines")
|
|
489
|
+
lines.CreateDoubleSidedAttr().Set(True)
|
|
490
|
+
pt_attr = lines.CreatePointsAttr()
|
|
491
|
+
vc_attr = lines.CreateCurveVertexCountsAttr()
|
|
492
|
+
if verts is not None:
|
|
493
|
+
pt_attr.Set(verts, 0)
|
|
494
|
+
vc_attr.Set([2] * (verts.size // 6), 0)
|
|
495
|
+
lines.CreatePurposeAttr().Set("render")
|
|
496
|
+
lines.CreateTypeAttr().Set("linear")
|
|
497
|
+
lines.CreateWidthsAttr([width])
|
|
498
|
+
lines.SetWidthsInterpolation("constant")
|
|
499
|
+
|
|
500
|
+
# Rounded endpoint are a primvar
|
|
501
|
+
primvarsAPI = UsdGeom.PrimvarsAPI(lines)
|
|
502
|
+
endCaps = primvarsAPI.CreatePrimvar(
|
|
503
|
+
"endcaps", Sdf.ValueTypeNames.Int, UsdGeom.Tokens.constant
|
|
360
504
|
)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if
|
|
370
|
-
|
|
371
|
-
|
|
505
|
+
endCaps.Set(2) # Rounded = 2
|
|
506
|
+
|
|
507
|
+
prim = lines.GetPrim()
|
|
508
|
+
wireframe = width == 0.0
|
|
509
|
+
prim.CreateAttribute("omni:scene:visualization:drawWireframe", Sdf.ValueTypeNames.Bool).Set(
|
|
510
|
+
wireframe
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
if (tcoords is not None) and var_cmd:
|
|
514
|
+
primvarsAPI = UsdGeom.PrimvarsAPI(lines)
|
|
515
|
+
texCoords = primvarsAPI.CreatePrimvar(
|
|
516
|
+
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
517
|
+
)
|
|
518
|
+
if tcoords is not None and var_cmd is not None:
|
|
519
|
+
texCoords.Set(tcoords, 0)
|
|
520
|
+
texCoords.SetInterpolation("vertex")
|
|
521
|
+
stage.SetDefaultPrim(part_prim)
|
|
522
|
+
stage.SetStartTimeCode(0)
|
|
523
|
+
stage.SetEndTimeCode(0)
|
|
524
|
+
|
|
525
|
+
self.create_dsg_material(
|
|
526
|
+
stage,
|
|
527
|
+
lines,
|
|
528
|
+
str(lines_prim.GetPath()),
|
|
529
|
+
diffuse=diffuse,
|
|
530
|
+
variable=var_cmd,
|
|
531
|
+
mat_info=mat_info,
|
|
532
|
+
)
|
|
533
|
+
stage.Save()
|
|
534
|
+
return True
|
|
372
535
|
|
|
373
536
|
def create_dsg_lines(
|
|
374
537
|
self,
|
|
@@ -389,78 +552,97 @@ class OmniverseWrapper(object):
|
|
|
389
552
|
# include the line width in the hash
|
|
390
553
|
part_hash.update(str(self.line_width).encode("utf-8"))
|
|
391
554
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
).Set(wireframe)
|
|
427
|
-
if (tcoords is not None) and var_cmd:
|
|
428
|
-
primvarsAPI = UsdGeom.PrimvarsAPI(lines)
|
|
429
|
-
texCoords = primvarsAPI.CreatePrimvar(
|
|
430
|
-
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
431
|
-
)
|
|
432
|
-
texCoords.Set(tcoords)
|
|
433
|
-
texCoords.SetInterpolation("vertex")
|
|
434
|
-
part_prim = part_stage.GetPrimAtPath("/" + partname)
|
|
435
|
-
part_stage.SetDefaultPrim(part_prim)
|
|
436
|
-
|
|
437
|
-
# Currently, this will never happen, but it is a setup for rigid body transforms
|
|
438
|
-
# At present, the group transforms have been cooked into the vertices so this is not needed
|
|
439
|
-
matrixOp = xform.AddXformOp(
|
|
440
|
-
UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
|
|
441
|
-
)
|
|
442
|
-
matrixOp.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
443
|
-
|
|
444
|
-
self.create_dsg_material(
|
|
445
|
-
part_stage,
|
|
446
|
-
lines,
|
|
447
|
-
"/" + partname,
|
|
448
|
-
diffuse=diffuse,
|
|
449
|
-
variable=var_cmd,
|
|
450
|
-
mat_info=mat_info,
|
|
451
|
-
)
|
|
555
|
+
part_base_name = self.clean_name(name) + "_l"
|
|
556
|
+
partname = part_base_name + part_hash.hexdigest()
|
|
557
|
+
stage_name = "/Parts/" + partname + self._ext
|
|
558
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + self._ext))
|
|
559
|
+
|
|
560
|
+
# Make the manifest file - once for all timesteps
|
|
561
|
+
part_manifest_url_relative = "./Parts/" + part_base_name + "_manifest" + self._ext
|
|
562
|
+
part_manifest_url = self.stage_url(part_manifest_url_relative)
|
|
563
|
+
created_file = self.create_dsg_lines_file(
|
|
564
|
+
part_manifest_url,
|
|
565
|
+
str(parent_prim.GetPath()),
|
|
566
|
+
None,
|
|
567
|
+
width,
|
|
568
|
+
None,
|
|
569
|
+
diffuse,
|
|
570
|
+
variable,
|
|
571
|
+
mat_info,
|
|
572
|
+
True,
|
|
573
|
+
)
|
|
574
|
+
if created_file:
|
|
575
|
+
self._stage.GetRootLayer().subLayerPaths.append(part_manifest_url_relative)
|
|
576
|
+
|
|
577
|
+
# Make the per-timestep file
|
|
578
|
+
created_file = self.create_dsg_lines_file(
|
|
579
|
+
part_stage_url,
|
|
580
|
+
str(parent_prim.GetPath()),
|
|
581
|
+
verts,
|
|
582
|
+
width,
|
|
583
|
+
tcoords,
|
|
584
|
+
diffuse,
|
|
585
|
+
variable,
|
|
586
|
+
mat_info,
|
|
587
|
+
False,
|
|
588
|
+
)
|
|
452
589
|
|
|
453
|
-
|
|
590
|
+
# Glue the file into the main stage
|
|
591
|
+
path = parent_prim.GetPath().AppendChild("lines")
|
|
592
|
+
lines_prim = self._stage.OverridePrim(path)
|
|
593
|
+
self.add_timestep_valueclip(
|
|
594
|
+
part_base_name, "lines", lines_prim, part_manifest_url_relative, timeline, stage_name
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
return part_stage_url
|
|
598
|
+
|
|
599
|
+
# Common code to create the part manifest file and the file per timestep
|
|
600
|
+
def create_dsg_points_file(
|
|
601
|
+
self,
|
|
602
|
+
file_url, # SdfPath, location on disk
|
|
603
|
+
part_path: str, # base path name, such as "/Root/Case_1/Isosurface_part"
|
|
604
|
+
verts,
|
|
605
|
+
sizes,
|
|
606
|
+
colors,
|
|
607
|
+
default_size: float,
|
|
608
|
+
default_color,
|
|
609
|
+
is_manifest: bool,
|
|
610
|
+
):
|
|
611
|
+
if is_manifest and file_url in self._old_stages:
|
|
612
|
+
return False
|
|
613
|
+
if not is_manifest and os.path.exists(file_url):
|
|
614
|
+
return False
|
|
615
|
+
|
|
616
|
+
stage = Usd.Stage.CreateNew(file_url)
|
|
617
|
+
UsdGeom.SetStageUpAxis(stage, self._up_axis)
|
|
618
|
+
UsdGeom.SetStageMetersPerUnit(stage, 1.0 / self._units_per_meter)
|
|
619
|
+
self._old_stages.append(file_url)
|
|
620
|
+
|
|
621
|
+
part_prim = stage.OverridePrim(part_path)
|
|
622
|
+
points = UsdGeom.Points.Define(stage, part_path + "/points")
|
|
623
|
+
pt_attr = points.CreatePointsAttr()
|
|
624
|
+
w_attr = points.CreateWidthsAttr()
|
|
625
|
+
if verts is not None:
|
|
626
|
+
pt_attr.Set(verts, 0)
|
|
627
|
+
if sizes is not None and sizes.size == (verts.size // 3):
|
|
628
|
+
w_attr.Set(sizes, 0)
|
|
629
|
+
else:
|
|
630
|
+
w_attr.Set([default_size] * (verts.size // 3), 0)
|
|
454
631
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
632
|
+
colorAttr = points.GetPrim().GetAttribute("primvars:displayColor")
|
|
633
|
+
colorAttr.SetMetadata("interpolation", "vertex")
|
|
634
|
+
if verts is not None:
|
|
635
|
+
if colors is not None and colors.size == verts.size:
|
|
636
|
+
colorAttr.Set(colors, 0)
|
|
637
|
+
else:
|
|
638
|
+
colorAttr.Set([default_color[0:3]] * (verts.size // 3), 0)
|
|
459
639
|
|
|
460
|
-
|
|
461
|
-
|
|
640
|
+
stage.SetDefaultPrim(part_prim)
|
|
641
|
+
stage.SetStartTimeCode(0)
|
|
642
|
+
stage.SetEndTimeCode(0)
|
|
462
643
|
|
|
463
|
-
|
|
644
|
+
stage.Save()
|
|
645
|
+
return True
|
|
464
646
|
|
|
465
647
|
def create_dsg_points(
|
|
466
648
|
self,
|
|
@@ -477,53 +659,45 @@ class OmniverseWrapper(object):
|
|
|
477
659
|
timeline=[0.0, 0.0],
|
|
478
660
|
first_timestep=False,
|
|
479
661
|
):
|
|
480
|
-
|
|
481
|
-
partname =
|
|
482
|
-
stage_name = "/Parts/" + partname +
|
|
483
|
-
part_stage_url = self.stage_url(os.path.join("Parts", partname +
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
# At present, the group transforms have been cooked into the vertices so this is not needed
|
|
513
|
-
matrixOp = xform.AddXformOp(
|
|
514
|
-
UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
|
|
515
|
-
)
|
|
516
|
-
matrixOp.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
517
|
-
|
|
518
|
-
timestep_prim = self.add_timestep_group(parent_prim, timeline, first_timestep)
|
|
519
|
-
|
|
520
|
-
# glue it into our stage
|
|
521
|
-
path = timestep_prim.GetPath().AppendChild("part_ref_" + partname)
|
|
522
|
-
part_ref = self._stage.OverridePrim(path)
|
|
523
|
-
part_ref.GetReferences().AddReference("." + stage_name)
|
|
662
|
+
part_base_name = self.clean_name(name) + "_p"
|
|
663
|
+
partname = part_base_name + part_hash.hexdigest()
|
|
664
|
+
stage_name = "/Parts/" + partname + self._ext
|
|
665
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + self._ext))
|
|
666
|
+
|
|
667
|
+
# Make the manifest file - once for all timesteps
|
|
668
|
+
part_manifest_url_relative = "./Parts/" + part_base_name + "_manifest" + self._ext
|
|
669
|
+
part_manifest_url = self.stage_url(part_manifest_url_relative)
|
|
670
|
+
created_file = self.create_dsg_points_file(
|
|
671
|
+
part_manifest_url,
|
|
672
|
+
str(parent_prim.GetPath()),
|
|
673
|
+
None,
|
|
674
|
+
None,
|
|
675
|
+
None,
|
|
676
|
+
default_size,
|
|
677
|
+
default_color,
|
|
678
|
+
True,
|
|
679
|
+
)
|
|
680
|
+
if created_file:
|
|
681
|
+
self._stage.GetRootLayer().subLayerPaths.append(part_manifest_url_relative)
|
|
682
|
+
|
|
683
|
+
# Make the per-timestep file
|
|
684
|
+
created_file = self.create_dsg_points_file(
|
|
685
|
+
part_stage_url,
|
|
686
|
+
str(parent_prim.GetPath()),
|
|
687
|
+
verts,
|
|
688
|
+
sizes,
|
|
689
|
+
colors,
|
|
690
|
+
default_size,
|
|
691
|
+
default_color,
|
|
692
|
+
False,
|
|
693
|
+
)
|
|
524
694
|
|
|
525
|
-
|
|
526
|
-
|
|
695
|
+
# Glue the file into the main stage
|
|
696
|
+
path = parent_prim.GetPath().AppendChild("points")
|
|
697
|
+
points_prim = self._stage.OverridePrim(path)
|
|
698
|
+
self.add_timestep_valueclip(
|
|
699
|
+
part_base_name, "points", points_prim, part_manifest_url_relative, timeline, stage_name
|
|
700
|
+
)
|
|
527
701
|
|
|
528
702
|
return part_stage_url
|
|
529
703
|
|
|
@@ -615,9 +789,20 @@ class OmniverseWrapper(object):
|
|
|
615
789
|
shutil.rmtree(uriPath, ignore_errors=True, onerror=None)
|
|
616
790
|
shutil.copytree(f"{tempdir}/scratch/Textures", uriPath)
|
|
617
791
|
|
|
792
|
+
def create_xform_node(self, stage, name):
|
|
793
|
+
xform_node = UsdGeom.Xform.Define(stage, name)
|
|
794
|
+
xform_node.AddTranslateOp().Set((0, 0, 0))
|
|
795
|
+
xform_node.AddRotateXYZOp().Set((0, 0, 0))
|
|
796
|
+
xform_node.AddScaleOp().Set((1, 1, 1))
|
|
797
|
+
if self._centroid is not None:
|
|
798
|
+
xform_api = UsdGeom.XformCommonAPI(xform_node.GetPrim())
|
|
799
|
+
xform_api.SetPivot(Gf.Vec3f(self._centroid) * self._units_per_meter)
|
|
800
|
+
return xform_node
|
|
801
|
+
|
|
618
802
|
def create_dsg_root(self):
|
|
619
803
|
root_name = "/Root"
|
|
620
|
-
|
|
804
|
+
self.create_xform_node(self._stage, root_name)
|
|
805
|
+
|
|
621
806
|
# Define the defaultPrim as the /Root prim
|
|
622
807
|
root_prim = self._stage.GetPrimAtPath(root_name)
|
|
623
808
|
self._stage.SetDefaultPrim(root_prim)
|
|
@@ -627,8 +812,10 @@ class OmniverseWrapper(object):
|
|
|
627
812
|
if camera is not None:
|
|
628
813
|
cam_name = "/Root/Cam"
|
|
629
814
|
cam_prim = UsdGeom.Xform.Define(self._stage, cam_name)
|
|
630
|
-
|
|
631
|
-
|
|
815
|
+
s = self._units_per_meter
|
|
816
|
+
cam_pos = Gf.Vec3d(camera.lookfrom[0], camera.lookfrom[1], camera.lookfrom[2]) * s
|
|
817
|
+
target_pos = Gf.Vec3d(camera.lookat[0], camera.lookat[1], camera.lookat[2]) * s
|
|
818
|
+
|
|
632
819
|
up_vec = Gf.Vec3d(camera.upvector[0], camera.upvector[1], camera.upvector[2])
|
|
633
820
|
cam_prim = self._stage.GetPrimAtPath(cam_name)
|
|
634
821
|
geom_cam = UsdGeom.Camera(cam_prim)
|
|
@@ -647,7 +834,7 @@ class OmniverseWrapper(object):
|
|
|
647
834
|
cam = geom_cam.GetCamera()
|
|
648
835
|
# LOL, not sure why is might be correct, but so far it seems to work???
|
|
649
836
|
cam.focalLength = camera.fieldofview
|
|
650
|
-
dist = (target_pos - cam_pos).GetLength()
|
|
837
|
+
dist = (target_pos - cam_pos).GetLength()
|
|
651
838
|
cam.clippingRange = Gf.Range1f(0.1 * dist, 1000.0 * dist)
|
|
652
839
|
look_at = Gf.Matrix4d()
|
|
653
840
|
look_at.SetLookAt(cam_pos, target_pos, up_vec)
|
|
@@ -686,12 +873,8 @@ class OmniverseWrapper(object):
|
|
|
686
873
|
path = parent_prim.GetPath().AppendChild(self.clean_name(name))
|
|
687
874
|
group_prim = UsdGeom.Xform.Get(self._stage, path)
|
|
688
875
|
if not group_prim:
|
|
689
|
-
group_prim =
|
|
690
|
-
|
|
691
|
-
matrix_op = group_prim.AddXformOp(
|
|
692
|
-
UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
|
|
693
|
-
)
|
|
694
|
-
matrix_op.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
876
|
+
group_prim = self.create_xform_node(self._stage, path)
|
|
877
|
+
|
|
695
878
|
# Map kinds
|
|
696
879
|
kind = Kind.Tokens.group
|
|
697
880
|
if obj_type == "ENS_CASE":
|
|
@@ -699,7 +882,9 @@ class OmniverseWrapper(object):
|
|
|
699
882
|
elif obj_type == "ENS_PART":
|
|
700
883
|
kind = Kind.Tokens.component
|
|
701
884
|
Usd.ModelAPI(group_prim).SetKind(kind)
|
|
885
|
+
group_prim.GetPrim().SetDisplayName(name)
|
|
702
886
|
logging.info(f"Created group:'{name}' {str(obj_type)}")
|
|
887
|
+
|
|
703
888
|
return group_prim
|
|
704
889
|
|
|
705
890
|
def uploadMaterial(self):
|
|
@@ -732,6 +917,7 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
732
917
|
self._group_prims: Dict[int, Any] = dict()
|
|
733
918
|
self._root_prim = None
|
|
734
919
|
self._sent_textures = False
|
|
920
|
+
self._case_xform_applied_to_camera = False
|
|
735
921
|
|
|
736
922
|
def add_group(self, id: int, view: bool = False) -> None:
|
|
737
923
|
super().add_group(id, view)
|
|
@@ -752,10 +938,17 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
752
938
|
matrix = group.matrix4x4
|
|
753
939
|
# Is this a "case" group (it will contain part of the camera view in the matrix)
|
|
754
940
|
if obj_type == "ENS_CASE":
|
|
755
|
-
if
|
|
941
|
+
if self.session.scene_bounds is not None:
|
|
942
|
+
midx = (self.session.scene_bounds[3] + self.session.scene_bounds[0]) * 0.5
|
|
943
|
+
midy = (self.session.scene_bounds[4] + self.session.scene_bounds[1]) * 0.5
|
|
944
|
+
midz = (self.session.scene_bounds[5] + self.session.scene_bounds[2]) * 0.5
|
|
945
|
+
self._omni._centroid = [midx, midy, midz]
|
|
946
|
+
|
|
947
|
+
if not self.session.vrmode and not self._case_xform_applied_to_camera:
|
|
756
948
|
# if in camera mode, we need to update the camera matrix so we can
|
|
757
949
|
# use the identity matrix on this group. The camera should have been
|
|
758
950
|
# created in the "view" handler
|
|
951
|
+
self._case_xform_applied_to_camera = True
|
|
759
952
|
cam_name = "/Root/Cam"
|
|
760
953
|
cam_prim = self._omni._stage.GetPrimAtPath(cam_name) # type: ignore
|
|
761
954
|
geom_cam = UsdGeom.Camera(cam_prim)
|
|
@@ -763,9 +956,12 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
763
956
|
cam = geom_cam.GetCamera()
|
|
764
957
|
c = cam.transform
|
|
765
958
|
m = Gf.Matrix4d(*matrix).GetTranspose()
|
|
959
|
+
s = self._omni._units_per_meter
|
|
960
|
+
trans = m.GetRow(3)
|
|
961
|
+
trans = Gf.Vec4d(trans[0] * s, trans[1] * s, trans[2] * s, trans[3])
|
|
962
|
+
m.SetRow(3, trans)
|
|
766
963
|
# move the model transform to the camera transform
|
|
767
|
-
|
|
768
|
-
cam.transform = c * m.GetInverse() * sc
|
|
964
|
+
cam.transform = c * m.GetInverse()
|
|
769
965
|
|
|
770
966
|
# Determine if the camera is principally more Y, or Z up. X up not supported.
|
|
771
967
|
# Omniverse' built in navigator tries to keep this direction up
|
|
@@ -874,6 +1070,7 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
874
1070
|
has_triangles = True
|
|
875
1071
|
# Generate the mesh block
|
|
876
1072
|
_ = self._omni.create_dsg_mesh_block(
|
|
1073
|
+
part,
|
|
877
1074
|
name,
|
|
878
1075
|
obj_id,
|
|
879
1076
|
part.hash,
|
|
@@ -980,6 +1177,7 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
980
1177
|
self._omni.clear_cleaned_names()
|
|
981
1178
|
# clear the group Omni prims list
|
|
982
1179
|
self._group_prims = dict()
|
|
1180
|
+
self._case_xform_applied_to_camera = False
|
|
983
1181
|
|
|
984
1182
|
self._omni.create_new_stage()
|
|
985
1183
|
self._root_prim = self._omni.create_dsg_root()
|
|
@@ -71,10 +71,11 @@ class _Simba:
|
|
|
71
71
|
self.ensight.annotation.axis_global("off")
|
|
72
72
|
self.ensight.annotation.axis_local("off")
|
|
73
73
|
self.ensight.annotation.axis_model("off")
|
|
74
|
+
self.ensight.view_transf.zclip_float("OFF")
|
|
74
75
|
|
|
75
76
|
def get_center_of_rotation(self):
|
|
76
77
|
"""Get EnSight center of rotation."""
|
|
77
|
-
return self.ensight.objs.core.VPORTS[0].TRANSFORMCENTER
|
|
78
|
+
return self.ensight.objs.core.VPORTS[0].TRANSFORMCENTER.copy()
|
|
78
79
|
|
|
79
80
|
def auto_scale(self):
|
|
80
81
|
"""Auto scale view."""
|
|
@@ -157,44 +158,6 @@ class _Simba:
|
|
|
157
158
|
z = 0.25 * s
|
|
158
159
|
return np.array([x, y, z, w])
|
|
159
160
|
|
|
160
|
-
def compute_model_rotation_quaternion(self, camera_position, focal_point, view_up):
|
|
161
|
-
"""Compute the quaternion from the input camera."""
|
|
162
|
-
forward = self.normalize(np.array(focal_point) - np.array(camera_position))
|
|
163
|
-
right = self.normalize(np.cross(forward, view_up))
|
|
164
|
-
true_up = np.cross(right, forward)
|
|
165
|
-
camera_rotation = np.vstack([right, true_up, -forward]).T
|
|
166
|
-
model_rotation = camera_rotation.T
|
|
167
|
-
quat = self.rotation_matrix_to_quaternion(model_rotation)
|
|
168
|
-
return quat
|
|
169
|
-
|
|
170
|
-
@staticmethod
|
|
171
|
-
def quaternion_multiply(q1, q2):
|
|
172
|
-
x1, y1, z1, w1 = q1
|
|
173
|
-
x2, y2, z2, w2 = q2
|
|
174
|
-
w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
|
|
175
|
-
x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
|
|
176
|
-
y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
|
|
177
|
-
z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
|
|
178
|
-
return np.array([x, y, z, w])
|
|
179
|
-
|
|
180
|
-
def quaternion_to_euler(self, q):
|
|
181
|
-
q = self.normalize(q)
|
|
182
|
-
x, y, z, w = q
|
|
183
|
-
sinr_cosp = 2 * (w * x + y * z)
|
|
184
|
-
cosr_cosp = 1 - 2 * (x * x + y * y)
|
|
185
|
-
roll = np.arctan2(sinr_cosp, cosr_cosp)
|
|
186
|
-
|
|
187
|
-
sinp = 2 * (w * y - z * x)
|
|
188
|
-
if abs(sinp) >= 1:
|
|
189
|
-
pitch = np.pi / 2 * np.sign(sinp)
|
|
190
|
-
else:
|
|
191
|
-
pitch = np.arcsin(sinp)
|
|
192
|
-
siny_cosp = 2 * (w * z + x * y)
|
|
193
|
-
cosy_cosp = 1 - 2 * (y * y + z * z)
|
|
194
|
-
yaw = np.arctan2(siny_cosp, cosy_cosp)
|
|
195
|
-
|
|
196
|
-
return np.degrees([roll, pitch, yaw])
|
|
197
|
-
|
|
198
161
|
def compute_camera_from_ensight_opengl(self):
|
|
199
162
|
"""Simulate a rotating camera using the current quaternion."""
|
|
200
163
|
if isinstance(self.ensight, ModuleType):
|
|
@@ -207,38 +170,6 @@ class _Simba:
|
|
|
207
170
|
parallel_scale = 1 / data[9]
|
|
208
171
|
return camera_position, focal_point, self.views._normalize_vector(view_up), parallel_scale
|
|
209
172
|
|
|
210
|
-
def get_camera_axes(self):
|
|
211
|
-
"""
|
|
212
|
-
Returns the camera's local axes: right, up, and forward vectors.
|
|
213
|
-
These are useful for applying transformations in view space.
|
|
214
|
-
|
|
215
|
-
Parameters:
|
|
216
|
-
camera (dict): A dictionary with keys 'position', 'focal_point', and 'view_up'.
|
|
217
|
-
|
|
218
|
-
Returns:
|
|
219
|
-
right (np.ndarray): Right vector (X axis in view space).
|
|
220
|
-
up (np.ndarray): Up vector (Y axis in view space).
|
|
221
|
-
forw ard (np.ndarray): Forward vector (Z axis in view space, pointing from position to focal point).
|
|
222
|
-
"""
|
|
223
|
-
camera = self.get_camera()
|
|
224
|
-
position = np.array(camera["position"])
|
|
225
|
-
focal_point = np.array(camera["focal_point"])
|
|
226
|
-
view_up = np.array(camera["view_up"])
|
|
227
|
-
|
|
228
|
-
# Forward vector: from camera position to focal point
|
|
229
|
-
forward = focal_point - position
|
|
230
|
-
forward /= np.linalg.norm(forward)
|
|
231
|
-
|
|
232
|
-
# Right vector: cross product of forward and view_up
|
|
233
|
-
right = np.cross(forward, view_up)
|
|
234
|
-
right /= np.linalg.norm(right)
|
|
235
|
-
|
|
236
|
-
# Recompute up vector to ensure orthogonality
|
|
237
|
-
up = np.cross(right, forward)
|
|
238
|
-
up /= np.linalg.norm(up)
|
|
239
|
-
|
|
240
|
-
return right, up, forward
|
|
241
|
-
|
|
242
173
|
def set_camera(
|
|
243
174
|
self,
|
|
244
175
|
orthographic,
|
|
@@ -246,43 +177,41 @@ class _Simba:
|
|
|
246
177
|
position=None,
|
|
247
178
|
focal_point=None,
|
|
248
179
|
view_angle=None,
|
|
249
|
-
pan=None,
|
|
250
|
-
mousex=None,
|
|
251
|
-
mousey=None,
|
|
252
|
-
invert_y=False,
|
|
253
180
|
):
|
|
254
181
|
"""Set the EnSight camera settings from the VTK input."""
|
|
255
182
|
self.ensight.view_transf.function("global")
|
|
256
183
|
perspective = "OFF" if orthographic else "ON"
|
|
257
|
-
|
|
258
|
-
self.ensight.view.perspective(perspective)
|
|
184
|
+
self.ensight.view.perspective(perspective)
|
|
259
185
|
vport = self.ensight.objs.core.VPORTS[0]
|
|
260
|
-
|
|
261
|
-
vport.PERSPECTIVEANGLE = view_angle / 2
|
|
186
|
+
vport.PERSPECTIVEANGLE = view_angle / 2
|
|
262
187
|
if view_up and position and focal_point:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
188
|
+
current_camera = self.get_camera()
|
|
189
|
+
center = vport.TRANSFORMCENTER.copy()
|
|
190
|
+
if isinstance(self.ensight, ModuleType):
|
|
191
|
+
data = self.ensight.objs.core.VPORTS[0].simba_set_camera_helper(
|
|
192
|
+
position,
|
|
193
|
+
focal_point,
|
|
194
|
+
view_up,
|
|
195
|
+
current_camera["position"],
|
|
196
|
+
current_camera["focal_point"],
|
|
197
|
+
current_camera["view_up"],
|
|
198
|
+
center,
|
|
199
|
+
vport.ROTATION.copy(),
|
|
270
200
|
)
|
|
271
|
-
angles = self.quaternion_to_euler(q_relative)
|
|
272
|
-
self.ensight.view_transf.rotate(*angles)
|
|
273
201
|
else:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
202
|
+
current_position = current_camera["position"]
|
|
203
|
+
current_fp = current_camera["focal_point"]
|
|
204
|
+
current_up = current_camera["view_up"]
|
|
205
|
+
cmd = "ensight.objs.core.VPORTS[0].simba_set_camera_helper("
|
|
206
|
+
cmd += f"{position}, {focal_point}, {view_up}, {current_position}, "
|
|
207
|
+
cmd += f"{current_fp}, {current_up}, {center}, "
|
|
208
|
+
cmd += f"{vport.ROTATION.copy()})"
|
|
209
|
+
data = self.ensight._session.cmd(cmd)
|
|
210
|
+
self.ensight.view_transf.rotate(data[3], data[4], data[5])
|
|
211
|
+
self.ensight.view_transf.translate(data[0], data[1], 0)
|
|
284
212
|
|
|
285
213
|
self.render()
|
|
214
|
+
return self.get_camera()
|
|
286
215
|
|
|
287
216
|
def set_perspective(self, value):
|
|
288
217
|
self.ensight.view_transf.function("global")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ansys-pyensight-core
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.13
|
|
4
4
|
Summary: A python wrapper for Ansys EnSight
|
|
5
5
|
Author-email: "ANSYS, Inc." <pyansys.core@ansys.com>
|
|
6
6
|
Maintainer-email: "ANSYS, Inc." <pyansys.core@ansys.com>
|
|
@@ -25,7 +25,7 @@ Requires-Dist: numpy>=1.21.0,<3
|
|
|
25
25
|
Requires-Dist: Pillow>=9.3.0
|
|
26
26
|
Requires-Dist: pypng>=0.0.20
|
|
27
27
|
Requires-Dist: psutil>=5.9.2
|
|
28
|
-
Requires-Dist: usd-core==
|
|
28
|
+
Requires-Dist: usd-core==25.8; platform_machine != 'aarch64'
|
|
29
29
|
Requires-Dist: pygltflib>=1.16.2
|
|
30
30
|
Requires-Dist: grpcio<1.68.0
|
|
31
31
|
Requires-Dist: build>=0.10.0 ; extra == "dev"
|
|
@@ -46,7 +46,7 @@ Requires-Dist: sphinxcontrib.jquery==4.1 ; extra == "doc"
|
|
|
46
46
|
Requires-Dist: sphinxcontrib-openapi==0.8.4 ; extra == "doc"
|
|
47
47
|
Requires-Dist: coverage-badge==1.1.2 ; extra == "doc"
|
|
48
48
|
Requires-Dist: sphinxcontrib-video==0.2.1 ; extra == "doc"
|
|
49
|
-
Requires-Dist: usd-core>=
|
|
49
|
+
Requires-Dist: usd-core>=25.8 ; extra == "doc"
|
|
50
50
|
Requires-Dist: pygltflib>=1.16.2 ; extra == "doc"
|
|
51
51
|
Requires-Dist: pytest==8.3.5 ; extra == "tests"
|
|
52
52
|
Requires-Dist: pytest-cov==5.0.0 ; extra == "tests"
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
ansys/pyensight/core/__init__.py,sha256=BCfpDonS2OZRM2O6wjcyOYhLSgdy9hMQRl6G-_9Bvkg,845
|
|
2
2
|
ansys/pyensight/core/common.py,sha256=GhDxSoSscT9ObyTyEcuP0zeKKVJOpWVVYG0SwEby7CA,7575
|
|
3
3
|
ansys/pyensight/core/deep_pixel_view.html,sha256=6u4mGOuzJiPze8N8pIKJsEGPv5y6-zb38m9IfrarATE,3510
|
|
4
|
-
ansys/pyensight/core/dockerlauncher.py,sha256=
|
|
4
|
+
ansys/pyensight/core/dockerlauncher.py,sha256=iemgGEotlZjpX_tCvnAdLKRDNLaDuGKeYWihiqUSDNY,30523
|
|
5
5
|
ansys/pyensight/core/dvs.py,sha256=zRhizBkIUCk09BaR-7BxkC6EkIEjG7rU5vznh45QOzA,32018
|
|
6
6
|
ansys/pyensight/core/enscontext.py,sha256=GSKkjZt1QEPyHEQ59EEBgKGMik9vjCdR9coR4uX7fEw,12141
|
|
7
7
|
ansys/pyensight/core/enshell_grpc.py,sha256=OPK84J44kwjbAYQM4acb9zd-abYYkMNculKaWZeVDIk,17123
|
|
8
8
|
ansys/pyensight/core/ensight_grpc.py,sha256=IitEgMzBJTyTgQff0sXPvGkVnC2E9qRKw-HXCF-mVvA,29713
|
|
9
9
|
ansys/pyensight/core/ensobj.py,sha256=uDtM2KHcAwd4hu5pcUYWbSD729ApHGIvuqZhEq8PxTI,18558
|
|
10
10
|
ansys/pyensight/core/launch_ensight.py,sha256=iZJM6GdpzGRDLzrv1V2QZ5veIOpNSB5xPpJUFY7rBuo,10254
|
|
11
|
-
ansys/pyensight/core/launcher.py,sha256=
|
|
11
|
+
ansys/pyensight/core/launcher.py,sha256=_AH0Lr_kTFM6c5DYxEXp1pfrbYmoHWHajxnHgq_0CBo,13229
|
|
12
12
|
ansys/pyensight/core/libuserd.py,sha256=P6j-mgT6kDYiuRRDKEepJvZusBYa5jo4AQIwK__9R6Q,76020
|
|
13
13
|
ansys/pyensight/core/listobj.py,sha256=Trw87IxIMXtmUd1DzywRmMzORU704AG4scX4fqYmO6M,9340
|
|
14
|
-
ansys/pyensight/core/locallauncher.py,sha256=
|
|
14
|
+
ansys/pyensight/core/locallauncher.py,sha256=uv6B1gojx7FmT5e4RZQe2knKzChr0Ooh-Wr06QRbsoA,19146
|
|
15
15
|
ansys/pyensight/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
ansys/pyensight/core/renderable.py,sha256=KidVTVCZ4hKYq54pP9KoJ8OCxeWBXNUJYyKSwMZ2Sls,36362
|
|
17
17
|
ansys/pyensight/core/session.py,sha256=6iIzw_PPpJ0ry9jwLfNdm5e5x_ppzamYv7BS1rtNZDw,74455
|
|
@@ -20,18 +20,18 @@ ansys/pyensight/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
20
20
|
ansys/pyensight/core/utils/adr.py,sha256=XslZhlwcrSGzOlnhzprOv3ju_ppxxsWBjCnQL5KiNms,3570
|
|
21
21
|
ansys/pyensight/core/utils/dsg_server.py,sha256=2jCPhOplPrlDaQNSKSk9qo7bEgfcrhx0rHS_cqW2zAY,47003
|
|
22
22
|
ansys/pyensight/core/utils/export.py,sha256=GC0NbVl0_CXvUUfkbJl49yjTsP_58bJZyjE95q6ATUo,22604
|
|
23
|
-
ansys/pyensight/core/utils/omniverse.py,sha256=
|
|
23
|
+
ansys/pyensight/core/utils/omniverse.py,sha256=YhBrlnMcRmmf64jBNm6bV5x-Ej2VIjZOExwzmNHo1Ng,25007
|
|
24
24
|
ansys/pyensight/core/utils/omniverse_cli.py,sha256=ujoBbBGMYsYUn83nrk2dFkOd7kn7-6cs7ljBmmSXodw,20578
|
|
25
|
-
ansys/pyensight/core/utils/omniverse_dsg_server.py,sha256=
|
|
25
|
+
ansys/pyensight/core/utils/omniverse_dsg_server.py,sha256=75fGWqwVMLoACox08mwwWDA4pSTTqyjACVKhkeTRQ3Y,45994
|
|
26
26
|
ansys/pyensight/core/utils/omniverse_glb_server.py,sha256=dx2cfR036d3DY6meooNfLZOQpOMaiaLKqBjztpew2_Q,32167
|
|
27
27
|
ansys/pyensight/core/utils/parts.py,sha256=222XFRCjLgH7hho-cK9JrGCg3-KlTf54KIgc7y50sTE,52173
|
|
28
28
|
ansys/pyensight/core/utils/query.py,sha256=OXKDbf1sOTX0sUvtKcp64LhVl-BcrEsE43w8uMxLOYI,19828
|
|
29
29
|
ansys/pyensight/core/utils/readers.py,sha256=_IluAWz8mmoe5SM3hAewHIqlhtKMfEqrUJoQOlJ4U4I,12138
|
|
30
30
|
ansys/pyensight/core/utils/support.py,sha256=QI3z9ex7zJxjFbkCPba9DWqWgPFIThORqr0nvRfVjuc,4089
|
|
31
31
|
ansys/pyensight/core/utils/variables.py,sha256=ZUiJdDIeRcowrnLXaJQqGwA0RbrfXhc1s4o4v9A4PiY,95133
|
|
32
|
-
ansys/pyensight/core/utils/views.py,sha256=
|
|
32
|
+
ansys/pyensight/core/utils/views.py,sha256=vyO5w5Z1PFlz26FpXjXYDZyGFv2rUTGHJS5ovJvorvc,24675
|
|
33
33
|
ansys/pyensight/core/utils/resources/Materials/000_sky.exr,sha256=xAR1gFd2uxPZDnvgfegdhEhRaqKtZldQDiR_-1rHKO0,8819933
|
|
34
|
-
ansys_pyensight_core-0.10.
|
|
35
|
-
ansys_pyensight_core-0.10.
|
|
36
|
-
ansys_pyensight_core-0.10.
|
|
37
|
-
ansys_pyensight_core-0.10.
|
|
34
|
+
ansys_pyensight_core-0.10.13.dist-info/licenses/LICENSE,sha256=K6LiJHOa9IbWFelXmXNRzFr3zG45SOGZIN7vdLdURGU,1097
|
|
35
|
+
ansys_pyensight_core-0.10.13.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
36
|
+
ansys_pyensight_core-0.10.13.dist-info/METADATA,sha256=3Aa_Lyq0xtTJ15BAlpI1Y8S_wPxZKSvsRHtHszlJQlU,12201
|
|
37
|
+
ansys_pyensight_core-0.10.13.dist-info/RECORD,,
|
|
File without changes
|
{ansys_pyensight_core-0.10.10.dist-info → ansys_pyensight_core-0.10.13.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|