ansys-pyensight-core 0.8.4__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 (26) 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 +29 -11
  20. ansys/pyensight/core/utils/omniverse.py +151 -95
  21. ansys/pyensight/core/utils/omniverse_dsg_server.py +85 -36
  22. {ansys_pyensight_core-0.8.4.dist-info → ansys_pyensight_core-0.8.5.dist-info}/METADATA +1 -1
  23. ansys_pyensight_core-0.8.5.dist-info/RECORD +52 -0
  24. ansys_pyensight_core-0.8.4.dist-info/RECORD +0 -36
  25. {ansys_pyensight_core-0.8.4.dist-info → ansys_pyensight_core-0.8.5.dist-info}/LICENSE +0 -0
  26. {ansys_pyensight_core-0.8.4.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,7 +95,7 @@ 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
@@ -115,10 +116,8 @@ class Part(object):
115
116
  """
116
117
  if self.cmd is None:
117
118
  return None, None, None, None, None, None
118
- if self.conn_lines.size:
119
- self.session.log(
120
- f"Note: part '{self.cmd.name}' contains lines which are not currently supported."
121
- )
119
+ if self.conn_tris.size == 0:
120
+ self.session.log(f"Note: part '{self.cmd.name}' contains no triangles.")
122
121
  return None, None, None, None, None, None
123
122
  verts = self.coords
124
123
  if self.session.normalize_geometry and self.session.scene_bounds is not None:
@@ -237,7 +236,7 @@ class Part(object):
237
236
  tcoords = tmp
238
237
 
239
238
  self.session.log(
240
- 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."
241
240
  )
242
241
  command = self.cmd
243
242
 
@@ -395,11 +394,16 @@ class DSGSession(object):
395
394
  self._dsg = None
396
395
  self._normalize_geometry = normalize_geometry
397
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
398
401
  self._mesh_block_count = 0
399
402
  self._variables: Dict[int, Any] = dict()
400
403
  self._groups: Dict[int, Any] = dict()
401
404
  self._part: Part = Part(self)
402
405
  self._scene_bounds: Optional[List] = None
406
+ self._cur_timeline: List = [0.0, 0.0] # Start/End time for current update
403
407
  self._callback_handler.session = self
404
408
 
405
409
  @property
@@ -438,6 +442,20 @@ class DSGSession(object):
438
442
  def part(self) -> Part:
439
443
  return self._part
440
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
+
441
459
  @property
442
460
  def grpc(self) -> ensight_grpc.EnSightGRPC:
443
461
  return self._grpc
@@ -483,7 +501,7 @@ class DSGSession(object):
483
501
  def end(self):
484
502
  """Stop a gRPC connection to the EnSight instance"""
485
503
  self._callback_handler.end_connection()
486
- self._grpc.stop_server()
504
+ self._grpc.shutdown()
487
505
  self._shutdown = True
488
506
  self._thread.join()
489
507
  self._grpc.shutdown()
@@ -515,8 +533,6 @@ class DSGSession(object):
515
533
  cmd.init.allow_incremental_updates = False
516
534
  cmd.init.maximum_chunk_size = 1024 * 1024
517
535
  self._dsg_queue.put(cmd) # type:ignore
518
- # Handle the update messages
519
- self.handle_one_update()
520
536
 
521
537
  def _poll_messages(self) -> None:
522
538
  """Core interface to grab DSG events from gRPC and queue them for processing
@@ -626,7 +642,6 @@ class DSGSession(object):
626
642
  There is always a part being modified. This method completes the current part, committing
627
643
  it to the handler.
628
644
  """
629
- self._part.build()
630
645
  self._callback_handler.finalize_part(self.part)
631
646
  self._mesh_block_count += 1
632
647
 
@@ -686,10 +701,13 @@ class DSGSession(object):
686
701
  """Handle a DSG UPDATE_VIEW command
687
702
 
688
703
  Parameters
689
- ----------s
704
+ ----------
690
705
  view:
691
706
  The command coming from the EnSight stream.
692
707
  """
708
+ self._finish_part()
693
709
  self._scene_bounds = None
694
710
  self._groups[view.id] = view
711
+ if len(view.timeline) == 2:
712
+ self.cur_timeline = [view.timeline[0], view.timeline[1]]
695
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.
@@ -21,7 +24,7 @@ class Omniverse:
21
24
 
22
25
  Parameters
23
26
  ----------
24
- interface:
27
+ interface: Union["ensight_api.ensight", "ensight"]
25
28
  Entity that provides the ``ensight`` namespace. In the case of
26
29
  EnSight Python, the ``ensight`` module is passed. In the case
27
30
  of PyEnSight, ``Session.ensight`` is passed.
@@ -35,20 +38,93 @@ class Omniverse:
35
38
 
36
39
  Examples
37
40
  --------
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()
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}")