ansys-pyensight-core 0.10.12__tar.gz → 0.10.13__tar.gz

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.

Files changed (37) hide show
  1. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/PKG-INFO +1 -1
  2. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/pyproject.toml +1 -1
  3. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/dockerlauncher.py +50 -0
  4. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/launcher.py +4 -1
  5. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/locallauncher.py +56 -0
  6. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/omniverse.py +11 -1
  7. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/omniverse_dsg_server.py +26 -14
  8. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/views.py +21 -138
  9. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/LICENSE +0 -0
  10. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/README.rst +0 -0
  11. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/__init__.py +0 -0
  12. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/common.py +0 -0
  13. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/deep_pixel_view.html +0 -0
  14. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/dvs.py +0 -0
  15. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/enscontext.py +0 -0
  16. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/enshell_grpc.py +0 -0
  17. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/ensight_grpc.py +0 -0
  18. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/ensobj.py +0 -0
  19. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/launch_ensight.py +0 -0
  20. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/libuserd.py +0 -0
  21. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/listobj.py +0 -0
  22. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/py.typed +0 -0
  23. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/renderable.py +0 -0
  24. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/session.py +0 -0
  25. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/sgeo_poll.html +0 -0
  26. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/__init__.py +0 -0
  27. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/adr.py +0 -0
  28. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/dsg_server.py +0 -0
  29. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/export.py +0 -0
  30. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/omniverse_cli.py +0 -0
  31. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/omniverse_glb_server.py +0 -0
  32. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/parts.py +0 -0
  33. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/query.py +0 -0
  34. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/readers.py +0 -0
  35. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/resources/Materials/000_sky.exr +0 -0
  36. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/support.py +0 -0
  37. {ansys_pyensight_core-0.10.12 → ansys_pyensight_core-0.10.13}/src/ansys/pyensight/core/utils/variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ansys-pyensight-core
3
- Version: 0.10.12
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>
@@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi"
6
6
 
7
7
  [project]
8
8
  name = "ansys-pyensight-core"
9
- version = "0.10.12"
9
+ version = "0.10.13"
10
10
  description = "A python wrapper for Ansys EnSight"
11
11
  readme = "README.rst"
12
12
  requires-python = ">=3.10,<3.14"
@@ -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.
@@ -272,7 +272,17 @@ def find_app(ansys_installation: Optional[str] = None) -> Optional[str]:
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:
@@ -38,7 +38,7 @@ import numpy
38
38
  import png
39
39
 
40
40
  try:
41
- from pxr import Gf, Sdf, Usd, UsdGeom, UsdLux, UsdShade
41
+ from pxr import Gf, Kind, Sdf, Usd, UsdGeom, UsdLux, UsdShade
42
42
  except ModuleNotFoundError:
43
43
  if sys.version_info.minor >= 14:
44
44
  warnings.warn("USD Export not supported for Python >= 3.14")
@@ -77,6 +77,7 @@ class OmniverseWrapper(object):
77
77
  self.destination = destination
78
78
 
79
79
  self._line_width = line_width
80
+ self._centroid: Optional[list] = None
80
81
  # Record the files per timestep, per mesh type. {part_name: {"surfaces": [], "lines": [], "points": []} }
81
82
  self._time_files: dict = {}
82
83
 
@@ -307,8 +308,7 @@ class OmniverseWrapper(object):
307
308
 
308
309
  part_prim = stage.OverridePrim(part_path)
309
310
 
310
- surfaces_prim = UsdGeom.Xform.Define(stage, part_path + "/surfaces")
311
- surfaces_prim.AddXformOp(UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble)
311
+ surfaces_prim = self.create_xform_node(stage, part_path + "/surfaces")
312
312
  mesh = UsdGeom.Mesh.Define(stage, str(surfaces_prim.GetPath()) + "/Mesh")
313
313
  mesh.CreateDoubleSidedAttr().Set(True)
314
314
  pt_attr = mesh.CreatePointsAttr()
@@ -483,9 +483,8 @@ class OmniverseWrapper(object):
483
483
 
484
484
  part_prim = stage.OverridePrim(part_path)
485
485
 
486
- lines_prim = UsdGeom.Xform.Define(stage, part_path + "/lines")
486
+ lines_prim = self.create_xform_node(stage, part_path + "/lines")
487
487
 
488
- lines_prim.AddXformOp(UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble)
489
488
  lines = UsdGeom.BasisCurves.Define(stage, str(lines_prim.GetPath()) + "/Lines")
490
489
  lines.CreateDoubleSidedAttr().Set(True)
491
490
  pt_attr = lines.CreatePointsAttr()
@@ -790,9 +789,20 @@ class OmniverseWrapper(object):
790
789
  shutil.rmtree(uriPath, ignore_errors=True, onerror=None)
791
790
  shutil.copytree(f"{tempdir}/scratch/Textures", uriPath)
792
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
+
793
802
  def create_dsg_root(self):
794
803
  root_name = "/Root"
795
- root_prim = UsdGeom.Xform.Define(self._stage, root_name)
804
+ self.create_xform_node(self._stage, root_name)
805
+
796
806
  # Define the defaultPrim as the /Root prim
797
807
  root_prim = self._stage.GetPrimAtPath(root_name)
798
808
  self._stage.SetDefaultPrim(root_prim)
@@ -863,22 +873,18 @@ class OmniverseWrapper(object):
863
873
  path = parent_prim.GetPath().AppendChild(self.clean_name(name))
864
874
  group_prim = UsdGeom.Xform.Get(self._stage, path)
865
875
  if not group_prim:
