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.

@@ -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
@@ -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
- _ = requests.get(url)
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", "omni_viewer"))
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
- local_dev_omni = os.path.join(ansys_installation, "omni_build")
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", "omni_viewer"))
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", "omni_viewer"))
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 >= 13:
44
- warnings.warn("USD Export not supported for Python >= 3.13")
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.usd"
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
- stages_unremoved.append(stage)
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
- partname = self.clean_name(name + part_hash.hexdigest())
299
- stage_name = "/Parts/" + partname + ".usd"
300
- part_stage_url = self.stage_url(os.path.join("Parts", partname + ".usd"))
301
- part_stage = None
302
-
303
- if not os.path.exists(part_stage_url):
304
- part_stage = Usd.Stage.CreateNew(part_stage_url)
305
- UsdGeom.SetStageUpAxis(part_stage, self._up_axis)
306
- UsdGeom.SetStageMetersPerUnit(part_stage, 1.0 / self._units_per_meter)
307
- self._old_stages.append(part_stage_url)
308
- xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
309
- mesh = UsdGeom.Mesh.Define(part_stage, "/" + partname + "/Mesh")
310
- # mesh.CreateDisplayColorAttr()
311
- mesh.CreateDoubleSidedAttr().Set(True)
312
- mesh.CreatePointsAttr(verts)
313
- mesh.CreateNormalsAttr(normals)
314
- mesh.CreateFaceVertexCountsAttr([3] * (conn.size // 3))
315
- mesh.CreateFaceVertexIndicesAttr(conn)
316
- if (tcoords is not None) and variable:
317
- primvarsAPI = UsdGeom.PrimvarsAPI(mesh)
318
- texCoords = primvarsAPI.CreatePrimvar(
319
- "st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.varying
320
- )
321
- texCoords.Set(tcoords)
322
- texCoords.SetInterpolation("vertex")
323
- part_prim = part_stage.GetPrimAtPath("/" + partname)
324
- part_stage.SetDefaultPrim(part_prim)
325
-
326
- # Currently, this will never happen, but it is a setup for rigid body transforms
327
- # At present, the group transforms have been cooked into the vertices so this is not needed
328
- matrixOp = xform.AddXformOp(
329
- UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
330
- )
331
- matrixOp.Set(Gf.Matrix4d(*matrix).GetTranspose())
332
-
333
- self.create_dsg_material(
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
- timestep_prim = self.add_timestep_group(parent_prim, timeline, first_timestep)
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
- # glue it into our stage
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
- if part_stage is not None:
350
- part_stage.GetRootLayer().Save()
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
- return part_stage_url
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
- def add_timestep_group(
355
- self, parent_prim: UsdGeom.Xform, timeline: List[float], first_timestep: bool
356
- ) -> UsdGeom.Xform:
357
- # add a layer in the group hierarchy for the timestep
358
- timestep_group_path = parent_prim.GetPath().AppendChild(
359
- self.clean_name("t" + str(timeline[0]), None)
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
- timestep_prim = UsdGeom.Xform.Define(self._stage, timestep_group_path)
362
- visibility_attr = UsdGeom.Imageable(timestep_prim).GetVisibilityAttr()
363
- if first_timestep:
364
- visibility_attr.Set("inherited", Usd.TimeCode.EarliestTime())
365
- else:
366
- visibility_attr.Set("invisible", Usd.TimeCode.EarliestTime())
367
- visibility_attr.Set("inherited", timeline[0] * self._time_codes_per_second)
368
- # Final timestep has timeline[0]==timeline[1]. Leave final timestep visible.
369
- if timeline[0] < timeline[1]:
370
- visibility_attr.Set("invisible", timeline[1] * self._time_codes_per_second)
371
- return timestep_prim
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
- # 1D texture map for variables https://graphics.pixar.com/usd/release/tut_simple_shading.html
393
- # create the part usd object
394
- partname = self.clean_name(name + part_hash.hexdigest()) + "_l"
395
- stage_name = "/Parts/" + partname + ".usd"
396
- part_stage_url = self.stage_url(os.path.join("Parts", partname + ".usd"))
397
- part_stage = None
398
-
399
- var_cmd = variable
400
-
401
- if not os.path.exists(part_stage_url):
402
- part_stage = Usd.Stage.CreateNew(part_stage_url)
403
- UsdGeom.SetStageUpAxis(part_stage, self._up_axis)
404
- UsdGeom.SetStageMetersPerUnit(part_stage, 1.0 / self._units_per_meter)
405
- self._old_stages.append(part_stage_url)
406
- xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
407
- lines = UsdGeom.BasisCurves.Define(part_stage, "/" + partname + "/Lines")
408
- lines.CreateDoubleSidedAttr().Set(True)
409
- lines.CreatePointsAttr(verts)
410
- lines.CreateCurveVertexCountsAttr([2] * (verts.size // 6))
411
- lines.CreatePurposeAttr().Set("render")
412
- lines.CreateTypeAttr().Set("linear")
413
- lines.CreateWidthsAttr([width])
414
- lines.SetWidthsInterpolation("constant")
415
- # Rounded endpoint are a primvar
416
- primvarsAPI = UsdGeom.PrimvarsAPI(lines)
417
- endCaps = primvarsAPI.CreatePrimvar(
418
- "endcaps", Sdf.ValueTypeNames.Int, UsdGeom.Tokens.constant
419
- )
420
- endCaps.Set(2) # Rounded = 2
421
-
422
- prim = lines.GetPrim()
423
- wireframe = width == 0.0
424
- prim.CreateAttribute(
425
- "omni:scene:visualization:drawWireframe", Sdf.ValueTypeNames.Bool
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
- timestep_prim = self.add_timestep_group(parent_prim, timeline, first_timestep)
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
- # glue it into our stage
456
- path = timestep_prim.GetPath().AppendChild("part_ref_" + partname)
457
- part_ref = self._stage.OverridePrim(path)
458
- part_ref.GetReferences().AddReference("." + stage_name)
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
- if part_stage is not None:
461
- part_stage.GetRootLayer().Save()
640
+ stage.SetDefaultPrim(part_prim)
641
+ stage.SetStartTimeCode(0)
642
+ stage.SetEndTimeCode(0)
462
643
 
463
- return part_stage_url
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
- # create the part usd object
481
- partname = self.clean_name(name + part_hash.hexdigest())
482
- stage_name = "/Parts/" + partname + ".usd"
483
- part_stage_url = self.stage_url(os.path.join("Parts", partname + ".usd"))
484
- part_stage = None
485
-
486
- if not os.path.exists(part_stage_url):
487
- part_stage = Usd.Stage.CreateNew(part_stage_url)
488
- UsdGeom.SetStageUpAxis(part_stage, self._up_axis)
489
- UsdGeom.SetStageMetersPerUnit(part_stage, 1.0 / self._units_per_meter)
490
- self._old_stages.append(part_stage_url)
491
- xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
492
-
493
- points = UsdGeom.Points.Define(part_stage, "/" + partname + "/Points")
494
- # points.GetPointsAttr().Set(Vt.Vec3fArray(verts.tolist()))
495
- points.GetPointsAttr().Set(verts)
496
- if sizes is not None and sizes.size == (verts.size // 3):
497
- points.GetWidthsAttr().Set(sizes)
498
- else:
499
- points.GetWidthsAttr().Set([default_size] * (verts.size // 3))
500
-
501
- colorAttr = points.GetPrim().GetAttribute("primvars:displayColor")
502
- colorAttr.SetMetadata("interpolation", "vertex")
503
- if colors is not None and colors.size == verts.size:
504
- colorAttr.Set(colors)
505
- else:
506
- colorAttr.Set([default_color[0:3]] * (verts.size // 3))
507
-
508
- part_prim = part_stage.GetPrimAtPath("/" + partname)
509
- part_stage.SetDefaultPrim(part_prim)
510
-
511
- # Currently, this will never happen, but it is a setup for rigid body transforms
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
- if part_stage is not None:
526
- part_stage.GetRootLayer().Save()
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
- root_prim = UsdGeom.Xform.Define(self._stage, root_name)
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
- cam_pos = Gf.Vec3d(camera.lookfrom[0], camera.lookfrom[1], camera.lookfrom[2])
631
- target_pos = Gf.Vec3d(camera.lookat[0], camera.lookat[1], camera.lookat[2])
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() * self._units_per_meter
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 = UsdGeom.Xform.Define(self._stage, path)
690
- # At present, the group transforms have been cooked into the vertices so this is not needed
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 not self.session.vrmode:
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
- sc = Gf.Matrix4d(self._omni._units_per_meter)
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
- if orthographic:
258
- self.ensight.view.perspective(perspective)
184
+ self.ensight.view.perspective(perspective)
259
185
  vport = self.ensight.objs.core.VPORTS[0]
260
- if view_angle:
261
- vport.PERSPECTIVEANGLE = view_angle / 2
186
+ vport.PERSPECTIVEANGLE = view_angle / 2
262
187
  if view_up and position and focal_point:
263
- if not pan:
264
- q_current = self.normalize(np.array(vport.ROTATION.copy()))
265
- q_target = self.normalize(
266
- self.compute_model_rotation_quaternion(position, focal_point, view_up)
267
- )
268
- q_relative = self.quaternion_multiply(
269
- q_target, np.array([-q_current[0], -q_current[1], -q_current[2], q_current[3]])
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
- if mousex and mousey:
275
- self.screen_to_world(
276
- mousex=mousex, mousey=mousey, invert_y=invert_y, set_center=True
277
- )
278
- current_camera = self.get_camera()
279
- right, up, _ = self.get_camera_axes()
280
- translation_vector = np.array(position) - np.array(current_camera["position"])
281
- dx = np.dot(translation_vector, right)
282
- dy = np.dot(translation_vector, up)
283
- self.ensight.view_transf.translate(-dx, -dy, 0)
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.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==24.8; python_version < '3.13' and platform_machine != 'aarch64'
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>=24.8 ; extra == "doc"
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=1ufL3uG170ZmZ5KnhwcFh07iMIbqNCDjD1mC0u7MH20,28371
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=x6L1E6OGpqL2jrDdIGI0p4BxQZhHt93yl1Pwj_2lAiQ,13143
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=b9sgHY1Fw_1lV4h4BE7GybL71GIRPDFw56Mt-5SQYEw,17256
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=Xnjw8XiIzTCOHKvpb7dH-P3ObA3dQHJvxXEsrRt6XvI,24738
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=MuHVurd8zaSIsQkvrY94X_IJSldFShnzEoGItjvZTTw,41067
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=16HhsxhrwjH_vRtyN96S5rItCfb8ACdVIQkBuEGgPh8,27273
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.10.dist-info/licenses/LICENSE,sha256=K6LiJHOa9IbWFelXmXNRzFr3zG45SOGZIN7vdLdURGU,1097
35
- ansys_pyensight_core-0.10.10.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
36
- ansys_pyensight_core-0.10.10.dist-info/METADATA,sha256=j2qVkHFkgjFLJbBjOz82BXIEuaVmP-lB_UQC8T5bhxQ,12229
37
- ansys_pyensight_core-0.10.10.dist-info/RECORD,,
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,,