ansys-pyensight-core 0.8.3__py3-none-any.whl → 0.8.5__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.

Files changed (28) hide show
  1. ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/__init__.py +1 -0
  2. ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py +366 -0
  3. ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml +58 -0
  4. ansys/pyensight/core/exts/ansys.geometry.service/data/icon.png +0 -0
  5. ansys/pyensight/core/exts/ansys.geometry.service/data/preview.png +0 -0
  6. ansys/pyensight/core/exts/ansys.geometry.service/docs/CHANGELOG.md +8 -0
  7. ansys/pyensight/core/exts/ansys.geometry.service/docs/README.md +13 -0
  8. ansys/pyensight/core/exts/ansys.geometry.service/docs/index.rst +18 -0
  9. ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/__init__.py +1 -0
  10. ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +170 -0
  11. ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml +49 -0
  12. ansys/pyensight/core/exts/ansys.geometry.serviceui/data/icon.png +0 -0
  13. ansys/pyensight/core/exts/ansys.geometry.serviceui/data/preview.png +0 -0
  14. ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/CHANGELOG.md +8 -0
  15. ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/README.md +13 -0
  16. ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/index.rst +18 -0
  17. ansys/pyensight/core/launcher.py +36 -1
  18. ansys/pyensight/core/locallauncher.py +3 -1
  19. ansys/pyensight/core/utils/dsg_server.py +32 -17
  20. ansys/pyensight/core/utils/omniverse.py +160 -104
  21. ansys/pyensight/core/utils/omniverse_dsg_server.py +85 -36
  22. ansys/pyensight/core/utils/parts.py +5 -5
  23. ansys/pyensight/core/utils/variables.py +36 -1
  24. {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/METADATA +4 -4
  25. ansys_pyensight_core-0.8.5.dist-info/RECORD +52 -0
  26. ansys_pyensight_core-0.8.3.dist-info/RECORD +0 -36
  27. {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/LICENSE +0 -0
  28. {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,49 @@
1
+ [package]
2
+ # Semantic Versioning is used: https://semver.org/
3
+ version = "0.8.5"
4
+
5
+ # Lists people or organizations that are considered the "authors" of the package.
6
+ authors = ["ANSYS"]
7
+
8
+ # The title and description fields are primarily for displaying extension info in UI
9
+ title = "ANSYS Omniverse Geometry Service GUI"
10
+ description = "A geometry synchronization service that enables export of geometry scenes from ANSYS products to Omniverse."
11
+
12
+ # Path (relative to the root) or content of readme markdown file for UI.
13
+ readme = "docs/README.md"
14
+
15
+ # URL of the extension source repository.
16
+ repository = "https://github.com/ansys/pyensight"
17
+
18
+ # One of categories for UI.
19
+ category = "simulation"
20
+
21
+ # Keywords for the extension
22
+ keywords = ["ANSYS", "EnSight", "PyEnSight", "Fluent", "kit"]
23
+
24
+ # Location of change log file in target (final) folder of extension, relative to the root.
25
+ # More info on writing changelog: https://keepachangelog.com/en/1.0.0/
26
+ changelog = "docs/CHANGELOG.md"
27
+
28
+ # Preview image and icon. Folder named "data" automatically goes in git lfs (see .gitattributes file).
29
+ # Preview image is shown in "Overview" of Extensions window. Screenshot of an extension might be a good preview image.
30
+ preview_image = "data/preview.png"
31
+
32
+ # Icon is shown in Extensions window, it is recommended to be square, of size 256x256.
33
+ icon = "data/icon.png"
34
+
35
+ # Use omni.ui to build simple UI
36
+ [dependencies]
37
+ "omni.kit.uiapp" = {}
38
+ "ansys.geometry.service" = {}
39
+
40
+ # Main python module this extension provides, it will be publicly available as "import ansys.geometry.serviceui".
41
+ [[python.module]]
42
+ name = "ansys.geometry.serviceui"
43
+
44
+ [[test]]
45
+ # Extra dependencies only to be used during test run
46
+ dependencies = [
47
+ "omni.kit.ui_test" # UI testing extension
48
+ ]
49
+
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
+
5
+
6
+ ## [0.9.0] - 2024-06-28
7
+ - Initial kit version of ANSYS Geometry Service GUI
8
+
@@ -0,0 +1,13 @@
1
+ # ANSYS Omniverse Geometry Service GUI [ansys.geometry.serviceui]
2
+
3
+ This Omniverse extension is a UI interface to the [ansys.geometry.service]
4
+ kit extension. It allows an Omniverse application user to connect to
5
+ a running copy of ANSYS EnSight or other application that supports the
6
+ Dynamic Scene Graph gRPC protocol. The GUI allows for the remote scene
7
+ to be pulled, on request, into a specified Omniverse location.
8
+
9
+
10
+ For more details on this extension see:
11
+ https://ensight.docs.pyansys.com/version/dev/user_guide/omniverse_info.html
12
+
13
+
@@ -0,0 +1,18 @@
1
+ ansys.geometry.serviceui
2
+ ########################
3
+
4
+
5
+ .. toctree::
6
+ :maxdepth: 1
7
+
8
+ README
9
+ CHANGELOG
10
+
11
+
12
+ .. automodule::"ansys.geometry.serviceui"
13
+ :platform: Windows-x86_64, Linux-x86_64
14
+ :members:
15
+ :undoc-members:
16
+ :show-inheritance:
17
+ :imported-members:
18
+ :exclude-members: contextmanager
@@ -13,10 +13,13 @@ Examples:
13
13
  """
14
14
  import os.path
15
15
  import platform
16
+ import random
17
+ import re
16
18
  import socket
17
19
  from typing import TYPE_CHECKING, Dict, List, Optional
18
20
  import warnings
19
21
 
22
+ import psutil
20
23
  import requests
21
24
 
22
25
  if TYPE_CHECKING:
@@ -165,6 +168,38 @@ class Launcher:
165
168
  """
166
169
  return
167
170
 
171
+ def _find_ports_used_by_other_pyensight_and_ensight(self):
172
+ """Find ports to avoid when looking for empty ports.
173
+
174
+ The ports are found iterating the current processes and
175
+ looking for PyEnSight/EnSight sessions and their command
176
+ lines.
177
+ """
178
+ pyensight_found = []
179
+ ensight_found = []
180
+ for process in psutil.process_iter():
181
+ try:
182
+ process_cmdline = process.cmdline()
183
+ except (psutil.AccessDenied, psutil.ZombieProcess):
184
+ continue
185
+ if not process_cmdline:
186
+ continue
187
+ if len(process_cmdline) > 1:
188
+ if "websocketserver.py" in os.path.basename(process_cmdline[1]):
189
+ pyensight_found.append(process_cmdline)
190
+ if any(["ensight" in os.path.basename(x) for x in process_cmdline]):
191
+ if any([x == "-ports" for x in process_cmdline]):
192
+ ensight_found.append(process_cmdline)
193
+ ports = []
194
+ for command_line in pyensight_found:
195
+ for command in command_line:
196
+ if re.match(r"^\d{4,5}$", command):
197
+ ports.append(int(command))
198
+ for command_line in ensight_found:
199
+ idx = command_line.index("-ports") + 1
200
+ ports.append(int(command_line[idx]))
201
+ return list(set(ports))
202
+
168
203
  @staticmethod
169
204
  def _find_unused_ports(count: int, avoid: Optional[List[int]] = None) -> Optional[List[int]]:
170
205
  """Find "count" unused ports on the host system
@@ -191,7 +226,7 @@ class Launcher:
191
226
  ports = list()
192
227
 
193
228
  # pick a starting port number
194
- start = os.getpid() % 64000
229
+ start = random.randint(1024, 64000)
195
230
  # We will scan for 65530 ports unless end is specified
196
231
  port_mod = 65530
197
232
  end = start + port_mod - 1
@@ -123,7 +123,8 @@ class LocalLauncher(Launcher):
123
123
  self.session_directory = tempfile.mkdtemp(prefix="pyensight_")
124
124
 
125
125
  # gRPC port, VNC port, websocketserver ws, websocketserver html
126
- self._ports = self._find_unused_ports(4)
126
+ to_avoid = self._find_ports_used_by_other_pyensight_and_ensight()
127
+ self._ports = self._find_unused_ports(5, avoid=to_avoid)
127
128
  if self._ports is None:
128
129
  raise RuntimeError("Unable to allocate local ports for EnSight session")
129
130
  is_windows = self._is_windows()
@@ -152,6 +153,7 @@ class LocalLauncher(Launcher):
152
153
  cmd.extend(["-grpc_server", str(self._ports[0])])
153
154
  vnc_url = f"vnc://%%3Frfb_port={self._ports[1]}%%26use_auth=0"
154
155
  cmd.extend(["-vnc", vnc_url])
156
+ cmd.extend(["-ports", str(self._ports[4])])
155
157
 
156
158
  use_egl = self._use_egl()
157
159
 
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  import queue
4
+ import sys
4
5
  import threading
5
6
  from typing import Any, Dict, List, Optional
6
7
 
@@ -94,15 +95,12 @@ class Part(object):
94
95
  else:
95
96
  self.tcoords_var_id = None
96
97
 
97
- def build(self):
98
+ def nodal_surface_rep(self):
98
99
  """
99
100
  This function processes the geometry arrays and converts them into nodal representation.
100
101
  It will duplicate triangles as needed (to preserve element normals) and will convert
101
102
  variable data into texture coordinates.
102
103
 
103
- Note: this call can only be made once as the internal structures are released when this
104
- call is made.
105
-
106
104
  Returns
107
105
  -------
108
106
  On failure, the method returns None for the first return value. The returned tuple is:
@@ -118,11 +116,8 @@ class Part(object):
118
116
  """
119
117
  if self.cmd is None:
120
118
  return None, None, None, None, None, None
121
- if self.conn_lines.size:
122
- self.session.log(
123
- f"Note: part '{self.cmd.name}' contains lines which are not currently supported."
124
- )
125
- self.cmd = None
119
+ if self.conn_tris.size == 0:
120
+ self.session.log(f"Note: part '{self.cmd.name}' contains no triangles.")
126
121
  return None, None, None, None, None, None
127
122
  verts = self.coords
128
123
  if self.session.normalize_geometry and self.session.scene_bounds is not None:
@@ -241,7 +236,7 @@ class Part(object):
241
236
  tcoords = tmp
242
237
 
243
238
  self.session.log(
244
- f"Part '{self.cmd.name}' defined: {self.coords.size/3} verts, {self.conn_tris.size/3} tris, {self.conn_lines.size/2} lines."
239
+ f"Part '{self.cmd.name}' defined: {self.coords.size/3} verts, {self.conn_tris.size/3} tris."
245
240
  )
246
241
  command = self.cmd
247
242
 
@@ -285,8 +280,9 @@ class UpdateHandler(object):
285
280
 
286
281
  def finalize_part(self, part: Part) -> None:
287
282
  """Called when all the updates on a Part object have been completed.
288
- Note: this should be called after the subclass has processed the part
289
- as the part command will be destroyed by this call.
283
+
284
+ Note: this superclass method should be called after the subclass has processed
285
+ the part geometry as the saved part command will be destroyed by this call.
290
286
  """
291
287
  if part.cmd:
292
288
  self.session.log(f"Part finalized: {part.cmd.name}")
@@ -398,11 +394,16 @@ class DSGSession(object):
398
394
  self._dsg = None
399
395
  self._normalize_geometry = normalize_geometry
400
396
  self._vrmode = vrmode
397
+ self._time_limits = [
398
+ sys.float_info.max,
399
+ -sys.float_info.max,
400
+ ] # Min/max across all time steps
401
401
  self._mesh_block_count = 0
402
402
  self._variables: Dict[int, Any] = dict()
403
403
  self._groups: Dict[int, Any] = dict()
404
404
  self._part: Part = Part(self)
405
405
  self._scene_bounds: Optional[List] = None
406
+ self._cur_timeline: List = [0.0, 0.0] # Start/End time for current update
406
407
  self._callback_handler.session = self
407
408
 
408
409
  @property
@@ -441,6 +442,20 @@ class DSGSession(object):
441
442
  def part(self) -> Part:
442
443
  return self._part
443
444
 
445
+ @property
446
+ def time_limits(self) -> List:
447
+ return self._time_limits
448
+
449
+ @property
450
+ def cur_timeline(self) -> List:
451
+ return self._cur_timeline
452
+
453
+ @cur_timeline.setter
454
+ def cur_timeline(self, timeline: List) -> None:
455
+ self._cur_timeline = timeline
456
+ self._time_limits[0] = min(self._time_limits[0], self._cur_timeline[0])
457
+ self._time_limits[1] = max(self._time_limits[1], self._cur_timeline[1])
458
+
444
459
  @property
445
460
  def grpc(self) -> ensight_grpc.EnSightGRPC:
446
461
  return self._grpc
@@ -486,7 +501,7 @@ class DSGSession(object):
486
501
  def end(self):
487
502
  """Stop a gRPC connection to the EnSight instance"""
488
503
  self._callback_handler.end_connection()
489
- self._grpc.stop_server()
504
+ self._grpc.shutdown()
490
505
  self._shutdown = True
491
506
  self._thread.join()
492
507
  self._grpc.shutdown()
@@ -518,8 +533,6 @@ class DSGSession(object):
518
533
  cmd.init.allow_incremental_updates = False
519
534
  cmd.init.maximum_chunk_size = 1024 * 1024
520
535
  self._dsg_queue.put(cmd) # type:ignore
521
- # Handle the update messages
522
- self.handle_one_update()
523
536
 
524
537
  def _poll_messages(self) -> None:
525
538
  """Core interface to grab DSG events from gRPC and queue them for processing
@@ -629,7 +642,6 @@ class DSGSession(object):
629
642
  There is always a part being modified. This method completes the current part, committing
630
643
  it to the handler.
631
644
  """
632
- self._part.build()
633
645
  self._callback_handler.finalize_part(self.part)
634
646
  self._mesh_block_count += 1
635
647
 
@@ -689,10 +701,13 @@ class DSGSession(object):
689
701
  """Handle a DSG UPDATE_VIEW command
690
702
 
691
703
  Parameters
692
- ----------s
704
+ ----------
693
705
  view:
694
706
  The command coming from the EnSight stream.
695
707
  """
708
+ self._finish_part()
696
709
  self._scene_bounds = None
697
710
  self._groups[view.id] = view
711
+ if len(view.timeline) == 2:
712
+ self.cur_timeline = [view.timeline[0], view.timeline[1]]
698
713
  self._callback_handler.add_group(view.id, view=True)
@@ -1,8 +1,9 @@
1
+ import glob
1
2
  import os
2
3
  import subprocess
3
4
  import sys
4
5
  from types import ModuleType
5
- from typing import TYPE_CHECKING, List, Optional, Union
6
+ from typing import TYPE_CHECKING, Optional, Union
6
7
 
7
8
  import psutil
8
9
 
@@ -12,6 +13,8 @@ if TYPE_CHECKING:
12
13
  except ImportError:
13
14
  from ansys.api.pyensight import ensight_api
14
15
 
16
+ import ansys.pyensight.core
17
+
15
18
 
16
19
  class Omniverse:
17
20
  """Provides the ``ensight.utils.omniverse`` interface.
@@ -19,36 +22,109 @@ class Omniverse:
19
22
  The omniverse class methods provide an interface between an EnSight session
20
23
  and an Omniverse instance. See :ref:`omniverse_info` for additional details.
21
24
 
22
- Note
23
- ----
24
- This interface is only available when using pyensight (they do not work with
25
- the ensight Python interpreter) and the module must be used in an interpreter
26
- that includes the Omniverse Python modules (e.g. omni and pxr). Only a single
27
- Omniverse connection can be established within a single pyensight session.
28
-
29
25
  Parameters
30
26
  ----------
31
- interface:
27
+ interface: Union["ensight_api.ensight", "ensight"]
32
28
  Entity that provides the ``ensight`` namespace. In the case of
33
29
  EnSight Python, the ``ensight`` module is passed. In the case
34
30
  of PyEnSight, ``Session.ensight`` is passed.
35
31
 
36
- Example
37
- -------
38
- ::
39
- from ansys.pyensight.core import LocalLauncher
40
- session = LocalLauncher().start()
41
- ov = session.ensight.utils.omniverse
42
- ov.create_connection()
43
- ov.update()
44
- ov.close_connection()
32
+ Notes
33
+ -----
34
+ This interface is only available when using pyensight (they do not work with
35
+ the ensight Python interpreter) and the module must be used in an interpreter
36
+ that includes the Omniverse Python modules (e.g. omni and pxr). Only a single
37
+ Omniverse connection can be established within a single pyensight session.
38
+
39
+ Examples
40
+ --------
41
+
42
+ >>> from ansys.pyensight.core import LocalLauncher
43
+ >>> session = LocalLauncher().start()
44
+ >>> ov = session.ensight.utils.omniverse
45
+ >>> ov.create_connection()
46
+ >>> ov.update()
47
+ >>> ov.close_connection()
45
48
 
46
49
  """
47
50
 
48
51
  def __init__(self, interface: Union["ensight_api.ensight", "ensight"]):
49
52
  self._ensight = interface
50
53
  self._server_pid: Optional[int] = None
51
- self._interpreter: List[str] = []
54
+ self._interpreter: str = ""
55
+
56
+ @staticmethod
57
+ def find_kit_filename(fallback_directory: Optional[str] = None) -> Optional[str]:
58
+ """
59
+ Use a combination of the current omniverse application and the information
60
+ in the local .nvidia-omniverse/config/omniverse.toml file to come up with
61
+ the pathname of a kit executable suitable for hosting another copy of the
62
+ ansys.geometry.server kit.
63
+
64
+ Returns
65
+ -------
66
+ Optional[str]
67
+ The pathname of a kit executable or None
68
+
69
+ """
70
+ # parse the toml config file for the location of the installed apps
71
+ try:
72
+ import tomllib
73
+ except ModuleNotFoundError:
74
+ import pip._vendor.tomli as tomllib
75
+
76
+ homedir = os.path.expanduser("~")
77
+ ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
78
+ if not os.path.exists(ov_config):
79
+ return None
80
+ # read the Omniverse configuration toml file
81
+ with open(ov_config, "r") as ov_file:
82
+ ov_data = ov_file.read()
83
+ config = tomllib.loads(ov_data)
84
+ appdir = config.get("paths", {}).get("library_root", fallback_directory)
85
+
86
+ # If we are running inside an Omniverse app, use that information
87
+ try:
88
+ import omni.kit.app
89
+
90
+ # get the current application
91
+ app = omni.kit.app.get_app()
92
+ app_name = app.get_app_filename().split(".")[-1]
93
+ app_version = app.get_app_version().split("-")[0]
94
+ # and where it is installed
95
+ appdir = os.path.join(appdir, f"{app_name}-{app_version}")
96
+ except ModuleNotFoundError:
97
+ # Names should be like: "C:\\Users\\foo\\AppData\\Local\\ov\\pkg\\create-2023.2.3\\launcher.toml"
98
+ target = None
99
+ target_version = None
100
+ for d in glob.glob(os.path.join(appdir, "*", "launcher.toml")):
101
+ test_dir = os.path.dirname(d)
102
+ # the name will be something like "create-2023.2.3"
103
+ name = os.path.basename(test_dir).split("-")
104
+ if len(name) != 2:
105
+ continue
106
+ if name[0] not in ("kit", "create", "view"):
107
+ continue
108
+ if (target_version is None) or (name[1] > target_version):
109
+ target = test_dir
110
+ target_version = name[1]
111
+ if target is None:
112
+ return None
113
+ appdir = target
114
+
115
+ # Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
116
+ # Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
117
+ exe_names = ["kit.sh", "kit"]
118
+ if sys.platform.startswith("win"):
119
+ exe_names = ["kit.bat", "kit.exe"]
120
+
121
+ # look in 4 places...
122
+ for dir_name in [appdir, os.path.join(appdir, "kit")]:
123
+ for exe_name in exe_names:
124
+ if os.path.exists(os.path.join(dir_name, exe_name)):
125
+ return os.path.join(dir_name, exe_name)
126
+
127
+ return None
52
128
 
53
129
  def _check_modules(self) -> None:
54
130
  """Verify that the Python interpreter is correct
@@ -60,53 +136,26 @@ class Omniverse:
60
136
 
61
137
  Raises
62
138
  ------
63
- RuntimeError if the necessary modules are missing.
139
+ RuntimeError
140
+ if the necessary modules are missing.
64
141
 
65
142
  """
66
143
  # One time check for this
67
144
  if len(self._interpreter):
68
145
  return
69
- try:
70
- # Note: the EnSight embedded interpreter will not have these
71
- import omni.client # noqa: F401
72
- except ImportError:
73
- raise RuntimeError("The module requires the omni module to be installed.") from None
74
146
 
75
- try:
76
- # if we can import pxr, then we can just use sys.executable
77
- from pxr import Gf, Sdf, Usd, UsdGeom, UsdLux, UsdShade # noqa: F401
78
-
79
- if os.path.basename(sys.executable).startswith("kit"):
80
- # we are running inside of an Omniverse app like Create, use the 'kit' script
81
- raise ImportError("Internal retry")
82
-
83
- self._interpreter = [sys.executable]
147
+ kit_exe = self.find_kit_filename()
148
+ if kit_exe:
149
+ self._interpreter = kit_exe
84
150
  return
85
- except ImportError:
86
- # Can we find 'kit.bat' or 'kit.sh' (we may be running in it)?
87
- # Interesting cases: something/kit/python/python.exe,
88
- # something/kit/kit.exe. All mapped to something/kit.{bat,sh} if found.
89
- ov_dir = os.path.dirname(sys.executable)
90
- for _ in range(3):
91
- for name in ("kit.bat", "kit.sh"):
92
- exe_name = os.path.join(ov_dir, name)
93
- if os.path.exists(exe_name):
94
- self._interpreter = [
95
- exe_name,
96
- "--enable",
97
- "omni.client",
98
- "--enable",
99
- "omni.usd",
100
- "--exec",
101
- ]
102
- return
103
- ov_dir = os.path.dirname(ov_dir)
104
- raise RuntimeError("Unable to detect a copy of the Omniverse kit executable.") from None
105
-
106
- def _is_running_omniverse(self) -> bool:
151
+ raise RuntimeError("Unable to detect a copy of the Omniverse kit executable.") from None
152
+
153
+ def is_running_omniverse(self) -> bool:
107
154
  """Check that an Omniverse connection is active
155
+
108
156
  Returns
109
157
  -------
158
+ bool
110
159
  True if the connection is active, False otherwise.
111
160
  """
112
161
  if self._server_pid is None:
@@ -121,8 +170,10 @@ class Omniverse:
121
170
  omniverse_path: str,
122
171
  include_camera: bool = False,
123
172
  normalize_geometry: bool = False,
173
+ temporal: bool = False,
124
174
  live: bool = True,
125
175
  debug_filename: str = "",
176
+ options: dict = {},
126
177
  ) -> None:
127
178
  """Ensure that an EnSight dsg -> omniverse server is running
128
179
 
@@ -143,6 +194,8 @@ class Omniverse:
143
194
  Omniverse units are in meters. If the source dataset is not in the correct
144
195
  unit system or is just too large/small, this option will remap the geometry
145
196
  to a unit cube. Defaults to False.
197
+ temporal : bool
198
+ If True, save all timesteps.
146
199
  live : bool
147
200
  If True, one can call 'update()' to send updated geometry to Omniverse.
148
201
  If False, the Omniverse connection will push a single update and then
@@ -150,53 +203,46 @@ class Omniverse:
150
203
  debug_filename : str
151
204
  If the name of a file is provided, it will be used to save logging information on
152
205
  the connection between EnSight and Omniverse.
153
-
206
+ options : dict
207
+ Allows for a fallback for the grpc host/port and the security token.
154
208
  """
155
209
  if not isinstance(self._ensight, ModuleType):
156
210
  self._ensight._session.ensight_version_check("2023 R2")
157
211
  self._check_modules()
158
- if self._is_running_omniverse():
212
+ if self.is_running_omniverse():
159
213
  raise RuntimeError("An Omniverse server connection is already active.")
160
- # Make sure the internal ui module is loaded
161
- self._ensight._session.cmd("import enspyqtgui_int", do_eval=False)
162
- # Get the gRPC connection details and use them to launch the service
163
- port = self._ensight._session.grpc.port()
164
- hostname = self._ensight._session.grpc.host
165
- token = self._ensight._session.grpc.security_token
166
- script_name = "omniverse_dsg_server.py"
167
- working_dir = os.path.dirname(__file__)
168
- cmd = [
169
- script_name,
170
- "--host",
171
- hostname,
172
- "--port",
173
- str(port),
174
- "--path",
175
- omniverse_path,
176
- ]
177
- if live:
178
- cmd.extend(["--live"])
179
- if include_camera:
180
- cmd.extend(["--vrmode"])
214
+ if not isinstance(self._ensight, ModuleType):
215
+ # Make sure the internal ui module is loaded
216
+ self._ensight._session.cmd("import enspyqtgui_int", do_eval=False)
217
+ # Get the gRPC connection details and use them to launch the service
218
+ port = self._ensight._session.grpc.port()
219
+ hostname = self._ensight._session.grpc.host
220
+ token = self._ensight._session.grpc.security_token
221
+ else:
222
+ hostname = options.get("host", "127.0.0.1")
223
+ port = options.get("port", 12345)
224
+ token = options.get("security", "")
225
+
226
+ # Launch the server via the 'ansys.geometry.service' kit
227
+ dsg_uri = f"grpc://{hostname}:{port}"
228
+ kit_dir = os.path.join(os.path.dirname(ansys.pyensight.core.__file__), "exts")
229
+ cmd = [self._interpreter]
230
+ cmd.extend(["--ext-folder", kit_dir])
231
+ cmd.extend(["--enable", "ansys.geometry.service"])
181
232
  if token:
182
- cmd.extend(["--security", token])
183
- # if temporal:
184
- # cmd.extend(["--animation"])
185
- # else:
186
- # cmd.extend(["--no-animation"])
187
- if debug_filename:
188
- cmd.extend(["--log_file", debug_filename])
189
- cmd.extend(["--verbose", "1"])
233
+ cmd.append(f"--/exts/ansys.geometry.service/securityCode={token}")
234
+ if temporal:
235
+ cmd.append("--/exts/ansys.geometry.service/temporal=1")
236
+ if not include_camera:
237
+ cmd.append("--/exts/ansys.geometry.service/vrmode=1")
190
238
  if normalize_geometry:
191
- cmd.extend(["--normalize_geometry"])
192
- # if using kit.bat, convert args into a string, otherwise, just use them
193
- cmdline = []
194
- cmdline.extend(self._interpreter)
195
- if len(self._interpreter) > 1:
196
- cmd = [" ".join(cmd)]
197
- cmdline.extend(cmd)
239
+ cmd.append("--/exts/ansys.geometry.service/normalizeGeometry=1")
240
+ cmd.append(f"--/exts/ansys.geometry.service/omniUrl={omniverse_path}")
241
+ cmd.append(f"--/exts/ansys.geometry.service/dsgUrl={dsg_uri}")
242
+ cmd.append("--/exts/ansys.geometry.service/run=1")
198
243
  env_vars = os.environ.copy()
199
- process = subprocess.Popen(cmdline, close_fds=True, env=env_vars, cwd=working_dir)
244
+ working_dir = os.path.join(os.path.dirname(ansys.pyensight.core.__file__), "utils")
245
+ process = subprocess.Popen(cmd, close_fds=True, env=env_vars, cwd=working_dir)
200
246
  self._server_pid = process.pid
201
247
 
202
248
  def close_connection(self) -> None:
@@ -206,7 +252,7 @@ class Omniverse:
206
252
 
207
253
  """
208
254
  self._check_modules()
209
- if not self._is_running_omniverse():
255
+ if not self.is_running_omniverse():
210
256
  return
211
257
  proc = psutil.Process(self._server_pid)
212
258
  for child in proc.children(recursive=True):
@@ -223,17 +269,27 @@ class Omniverse:
223
269
  pass
224
270
  self._server_pid = None
225
271
 
226
- def update(self) -> None:
272
+ def update(self, temporal: bool = False) -> None:
227
273
  """Update the geometry in Omniverse
228
274
 
229
- Push the current EnSight scene to the current Omniverse connection.
275
+ Export the current EnSight scene to the current Omniverse connection.
230
276
 
277
+ Parameters
278
+ ----------
279
+ temporal : bool
280
+ If True, export all timesteps.
231
281
  """
232
- if not isinstance(self._ensight, ModuleType):
233
- self._ensight._session.ensight_version_check("2023 R2")
282
+ update_cmd = "dynamicscenegraph://localhost/client/update"
283
+ if temporal:
284
+ update_cmd += "?timesteps=1"
234
285
  self._check_modules()
235
- if not self._is_running_omniverse():
286
+ if not self.is_running_omniverse():
236
287
  raise RuntimeError("No Omniverse server connection is currently active.")
237
- update_cmd = "dynamicscenegraph://localhost/client/update"
238
- cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
239
- self._ensight._session.cmd(cmd, do_eval=False)
288
+ if not isinstance(self._ensight, ModuleType):
289
+ self._ensight._session.ensight_version_check("2023 R2")
290
+ cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
291
+ self._ensight._session.cmd(cmd, do_eval=False)
292
+ else:
293
+ import enspyqtgui_int
294
+
295
+ enspyqtgui_int.dynamic_scene_graph_command(f"{update_cmd}")