866
- group_prim = UsdGeom.Xform.Define(self._stage, path)
867
- # At present, the group transforms have been cooked into the vertices so this is not needed
868
- matrix_op = group_prim.AddXformOp(
869
- UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble
870
- )
871
- matrix_op.Set(Gf.Matrix4d(*matrix).GetTranspose())
876
+ group_prim = self.create_xform_node(self._stage, path)
877
+
872
878
  # Map kinds
873
- """
874
879
  kind = Kind.Tokens.group
875
880
  if obj_type == "ENS_CASE":
876
881
  kind = Kind.Tokens.assembly
877
882
  elif obj_type == "ENS_PART":
878
883
  kind = Kind.Tokens.component
879
884
  Usd.ModelAPI(group_prim).SetKind(kind)
885
+ group_prim.GetPrim().SetDisplayName(name)
880
886
  logging.info(f"Created group:'{name}' {str(obj_type)}")
881
- """
887
+
882
888
  return group_prim
883
889
 
884
890
  def uploadMaterial(self):
@@ -932,6 +938,12 @@ class OmniverseUpdateHandler(UpdateHandler):
932
938
  matrix = group.matrix4x4
933
939
  # Is this a "case" group (it will contain part of the camera view in the matrix)
934
940
  if obj_type == "ENS_CASE":
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
+
935
947
  if not self.session.vrmode and not self._case_xform_applied_to_camera:
936
948
  # if in camera mode, we need to update the camera matrix so we can
937
949
  # use the identity matrix on this group. The camera should have been
@@ -107,7 +107,6 @@ 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
111
110
  return {
112
111
  "orthographic": not vport.PERSPECTIVE,
113
112
  "view_up": view_up,
@@ -120,8 +119,6 @@ class _Simba:
120
119
  "reset_parallel_scale": self._original_parallel_scale,
121
120
  "reset_view_up": self._original_view_up,
122
121
  "reset_view_angle": self._original_view_angle,
123
- "near_plane": clipping_range[0],
124
- "far_plane": clipping_range[1],
125
122
  }
126
123
 
127
124
  @staticmethod
@@ -161,44 +158,6 @@ class _Simba:
161
158
  z = 0.25 * s
162
159
  return np.array([x, y, z, w])
163
160
 
164
- def compute_model_rotation_quaternion(self, camera_position, focal_point, view_up):
165
- """Compute the quaternion from the input camera."""
166
- forward = self.normalize(np.array(focal_point) - np.array(camera_position))
167
- right = self.normalize(np.cross(forward, view_up))
168
- true_up = np.cross(right, forward)
169
- camera_rotation = np.vstack([right, true_up, -forward]).T
170
- model_rotation = camera_rotation.T
171
- quat = self.rotation_matrix_to_quaternion(model_rotation)
172
- return quat
173
-
174
- @staticmethod
175
- def quaternion_multiply(q1, q2):
176
- x1, y1, z1, w1 = q1
177
- x2, y2, z2, w2 = q2
178
- w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
179
- x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
180
- y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
181
- z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
182
- return np.array([x, y, z, w])
183
-
184
- def quaternion_to_euler(self, q):
185
- q = self.normalize(q)
186
- x, y, z, w = q
187
- sinr_cosp = 2 * (w * x + y * z)
188
- cosr_cosp = 1 - 2 * (x * x + y * y)
189
- roll = np.arctan2(sinr_cosp, cosr_cosp)
190
-
191
- sinp = 2 * (w * y - z * x)
192
- if abs(sinp) >= 1:
193
- pitch = np.pi / 2 * np.sign(sinp)
194
- else:
195
- pitch = np.arcsin(sinp)
196
- siny_cosp = 2 * (w * z + x * y)
197
- cosy_cosp = 1 - 2 * (y * y + z * z)
198
- yaw = np.arctan2(siny_cosp, cosy_cosp)
199
-
200
- return np.degrees([roll, pitch, yaw])
201
-
202
161
  def compute_camera_from_ensight_opengl(self):
203
162
  """Simulate a rotating camera using the current quaternion."""
204
163
  if isinstance(self.ensight, ModuleType):
@@ -211,45 +170,6 @@ class _Simba:
211
170
  parallel_scale = 1 / data[9]
212
171
  return camera_position, focal_point, self.views._normalize_vector(view_up), parallel_scale
213
172
 
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
-
253
173
  def set_camera(
254
174
  self,
255
175
  orthographic,
@@ -257,75 +177,38 @@ class _Simba:
257
177
  position=None,
258
178
  focal_point=None,
259
179
  view_angle=None,
260
- pan=None,
261
- mousex=None,
262
- mousey=None,
263
- invert_y=False,
264
180
  ):
265
181
  """Set the EnSight camera settings from the VTK input."""
266
182
  self.ensight.view_transf.function("global")
267
183
  perspective = "OFF" if orthographic else "ON"
268
- if orthographic:
269
- self.ensight.view.perspective(perspective)
184
+ self.ensight.view.perspective(perspective)
270
185
  vport = self.ensight.objs.core.VPORTS[0]
271
- if view_angle:
272
- vport.PERSPECTIVEANGLE = view_angle / 2
186
+ vport.PERSPECTIVEANGLE = view_angle / 2
273
187
  if view_up and position and focal_point:
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
188
  current_camera = self.get_camera()
286
189
  center = vport.TRANSFORMCENTER.copy()
287
- pos0, focal0, up0 = map(
288
- np.array,
289
- [
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,
290
195
  current_camera["position"],
291
196
  current_camera["focal_point"],
292
197
  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)
198
+ center,
199
+ vport.ROTATION.copy(),
200
+ )
201
+ else:
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)
329
212
 
330
213
  self.render()
331
214
  return self.get_camera()