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.
- ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/__init__.py +1 -0
- ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py +366 -0
- ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml +58 -0
- ansys/pyensight/core/exts/ansys.geometry.service/data/icon.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.service/data/preview.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/CHANGELOG.md +8 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/README.md +13 -0
- ansys/pyensight/core/exts/ansys.geometry.service/docs/index.rst +18 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/__init__.py +1 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +170 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml +49 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/data/icon.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/data/preview.png +0 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/CHANGELOG.md +8 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/README.md +13 -0
- ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/index.rst +18 -0
- ansys/pyensight/core/launcher.py +36 -1
- ansys/pyensight/core/locallauncher.py +3 -1
- ansys/pyensight/core/utils/dsg_server.py +32 -17
- ansys/pyensight/core/utils/omniverse.py +160 -104
- ansys/pyensight/core/utils/omniverse_dsg_server.py +85 -36
- ansys/pyensight/core/utils/parts.py +5 -5
- ansys/pyensight/core/utils/variables.py +36 -1
- {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/METADATA +4 -4
- ansys_pyensight_core-0.8.5.dist-info/RECORD +52 -0
- ansys_pyensight_core-0.8.3.dist-info/RECORD +0 -36
- {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/LICENSE +0 -0
- {ansys_pyensight_core-0.8.3.dist-info → ansys_pyensight_core-0.8.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .extension import *
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import carb.settings
|
|
9
|
+
import omni.ext
|
|
10
|
+
import omni.kit.app
|
|
11
|
+
import omni.kit.pipapi
|
|
12
|
+
import psutil
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import ansys.pyensight.core
|
|
16
|
+
except ModuleNotFoundError:
|
|
17
|
+
logging.warning("ansys.geometry.server - Installing ansys-pyensight-core")
|
|
18
|
+
omni.kit.pipapi.install("ansys-pyensight-core")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_kit_filename() -> Optional[str]:
|
|
22
|
+
"""
|
|
23
|
+
Use a combination of the current omniverse application and the information
|
|
24
|
+
in the local .nvidia-omniverse/config/omniverse.toml file to come up with
|
|
25
|
+
the pathname of a kit executable suitable for hosting another copy of the
|
|
26
|
+
ansys.geometry.server kit.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
The pathname of a kit executable or None
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
# get the current application
|
|
34
|
+
app = omni.kit.app.get_app()
|
|
35
|
+
app_name = app.get_app_filename().split(".")[-1]
|
|
36
|
+
app_version = app.get_app_version().split("-")[0]
|
|
37
|
+
|
|
38
|
+
# parse the toml config file for the location of the installed apps
|
|
39
|
+
try:
|
|
40
|
+
import tomllib
|
|
41
|
+
except ModuleNotFoundError:
|
|
42
|
+
import pip._vendor.tomli as tomllib
|
|
43
|
+
|
|
44
|
+
homedir = os.path.expanduser("~")
|
|
45
|
+
ov_config = os.path.join(homedir, ".nvidia-omniverse", "config", "omniverse.toml")
|
|
46
|
+
with open(ov_config, "r") as ov_file:
|
|
47
|
+
ov_data = ov_file.read()
|
|
48
|
+
config = tomllib.loads(ov_data)
|
|
49
|
+
appdir = config.get("paths", {}).get("library_root", None)
|
|
50
|
+
appdir = os.path.join(appdir, f"{app_name}-{app_version}")
|
|
51
|
+
|
|
52
|
+
# Windows: 'kit.bat' in '.' or 'kit' followed by 'kit.exe' in '.' or 'kit'
|
|
53
|
+
# Linux: 'kit.sh' in '.' or 'kit' followed by 'kit' in '.' or 'kit'
|
|
54
|
+
exe_names = ["kit.sh", "kit"]
|
|
55
|
+
if sys.platform.startswith("win"):
|
|
56
|
+
exe_names = ["kit.bat", "kit.exe"]
|
|
57
|
+
|
|
58
|
+
# look in 4 places...
|
|
59
|
+
for dir_name in [appdir, os.path.join(appdir, "kit")]:
|
|
60
|
+
for name in exe_names:
|
|
61
|
+
if os.path.exists(os.path.join(dir_name, name)):
|
|
62
|
+
return os.path.join(dir_name, name)
|
|
63
|
+
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AnsysGeometryServiceServerExtension(omni.ext.IExt):
|
|
68
|
+
"""
|
|
69
|
+
This class is an Omniverse kit. The kit is capable of creating a
|
|
70
|
+
connection to an Ansys Distributed Scene Graph service and pushing
|
|
71
|
+
the graph into an Omniverse Nucleus.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
_service_instance = None
|
|
75
|
+
|
|
76
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
77
|
+
super().__init__(*args, **kwargs)
|
|
78
|
+
ext_name = __name__.rsplit(".", 1)[0]
|
|
79
|
+
self._logger = logging.getLogger(ext_name)
|
|
80
|
+
self._dsg_uri = self._setting("dsgUrl", "ENSIGHT_GRPC_URI")
|
|
81
|
+
self._omni_uri = self._setting("omniUrl", "ENSIGHT_OMNI_URI")
|
|
82
|
+
self._security_token = self._setting("securityCode", "ENSIGHT_SECURITY_TOKEN")
|
|
83
|
+
self._temporal = self._setting("temporal") != "0"
|
|
84
|
+
self._vrmode = self._setting("vrmode") != "0"
|
|
85
|
+
self._normalize_geometry = self._setting("normalizeGeometry") != "0"
|
|
86
|
+
self._version = "unknown"
|
|
87
|
+
self._shutdown = False
|
|
88
|
+
self._server_process = None
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def dsg_uri(self) -> str:
|
|
92
|
+
"""The endpoint of a Dynamic Scene Graph service: grpc://{hostname}:{port}"""
|
|
93
|
+
return self._dsg_uri
|
|
94
|
+
|
|
95
|
+
@dsg_uri.setter
|
|
96
|
+
def dsg_uri(self, uri: str) -> None:
|
|
97
|
+
self._dsg_uri = uri
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def omni_uri(self) -> str:
|
|
101
|
+
"""The endpoint of an Omniverse Nucleus service: omniverse://{hostname}/{path}"""
|
|
102
|
+
return self._omni_uri
|
|
103
|
+
|
|
104
|
+
@omni_uri.setter
|
|
105
|
+
def omni_uri(self, value: str) -> None:
|
|
106
|
+
self._omni_uri = value
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def security_token(self) -> str:
|
|
110
|
+
"""The security token of the DSG service instance."""
|
|
111
|
+
return self._security_token
|
|
112
|
+
|
|
113
|
+
@security_token.setter
|
|
114
|
+
def security_token(self, value: str) -> None:
|
|
115
|
+
self._security_token = value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def temporal(self) -> bool:
|
|
119
|
+
"""If True, the DSG update should include all timesteps."""
|
|
120
|
+
return self._temporal
|
|
121
|
+
|
|
122
|
+
@temporal.setter
|
|
123
|
+
def temporal(self, value: bool) -> None:
|
|
124
|
+
self._temporal = bool(value)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def vrmode(self) -> bool:
|
|
128
|
+
"""If True, the DSG update should not include camera transforms."""
|
|
129
|
+
return self._vrmode
|
|
130
|
+
|
|
131
|
+
@vrmode.setter
|
|
132
|
+
def vrmode(self, value: bool) -> None:
|
|
133
|
+
self._vrmode = bool(value)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def normalize_geometry(self) -> bool:
|
|
137
|
+
"""If True, the DSG geometry should be remapped into normalized space."""
|
|
138
|
+
return self._normalize_geometry
|
|
139
|
+
|
|
140
|
+
@normalize_geometry.setter
|
|
141
|
+
def normalize_geometry(self, val: bool) -> None:
|
|
142
|
+
self._normalize_geometry = val
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def get_instance(cls) -> Optional["AnsysGeometryServiceServerExtension"]:
|
|
146
|
+
return cls._service_instance
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def _setting(cls, name: str, env_varname: str = "") -> str:
|
|
150
|
+
"""
|
|
151
|
+
Get a CLI option value. First check if any specified
|
|
152
|
+
environment variable is present and if so, return that value.
|
|
153
|
+
Next, check to see if a command line value is set and return
|
|
154
|
+
that. Finally, fall back to the value (if any) specified in
|
|
155
|
+
the kit toml file.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
name
|
|
160
|
+
The name of the command line flag to check the value of.
|
|
161
|
+
env_varname
|
|
162
|
+
Optional name of the environment variable to check the value of.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
A string or None.
|
|
167
|
+
"""
|
|
168
|
+
# any environmental variable trumps them all.
|
|
169
|
+
if env_varname:
|
|
170
|
+
value = os.environ.get(env_varname, None)
|
|
171
|
+
if value:
|
|
172
|
+
return value
|
|
173
|
+
settings = carb.settings.get_settings()
|
|
174
|
+
ext_name = __name__.rsplit(".", 1)[0]
|
|
175
|
+
s = f"/exts/{ext_name}/{name}"
|
|
176
|
+
return settings.get(s)
|
|
177
|
+
|
|
178
|
+
def info(self, text: str) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Send message to the logger at the info level.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
text
|
|
185
|
+
The message to send.
|
|
186
|
+
"""
|
|
187
|
+
self._logger.info(text)
|
|
188
|
+
|
|
189
|
+
def warning(self, text: str) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Send message to the logger at the warning level.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
text
|
|
196
|
+
The message to send.
|
|
197
|
+
"""
|
|
198
|
+
self._logger.warning(text)
|
|
199
|
+
|
|
200
|
+
def error(self, text: str) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Send message to the logger at the error level.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
text
|
|
207
|
+
The message to send.
|
|
208
|
+
"""
|
|
209
|
+
self._logger.error(text)
|
|
210
|
+
|
|
211
|
+
def on_startup(self, ext_id: str) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Called by Omniverse when the kit instance is started.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
ext_id
|
|
218
|
+
The specific version of the kit.
|
|
219
|
+
"""
|
|
220
|
+
self._version = ext_id
|
|
221
|
+
self.info(f"ANSYS geometry service server startup: {self._version}")
|
|
222
|
+
AnsysGeometryServiceServerExtension._service_instance = self
|
|
223
|
+
if self._setting("help") is not None:
|
|
224
|
+
self.help()
|
|
225
|
+
elif self._setting("run") is not None:
|
|
226
|
+
self.run_server()
|
|
227
|
+
|
|
228
|
+
def on_shutdown(self) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Called by Omniverse when the kit instance is shutting down.
|
|
231
|
+
"""
|
|
232
|
+
self.info("ANSYS geometry service server shutdown")
|
|
233
|
+
self.shutdown()
|
|
234
|
+
AnsysGeometryServiceServerExtension._service_instance = None
|
|
235
|
+
|
|
236
|
+
def help(self) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Send the CLI help output to logging.
|
|
239
|
+
"""
|
|
240
|
+
self.warning(f"ANSYS Omniverse Geometry Service: {self._version}")
|
|
241
|
+
self.warning(" --/exts/ansys.geometry.service/help=1")
|
|
242
|
+
self.warning(" Display this help.")
|
|
243
|
+
self.warning(" --/exts/ansys.geometry.service/run=1")
|
|
244
|
+
self.warning(" Run the server.")
|
|
245
|
+
self.warning(" --/exts/ansys.geometry.service/omniUrl=URL")
|
|
246
|
+
self.warning(f" Omniverse pathname. (default: {self.omni_uri})")
|
|
247
|
+
self.warning(" --/exts/ansys.geometry.service/dsgUrl=URL")
|
|
248
|
+
self.warning(f" Dynamic Scene Graph connection URL. (default: {self.dsg_uri})")
|
|
249
|
+
self.warning(" --/exts/ansys.geometry.service/securityCode=TOKEN")
|
|
250
|
+
self.warning(f" Dynamic Scene Graph security token. (default: {self.security_token})")
|
|
251
|
+
self.warning(" --/exts/ansys.geometry.service/temporal=0|1")
|
|
252
|
+
self.warning(
|
|
253
|
+
f" If non-zero, include all timeseteps in the scene. (default: {self.temporal})"
|
|
254
|
+
)
|
|
255
|
+
self.warning(" --/exts/ansys.geometry.service/vrmode=0|1")
|
|
256
|
+
self.warning(
|
|
257
|
+
f" If non-zero, do not include a camera in the scene. (default: {self.vrmode})"
|
|
258
|
+
)
|
|
259
|
+
self.warning(" --/exts/ansys.geometry.service/normalizeGeometry=0|1")
|
|
260
|
+
self.warning(
|
|
261
|
+
f" If non-zero, remap the geometry to the domain [-1,-1,-1]-[1,1,1]. (default: {self.normalize_geometry})"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def stop_server(self) -> None:
|
|
265
|
+
"""
|
|
266
|
+
If a DSG server connection has been started, stop it. It could be in
|
|
267
|
+
process or a subprocess.
|
|
268
|
+
"""
|
|
269
|
+
self._shutdown = True
|
|
270
|
+
if self._server_process:
|
|
271
|
+
for child in psutil.Process(self._server_process.pid).children(recursive=True):
|
|
272
|
+
child.kill()
|
|
273
|
+
self._server_process.kill()
|
|
274
|
+
self._server_process = None
|
|
275
|
+
|
|
276
|
+
def launch_server(self) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Launch a DSG to Omniverse server as a subprocess.
|
|
279
|
+
"""
|
|
280
|
+
if self._server_process:
|
|
281
|
+
self.warning("Only a single subprocess server is supported.")
|
|
282
|
+
return
|
|
283
|
+
kit_name = find_kit_filename()
|
|
284
|
+
if kit_name is None:
|
|
285
|
+
self.warning("Unable to determine a kit executable pathname.")
|
|
286
|
+
return
|
|
287
|
+
self.info(f"Using {kit_name} to launch the server")
|
|
288
|
+
cmd = [kit_name]
|
|
289
|
+
# kit extension location
|
|
290
|
+
kit_dir = __file__
|
|
291
|
+
for _ in range(5):
|
|
292
|
+
kit_dir = os.path.dirname(kit_dir)
|
|
293
|
+
cmd.extend(["--ext-folder", kit_dir])
|
|
294
|
+
cmd.extend(["--enable", "ansys.geometry.service"])
|
|
295
|
+
if self.security_token:
|
|
296
|
+
cmd.append(f"--/exts/ansys.geometry.service/securityCode={self.security_token}")
|
|
297
|
+
if self.temporal:
|
|
298
|
+
cmd.append("--/exts/ansys.geometry.service/temporal=1")
|
|
299
|
+
if self.vrmode:
|
|
300
|
+
cmd.append("--/exts/ansys.geometry.service/vrmode=1")
|
|
301
|
+
if self.normalize_geometry:
|
|
302
|
+
cmd.append("--/exts/ansys.geometry.service/normalizeGeometry=1")
|
|
303
|
+
cmd.append(f"--/exts/ansys.geometry.service/omniUrl={self.omni_uri}")
|
|
304
|
+
cmd.append(f"--/exts/ansys.geometry.service/dsgUrl={self.dsg_uri}")
|
|
305
|
+
cmd.append("--/exts/ansys.geometry.service/run=1")
|
|
306
|
+
env_vars = os.environ.copy()
|
|
307
|
+
working_dir = os.path.join(os.path.dirname(ansys.pyensight.core.__file__), "utils")
|
|
308
|
+
self._server_process = subprocess.Popen(cmd, close_fds=True, env=env_vars, cwd=working_dir)
|
|
309
|
+
|
|
310
|
+
def run_server(self) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Run a DSG to Omniverse server in process.
|
|
313
|
+
|
|
314
|
+
Note: this method does not return until the DSG connection is dropped or
|
|
315
|
+
self.stop_server() has been called.
|
|
316
|
+
"""
|
|
317
|
+
try:
|
|
318
|
+
import ansys.pyensight.core.utils.dsg_server as dsg_server
|
|
319
|
+
import ansys.pyensight.core.utils.omniverse_dsg_server as ov_dsg_server
|
|
320
|
+
except ImportError as e:
|
|
321
|
+
self.error(f"Unable to load DSG service core: {str(e)}")
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
# Note: This is temporary. The correct fix will be included in
|
|
325
|
+
# the pyensight 0.8.5 wheel. The OmniverseWrapper assumes the CWD
|
|
326
|
+
# to be the directory with the "resource" directory.
|
|
327
|
+
os.chdir(os.path.dirname(ov_dsg_server.__file__))
|
|
328
|
+
|
|
329
|
+
# Build the Omniverse connection
|
|
330
|
+
omni_link = ov_dsg_server.OmniverseWrapper(path=self._omni_uri, verbose=1)
|
|
331
|
+
self.info("Omniverse connection established.")
|
|
332
|
+
|
|
333
|
+
# parse the DSG USI
|
|
334
|
+
parsed = urlparse(self.dsg_uri)
|
|
335
|
+
port = parsed.port
|
|
336
|
+
host = parsed.hostname
|
|
337
|
+
|
|
338
|
+
# link it to a DSG session
|
|
339
|
+
update_handler = ov_dsg_server.OmniverseUpdateHandler(omni_link)
|
|
340
|
+
dsg_link = dsg_server.DSGSession(
|
|
341
|
+
port=port,
|
|
342
|
+
host=host,
|
|
343
|
+
vrmode=self.vrmode,
|
|
344
|
+
security_code=self.security_token,
|
|
345
|
+
verbose=1,
|
|
346
|
+
normalize_geometry=self.normalize_geometry,
|
|
347
|
+
handler=update_handler,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Start the DSG link
|
|
351
|
+
self.info(f"Making DSG connection to: {self.dsg_uri}")
|
|
352
|
+
err = dsg_link.start()
|
|
353
|
+
if err < 0:
|
|
354
|
+
self.error("Omniverse connection failed.")
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
# Initial pull request
|
|
358
|
+
dsg_link.request_an_update(animation=self.temporal)
|
|
359
|
+
|
|
360
|
+
# until the link is dropped, continue
|
|
361
|
+
while not dsg_link.is_shutdown() and not self._shutdown:
|
|
362
|
+
dsg_link.handle_one_update()
|
|
363
|
+
|
|
364
|
+
self.info("Shutting down DSG connection")
|
|
365
|
+
dsg_link.end()
|
|
366
|
+
omni_link.shutdown()
|
|
@@ -0,0 +1,58 @@
|
|
|
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 Server"
|
|
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.pipapi" = {}
|
|
38
|
+
"omni.client" = {}
|
|
39
|
+
"omni.usd" = {}
|
|
40
|
+
|
|
41
|
+
# Main python module this extension provides, it will be publicly available as "import ansys.geometry.service".
|
|
42
|
+
[[python.module]]
|
|
43
|
+
name = "ansys.geometry.service"
|
|
44
|
+
|
|
45
|
+
[[test]]
|
|
46
|
+
# Extra dependencies only to be used during test run
|
|
47
|
+
dependencies = [
|
|
48
|
+
"omni.kit.ui_test" # UI testing extension
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[settings]
|
|
52
|
+
# CLI setting defaults (note: "help" and "run" are also supported)
|
|
53
|
+
exts."ansys.geometry.service".dsgUrl = "grpc://127.0.0.1:5234"
|
|
54
|
+
exts."ansys.geometry.service".omniUrl = "omniverse://localhost/Users/test"
|
|
55
|
+
exts."ansys.geometry.service".securityCode = ""
|
|
56
|
+
exts."ansys.geometry.service".temporal = "0"
|
|
57
|
+
exts."ansys.geometry.service".vrmode = "0"
|
|
58
|
+
exts."ansys.geometry.service".normalizeGeometry = "0"
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ANSYS Omniverse Geometry Service [ansys.geometry.service]
|
|
2
|
+
|
|
3
|
+
The Omniverse extension provides a dynamic connection between an ANSYS
|
|
4
|
+
geometry source (e.g. ANSYS EnSight) and an Omniverse instance. The
|
|
5
|
+
connection runs as a standalone service capable of supporting scripted
|
|
6
|
+
execution utilizing just a kit CLI. It can also be launched via
|
|
7
|
+
PyEnSight and via a simple GUI provided by the [ansys.geometry.serviceui]
|
|
8
|
+
kit extension.
|
|
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.service
|
|
2
|
+
######################
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
.. toctree::
|
|
6
|
+
:maxdepth: 1
|
|
7
|
+
|
|
8
|
+
README
|
|
9
|
+
CHANGELOG
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.. automodule::"ansys.geometry.service"
|
|
13
|
+
:platform: Windows-x86_64, Linux-x86_64
|
|
14
|
+
:members:
|
|
15
|
+
:undoc-members:
|
|
16
|
+
:show-inheritance:
|
|
17
|
+
:imported-members:
|
|
18
|
+
:exclude-members: contextmanager
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .extension import *
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
import ansys.geometry.service
|
|
6
|
+
import omni.ext
|
|
7
|
+
import omni.ui as ui
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnsysGeometryServiceUIExtension(omni.ext.IExt):
|
|
11
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
self._window: Any = None
|
|
14
|
+
self._label_w: Any = None
|
|
15
|
+
self._logger = logging.getLogger(__name__.rsplit(".", 1)[0])
|
|
16
|
+
self._grpc = None
|
|
17
|
+
self._dsg_uri_w = None
|
|
18
|
+
self._dsg_token_w = None
|
|
19
|
+
self._omni_uri_w = None
|
|
20
|
+
self._temporal_w = None
|
|
21
|
+
self._vrmode_w = None
|
|
22
|
+
self._normalize_w = None
|
|
23
|
+
self._connect_w = None
|
|
24
|
+
self._update_w = None
|
|
25
|
+
self._connected = False
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def service(self) -> Optional["AnsysGeometryServiceUIExtension"]:
|
|
29
|
+
return ansys.geometry.service.AnsysGeometryServiceServerExtension.get_instance()
|
|
30
|
+
|
|
31
|
+
def info(self, text: str) -> None:
|
|
32
|
+
self._logger.info(text)
|
|
33
|
+
|
|
34
|
+
def warning(self, text: str) -> None:
|
|
35
|
+
self._logger.warning(text)
|
|
36
|
+
|
|
37
|
+
def error(self, text: str) -> None:
|
|
38
|
+
self._logger.error(text)
|
|
39
|
+
|
|
40
|
+
def launch_server(self) -> None:
|
|
41
|
+
if self._connected:
|
|
42
|
+
return
|
|
43
|
+
self.service.dsg_uri = self._dsg_uri_w.model.as_string
|
|
44
|
+
self.service.security_token = self._dsg_token_w.model.as_string
|
|
45
|
+
self.service.omni_uri = self._omni_uri_w.model.as_string
|
|
46
|
+
self.service.temporal = self._temporal_w.model.as_bool
|
|
47
|
+
self.service.vrmode = self._vrmode_w.model.as_bool
|
|
48
|
+
self.service.normalize_geometry = self._normalize_w.model.as_bool
|
|
49
|
+
self.service.launch_server()
|
|
50
|
+
|
|
51
|
+
# parse the DSG USI
|
|
52
|
+
parsed = urlparse(self.service.dsg_uri)
|
|
53
|
+
port = parsed.port
|
|
54
|
+
host = parsed.hostname
|
|
55
|
+
|
|
56
|
+
# make a direct grpc connection to the DSG server
|
|
57
|
+
from ansys.pyensight.core import ensight_grpc # pylint: disable=import-outside-toplevel
|
|
58
|
+
|
|
59
|
+
self._grpc = ensight_grpc.EnSightGRPC(
|
|
60
|
+
host=host, port=port, secret_key=self.service.security_token
|
|
61
|
+
)
|
|
62
|
+
self._grpc.connect()
|
|
63
|
+
|
|
64
|
+
self.info("Connected to DSG service")
|
|
65
|
+
self._connected = True
|
|
66
|
+
|
|
67
|
+
def stop_server(self) -> None:
|
|
68
|
+
if not self._connected:
|
|
69
|
+
return
|
|
70
|
+
self.service.stop_server()
|
|
71
|
+
self._grpc.shutdown()
|
|
72
|
+
|
|
73
|
+
self.info("Disconnect from DSG service")
|
|
74
|
+
self._connected = False
|
|
75
|
+
|
|
76
|
+
def connect_cb(self) -> None:
|
|
77
|
+
if self.service is None:
|
|
78
|
+
self.error("Unable to find ansys.geometry.service instance")
|
|
79
|
+
return
|
|
80
|
+
if self._connected:
|
|
81
|
+
self.stop_server()
|
|
82
|
+
else:
|
|
83
|
+
self.launch_server()
|
|
84
|
+
self.update_ui()
|
|
85
|
+
|
|
86
|
+
def update_cb(self) -> None:
|
|
87
|
+
if not self._connected:
|
|
88
|
+
self.error("No DSG service connected")
|
|
89
|
+
return
|
|
90
|
+
self._grpc.command("import enspyqtgui_int", do_eval=False)
|
|
91
|
+
update_cmd = "dynamicscenegraph://localhost/client/update"
|
|
92
|
+
if self._temporal_w.model.as_bool:
|
|
93
|
+
update_cmd += "?timesteps=1"
|
|
94
|
+
cmd = f'enspyqtgui_int.dynamic_scene_graph_command("{update_cmd}")'
|
|
95
|
+
self._grpc.command(cmd, do_eval=False)
|
|
96
|
+
|
|
97
|
+
def on_startup(self, ext_id: str) -> None:
|
|
98
|
+
self.info(f"ANSYS geometry service GUI startup: {ext_id}")
|
|
99
|
+
if self.service is None:
|
|
100
|
+
self.error("Unable to find ansys.geometry.service instance")
|
|
101
|
+
self.build_ui()
|
|
102
|
+
self.update_ui()
|
|
103
|
+
|
|
104
|
+
def update_ui(self) -> None:
|
|
105
|
+
if self._connected:
|
|
106
|
+
self._connect_w.text = "Disconnect from DSG Server"
|
|
107
|
+
else:
|
|
108
|
+
self._connect_w.text = "Connect to DSG Server"
|
|
109
|
+
self._update_w.enabled = self._connected
|
|
110
|
+
self._temporal_w.enabled = True
|
|
111
|
+
self._vrmode_w.enabled = not self._connected
|
|
112
|
+
self._normalize_w.enabled = not self._connected
|
|
113
|
+
self._dsg_uri_w.enabled = not self._connected
|
|
114
|
+
self._dsg_token_w.enabled = not self._connected
|
|
115
|
+
self._omni_uri_w.enabled = not self._connected
|
|
116
|
+
|
|
117
|
+
def build_ui(self) -> None:
|
|
118
|
+
self._window = ui.Window("ANSYS Geometry Service")
|
|
119
|
+
with self._window.frame:
|
|
120
|
+
with ui.VStack(height=0, spacing=5):
|
|
121
|
+
self._label_w = ui.Label("No connected DSG server")
|
|
122
|
+
|
|
123
|
+
with ui.HStack(spacing=5):
|
|
124
|
+
ui.Label("DSG Service URI:", alignment=ui.Alignment.RIGHT_CENTER, width=0)
|
|
125
|
+
self._dsg_uri_w = ui.StringField()
|
|
126
|
+
self._dsg_uri_w.model.as_string = self.service.dsg_uri
|
|
127
|
+
|
|
128
|
+
with ui.HStack(spacing=5):
|
|
129
|
+
ui.Label("DSG security code:", alignment=ui.Alignment.RIGHT_CENTER, width=0)
|
|
130
|
+
self._dsg_token_w = ui.StringField(password_mode=True)
|
|
131
|
+
self._dsg_token_w.model.as_string = self.service.security_token
|
|
132
|
+
|
|
133
|
+
with ui.HStack(spacing=5):
|
|
134
|
+
ui.Label("Omniverse URI:", alignment=ui.Alignment.RIGHT_CENTER, width=0)
|
|
135
|
+
self._omni_uri_w = ui.StringField()
|
|
136
|
+
self._omni_uri_w.model.as_string = self.service.omni_uri
|
|
137
|
+
|
|
138
|
+
with ui.HStack(spacing=5):
|
|
139
|
+
with ui.HStack(spacing=5):
|
|
140
|
+
self._temporal_w = ui.CheckBox(width=0)
|
|
141
|
+
self._temporal_w.model.set_value(self.service.temporal)
|
|
142
|
+
ui.Label("Temporal", alignment=ui.Alignment.LEFT_CENTER)
|
|
143
|
+
|
|
144
|
+
with ui.HStack(spacing=5):
|
|
145
|
+
self._vrmode_w = ui.CheckBox(width=0)
|
|
146
|
+
self._vrmode_w.model.set_value(self.service.vrmode)
|
|
147
|
+
ui.Label("VR Mode", alignment=ui.Alignment.LEFT_CENTER)
|
|
148
|
+
|
|
149
|
+
with ui.HStack(spacing=5):
|
|
150
|
+
self._normalize_w = ui.CheckBox(width=0)
|
|
151
|
+
self._normalize_w.model.set_value(self.service.normalize_geometry)
|
|
152
|
+
ui.Label("Normalize", alignment=ui.Alignment.LEFT_CENTER)
|
|
153
|
+
|
|
154
|
+
with ui.HStack():
|
|
155
|
+
self._connect_w = ui.Button("Connect to DSG Server", clicked_fn=self.connect_cb)
|
|
156
|
+
self._update_w = ui.Button("Request Update", clicked_fn=self.update_cb)
|
|
157
|
+
|
|
158
|
+
def on_shutdown(self) -> None:
|
|
159
|
+
self.info("ANSYS geometry service shutdown")
|
|
160
|
+
self.stop_server()
|
|
161
|
+
self._window = None
|
|
162
|
+
self._label_w = None
|
|
163
|
+
self._dsg_uri_w = None
|
|
164
|
+
self._dsg_token_w = None
|
|
165
|
+
self._omni_uri_w = None
|
|
166
|
+
self._temporal_w = None
|
|
167
|
+
self._vrmode_w = None
|
|
168
|
+
self._normalize_w = None
|
|
169
|
+
self._connect_w = None
|
|
170
|
+
self._update_w = None
|