ansys-pyensight-core 0.10.5__tar.gz → 0.10.7__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.
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/PKG-INFO +2 -1
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/pyproject.toml +4 -3
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/omniverse.py +55 -11
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/omniverse_dsg_server.py +16 -5
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/views.py +235 -3
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/LICENSE +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/README.rst +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/__init__.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/common.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/deep_pixel_view.html +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/dockerlauncher.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/dvs.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/enscontext.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/enshell_grpc.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/ensight_grpc.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/ensobj.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/launch_ensight.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/launcher.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/libuserd.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/listobj.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/locallauncher.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/py.typed +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/renderable.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/session.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/sgeo_poll.html +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/__init__.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/adr.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/dsg_server.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/export.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/omniverse_cli.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/omniverse_glb_server.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/parts.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/query.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/readers.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/resources/Materials/000_sky.exr +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/support.py +0 -0
- {ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/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.
|
|
3
|
+
Version: 0.10.7
|
|
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>
|
|
@@ -27,6 +27,7 @@ Requires-Dist: pypng>=0.0.20
|
|
|
27
27
|
Requires-Dist: psutil>=5.9.2
|
|
28
28
|
Requires-Dist: usd-core==24.8; python_version < '3.13' and platform_machine != 'aarch64'
|
|
29
29
|
Requires-Dist: pygltflib>=1.16.2
|
|
30
|
+
Requires-Dist: grpcio<1.68.0
|
|
30
31
|
Requires-Dist: build>=0.10.0 ; extra == "dev"
|
|
31
32
|
Requires-Dist: bump2version>=1.0.1 ; extra == "dev"
|
|
32
33
|
Requires-Dist: ipdb>=0.9.4 ; extra == "dev"
|
|
@@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "ansys-pyensight-core"
|
|
9
|
-
version = "0.10.
|
|
9
|
+
version = "0.10.7"
|
|
10
10
|
description = "A python wrapper for Ansys EnSight"
|
|
11
11
|
readme = "README.rst"
|
|
12
12
|
requires-python = ">=3.10,<3.14"
|
|
@@ -36,7 +36,8 @@ dependencies = [
|
|
|
36
36
|
"pypng>=0.0.20",
|
|
37
37
|
"psutil>=5.9.2",
|
|
38
38
|
"usd-core==24.8; python_version < '3.13' and platform_machine != 'aarch64'",
|
|
39
|
-
"pygltflib>=1.16.2"
|
|
39
|
+
"pygltflib>=1.16.2",
|
|
40
|
+
"grpcio<1.68.0",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.optional-dependencies]
|
|
@@ -160,7 +161,7 @@ recursive = true
|
|
|
160
161
|
exclude = ["venv/*", "tests/*"]
|
|
161
162
|
|
|
162
163
|
[tool.mypy]
|
|
163
|
-
python_version = 3.10
|
|
164
|
+
python_version = "3.10"
|
|
164
165
|
strict = false
|
|
165
166
|
namespace_packages = true
|
|
166
167
|
explicit_package_bases = true
|
|
@@ -5,6 +5,7 @@ import platform
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
7
|
import tempfile
|
|
8
|
+
import threading
|
|
8
9
|
from types import ModuleType
|
|
9
10
|
from typing import TYPE_CHECKING, List, Optional, Union
|
|
10
11
|
import uuid
|
|
@@ -35,8 +36,15 @@ class OmniverseKitInstance:
|
|
|
35
36
|
The process id of the launched instance
|
|
36
37
|
"""
|
|
37
38
|
|
|
38
|
-
def __init__(self,
|
|
39
|
-
self.
|
|
39
|
+
def __init__(self, proc: subprocess.Popen) -> None:
|
|
40
|
+
self._proc: subprocess.Popen = proc
|
|
41
|
+
self._returncode: Optional[int] = None
|
|
42
|
+
self._rendering = False
|
|
43
|
+
self._lines_read = 0
|
|
44
|
+
self._scanner_thread = threading.Thread(
|
|
45
|
+
target=OmniverseKitInstance._scan_stdout, args=(self,)
|
|
46
|
+
)
|
|
47
|
+
self._scanner_thread.start()
|
|
40
48
|
|
|
41
49
|
def __del__(self) -> None:
|
|
42
50
|
"""Close down the instance on delete"""
|
|
@@ -50,7 +58,7 @@ class OmniverseKitInstance:
|
|
|
50
58
|
"""
|
|
51
59
|
if not self.is_running():
|
|
52
60
|
return
|
|
53
|
-
proc = psutil.Process(self.
|
|
61
|
+
proc = psutil.Process(self._proc.pid)
|
|
54
62
|
for child in proc.children(recursive=True):
|
|
55
63
|
if psutil.pid_exists(child.pid):
|
|
56
64
|
# This can be a race condition, so it is ok if the child is dead already
|
|
@@ -63,7 +71,29 @@ class OmniverseKitInstance:
|
|
|
63
71
|
proc.terminate()
|
|
64
72
|
except psutil.NoSuchProcess:
|
|
65
73
|
pass
|
|
66
|
-
self.
|
|
74
|
+
self._scanner_thread.join()
|
|
75
|
+
|
|
76
|
+
# On a forced close, set a return code of 0
|
|
77
|
+
self._returncode = 0
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _scan_stdout(oki: "OmniverseKitInstance"):
|
|
81
|
+
while oki._proc and oki._proc.poll() is None:
|
|
82
|
+
if oki._proc.stdout is not None:
|
|
83
|
+
output_line = oki._proc.stdout.readline().decode("utf-8")
|
|
84
|
+
oki._lines_read = oki._lines_read + 1
|
|
85
|
+
if "RTX ready" in output_line:
|
|
86
|
+
oki._rendering = True
|
|
87
|
+
|
|
88
|
+
def is_rendering(self) -> bool:
|
|
89
|
+
"""Check if the instance has finished launching and is ready to render
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
bool
|
|
94
|
+
True if the instance is ready to render.
|
|
95
|
+
"""
|
|
96
|
+
return self.is_running() and self._rendering
|
|
67
97
|
|
|
68
98
|
def is_running(self) -> bool:
|
|
69
99
|
"""Check if the instance is still running
|
|
@@ -73,13 +103,27 @@ class OmniverseKitInstance:
|
|
|
73
103
|
bool
|
|
74
104
|
True if the instance is still running.
|
|
75
105
|
"""
|
|
76
|
-
if
|
|
106
|
+
if self._proc is None:
|
|
77
107
|
return False
|
|
78
|
-
if
|
|
108
|
+
if self._proc.poll() is None:
|
|
79
109
|
return True
|
|
80
|
-
self._pid = None
|
|
81
110
|
return False
|
|
82
111
|
|
|
112
|
+
def returncode(self) -> Optional[int]:
|
|
113
|
+
"""Get the return code if the process has stopped, or None if still running
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
int or None
|
|
118
|
+
Get the return code if the process has stopped, or None if still running
|
|
119
|
+
"""
|
|
120
|
+
if self._returncode is not None:
|
|
121
|
+
return self._returncode
|
|
122
|
+
if self.is_running():
|
|
123
|
+
return None
|
|
124
|
+
self._returncode = self._proc.returncode
|
|
125
|
+
return self._returncode
|
|
126
|
+
|
|
83
127
|
|
|
84
128
|
# Deprecated
|
|
85
129
|
def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
|
|
@@ -216,8 +260,8 @@ def launch_kit_instance(
|
|
|
216
260
|
cmd.append("--/log/enabled=true")
|
|
217
261
|
# Launch the process
|
|
218
262
|
env_vars = os.environ.copy()
|
|
219
|
-
p = subprocess.Popen(cmd, stdout=subprocess.
|
|
220
|
-
return OmniverseKitInstance(p
|
|
263
|
+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env_vars)
|
|
264
|
+
return OmniverseKitInstance(p)
|
|
221
265
|
|
|
222
266
|
|
|
223
267
|
def find_app(ansys_installation: Optional[str] = None) -> Optional[str]:
|
|
@@ -322,8 +366,8 @@ def launch_app(
|
|
|
322
366
|
|
|
323
367
|
# Launch the process
|
|
324
368
|
env_vars = os.environ.copy()
|
|
325
|
-
p = subprocess.Popen(cmd, stdout=subprocess.
|
|
326
|
-
return OmniverseKitInstance(p
|
|
369
|
+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env_vars)
|
|
370
|
+
return OmniverseKitInstance(p)
|
|
327
371
|
|
|
328
372
|
|
|
329
373
|
class Omniverse:
|
|
@@ -70,7 +70,7 @@ class OmniverseWrapper(object):
|
|
|
70
70
|
self._time_codes_per_second: float = 120.0
|
|
71
71
|
# Omniverse content currently only scales correctly for scenes in cm. DJB, Feb 2025
|
|
72
72
|
self._units_per_meter: float = 100.0
|
|
73
|
-
|
|
73
|
+
self._up_axis: str = UsdGeom.Tokens.y
|
|
74
74
|
if destination:
|
|
75
75
|
self.destination = destination
|
|
76
76
|
|
|
@@ -163,7 +163,7 @@ class OmniverseWrapper(object):
|
|
|
163
163
|
self._stage = Usd.Stage.CreateNew(self.stage_url())
|
|
164
164
|
# record the stage in the "_old_stages" list.
|
|
165
165
|
self._old_stages.append(self.stage_url())
|
|
166
|
-
UsdGeom.SetStageUpAxis(self._stage,
|
|
166
|
+
UsdGeom.SetStageUpAxis(self._stage, self._up_axis)
|
|
167
167
|
UsdGeom.SetStageMetersPerUnit(self._stage, 1.0 / self._units_per_meter)
|
|
168
168
|
logging.info(f"Created stage: {self.stage_url()}")
|
|
169
169
|
|
|
@@ -302,7 +302,7 @@ class OmniverseWrapper(object):
|
|
|
302
302
|
|
|
303
303
|
if not os.path.exists(part_stage_url):
|
|
304
304
|
part_stage = Usd.Stage.CreateNew(part_stage_url)
|
|
305
|
-
UsdGeom.SetStageUpAxis(part_stage,
|
|
305
|
+
UsdGeom.SetStageUpAxis(part_stage, self._up_axis)
|
|
306
306
|
UsdGeom.SetStageMetersPerUnit(part_stage, 1.0 / self._units_per_meter)
|
|
307
307
|
self._old_stages.append(part_stage_url)
|
|
308
308
|
xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
|
|
@@ -400,7 +400,7 @@ class OmniverseWrapper(object):
|
|
|
400
400
|
|
|
401
401
|
if not os.path.exists(part_stage_url):
|
|
402
402
|
part_stage = Usd.Stage.CreateNew(part_stage_url)
|
|
403
|
-
UsdGeom.SetStageUpAxis(part_stage,
|
|
403
|
+
UsdGeom.SetStageUpAxis(part_stage, self._up_axis)
|
|
404
404
|
UsdGeom.SetStageMetersPerUnit(part_stage, 1.0 / self._units_per_meter)
|
|
405
405
|
self._old_stages.append(part_stage_url)
|
|
406
406
|
xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
|
|
@@ -485,7 +485,7 @@ class OmniverseWrapper(object):
|
|
|
485
485
|
|
|
486
486
|
if not os.path.exists(part_stage_url):
|
|
487
487
|
part_stage = Usd.Stage.CreateNew(part_stage_url)
|
|
488
|
-
UsdGeom.SetStageUpAxis(part_stage,
|
|
488
|
+
UsdGeom.SetStageUpAxis(part_stage, self._up_axis)
|
|
489
489
|
UsdGeom.SetStageMetersPerUnit(part_stage, 1.0 / self._units_per_meter)
|
|
490
490
|
self._old_stages.append(part_stage_url)
|
|
491
491
|
xform = UsdGeom.Xform.Define(part_stage, "/" + partname)
|
|
@@ -766,6 +766,17 @@ class OmniverseUpdateHandler(UpdateHandler):
|
|
|
766
766
|
# move the model transform to the camera transform
|
|
767
767
|
sc = Gf.Matrix4d(self._omni._units_per_meter)
|
|
768
768
|
cam.transform = c * m.GetInverse() * sc
|
|
769
|
+
|
|
770
|
+
# Determine if the camera is principally more Y, or Z up. X up not supported.
|
|
771
|
+
# Omniverse' built in navigator tries to keep this direction up
|
|
772
|
+
# If the view is principally -Y, there is no good choice. +Y is least bad.
|
|
773
|
+
cam_upvec = Gf.Vec4d(0, 1, 0, 0) * cam.transform
|
|
774
|
+
if abs(cam_upvec[1]) >= abs(cam_upvec[2]):
|
|
775
|
+
self._up_axis = UsdGeom.Tokens.y
|
|
776
|
+
else:
|
|
777
|
+
self._up_axis = UsdGeom.Tokens.z
|
|
778
|
+
UsdGeom.SetStageUpAxis(self._omni._stage, self._up_axis)
|
|
779
|
+
|
|
769
780
|
# set the updated camera
|
|
770
781
|
geom_cam.SetFromCamera(cam)
|
|
771
782
|
# apply the inverse cam transform to move the center of interest
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/views.py
RENAMED
|
@@ -18,9 +18,13 @@ Example to set an isometric view:
|
|
|
18
18
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
import math
|
|
23
|
+
from types import ModuleType
|
|
22
24
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
|
23
25
|
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
24
28
|
if TYPE_CHECKING:
|
|
25
29
|
try:
|
|
26
30
|
import ensight
|
|
@@ -28,14 +32,239 @@ if TYPE_CHECKING:
|
|
|
28
32
|
from ansys.api.pyensight import ensight_api
|
|
29
33
|
|
|
30
34
|
|
|
35
|
+
VIEW_DICT = {
|
|
36
|
+
"x+": (1, 0, 0),
|
|
37
|
+
"x-": (-1, 0, 0),
|
|
38
|
+
"y+": (0, 1, 0),
|
|
39
|
+
"y-": (0, -1, 0),
|
|
40
|
+
"z+": (0, 0, 1),
|
|
41
|
+
"z-": (0, 0, -1),
|
|
42
|
+
"isometric": (1, 1, 1),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _Simba:
|
|
47
|
+
"""Hidden class to manage the interactor layer in simba"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, ensight: Union["ensight_api.ensight", "ensight"], views: "Views"):
|
|
50
|
+
self.ensight = ensight
|
|
51
|
+
self.views = views
|
|
52
|
+
self._original_look_at = None
|
|
53
|
+
self._original_look_from = None
|
|
54
|
+
self._original_parallel_scale = None
|
|
55
|
+
self._original_view_angle = None
|
|
56
|
+
self._original_view_up = None
|
|
57
|
+
|
|
58
|
+
def _initialize_simba_view(self):
|
|
59
|
+
"""Initialize the data for resetting the camera."""
|
|
60
|
+
vport = self.ensight.objs.core.VPORTS[0]
|
|
61
|
+
near_clip = vport.ZCLIPLIMITS[0]
|
|
62
|
+
view_angle = 2 * vport.PERSPECTIVEANGLE
|
|
63
|
+
self._original_parallel_scale = near_clip * math.tan(math.radians(view_angle) / 2)
|
|
64
|
+
self._original_view_angle = view_angle
|
|
65
|
+
(
|
|
66
|
+
self._original_look_from,
|
|
67
|
+
self._original_look_at,
|
|
68
|
+
self._original_view_up,
|
|
69
|
+
self._original_parallel_scale,
|
|
70
|
+
) = self.compute_camera_from_ensight_opengl()
|
|
71
|
+
self.ensight.annotation.axis_global("off")
|
|
72
|
+
self.ensight.annotation.axis_local("off")
|
|
73
|
+
self.ensight.annotation.axis_model("off")
|
|
74
|
+
|
|
75
|
+
def get_center_of_rotation(self):
|
|
76
|
+
"""Get EnSight center of rotation."""
|
|
77
|
+
return self.ensight.objs.core.VPORTS[0].TRANSFORMCENTER
|
|
78
|
+
|
|
79
|
+
def auto_scale(self):
|
|
80
|
+
"""Auto scale view."""
|
|
81
|
+
self.ensight.view_transf.fit()
|
|
82
|
+
self._initialize_simba_view()
|
|
83
|
+
self.render()
|
|
84
|
+
return self.get_camera()
|
|
85
|
+
|
|
86
|
+
def set_view(self, value: str):
|
|
87
|
+
"""Set the view."""
|
|
88
|
+
if value != "isometric":
|
|
89
|
+
new_value = value[1].upper() + value[0]
|
|
90
|
+
self.ensight.view_transf.view_recall(new_value)
|
|
91
|
+
else:
|
|
92
|
+
self.views.set_view_direction(
|
|
93
|
+
1, 1, 1, perspective=self.ensight.objs.core.vports[0].PERSPECTIVE
|
|
94
|
+
)
|
|
95
|
+
self.auto_scale()
|
|
96
|
+
return self.get_camera()
|
|
97
|
+
|
|
98
|
+
def get_camera(self):
|
|
99
|
+
"""Get EnSight camera settings in VTK format."""
|
|
100
|
+
vport = self.ensight.objs.core.VPORTS[0]
|
|
101
|
+
position, focal_point, view_up, parallel_scale = self.compute_camera_from_ensight_opengl()
|
|
102
|
+
vport = self.ensight.objs.core.VPORTS[0]
|
|
103
|
+
view_angle = 2 * vport.PERSPECTIVEANGLE
|
|
104
|
+
# The parameter parallel scale is the actual parallel scale only
|
|
105
|
+
# if the vport is in orthographic mode. If not, it is defined as the
|
|
106
|
+
# inverge of the tangent of half of the field of view
|
|
107
|
+
parallel_scale = parallel_scale
|
|
108
|
+
return {
|
|
109
|
+
"orthographic": not vport.PERSPECTIVE,
|
|
110
|
+
"view_up": view_up,
|
|
111
|
+
"position": position,
|
|
112
|
+
"focal_point": focal_point,
|
|
113
|
+
"view_angle": view_angle,
|
|
114
|
+
"parallel_scale": parallel_scale,
|
|
115
|
+
"reset_focal_point": self._original_look_at,
|
|
116
|
+
"reset_position": self._original_look_from,
|
|
117
|
+
"reset_parallel_scale": self._original_parallel_scale,
|
|
118
|
+
"reset_view_up": self._original_view_up,
|
|
119
|
+
"reset_view_angle": self._original_view_angle,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def normalize(v):
|
|
124
|
+
"""Normalize a numpy vector."""
|
|
125
|
+
norm = np.linalg.norm(v)
|
|
126
|
+
return v / norm if norm > 0 else v
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def rotation_matrix_to_quaternion(m):
|
|
130
|
+
"""Convert a numpy rotation matrix to a quaternion."""
|
|
131
|
+
trace = np.trace(m)
|
|
132
|
+
if trace > 0:
|
|
133
|
+
s = 0.5 / np.sqrt(trace + 1.0)
|
|
134
|
+
w = 0.25 / s
|
|
135
|
+
x = (m[2, 1] - m[1, 2]) * s
|
|
136
|
+
y = (m[0, 2] - m[2, 0]) * s
|
|
137
|
+
z = (m[1, 0] - m[0, 1]) * s
|
|
138
|
+
else:
|
|
139
|
+
if m[0, 0] > m[1, 1] and m[0, 0] > m[2, 2]:
|
|
140
|
+
s = 2.0 * np.sqrt(1.0 + m[0, 0] - m[1, 1] - m[2, 2])
|
|
141
|
+
w = (m[2, 1] - m[1, 2]) / s
|
|
142
|
+
x = 0.25 * s
|
|
143
|
+
y = (m[0, 1] + m[1, 0]) / s
|
|
144
|
+
z = (m[0, 2] + m[2, 0]) / s
|
|
145
|
+
elif m[1, 1] > m[2, 2]:
|
|
146
|
+
s = 2.0 * np.sqrt(1.0 + m[1, 1] - m[0, 0] - m[2, 2])
|
|
147
|
+
w = (m[0, 2] - m[2, 0]) / s
|
|
148
|
+
x = (m[0, 1] + m[1, 0]) / s
|
|
149
|
+
y = 0.25 * s
|
|
150
|
+
z = (m[1, 2] + m[2, 1]) / s
|
|
151
|
+
else:
|
|
152
|
+
s = 2.0 * np.sqrt(1.0 + m[2, 2] - m[0, 0] - m[1, 1])
|
|
153
|
+
w = (m[1, 0] - m[0, 1]) / s
|
|
154
|
+
x = (m[0, 2] + m[2, 0]) / s
|
|
155
|
+
y = (m[1, 2] + m[2, 1]) / s
|
|
156
|
+
z = 0.25 * s
|
|
157
|
+
return np.array([x, y, z, w])
|
|
158
|
+
|
|
159
|
+
def compute_model_rotation_quaternion(self, camera_position, focal_point, view_up):
|
|
160
|
+
"""Compute the quaternion from the input camera."""
|
|
161
|
+
forward = self.normalize(np.array(focal_point) - np.array(camera_position))
|
|
162
|
+
right = self.normalize(np.cross(forward, view_up))
|
|
163
|
+
true_up = np.cross(right, forward)
|
|
164
|
+
camera_rotation = np.vstack([right, true_up, -forward]).T
|
|
165
|
+
model_rotation = camera_rotation.T
|
|
166
|
+
quat = self.rotation_matrix_to_quaternion(model_rotation)
|
|
167
|
+
return quat
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def quaternion_multiply(q1, q2):
|
|
171
|
+
x1, y1, z1, w1 = q1
|
|
172
|
+
x2, y2, z2, w2 = q2
|
|
173
|
+
w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
|
|
174
|
+
x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
|
|
175
|
+
y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
|
|
176
|
+
z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
|
|
177
|
+
return np.array([x, y, z, w])
|
|
178
|
+
|
|
179
|
+
def quaternion_to_euler(self, q):
|
|
180
|
+
q = self.normalize(q)
|
|
181
|
+
x, y, z, w = q
|
|
182
|
+
sinr_cosp = 2 * (w * x + y * z)
|
|
183
|
+
cosr_cosp = 1 - 2 * (x * x + y * y)
|
|
184
|
+
roll = np.arctan2(sinr_cosp, cosr_cosp)
|
|
185
|
+
|
|
186
|
+
sinp = 2 * (w * y - z * x)
|
|
187
|
+
if abs(sinp) >= 1:
|
|
188
|
+
pitch = np.pi / 2 * np.sign(sinp)
|
|
189
|
+
else:
|
|
190
|
+
pitch = np.arcsin(sinp)
|
|
191
|
+
siny_cosp = 2 * (w * z + x * y)
|
|
192
|
+
cosy_cosp = 1 - 2 * (y * y + z * z)
|
|
193
|
+
yaw = np.arctan2(siny_cosp, cosy_cosp)
|
|
194
|
+
|
|
195
|
+
return np.degrees([roll, pitch, yaw])
|
|
196
|
+
|
|
197
|
+
def compute_camera_from_ensight_opengl(self):
|
|
198
|
+
"""Simulate a rotating camera using the current quaternion."""
|
|
199
|
+
if isinstance(self.ensight, ModuleType):
|
|
200
|
+
data = self.ensight.objs.core.VPORTS[0].simba_camera()
|
|
201
|
+
else:
|
|
202
|
+
data = self.ensight._session.cmd("ensight.objs.core.VPORTS[0].simba_camera())")
|
|
203
|
+
camera_position = [data[0], data[1], data[2]]
|
|
204
|
+
focal_point = [data[3], data[4], data[5]]
|
|
205
|
+
view_up = [data[6], data[7], data[8]]
|
|
206
|
+
parallel_scale = 1 / data[9]
|
|
207
|
+
return camera_position, focal_point, self.views._normalize_vector(view_up), parallel_scale
|
|
208
|
+
|
|
209
|
+
def set_camera(
|
|
210
|
+
self, orthographic, view_up=None, position=None, focal_point=None, view_angle=None
|
|
211
|
+
):
|
|
212
|
+
"""Set the EnSight camera settings from the VTK input."""
|
|
213
|
+
perspective = "OFF" if orthographic else "ON"
|
|
214
|
+
if orthographic:
|
|
215
|
+
self.ensight.view.perspective(perspective)
|
|
216
|
+
vport = self.ensight.objs.core.VPORTS[0]
|
|
217
|
+
if view_angle:
|
|
218
|
+
vport.PERSPECTIVEANGLE = view_angle / 2
|
|
219
|
+
|
|
220
|
+
if view_up and position and focal_point:
|
|
221
|
+
q_current = self.normalize(np.array(vport.ROTATION.copy()))
|
|
222
|
+
q_target = self.normalize(
|
|
223
|
+
self.compute_model_rotation_quaternion(position, focal_point, view_up)
|
|
224
|
+
)
|
|
225
|
+
q_relative = self.quaternion_multiply(
|
|
226
|
+
q_target, np.array([-q_current[0], -q_current[1], -q_current[2], q_current[3]])
|
|
227
|
+
)
|
|
228
|
+
angles = self.quaternion_to_euler(q_relative)
|
|
229
|
+
self.ensight.view_transf.rotate(*angles)
|
|
230
|
+
self.render()
|
|
231
|
+
|
|
232
|
+
def set_perspective(self, value):
|
|
233
|
+
vport = self.ensight.objs.core.VPORTS[0]
|
|
234
|
+
self.ensight.view.perspective(value)
|
|
235
|
+
vport.PERSPECTIVE = value == "ON"
|
|
236
|
+
self.ensight.view_transf.zoom(1)
|
|
237
|
+
self.ensight.view_transf.rotate(0, 0, 0)
|
|
238
|
+
self.render()
|
|
239
|
+
return self.get_camera()
|
|
240
|
+
|
|
241
|
+
def screen_to_world(self, mousex, mousey, invert_y=False, set_center=False):
|
|
242
|
+
mousex = int(mousex)
|
|
243
|
+
mousey = int(mousey)
|
|
244
|
+
if isinstance(self.ensight, ModuleType):
|
|
245
|
+
model_point = self.ensight.objs.core.VPORTS[0].screen_to_coords(
|
|
246
|
+
mousex, mousey, invert_y, set_center
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
model_point = self.ensight._session.cmd(
|
|
250
|
+
f"ensight.objs.core.VPORTS[0].screen_to_coords({mousex}, {mousey}, {invert_y}, {set_center})"
|
|
251
|
+
)
|
|
252
|
+
self.render()
|
|
253
|
+
return {"model_point": model_point, "camera": self.get_camera()}
|
|
254
|
+
|
|
255
|
+
def render(self):
|
|
256
|
+
self.ensight.render()
|
|
257
|
+
self.ensight.refresh(1)
|
|
258
|
+
|
|
259
|
+
|
|
31
260
|
class Views:
|
|
32
261
|
"""Controls the view in the current EnSight ``Session`` instance."""
|
|
33
262
|
|
|
34
263
|
def __init__(self, ensight: Union["ensight_api.ensight", "ensight"]):
|
|
35
264
|
self.ensight = ensight
|
|
36
265
|
self._views_dict: Dict[str, Tuple[int, List[float]]] = {}
|
|
266
|
+
self._simba = _Simba(ensight, self)
|
|
37
267
|
|
|
38
|
-
# Utilities
|
|
39
268
|
@staticmethod
|
|
40
269
|
def _normalize_vector(direction: List[float]) -> List[float]:
|
|
41
270
|
"""Return the normalized input (3D) vector.
|
|
@@ -295,14 +524,17 @@ class Views:
|
|
|
295
524
|
vportindex : int, optional
|
|
296
525
|
Viewport to set the view direction for. The default is ``0``.
|
|
297
526
|
"""
|
|
298
|
-
self.ensight.view.perspective("OFF")
|
|
299
|
-
direction = [xdir, ydir, zdir]
|
|
300
527
|
vport = self.ensight.objs.core.VPORTS[vportindex]
|
|
528
|
+
if not perspective:
|
|
529
|
+
self.ensight.view.perspective("OFF")
|
|
530
|
+
vport.PERSPECTIVE = False
|
|
531
|
+
direction = [xdir, ydir, zdir]
|
|
301
532
|
rots = vport.ROTATION.copy()
|
|
302
533
|
rots[0:4] = self._convert_view_direction_to_quaternion(direction, up_axis=up_axis)
|
|
303
534
|
vport.ROTATION = rots
|
|
304
535
|
if perspective:
|
|
305
536
|
self.ensight.view.perspective("ON")
|
|
537
|
+
vport.PERSPECTIVE = True
|
|
306
538
|
self.save_current_view(name=name, vportindex=vportindex)
|
|
307
539
|
|
|
308
540
|
def save_current_view(
|
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/__init__.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/common.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/enscontext.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/enshell_grpc.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/ensight_grpc.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/ensobj.py
RENAMED
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/launcher.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/libuserd.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/listobj.py
RENAMED
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/py.typed
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/renderable.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/session.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/sgeo_poll.html
RENAMED
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/adr.py
RENAMED
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/export.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/parts.py
RENAMED
|
File without changes
|
{ansys_pyensight_core-0.10.5 → ansys_pyensight_core-0.10.7}/src/ansys/pyensight/core/utils/query.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|