ansys-pyensight-core 0.10.9__py3-none-any.whl → 0.10.12__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/utils/omniverse.py +3 -3
- ansys/pyensight/core/utils/omniverse_dsg_server.py +377 -191
- ansys/pyensight/core/utils/views.py +166 -21
- {ansys_pyensight_core-0.10.9.dist-info → ansys_pyensight_core-0.10.12.dist-info}/METADATA +3 -3
- {ansys_pyensight_core-0.10.9.dist-info → ansys_pyensight_core-0.10.12.dist-info}/RECORD +7 -7
- {ansys_pyensight_core-0.10.9.dist-info → ansys_pyensight_core-0.10.12.dist-info}/WHEEL +0 -0
- {ansys_pyensight_core-0.10.9.dist-info → ansys_pyensight_core-0.10.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -268,7 +268,7 @@ 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
|
|
@@ -277,7 +277,7 @@ def find_app(ansys_installation: Optional[str] = None) -> Optional[str]:
|
|
|
277
277
|
dirs_to_check.append(local_dev_omni)
|
|
278
278
|
if "PYENSIGHT_ANSYS_INSTALLATION" in os.environ:
|
|
279
279
|
env_inst = os.environ["PYENSIGHT_ANSYS_INSTALLATION"]
|
|
280
|
-
dirs_to_check.append(os.path.join(env_inst, "tp", "
|
|
280
|
+
dirs_to_check.append(os.path.join(env_inst, "tp", "showcase"))
|
|
281
281
|
|
|
282
282
|
# Look for most recent Ansys install, 25.2 or later
|
|
283
283
|
awp_roots = []
|
|
@@ -286,7 +286,7 @@ def find_app(ansys_installation: Optional[str] = None) -> Optional[str]:
|
|
|
286
286
|
awp_roots.append(env_name)
|
|
287
287
|
awp_roots.sort(reverse=True)
|
|
288
288
|
for env_name in awp_roots:
|
|
289
|
-
dirs_to_check.append(os.path.join(os.environ[env_name], "tp", "
|
|
289
|
+
dirs_to_check.append(os.path.join(os.environ[env_name], "tp", "showcase"))
|
|
290
290
|
|
|
291
291
|
# check all the collected locations in order
|
|
292
292
|
for install_dir in dirs_to_check:
|
|
@@ -38,10 +38,10 @@ import numpy
|
|
|
38
38
|
import png
|
|
39
39
|
|
|
40
40
|
try:
|
|
41
|
-
from pxr import Gf,
|
|
41
|
+
from pxr import Gf, 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,8 @@ class OmniverseWrapper(object):
|
|
|
75
77
|
self.destination = destination
|
|
76
78
|
|
|
77
79
|
self._line_width = line_width
|
|
80
|
+
# Record the files per timestep, per mesh type. {part_name: {"surfaces": [], "lines": [], "points": []} }
|
|
81
|
+
self._time_files: dict = {}
|
|
78
82
|
|
|
79
83
|
@property
|
|
80
84
|
def destination(self) -> str:
|
|
@@ -148,7 +152,8 @@ class OmniverseWrapper(object):
|
|
|
148
152
|
else:
|
|
149
153
|
shutil.rmtree(stage, ignore_errors=True, onerror=None)
|
|
150
154
|
except OSError:
|
|
151
|
-
|
|
155
|
+
if not stage.endswith("_manifest" + self._ext):
|
|
156
|
+
stages_unremoved.append(stage)
|
|
152
157
|
self._old_stages = stages_unremoved
|
|
153
158
|
|
|
154
159
|
def create_new_stage(self) -> None:
|
|
@@ -276,8 +281,75 @@ class OmniverseWrapper(object):
|
|
|
276
281
|
t = [t[0] * trans_convert, t[1] * trans_convert, t[2] * trans_convert]
|
|
277
282
|
return s, r, t
|
|
278
283
|
|
|
284
|
+
# Common code to create the part manifest file and the file per timestep
|
|
285
|
+
def create_dsg_surfaces_file(
|
|
286
|
+
self,
|
|
287
|
+
file_url, # SdfPath, location on disk
|
|
288
|
+
part_path: str, # base path name, such as "/Root/Case_1/Isosurface_part"
|
|
289
|
+
verts,
|
|
290
|
+
normals,
|
|
291
|
+
conn,
|
|
292
|
+
tcoords,
|
|
293
|
+
diffuse,
|
|
294
|
+
variable,
|
|
295
|
+
mat_info,
|
|
296
|
+
is_manifest: bool,
|
|
297
|
+
):
|
|
298
|
+
if is_manifest and file_url in self._old_stages:
|
|
299
|
+
return False
|
|
300
|
+
if not is_manifest and os.path.exists(file_url):
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
stage = Usd.Stage.CreateNew(file_url)
|
|
304
|
+
UsdGeom.SetStageUpAxis(stage, self._up_axis)
|
|
305
|
+
UsdGeom.SetStageMetersPerUnit(stage, 1.0 / self._units_per_meter)
|
|
306
|
+
self._old_stages.append(file_url)
|
|
307
|
+
|
|
308
|
+
part_prim = stage.OverridePrim(part_path)
|
|
309
|
+
|
|
310
|
+
surfaces_prim = UsdGeom.Xform.Define(stage, part_path + "/surfaces")
|
|
311
|
+
surfaces_prim.AddXformOp(UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble)
|
|
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,174 @@ 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
|
-
|
|
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
|
+
)
|
|
325
409
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
diffuse=diffuse,
|
|
338
|
-
variable=variable,
|
|
339
|
-
mat_info=mat_info,
|
|
340
|
-
)
|
|
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
|
+
)
|
|
341
421
|
|
|
342
|
-
|
|
422
|
+
return part_stage_url
|
|
343
423
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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]
|
|
348
428
|
|
|
349
|
-
|
|
350
|
-
|
|
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))
|
|
351
460
|
|
|
352
|
-
|
|
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 = UsdGeom.Xform.Define(stage, part_path + "/lines")
|
|
487
|
+
|
|
488
|
+
lines_prim.AddXformOp(UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble)
|
|
489
|
+
lines = UsdGeom.BasisCurves.Define(stage, str(lines_prim.GetPath()) + "/Lines")
|
|
490
|
+
lines.CreateDoubleSidedAttr().Set(True)
|
|
491
|
+
pt_attr = lines.CreatePointsAttr()
|
|
492
|
+
vc_attr = lines.CreateCurveVertexCountsAttr()
|
|
493
|
+
if verts is not None:
|
|
494
|
+
pt_attr.Set(verts, 0)
|
|
495
|
+
vc_attr.Set([2] * (verts.size // 6), 0)
|
|
496
|
+
lines.CreatePurposeAttr().Set("render")
|
|
497
|
+
lines.CreateTypeAttr().Set("linear")
|
|
498
|
+
lines.CreateWidthsAttr([width])
|
|
499
|
+
lines.SetWidthsInterpolation("constant")
|
|
500
|
+
|
|
501
|
+
# Rounded endpoint are a primvar
|
|
502
|
+
primvarsAPI = UsdGeom.PrimvarsAPI(lines)
|
|
503
|
+
endCaps = primvarsAPI.CreatePrimvar(
|
|
504
|
+
"endcaps", Sdf.ValueTypeNames.Int, UsdGeom.Tokens.constant
|
|
505
|
+
)
|
|
506
|
+
endCaps.Set(2) # Rounded = 2
|
|
353
507
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
timestep_group_path = parent_prim.GetPath().AppendChild(
|
|
359
|
-
self.clean_name("t" + str(timeline[0]), None)
|
|
508
|
+
prim = lines.GetPrim()
|
|
509
|
+
wireframe = width == 0.0
|
|
510
|
+
prim.CreateAttribute("omni:scene:visualization:drawWireframe", Sdf.ValueTypeNames.Bool).Set(
|
|
511
|
+
wireframe
|
|
360
512
|
)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
513
|
+
|
|
514
|
+
if (tcoords is not None) and var_cmd:
|
|
515
|
+
primvarsAPI = UsdGeom.PrimvarsAPI(lines)
|
|
516
|
+
texCoords = primvarsAPI.CreatePrimvar(
|
|
517
|
+
"st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
|
|
518
|
+
)
|
|
519
|
+
if tcoords is not None and var_cmd is not None:
|
|
520
|
+
texCoords.Set(tcoords, 0)
|
|
521
|
+
texCoords.SetInterpolation("vertex")
|
|
522
|
+
stage.SetDefaultPrim(part_prim)
|
|
523
|
+
stage.SetStartTimeCode(0)
|
|
524
|
+
stage.SetEndTimeCode(0)
|
|
525
|
+
|
|
526
|
+
self.create_dsg_material(
|
|
527
|
+
stage,
|
|
528
|
+
lines,
|
|
529
|
+
str(lines_prim.GetPath()),
|
|
530
|
+
diffuse=diffuse,
|
|
531
|
+
variable=var_cmd,
|
|
532
|
+
mat_info=mat_info,
|
|
533
|
+
)
|
|
534
|
+
stage.Save()
|
|
535
|
+
return True
|
|
372
536
|
|
|
373
537
|
def create_dsg_lines(
|
|
374
538
|
self,
|
|
@@ -389,78 +553,97 @@ class OmniverseWrapper(object):
|
|
|
389
553
|
# include the line width in the hash
|
|
390
554
|
part_hash.update(str(self.line_width).encode("utf-8"))
|
|
391
555
|
|
|
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)
|
|
556
|
+
part_base_name = self.clean_name(name) + "_l"
|
|
557
|
+
partname = part_base_name + part_hash.hexdigest()
|
|
558
|
+
stage_name = "/Parts/" + partname + self._ext
|
|
559
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + self._ext))
|
|
560
|
+
|
|
561
|
+
# Make the manifest file - once for all timesteps
|
|
562
|
+
part_manifest_url_relative = "./Parts/" + part_base_name + "_manifest" + self._ext
|
|
563
|
+
part_manifest_url = self.stage_url(part_manifest_url_relative)
|
|
564
|
+
created_file = self.create_dsg_lines_file(
|
|
565
|
+
part_manifest_url,
|
|
566
|
+
str(parent_prim.GetPath()),
|
|
567
|
+
None,
|
|
568
|
+
width,
|
|
569
|
+
None,
|
|
570
|
+
diffuse,
|
|
571
|
+
variable,
|
|
572
|
+
mat_info,
|
|
573
|
+
True,
|
|
574
|
+
)
|
|
575
|
+
if created_file:
|
|
576
|
+
self._stage.GetRootLayer().subLayerPaths.append(part_manifest_url_relative)
|
|
577
|
+
|
|
578
|
+
# Make the per-timestep file
|
|
579
|
+
created_file = self.create_dsg_lines_file(
|
|
580
|
+
part_stage_url,
|
|
581
|
+
str(parent_prim.GetPath()),
|
|
582
|
+
verts,
|
|
583
|
+
width,
|
|
584
|
+
tcoords,
|
|
585
|
+
diffuse,
|
|
586
|
+
variable,
|
|
587
|
+
mat_info,
|
|
588
|
+
False,
|
|
589
|
+
)
|
|
436
590
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
)
|
|
591
|
+
# Glue the file into the main stage
|
|
592
|
+
path = parent_prim.GetPath().AppendChild("lines")
|
|
593
|
+
lines_prim = self._stage.OverridePrim(path)
|
|
594
|
+
self.add_timestep_valueclip(
|
|
595
|
+
part_base_name, "lines", lines_prim, part_manifest_url_relative, timeline, stage_name
|
|
596
|
+
)
|
|
452
597
|
|
|
453
|
-
|
|
598
|
+
return part_stage_url
|
|
454
599
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
600
|
+
# Common code to create the part manifest file and the file per timestep
|
|
601
|
+
def create_dsg_points_file(
|
|
602
|
+
self,
|
|
603
|
+
file_url, # SdfPath, location on disk
|
|
604
|
+
part_path: str, # base path name, such as "/Root/Case_1/Isosurface_part"
|
|
605
|
+
verts,
|
|
606
|
+
sizes,
|
|
607
|
+
colors,
|
|
608
|
+
default_size: float,
|
|
609
|
+
default_color,
|
|
610
|
+
is_manifest: bool,
|
|
611
|
+
):
|
|
612
|
+
if is_manifest and file_url in self._old_stages:
|
|
613
|
+
return False
|
|
614
|
+
if not is_manifest and os.path.exists(file_url):
|
|
615
|
+
return False
|
|
616
|
+
|
|
617
|
+
stage = Usd.Stage.CreateNew(file_url)
|
|
618
|
+
UsdGeom.SetStageUpAxis(stage, self._up_axis)
|
|
619
|
+
UsdGeom.SetStageMetersPerUnit(stage, 1.0 / self._units_per_meter)
|
|
620
|
+
self._old_stages.append(file_url)
|
|
621
|
+
|
|
622
|
+
part_prim = stage.OverridePrim(part_path)
|
|
623
|
+
points = UsdGeom.Points.Define(stage, part_path + "/points")
|
|
624
|
+
pt_attr = points.CreatePointsAttr()
|
|
625
|
+
w_attr = points.CreateWidthsAttr()
|
|
626
|
+
if verts is not None:
|
|
627
|
+
pt_attr.Set(verts, 0)
|
|
628
|
+
if sizes is not None and sizes.size == (verts.size // 3):
|
|
629
|
+
w_attr.Set(sizes, 0)
|
|
630
|
+
else:
|
|
631
|
+
w_attr.Set([default_size] * (verts.size // 3), 0)
|
|
459
632
|
|
|
460
|
-
|
|
461
|
-
|
|
633
|
+
colorAttr = points.GetPrim().GetAttribute("primvars:displayColor")
|
|
634
|
+
colorAttr.SetMetadata("interpolation", "vertex")
|
|
635
|
+
if verts is not None:
|
|
636
|
+
if colors is not None and colors.size == verts.size:
|
|
637
|
+
colorAttr.Set(colors, 0)
|
|
638
|
+
else:
|
|
639
|
+
colorAttr.Set([default_color[0:3]] * (verts.size // 3), 0)
|
|
462
640
|
|
|
463
|
-
|
|
641
|
+
stage.SetDefaultPrim(part_prim)
|
|
642
|
+
stage.SetStartTimeCode(0)
|
|
643
|
+
stage.SetEndTimeCode(0)
|
|
644
|
+
|
|
645
|
+
stage.Save()
|
|
646
|
+
return True
|
|
464
647
|
|
|
465
648
|
def create_dsg_points(
|
|
466
649
|
self,
|
|
@@ -477,53 +660,45 @@ class OmniverseWrapper(object):
|
|
|
477
660
|
timeline=[0.0, 0.0],
|
|
478
661
|
first_timestep=False,
|
|
479
662
|
):
|
|
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)
|
|
663
|
+
part_base_name = self.clean_name(name) + "_p"
|
|
664
|
+
partname = part_base_name + part_hash.hexdigest()
|
|
665
|
+
stage_name = "/Parts/" + partname + self._ext
|
|
666
|
+
part_stage_url = self.stage_url(os.path.join("Parts", partname + self._ext))
|
|
667
|
+
|
|
668
|
+
# Make the manifest file - once for all timesteps
|
|
669
|
+
part_manifest_url_relative = "./Parts/" + part_base_name + "_manifest" + self._ext
|
|
670
|
+
part_manifest_url = self.stage_url(part_manifest_url_relative)
|
|
671
|
+
created_file = self.create_dsg_points_file(
|
|
672
|
+
part_manifest_url,
|
|
673
|
+
str(parent_prim.GetPath()),
|
|
674
|
+
None,
|
|
675
|
+
None,
|
|
676
|
+
None,
|
|
677
|
+
default_size,
|
|
678
|
+
default_color,
|
|
679
|
+
True,
|
|
680
|
+
)
|
|
681
|
+
if created_file:
|
|
682
|
+
self._stage.GetRootLayer().subLayerPaths.append(part_manifest_url_relative)
|
|
683
|
+
|
|
684
|
+
# Make the per-timestep file
|
|
685
|
+
created_file = self.create_dsg_points_file(
|
|
686
|
+
part_stage_url,
|
|
687
|
+
str(parent_prim.GetPath()),
|
|
688
|
+
verts,
|
|
689
|
+
sizes,
|
|
690
|
+
colors,
|
|
691
|
+
default_size,
|
|
692
|
+
default_color,
|
|
693
|
+
False,
|
|
694
|
+
)
|
|
524
695
|
|
|
525
|
-
|
|
526
|
-
|
|
696
|
+
# Glue the file into the main stage
|
|
697
|
+
path = parent_prim.GetPath().AppendChild("points")
|
|
698
|
+
points_prim = self._stage.OverridePrim(path)
|
|
699
|
+
self.add_timestep_valueclip(
|
|
700
|
+
part_base_name, "points", points_prim, part_manifest_url_relative, timeline, stage_name
|
|
701
|
+
)
|
|
527
702
|
|
|
528
703
|
return part_stage_url
|
|
529
704
|
|
|
@@ -627,8 +802,10 @@ class OmniverseWrapper(object):
|
|
|
627
802
|
if camera is not None:
|
|
628
803
|
cam_name = "/Root/Cam"
|
|
629
804
|
cam_prim = UsdGeom.Xform.Define(self._stage, cam_name)
|
|
630
|
-
|
|
631
|
-
|
|
805
|
+
s = self._units_per_meter
|
|
806
|
+
cam_pos = Gf.Vec3d(camera.lookfrom[0], camera.lookfrom[1], camera.lookfrom[2]) * s
|
|
807
|
+
target_pos = Gf.Vec3d(camera.lookat[0], camera.lookat[1], camera.lookat[2]) * s
|
|
808
|
+
|
|
632
809
|
up_vec = Gf.Vec3d(camera.upvector[0], camera.upvector[1], camera.upvector[2])
|
|
633
810
|
cam_prim = self._stage.GetPrimAtPath(cam_name)
|
|
634
811
|
geom_cam = UsdGeom.Camera(cam_prim)
|
|
@@ -647,7 +824,7 @@ class OmniverseWrapper(object):
|
|
|
647
824
|
cam = geom_cam.GetCamera()
|
|
648
825
|
# LOL, not sure why is might be correct, but so far it seems to work???
|
|
649
826
|
cam.focalLength = camera.fieldofview
|
|
650
|
-
dist = (target_pos - cam_pos).GetLength()
|
|
827
|
+
dist = (target_pos - cam_pos).GetLength()
|
|
651
828
|
cam.clippingRange = Gf.Range1f(0.1 * dist, 1000.0 * dist)
|
|
652
829
|
look_at = Gf.Matrix4d()
|
|
653
830
|
look_at.SetLookAt(cam_pos, target_pos, up_vec)
|
|
@@ -693,6 +870,7 @@ class OmniverseWrapper(object):
|
|
|
693
870
|
)
|
|
694
871
|
matrix_op.Set(Gf.Matrix4d(*matrix).GetTranspose())
|
|
695
872
|
# Map kinds
|
|
873
|
+
"""
|
|
696
874
|
kind = Kind.Tokens.group
|
|
697
875
|
if obj_type == "ENS_CASE":
|
|
698
876
|
kind = Kind.Tokens.assembly
|
|
@@ -700,6 +878,7 @@ class OmniverseWrapper(object):
|
|
|
700
878
|
kind = Kind.Tokens.component
|
|
701
879
|
Usd.ModelAPI(group_prim).SetKind(kind)
|
|
702
880
|
logging.info(f"Created group:'{name}' {str(obj_type)}")
|
|
881
|
+
"""
|
|
703
882
|
return group_prim
|
|
704
883
|
|
|
705
884
|
def uploadMaterial(self):
|
|
@@ -732,6 +911,7 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
732
911
|
self._group_prims: Dict[int, Any] = dict()
|
|
733
912
|
self._root_prim = None
|
|
734
913
|
self._sent_textures = False
|
|
914
|
+
self._case_xform_applied_to_camera = False
|
|
735
915
|
|
|
736
916
|
def add_group(self, id: int, view: bool = False) -> None:
|
|
737
917
|
super().add_group(id, view)
|
|
@@ -752,10 +932,11 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
752
932
|
matrix = group.matrix4x4
|
|
753
933
|
# Is this a "case" group (it will contain part of the camera view in the matrix)
|
|
754
934
|
if obj_type == "ENS_CASE":
|
|
755
|
-
if not self.session.vrmode:
|
|
935
|
+
if not self.session.vrmode and not self._case_xform_applied_to_camera:
|
|
756
936
|
# if in camera mode, we need to update the camera matrix so we can
|
|
757
937
|
# use the identity matrix on this group. The camera should have been
|
|
758
938
|
# created in the "view" handler
|
|
939
|
+
self._case_xform_applied_to_camera = True
|
|
759
940
|
cam_name = "/Root/Cam"
|
|
760
941
|
cam_prim = self._omni._stage.GetPrimAtPath(cam_name) # type: ignore
|
|
761
942
|
geom_cam = UsdGeom.Camera(cam_prim)
|
|
@@ -763,9 +944,12 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
763
944
|
cam = geom_cam.GetCamera()
|
|
764
945
|
c = cam.transform
|
|
765
946
|
m = Gf.Matrix4d(*matrix).GetTranspose()
|
|
947
|
+
s = self._omni._units_per_meter
|
|
948
|
+
trans = m.GetRow(3)
|
|
949
|
+
trans = Gf.Vec4d(trans[0] * s, trans[1] * s, trans[2] * s, trans[3])
|
|
950
|
+
m.SetRow(3, trans)
|
|
766
951
|
# move the model transform to the camera transform
|
|
767
|
-
|
|
768
|
-
cam.transform = c * m.GetInverse() * sc
|
|
952
|
+
cam.transform = c * m.GetInverse()
|
|
769
953
|
|
|
770
954
|
# Determine if the camera is principally more Y, or Z up. X up not supported.
|
|
771
955
|
# Omniverse' built in navigator tries to keep this direction up
|
|
@@ -874,6 +1058,7 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
874
1058
|
has_triangles = True
|
|
875
1059
|
# Generate the mesh block
|
|
876
1060
|
_ = self._omni.create_dsg_mesh_block(
|
|
1061
|
+
part,
|
|
877
1062
|
name,
|
|
878
1063
|
obj_id,
|
|
879
1064
|
part.hash,
|
|
@@ -980,6 +1165,7 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
980
1165
|
self._omni.clear_cleaned_names()
|
|
981
1166
|
# clear the group Omni prims list
|
|
982
1167
|
self._group_prims = dict()
|
|
1168
|
+
self._case_xform_applied_to_camera = False
|
|
983
1169
|
|
|
984
1170
|
self._omni.create_new_stage()
|
|
985
1171
|
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."""
|
|
@@ -94,8 +95,7 @@ class _Simba:
|
|
|
94
95
|
self.views.set_view_direction(
|
|
95
96
|
1, 1, 1, perspective=self.ensight.objs.core.vports[0].PERSPECTIVE
|
|
96
97
|
)
|
|
97
|
-
self.auto_scale()
|
|
98
|
-
return self.get_camera()
|
|
98
|
+
return self.auto_scale()
|
|
99
99
|
|
|
100
100
|
def get_camera(self):
|
|
101
101
|
"""Get EnSight camera settings in VTK format."""
|
|
@@ -107,6 +107,7 @@ class _Simba:
|
|
|
107
107
|
# if the vport is in orthographic mode. If not, it is defined as the
|
|
108
108
|
# inverge of the tangent of half of the field of view
|
|
109
109
|
parallel_scale = parallel_scale
|
|
110
|
+
clipping_range = vport.ZCLIPLIMITS
|
|
110
111
|
return {
|
|
111
112
|
"orthographic": not vport.PERSPECTIVE,
|
|
112
113
|
"view_up": view_up,
|
|
@@ -119,6 +120,8 @@ class _Simba:
|
|
|
119
120
|
"reset_parallel_scale": self._original_parallel_scale,
|
|
120
121
|
"reset_view_up": self._original_view_up,
|
|
121
122
|
"reset_view_angle": self._original_view_angle,
|
|
123
|
+
"near_plane": clipping_range[0],
|
|
124
|
+
"far_plane": clipping_range[1],
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
@staticmethod
|
|
@@ -208,8 +211,56 @@ class _Simba:
|
|
|
208
211
|
parallel_scale = 1 / data[9]
|
|
209
212
|
return camera_position, focal_point, self.views._normalize_vector(view_up), parallel_scale
|
|
210
213
|
|
|
214
|
+
def get_camera_axes(self):
|
|
215
|
+
"""
|
|
216
|
+
Returns the camera's local axes: right, up, and forward vectors.
|
|
217
|
+
These are useful for applying transformations in view space.
|
|
218
|
+
|
|
219
|
+
Parameters:
|
|
220
|
+
camera (dict): A dictionary with keys 'position', 'focal_point', and 'view_up'.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
right (np.ndarray): Right vector (X axis in view space).
|
|
224
|
+
up (np.ndarray): Up vector (Y axis in view space).
|
|
225
|
+
forw ard (np.ndarray): Forward vector (Z axis in view space, pointing from position to focal point).
|
|
226
|
+
"""
|
|
227
|
+
camera = self.get_camera()
|
|
228
|
+
position = np.array(camera["position"])
|
|
229
|
+
focal_point = np.array(camera["focal_point"])
|
|
230
|
+
view_up = np.array(camera["view_up"])
|
|
231
|
+
|
|
232
|
+
# Forward vector: from camera position to focal point
|
|
233
|
+
forward = focal_point - position
|
|
234
|
+
forward /= np.linalg.norm(forward)
|
|
235
|
+
|
|
236
|
+
# Right vector: cross product of forward and view_up
|
|
237
|
+
right = np.cross(forward, view_up)
|
|
238
|
+
right /= np.linalg.norm(right)
|
|
239
|
+
|
|
240
|
+
# Recompute up vector to ensure orthogonality
|
|
241
|
+
up = np.cross(right, forward)
|
|
242
|
+
up /= np.linalg.norm(up)
|
|
243
|
+
|
|
244
|
+
return right, up, forward
|
|
245
|
+
|
|
246
|
+
def _arbitrary_orthogonal(self, v):
|
|
247
|
+
if abs(v[0]) < abs(v[1]) and abs(v[0]) < abs(v[2]):
|
|
248
|
+
return self.normalize(np.array(self.views._cross_product(v.tolist(), [1, 0, 0])))
|
|
249
|
+
elif abs(v[1]) < abs(v[2]):
|
|
250
|
+
return self.normalize(np.array(self.views._cross_product(v.tolist(), [0, 1, 0])))
|
|
251
|
+
return self.normalize(np.array(self.views._cross_product(v.tolist(), [0, 0, 1])))
|
|
252
|
+
|
|
211
253
|
def set_camera(
|
|
212
|
-
self,
|
|
254
|
+
self,
|
|
255
|
+
orthographic,
|
|
256
|
+
view_up=None,
|
|
257
|
+
position=None,
|
|
258
|
+
focal_point=None,
|
|
259
|
+
view_angle=None,
|
|
260
|
+
pan=None,
|
|
261
|
+
mousex=None,
|
|
262
|
+
mousey=None,
|
|
263
|
+
invert_y=False,
|
|
213
264
|
):
|
|
214
265
|
"""Set the EnSight camera settings from the VTK input."""
|
|
215
266
|
self.ensight.view_transf.function("global")
|
|
@@ -219,21 +270,65 @@ class _Simba:
|
|
|
219
270
|
vport = self.ensight.objs.core.VPORTS[0]
|
|
220
271
|
if view_angle:
|
|
221
272
|
vport.PERSPECTIVEANGLE = view_angle / 2
|
|
222
|
-
|
|
223
273
|
if view_up and position and focal_point:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
274
|
+
# Compute relative rotation
|
|
275
|
+
q_current = self.normalize(np.array(vport.ROTATION.copy()))
|
|
276
|
+
q_target = self.normalize(
|
|
277
|
+
self.compute_model_rotation_quaternion(position, focal_point, view_up)
|
|
278
|
+
)
|
|
279
|
+
q_relative = self.quaternion_multiply(
|
|
280
|
+
q_target, np.array([-q_current[0], -q_current[1], -q_current[2], q_current[3]])
|
|
281
|
+
)
|
|
282
|
+
angles = self.quaternion_to_euler(q_relative)
|
|
283
|
+
self.ensight.view_transf.rotate(*angles)
|
|
284
|
+
# Decompose eventual translation from rotation
|
|
285
|
+
current_camera = self.get_camera()
|
|
286
|
+
center = vport.TRANSFORMCENTER.copy()
|
|
287
|
+
pos0, focal0, up0 = map(
|
|
288
|
+
np.array,
|
|
289
|
+
[
|
|
290
|
+
current_camera["position"],
|
|
291
|
+
current_camera["focal_point"],
|
|
292
|
+
current_camera["view_up"],
|
|
293
|
+
],
|
|
294
|
+
)
|
|
295
|
+
pos1, focal1, up1 = map(np.array, [position, focal_point, view_up])
|
|
296
|
+
dir0 = self.normalize(focal0 - pos0)
|
|
297
|
+
right0 = np.cross(dir0, up0)
|
|
298
|
+
norm = np.linalg.norm(right0)
|
|
299
|
+
if norm <= 1e-6:
|
|
300
|
+
right0 = self._arbitrary_orthogonal(dir0)
|
|
301
|
+
up0n = np.cross(right0, dir0)
|
|
302
|
+
dir1 = self.normalize(focal1 - pos1)
|
|
303
|
+
right1 = np.cross(dir1, up1)
|
|
304
|
+
norm = np.linalg.norm(right1)
|
|
305
|
+
if norm <= 1e-6:
|
|
306
|
+
right1 = self._arbitrary_orthogonal(dir1)
|
|
307
|
+
up1n = np.cross(right1, dir1)
|
|
308
|
+
# Now that orthonormal basis have been computed for the
|
|
309
|
+
# old and new camera, one can compute the rotation matrix
|
|
310
|
+
# that takes the old camera to the new one
|
|
311
|
+
A = np.stack([right0, up0n, dir0], axis=1)
|
|
312
|
+
B = np.stack([right1, up1n, dir1], axis=1)
|
|
313
|
+
R = B @ A.T
|
|
314
|
+
# Compute the rotated only vector from the old camera
|
|
315
|
+
# to the new camera direction
|
|
316
|
+
rotatedDistance = R @ (pos0 - center) + center
|
|
317
|
+
# Compute the view matrix for the rotated camera axes
|
|
318
|
+
rotated_focal = focal0 + (rotatedDistance - pos0)
|
|
319
|
+
rotated_dir = self.normalize(rotated_focal - rotatedDistance)
|
|
320
|
+
rotated_right = np.cross(rotated_dir, up0)
|
|
321
|
+
if np.linalg.norm(rotated_right) <= 1e-6:
|
|
322
|
+
rotated_right = self._arbitrary_orthogonal(rotated_dir)
|
|
323
|
+
rotated_up = np.cross(rotated_right, rotated_dir)
|
|
324
|
+
# Compute the world coordinates translation and move it to
|
|
325
|
+
# view space
|
|
326
|
+
offset = pos1 - rotatedDistance
|
|
327
|
+
translation = -np.stack([rotated_right, rotated_up, rotated_dir]) @ offset
|
|
328
|
+
self.ensight.view_transf.translate(translation[0], translation[1], 0)
|
|
329
|
+
|
|
236
330
|
self.render()
|
|
331
|
+
return self.get_camera()
|
|
237
332
|
|
|
238
333
|
def set_perspective(self, value):
|
|
239
334
|
self.ensight.view_transf.function("global")
|
|
@@ -265,7 +360,37 @@ class _Simba:
|
|
|
265
360
|
self.ensight.render()
|
|
266
361
|
self.ensight.refresh(1)
|
|
267
362
|
|
|
268
|
-
def
|
|
363
|
+
def _probe_setup(self, part_obj, get_probe_data=False):
|
|
364
|
+
self.ensight.query_interact.number_displayed(100)
|
|
365
|
+
self.ensight.query_interact.query("surface")
|
|
366
|
+
self.ensight.query_interact.display_id("OFF")
|
|
367
|
+
self.ensight.query_interact.label_always_on_top("ON")
|
|
368
|
+
self.ensight.query_interact.marker_size_normalized(2)
|
|
369
|
+
if get_probe_data:
|
|
370
|
+
variable_string = """Coordinates 'X' 'Y' 'Z'"""
|
|
371
|
+
variable_list = [variable_string]
|
|
372
|
+
variable_name = part_obj.COLORBYPALETTE
|
|
373
|
+
if variable_name:
|
|
374
|
+
if isinstance(variable_name, str):
|
|
375
|
+
variable_list.append(variable_name)
|
|
376
|
+
else:
|
|
377
|
+
if isinstance(variable_name, list):
|
|
378
|
+
if variable_name[0]:
|
|
379
|
+
variable_name = variable_name[0].DESCRIPTION
|
|
380
|
+
variable_list.append(variable_name)
|
|
381
|
+
else:
|
|
382
|
+
variable_name = None
|
|
383
|
+
if isinstance(self.ensight, ModuleType):
|
|
384
|
+
self.ensight.query_interact.select_varname_begin(*variable_list)
|
|
385
|
+
else:
|
|
386
|
+
command = "ensight.query_interact.select_varname_begin("
|
|
387
|
+
for var in variable_list:
|
|
388
|
+
command += var + ","
|
|
389
|
+
command = command[:-1] + ")"
|
|
390
|
+
self.ensight._session.cmd(command)
|
|
391
|
+
self.render()
|
|
392
|
+
|
|
393
|
+
def drag_allowed(self, mousex, mousey, invert_y=False, probe=False, get_probe_data=False):
|
|
269
394
|
"""Return True if the picked object is allowed dragging in the interactor."""
|
|
270
395
|
mousex = int(mousex)
|
|
271
396
|
mousey = int(mousey)
|
|
@@ -277,8 +402,14 @@ class _Simba:
|
|
|
277
402
|
part_id, tool_id = self.ensight._session.cmd(
|
|
278
403
|
f"ensight.objs.core.VPORTS[0].simba_what_is_picked({mousex}, {mousey}, {invert_y})"
|
|
279
404
|
)
|
|
405
|
+
coords = [None, None, None]
|
|
406
|
+
if probe:
|
|
407
|
+
screen_to_world = self.screen_to_world(
|
|
408
|
+
mousex=mousex, mousey=mousey, invert_y=invert_y, set_center=False
|
|
409
|
+
)
|
|
410
|
+
coords = screen_to_world["model_point"]
|
|
280
411
|
if tool_id > -1:
|
|
281
|
-
return True
|
|
412
|
+
return True, coords[0], coords[1], coords[2], False
|
|
282
413
|
part_types_allowed = [
|
|
283
414
|
self.ensight.objs.enums.PART_CLIP_PLANE,
|
|
284
415
|
self.ensight.objs.enums.PART_ISO_SURFACE,
|
|
@@ -286,8 +417,22 @@ class _Simba:
|
|
|
286
417
|
]
|
|
287
418
|
if part_id > -1:
|
|
288
419
|
part_obj = self.ensight.objs.core.PARTS.find(part_id, "PARTNUMBER")[0]
|
|
289
|
-
|
|
290
|
-
|
|
420
|
+
if probe:
|
|
421
|
+
width, height = tuple(self.ensight.objs.core.WINDOWSIZE)
|
|
422
|
+
if invert_y:
|
|
423
|
+
mousey = height - mousey
|
|
424
|
+
self.ensight.query_interact.number_displayed(100)
|
|
425
|
+
self.ensight.query_interact.query("surface")
|
|
426
|
+
self.ensight.query_interact.display_id("OFF")
|
|
427
|
+
self.ensight.query_interact.create(mousex / width, mousey / height)
|
|
428
|
+
self._probe_setup(part_obj, get_probe_data=get_probe_data)
|
|
429
|
+
return part_obj.PARTTYPE in part_types_allowed, coords[0], coords[1], coords[2], True
|
|
430
|
+
if (
|
|
431
|
+
get_probe_data and self.ensight.objs.core.PROBES[0].PROBE_DATA
|
|
432
|
+
): # In case we have picked a probe point
|
|
433
|
+
for part in self.ensight.objs.core.PARTS:
|
|
434
|
+
self._probe_setup(part, get_probe_data=get_probe_data)
|
|
435
|
+
return False, coords[0], coords[1], coords[2], False
|
|
291
436
|
|
|
292
437
|
|
|
293
438
|
class Views:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ansys-pyensight-core
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.12
|
|
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"
|
|
@@ -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=yxLeEX6J1D_NwWSn-sm_C6-VYiLvYehHCbj1b19BxNI,24729
|
|
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=KPsnHstndXiv9jePGW3qnqqjMycKwZUL2vH7Thvq-wA,45547
|
|
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=qkc_xPZ4Ea8dof6eqRwOd6dPrbruDTODoFzlnczpJGU,29578
|
|
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.12.dist-info/licenses/LICENSE,sha256=K6LiJHOa9IbWFelXmXNRzFr3zG45SOGZIN7vdLdURGU,1097
|
|
35
|
+
ansys_pyensight_core-0.10.12.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
36
|
+
ansys_pyensight_core-0.10.12.dist-info/METADATA,sha256=-wrfz96hNIMWHitCBnMbeldilHN4Nfi_NWL4gTpJ8qA,12201
|
|
37
|
+
ansys_pyensight_core-0.10.12.dist-info/RECORD,,
|
|
File without changes
|
{ansys_pyensight_core-0.10.9.dist-info → ansys_pyensight_core-0.10.12.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|