ansys-pyensight-core 0.11.0__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.
- ansys/pyensight/core/__init__.py +41 -0
- ansys/pyensight/core/common.py +341 -0
- ansys/pyensight/core/deep_pixel_view.html +98 -0
- ansys/pyensight/core/dockerlauncher.py +1124 -0
- ansys/pyensight/core/dvs.py +872 -0
- ansys/pyensight/core/enscontext.py +345 -0
- ansys/pyensight/core/enshell_grpc.py +641 -0
- ansys/pyensight/core/ensight_grpc.py +874 -0
- ansys/pyensight/core/ensobj.py +515 -0
- ansys/pyensight/core/launch_ensight.py +296 -0
- ansys/pyensight/core/launcher.py +388 -0
- ansys/pyensight/core/libuserd.py +2110 -0
- ansys/pyensight/core/listobj.py +280 -0
- ansys/pyensight/core/locallauncher.py +579 -0
- ansys/pyensight/core/py.typed +0 -0
- ansys/pyensight/core/renderable.py +880 -0
- ansys/pyensight/core/session.py +1923 -0
- ansys/pyensight/core/sgeo_poll.html +24 -0
- ansys/pyensight/core/utils/__init__.py +21 -0
- ansys/pyensight/core/utils/adr.py +111 -0
- ansys/pyensight/core/utils/dsg_server.py +1220 -0
- ansys/pyensight/core/utils/export.py +606 -0
- ansys/pyensight/core/utils/omniverse.py +769 -0
- ansys/pyensight/core/utils/omniverse_cli.py +614 -0
- ansys/pyensight/core/utils/omniverse_dsg_server.py +1196 -0
- ansys/pyensight/core/utils/omniverse_glb_server.py +848 -0
- ansys/pyensight/core/utils/parts.py +1221 -0
- ansys/pyensight/core/utils/query.py +487 -0
- ansys/pyensight/core/utils/readers.py +300 -0
- ansys/pyensight/core/utils/resources/Materials/000_sky.exr +0 -0
- ansys/pyensight/core/utils/support.py +128 -0
- ansys/pyensight/core/utils/variables.py +2019 -0
- ansys/pyensight/core/utils/views.py +674 -0
- ansys_pyensight_core-0.11.0.dist-info/METADATA +309 -0
- ansys_pyensight_core-0.11.0.dist-info/RECORD +37 -0
- ansys_pyensight_core-0.11.0.dist-info/WHEEL +4 -0
- ansys_pyensight_core-0.11.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
# Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""ensight_grpc module
|
|
24
|
+
|
|
25
|
+
This package defines the EnSightGRPC class which provides a simpler
|
|
26
|
+
interface to the EnSight gRPC interface, including event streams.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
from concurrent import futures
|
|
30
|
+
import os
|
|
31
|
+
import platform
|
|
32
|
+
import sys
|
|
33
|
+
import tempfile
|
|
34
|
+
import threading
|
|
35
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union
|
|
36
|
+
import uuid
|
|
37
|
+
|
|
38
|
+
from ansys.api.pyensight.v0 import dynamic_scene_graph_pb2_grpc, ensight_pb2, ensight_pb2_grpc
|
|
39
|
+
from ansys.tools.common.cyberchannel import create_channel
|
|
40
|
+
import grpc
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from ansys.pyensight.core.utils.dsg_server import DSGSession
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EnSightGRPC(object):
|
|
47
|
+
"""Wrapper around a gRPC connection to an EnSight instance
|
|
48
|
+
|
|
49
|
+
This class provides an asynchronous interface to the EnSight
|
|
50
|
+
core gRPC interface. It can handle remote event
|
|
51
|
+
streams, providing a much simpler interface to the EnSight
|
|
52
|
+
application. The default is to make a connection to an EnSight
|
|
53
|
+
gRPC server on port 12345 on the loopback host.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
host: str, optional
|
|
58
|
+
Hostname where there EnSight gRPC server is running.
|
|
59
|
+
port: int, optional
|
|
60
|
+
Port to make the gRPC connection to
|
|
61
|
+
secret_key: str, optional
|
|
62
|
+
Connection secret key
|
|
63
|
+
grpc_use_tcp_sockets: bool, optional
|
|
64
|
+
If using gRPC, and if True, then allow TCP Socket based connections
|
|
65
|
+
instead of only local connections.
|
|
66
|
+
grpc_allow_network_connections: bool, optional
|
|
67
|
+
If using gRPC and using TCP Socket based connections, listen on all networks.
|
|
68
|
+
grpc_disable_tls: bool, optional
|
|
69
|
+
If using gRPC and using TCP Socket based connections, disable TLS.
|
|
70
|
+
grpc_uds_pathname: str, optional
|
|
71
|
+
If using gRPC and using Unix Domain Socket based connections, explicitly
|
|
72
|
+
set the pathname to the shared UDS file instead of using the default.
|
|
73
|
+
disable_grpc_options: bool, optional
|
|
74
|
+
Whether to disable the gRPC options check, and allow to run older
|
|
75
|
+
versions of EnSight
|
|
76
|
+
WARNING:
|
|
77
|
+
Overriding the default values for these options: grpc_use_tcp_sockets, grpc_allow_network_connections,
|
|
78
|
+
and grpc_disable_tls
|
|
79
|
+
can possibly permit control of this computer and any data which resides on it.
|
|
80
|
+
Modification of this configuration is not recommended. Please see the
|
|
81
|
+
documentation for your installed product for additional information.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
host: str = "127.0.0.1",
|
|
87
|
+
port: int = 12345,
|
|
88
|
+
secret_key: str = "",
|
|
89
|
+
grpc_use_tcp_sockets: bool = False,
|
|
90
|
+
grpc_allow_network_connections: bool = False,
|
|
91
|
+
grpc_disable_tls: bool = False,
|
|
92
|
+
grpc_uds_pathname: Optional[str] = None,
|
|
93
|
+
disable_grpc_options: bool = False,
|
|
94
|
+
):
|
|
95
|
+
self._host = host
|
|
96
|
+
self._port = port
|
|
97
|
+
self._channel = None
|
|
98
|
+
self._stub = None
|
|
99
|
+
self._dsg_stub = None
|
|
100
|
+
self._security_token = secret_key
|
|
101
|
+
self._grpc_use_tcp_sockets = grpc_use_tcp_sockets
|
|
102
|
+
self._grpc_allow_network_connections = grpc_allow_network_connections
|
|
103
|
+
self._grpc_disable_tls = grpc_disable_tls
|
|
104
|
+
self._grpc_uds_pathname = grpc_uds_pathname
|
|
105
|
+
self._session_name: str = ""
|
|
106
|
+
# Streaming APIs
|
|
107
|
+
# Event (strings)
|
|
108
|
+
self._event_stream = None
|
|
109
|
+
self._event_thread: Optional[threading.Thread] = None
|
|
110
|
+
self._events: List[Any] = list()
|
|
111
|
+
# Callback for events (self._events not used)
|
|
112
|
+
self._event_callback: Optional[Callable] = None
|
|
113
|
+
self._prefix: Optional[str] = None
|
|
114
|
+
self._shmem_module = None
|
|
115
|
+
self._shmem_filename: Optional[str] = None
|
|
116
|
+
self._shmem_client = None
|
|
117
|
+
self._image_stream = None
|
|
118
|
+
self._image_thread = None
|
|
119
|
+
self._image = None
|
|
120
|
+
self._image_number = 0
|
|
121
|
+
self._sub_service = None
|
|
122
|
+
self._dsg_session: Optional["DSGSession"] = None
|
|
123
|
+
self._disable_grpc_options = disable_grpc_options
|
|
124
|
+
|
|
125
|
+
def set_dsg_session(self, dsg_session: "DSGSession"):
|
|
126
|
+
self._dsg_session = dsg_session
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def host(self) -> str:
|
|
130
|
+
"""The gRPC server (EnSight) hostname"""
|
|
131
|
+
return self._host
|
|
132
|
+
|
|
133
|
+
def port(self) -> int:
|
|
134
|
+
"""The gRPC server (EnSight) port number"""
|
|
135
|
+
return self._port
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def security_token(self) -> str:
|
|
139
|
+
"""The gRPC server (EnSight) secret key
|
|
140
|
+
|
|
141
|
+
EnSight supports a security token in either numeric (-security {int}) or
|
|
142
|
+
string (ENSIGHT_SECURITY_TOKEN environmental variable) form. If EnSight
|
|
143
|
+
is using a security token, all gRPC calls must include this token. This
|
|
144
|
+
call sets the token for all grPC calls made by this class.
|
|
145
|
+
"""
|
|
146
|
+
return self._security_token
|
|
147
|
+
|
|
148
|
+
@security_token.setter
|
|
149
|
+
def security_token(self, name: str) -> None:
|
|
150
|
+
self._security_token = name # pragma: no cover
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def grpc_use_tcp_sockets(self) -> bool:
|
|
154
|
+
"""Get whether to use Unix Domain Sockets or TCP Sockets for gRPC"""
|
|
155
|
+
return self._grpc_use_tcp_sockets
|
|
156
|
+
|
|
157
|
+
@grpc_use_tcp_sockets.setter
|
|
158
|
+
def grpc_use_tcp_sockets(self, use_sockets: bool) -> None:
|
|
159
|
+
"""Set whether to use Unix Domain Sockets or TCP Sockets for gRPC"""
|
|
160
|
+
self._grpc_use_tcp_sockets = use_sockets
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def grpc_allow_network_connections(self) -> bool:
|
|
164
|
+
"""Get whether to allow listening on all networks if using TCP Sockets for gRPC"""
|
|
165
|
+
return self._grpc_use_tcp_sockets
|
|
166
|
+
|
|
167
|
+
@grpc_allow_network_connections.setter
|
|
168
|
+
def grpc_allow_network_connections(self, allow: bool) -> None:
|
|
169
|
+
"""Set whether to allow listening on all networks if using TCP Sockets for gRPC"""
|
|
170
|
+
self._grpc_allow_network_connections = allow
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def grpc_disable_tls(self) -> bool:
|
|
174
|
+
"""Get whether to use TLS for TCP Sockets for gRPC"""
|
|
175
|
+
return self._grpc_disable_tls
|
|
176
|
+
|
|
177
|
+
@grpc_disable_tls.setter
|
|
178
|
+
def grpc_disable_tls(self, disable_tls: bool) -> None:
|
|
179
|
+
"""Set whether to use TLS for TCP Sockets for gRPC"""
|
|
180
|
+
self._grpc_disable_tls = disable_tls
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def grpc_uds_pathname(self) -> Optional[str]:
|
|
184
|
+
"""Get the pathname for the UDS file if not using the default for gRPC"""
|
|
185
|
+
return self._grpc_uds_pathname
|
|
186
|
+
|
|
187
|
+
@grpc_uds_pathname.setter
|
|
188
|
+
def grpc_uds_pathname(self, uds_pathname: str) -> None:
|
|
189
|
+
"""Set the pathname for the UDS file if not using the default for gRPC"""
|
|
190
|
+
self._grpc_uds_pathname = uds_pathname
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def session_name(self) -> str:
|
|
194
|
+
"""The gRPC server session name
|
|
195
|
+
|
|
196
|
+
EnSight gRPC calls can include the session name via 'session_name' metadata.
|
|
197
|
+
A client session may provide a session name via this property.
|
|
198
|
+
"""
|
|
199
|
+
return self._session_name
|
|
200
|
+
|
|
201
|
+
@session_name.setter
|
|
202
|
+
def session_name(self, name: str) -> None:
|
|
203
|
+
self._session_name = name
|
|
204
|
+
|
|
205
|
+
def shutdown(self, stop_ensight: bool = False, force: bool = False) -> None:
|
|
206
|
+
"""Close down the gRPC connection
|
|
207
|
+
|
|
208
|
+
Disconnect all connections to the gRPC server. If stop_ensight is True, send the
|
|
209
|
+
'Exit' command to the EnSight gRPC server.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
stop_ensight: bool, optional
|
|
214
|
+
if True, send an 'Exit' command to the gRPC server.
|
|
215
|
+
force: bool, optional
|
|
216
|
+
if stop_ensight and force are true, stop EnSight aggressively
|
|
217
|
+
"""
|
|
218
|
+
if self.is_connected(): # pragma: no cover
|
|
219
|
+
# if requested, send 'Exit'
|
|
220
|
+
if stop_ensight: # pragma: no cover
|
|
221
|
+
# the gRPC ExitRequest is exactly that, a request in some
|
|
222
|
+
# cases the operation needs to be forced
|
|
223
|
+
if force: # pragma: no cover
|
|
224
|
+
try:
|
|
225
|
+
self.command("ensight.exit(0)", do_eval=False)
|
|
226
|
+
except IOError: # pragma: no cover
|
|
227
|
+
# we expect this as the exit can result in the gRPC call failing
|
|
228
|
+
pass # pragma: no cover
|
|
229
|
+
else:
|
|
230
|
+
if self._stub: # pragma: no cover
|
|
231
|
+
_ = self._stub.Exit(
|
|
232
|
+
ensight_pb2.ExitRequest(), metadata=self._metadata()
|
|
233
|
+
) # pragma: no cover
|
|
234
|
+
# clean up control objects
|
|
235
|
+
self._stub = None
|
|
236
|
+
self._dsg_stub = None
|
|
237
|
+
if self._channel:
|
|
238
|
+
self._channel.close()
|
|
239
|
+
self._channel = None
|
|
240
|
+
if self._shmem_client:
|
|
241
|
+
if self._shmem_module:
|
|
242
|
+
self._shmem_module.stream_destroy(self._shmem_client)
|
|
243
|
+
else:
|
|
244
|
+
self.command("ensight_grpc_shmem.stream_destroy(enscl._shmem_client)")
|
|
245
|
+
self._shmem_client = None
|
|
246
|
+
|
|
247
|
+
def is_connected(self) -> bool:
|
|
248
|
+
"""Check to see if the gRPC connection is live
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
True if the connection is active.
|
|
253
|
+
"""
|
|
254
|
+
return self._channel is not None
|
|
255
|
+
|
|
256
|
+
def connect(self, timeout: float = 15.0) -> None:
|
|
257
|
+
"""Establish the gRPC connection to EnSight
|
|
258
|
+
|
|
259
|
+
Attempt to connect to an EnSight gRPC server using the host and port
|
|
260
|
+
established by the constructor. Note on failure, this function just
|
|
261
|
+
returns, but is_connected() will return False.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
timeout: float
|
|
266
|
+
how long to wait for the connection to timeout
|
|
267
|
+
"""
|
|
268
|
+
if self.is_connected():
|
|
269
|
+
return
|
|
270
|
+
# set up the channel
|
|
271
|
+
transport_mode = None
|
|
272
|
+
host = None
|
|
273
|
+
port = None
|
|
274
|
+
uds_service = None
|
|
275
|
+
uds_dir = None
|
|
276
|
+
options = [
|
|
277
|
+
("grpc.max_receive_message_length", -1),
|
|
278
|
+
("grpc.max_send_message_length", -1),
|
|
279
|
+
("grpc.testing.fixed_reconnect_backoff_ms", 1100),
|
|
280
|
+
]
|
|
281
|
+
if self._grpc_use_tcp_sockets:
|
|
282
|
+
host = self._host
|
|
283
|
+
transport_mode = "mtls"
|
|
284
|
+
if self._grpc_disable_tls:
|
|
285
|
+
transport_mode = "insecure"
|
|
286
|
+
port = self._port
|
|
287
|
+
else:
|
|
288
|
+
host = "127.0.0.1"
|
|
289
|
+
if sys.platform == "win32":
|
|
290
|
+
transport_mode = "wnua"
|
|
291
|
+
port = self._port
|
|
292
|
+
else:
|
|
293
|
+
transport_mode = "uds"
|
|
294
|
+
if self._grpc_uds_pathname:
|
|
295
|
+
uds_service = os.path.basename(self._grpc_uds_pathname)
|
|
296
|
+
uds_dir = os.path.dirname(self._grpc_uds_pathname)
|
|
297
|
+
else:
|
|
298
|
+
uds_dir = "/tmp"
|
|
299
|
+
uds_service = "greeter"
|
|
300
|
+
# Ignore the security options if the version of EnSight cannot handle them
|
|
301
|
+
if self._disable_grpc_options:
|
|
302
|
+
transport_mode = "insecure"
|
|
303
|
+
host = self._host
|
|
304
|
+
port = self._port
|
|
305
|
+
uds_dir = None
|
|
306
|
+
uds_service = None
|
|
307
|
+
self._channel = create_channel(
|
|
308
|
+
host=host,
|
|
309
|
+
port=port,
|
|
310
|
+
transport_mode=transport_mode,
|
|
311
|
+
uds_dir=uds_dir,
|
|
312
|
+
uds_service=uds_service,
|
|
313
|
+
grpc_options=options,
|
|
314
|
+
)
|
|
315
|
+
try:
|
|
316
|
+
grpc.channel_ready_future(self._channel).result(timeout=timeout)
|
|
317
|
+
except grpc.FutureTimeoutError: # pragma: no cover
|
|
318
|
+
self._channel = None # pragma: no cover
|
|
319
|
+
return # pragma: no cover
|
|
320
|
+
# hook up the stub interface
|
|
321
|
+
self._stub = ensight_pb2_grpc.EnSightServiceStub(self._channel)
|
|
322
|
+
self._dsg_stub = dynamic_scene_graph_pb2_grpc.DynamicSceneGraphServiceStub(self._channel)
|
|
323
|
+
|
|
324
|
+
def _metadata(self) -> List[Tuple[bytes, Union[str, bytes]]]:
|
|
325
|
+
"""Compute the gRPC stream metadata
|
|
326
|
+
|
|
327
|
+
Compute the list to be passed to the gRPC calls for things like security
|
|
328
|
+
and the session name.
|
|
329
|
+
|
|
330
|
+
"""
|
|
331
|
+
ret: List[Tuple[bytes, Union[str, bytes]]] = list()
|
|
332
|
+
s: Union[str, bytes]
|
|
333
|
+
if self._security_token: # pragma: no cover
|
|
334
|
+
s = self._security_token
|
|
335
|
+
if type(s) == str: # pragma: no cover
|
|
336
|
+
s = s.encode("utf-8")
|
|
337
|
+
ret.append((b"shared_secret", s))
|
|
338
|
+
if self.session_name: # pragma: no cover
|
|
339
|
+
s = self.session_name.encode("utf-8")
|
|
340
|
+
ret.append((b"session_name", s))
|
|
341
|
+
return ret
|
|
342
|
+
|
|
343
|
+
def render(
|
|
344
|
+
self,
|
|
345
|
+
width: int = 640,
|
|
346
|
+
height: int = 480,
|
|
347
|
+
aa: int = 1,
|
|
348
|
+
png: bool = True,
|
|
349
|
+
highlighting: bool = False,
|
|
350
|
+
) -> bytes:
|
|
351
|
+
"""Generate a rendering of the current EnSight scene
|
|
352
|
+
|
|
353
|
+
Render the current scene at a specific size and using a specific number of anti-aliasing
|
|
354
|
+
passes. The return value can be a byte array (width*height*3) bytes or a PNG image.
|
|
355
|
+
|
|
356
|
+
Parameters
|
|
357
|
+
----------
|
|
358
|
+
width: int, optional
|
|
359
|
+
width of the image to render
|
|
360
|
+
height: int, optional
|
|
361
|
+
height of the image to render
|
|
362
|
+
aa: int, optional
|
|
363
|
+
number of antialiasing passes to use in generating the image
|
|
364
|
+
png: bool, optional
|
|
365
|
+
if True, the return value is a PNG image bytestream. Otherwise, it is a simple
|
|
366
|
+
bytes object with width*height*3 values.
|
|
367
|
+
highlighting: bool, optional
|
|
368
|
+
if True, selection highlighting will be included in the image.
|
|
369
|
+
|
|
370
|
+
Returns
|
|
371
|
+
-------
|
|
372
|
+
bytes
|
|
373
|
+
bytes object representation of the rendered image
|
|
374
|
+
|
|
375
|
+
Raises
|
|
376
|
+
------
|
|
377
|
+
IOError if the operation fails
|
|
378
|
+
"""
|
|
379
|
+
self.connect()
|
|
380
|
+
ret_type = ensight_pb2.RenderRequest.IMAGE_RAW
|
|
381
|
+
if png: # pragma: no cover
|
|
382
|
+
ret_type = ensight_pb2.RenderRequest.IMAGE_PNG
|
|
383
|
+
response: Any
|
|
384
|
+
try:
|
|
385
|
+
if self._stub: # pragma: no cover
|
|
386
|
+
response = self._stub.RenderImage(
|
|
387
|
+
ensight_pb2.RenderRequest(
|
|
388
|
+
type=ret_type,
|
|
389
|
+
image_width=width,
|
|
390
|
+
image_height=height,
|
|
391
|
+
image_aa_passes=aa,
|
|
392
|
+
include_highlighting=highlighting,
|
|
393
|
+
),
|
|
394
|
+
metadata=self._metadata(),
|
|
395
|
+
)
|
|
396
|
+
except Exception: # pragma: no cover
|
|
397
|
+
raise IOError("gRPC connection dropped") # pragma: no cover
|
|
398
|
+
return response.value
|
|
399
|
+
|
|
400
|
+
def geometry(self) -> bytes:
|
|
401
|
+
"""Return the current scene geometry in glTF format
|
|
402
|
+
|
|
403
|
+
Package up the geometry currently being viewed in the EnSight session as
|
|
404
|
+
a glTF stream. Return this stream as an array of byte. Note: no
|
|
405
|
+
intermediate files are utilized.
|
|
406
|
+
|
|
407
|
+
Note: currently there is a limitation of glTF files to 2GB
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
bytes object representation of the glTF file
|
|
412
|
+
|
|
413
|
+
Raises
|
|
414
|
+
------
|
|
415
|
+
IOError if the operation fails
|
|
416
|
+
"""
|
|
417
|
+
self.connect()
|
|
418
|
+
response: Any
|
|
419
|
+
try:
|
|
420
|
+
if self._stub: # pragma: no cover
|
|
421
|
+
response = self._stub.GetGeometry(
|
|
422
|
+
ensight_pb2.GeometryRequest(type=ensight_pb2.GeometryRequest.GEOMETRY_GLB),
|
|
423
|
+
metadata=self._metadata(),
|
|
424
|
+
)
|
|
425
|
+
except Exception: # pragma: no cover
|
|
426
|
+
raise IOError("gRPC connection dropped") # pragma: no cover
|
|
427
|
+
return response.value
|
|
428
|
+
|
|
429
|
+
def command(self, command_string: str, do_eval: bool = True, json: bool = False) -> Any:
|
|
430
|
+
"""Send a Python command string to be executed in EnSight
|
|
431
|
+
|
|
432
|
+
The string will be run or evaluated in the EnSight Python interpreter via the
|
|
433
|
+
EnSightService::RunPython() gRPC all. If an exception or other error occurs, this
|
|
434
|
+
function will throw a RuntimeError. If do_eval is False, the return value will be None,
|
|
435
|
+
otherwise it will be the returned string (eval() will not be performed). If json is True,
|
|
436
|
+
the return value will be a JSON representation of the report execution result.
|
|
437
|
+
|
|
438
|
+
Parameters
|
|
439
|
+
----------
|
|
440
|
+
command_string: str
|
|
441
|
+
The string to execute
|
|
442
|
+
do_eval: bool, optional
|
|
443
|
+
If True, a return value will be computed and returned
|
|
444
|
+
json: bool, optional
|
|
445
|
+
If True and do_eval is True, the return value will be a JSON representation of
|
|
446
|
+
the evaluated value.
|
|
447
|
+
|
|
448
|
+
Returns
|
|
449
|
+
-------
|
|
450
|
+
Any
|
|
451
|
+
None, a string ready for Python eval() or a JSON string.
|
|
452
|
+
|
|
453
|
+
Raises
|
|
454
|
+
------
|
|
455
|
+
RuntimeError if the operation fails.
|
|
456
|
+
IOError if the communication fails.
|
|
457
|
+
"""
|
|
458
|
+
self.connect()
|
|
459
|
+
flags = ensight_pb2.PythonRequest.EXEC_RETURN_PYTHON
|
|
460
|
+
response: Any
|
|
461
|
+
if json: # pragma: no cover
|
|
462
|
+
flags = ensight_pb2.PythonRequest.EXEC_RETURN_JSON # pragma: no cover
|
|
463
|
+
if not do_eval:
|
|
464
|
+
flags = ensight_pb2.PythonRequest.EXEC_NO_RESULT
|
|
465
|
+
try:
|
|
466
|
+
if self._stub: # pragma: no cover
|
|
467
|
+
response = self._stub.RunPython(
|
|
468
|
+
ensight_pb2.PythonRequest(type=flags, command=command_string),
|
|
469
|
+
metadata=self._metadata(),
|
|
470
|
+
)
|
|
471
|
+
except Exception:
|
|
472
|
+
raise IOError("gRPC connection dropped")
|
|
473
|
+
if response.error < 0: # pragma: no cover
|
|
474
|
+
raise RuntimeError(response.value) # pragma: no cover
|
|
475
|
+
if flags == ensight_pb2.PythonRequest.EXEC_NO_RESULT:
|
|
476
|
+
return None
|
|
477
|
+
# This was moved externally so pre-processing could be performed
|
|
478
|
+
# elif flags == ensight_pb2.PythonRequest.EXEC_RETURN_PYTHON:
|
|
479
|
+
# return eval(response.value)
|
|
480
|
+
return response.value
|
|
481
|
+
|
|
482
|
+
def prefix(self) -> str:
|
|
483
|
+
"""Return the unique prefix for this instance.
|
|
484
|
+
|
|
485
|
+
Some EnSight gRPC APIs require a unique prefix so that EnSight can handle
|
|
486
|
+
multiple, simultaneous remote connections. This method will generate a GUID-based
|
|
487
|
+
prefix.
|
|
488
|
+
|
|
489
|
+
Returns
|
|
490
|
+
-------
|
|
491
|
+
str
|
|
492
|
+
A unique (for this session) prefix string of the form: grpc://{uuid}/
|
|
493
|
+
"""
|
|
494
|
+
# prefix URIs will have the format: "grpc://{uuid}/{callbackname}?enum={}&uid={}"
|
|
495
|
+
if self._prefix is None:
|
|
496
|
+
self._prefix = "grpc://" + str(uuid.uuid1()) + "/"
|
|
497
|
+
return self._prefix
|
|
498
|
+
|
|
499
|
+
def event_stream_enable(self, callback: Optional[Callable] = None) -> None:
|
|
500
|
+
"""Enable a simple gRPC-based event stream from EnSight
|
|
501
|
+
|
|
502
|
+
This method makes a EnSightService::GetEventStream() gRPC call into EnSight, returning
|
|
503
|
+
an ensightservice::EventReply stream. The method creates a thread to hold this
|
|
504
|
+
stream open and read new events from it. The thread adds the event strings to
|
|
505
|
+
a list of events stored on this instance. If callback is not None, the object
|
|
506
|
+
will be called with the event string, otherwise they can be retrieved using get_event().
|
|
507
|
+
"""
|
|
508
|
+
if self._event_stream is not None: # pragma: no cover
|
|
509
|
+
return # pragma: no cover
|
|
510
|
+
self._event_callback = callback
|
|
511
|
+
self.connect()
|
|
512
|
+
if self._stub: # pragma: no cover
|
|
513
|
+
self._event_stream = self._stub.GetEventStream(
|
|
514
|
+
ensight_pb2.EventStreamRequest(prefix=self.prefix()),
|
|
515
|
+
metadata=self._metadata(),
|
|
516
|
+
)
|
|
517
|
+
self._event_thread = threading.Thread(target=self._poll_events)
|
|
518
|
+
self._event_thread.daemon = True
|
|
519
|
+
self._event_thread.start()
|
|
520
|
+
|
|
521
|
+
def event_stream_is_enabled(self) -> bool:
|
|
522
|
+
"""Check to see if the event stream is enabled
|
|
523
|
+
|
|
524
|
+
If an event stream has been successfully established via
|
|
525
|
+
event_stream_enable(), then this function returns True.
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
True if a ensightservice::EventReply steam is active
|
|
530
|
+
"""
|
|
531
|
+
return self._event_stream is not None # pragma: no cover
|
|
532
|
+
|
|
533
|
+
def dynamic_scene_graph_stream(self, client_cmds): # pragma: no cover
|
|
534
|
+
"""Open up a dynamic scene graph stream
|
|
535
|
+
|
|
536
|
+
Make a DynamicSceneGraphService::GetSceneStream() rpc call and return
|
|
537
|
+
a ensightservice::SceneUpdateCommand stream instance.
|
|
538
|
+
|
|
539
|
+
Parameters
|
|
540
|
+
----------
|
|
541
|
+
client_cmds
|
|
542
|
+
iterator that produces ensightservice::SceneClientCommand objects
|
|
543
|
+
|
|
544
|
+
Returns
|
|
545
|
+
-------
|
|
546
|
+
ensightservice::SceneUpdateCommand stream instance
|
|
547
|
+
"""
|
|
548
|
+
self.connect()
|
|
549
|
+
return self._dsg_stub.GetSceneStream(client_cmds, metadata=self._metadata())
|
|
550
|
+
|
|
551
|
+
def get_event(self) -> Optional[str]: # pragma: no cover
|
|
552
|
+
"""Retrieve and remove the oldest ensightservice::EventReply string
|
|
553
|
+
|
|
554
|
+
When any of the event streaming systems is enabled, Python threads will receive the
|
|
555
|
+
event records and store them in this instance in an ordered fashion. This method
|
|
556
|
+
retrieves the oldest ensightservice::EventReply string in the queue.
|
|
557
|
+
|
|
558
|
+
Returns
|
|
559
|
+
-------
|
|
560
|
+
None or the oldest event string in the queue.
|
|
561
|
+
"""
|
|
562
|
+
try:
|
|
563
|
+
return self._events.pop(0)
|
|
564
|
+
except IndexError:
|
|
565
|
+
return None
|
|
566
|
+
|
|
567
|
+
def _put_event(self, evt: "ensight_pb2.EventReply") -> None:
|
|
568
|
+
"""Add an event record to the event queue on this instance
|
|
569
|
+
|
|
570
|
+
This method is used by threads to make the events they receive available to
|
|
571
|
+
calling applications via get_event().
|
|
572
|
+
"""
|
|
573
|
+
if self._event_callback: # pragma: no cover
|
|
574
|
+
self._event_callback(evt.tag)
|
|
575
|
+
return
|
|
576
|
+
self._events.append(evt.tag) # pragma: no cover
|
|
577
|
+
|
|
578
|
+
def _poll_events(self) -> None:
|
|
579
|
+
"""Internal method to handle event streams
|
|
580
|
+
|
|
581
|
+
This method is called by a Python thread to read events via the established
|
|
582
|
+
ensightservice::EventReply stream.
|
|
583
|
+
"""
|
|
584
|
+
try:
|
|
585
|
+
while self._stub is not None: # pragma: no cover
|
|
586
|
+
evt = self._event_stream.next()
|
|
587
|
+
self._put_event(evt)
|
|
588
|
+
except Exception:
|
|
589
|
+
# signal that the gRPC connection has broken
|
|
590
|
+
self._event_stream = None
|
|
591
|
+
self._event_thread = None
|
|
592
|
+
|
|
593
|
+
def _attempt_shared_mem_import(self):
|
|
594
|
+
try:
|
|
595
|
+
import ensight_grpc_shmem
|
|
596
|
+
|
|
597
|
+
self._shmem_module = ensight_grpc_shmem
|
|
598
|
+
except ModuleNotFoundError:
|
|
599
|
+
try:
|
|
600
|
+
self.command("import enve", do_eval=False)
|
|
601
|
+
cei_home = eval(self.command("enve.home()"))
|
|
602
|
+
self.command("import ceiversion", do_eval=False)
|
|
603
|
+
cei_version = eval(self.command("ceiversion.version_suffix"))
|
|
604
|
+
self.command("import sys", do_eval=False)
|
|
605
|
+
py_version = eval(self.command("sys.version_info[:3]"))
|
|
606
|
+
is_win = True if "Win" in platform.system() else False
|
|
607
|
+
plat = "win64" if is_win else "linux_2.6_64"
|
|
608
|
+
_lib = "DLLs" if is_win else f"lib/python{py_version[0]}.{py_version[1]}"
|
|
609
|
+
dll_loc = os.path.join(
|
|
610
|
+
cei_home,
|
|
611
|
+
f"apex{cei_version}",
|
|
612
|
+
"machines",
|
|
613
|
+
plat,
|
|
614
|
+
f"Python-{py_version[0]}.{py_version[1]}.{py_version[2]}",
|
|
615
|
+
_lib,
|
|
616
|
+
)
|
|
617
|
+
if os.path.exists(dll_loc):
|
|
618
|
+
sys.path.append(dll_loc)
|
|
619
|
+
import ensight_grpc_shmem
|
|
620
|
+
|
|
621
|
+
self._shmem_module = ensight_grpc_shmem
|
|
622
|
+
except ModuleNotFoundError:
|
|
623
|
+
pass
|
|
624
|
+
|
|
625
|
+
@classmethod
|
|
626
|
+
def _find_filename(cls, size=1024 * 1024 * 25):
|
|
627
|
+
"""Create a file on disk to support shared memory transport.
|
|
628
|
+
|
|
629
|
+
A file, 25MB in size, will be created using the pid of the current
|
|
630
|
+
process to generate the filename. It will be located in a temporary
|
|
631
|
+
directory.
|
|
632
|
+
"""
|
|
633
|
+
tempdir = tempfile.mkdtemp(prefix="pyensight_shmem")
|
|
634
|
+
for i in range(100):
|
|
635
|
+
filename = os.path.join(tempdir, "shmem_{}.bin".format(os.getpid() + i))
|
|
636
|
+
if not os.path.exists(filename):
|
|
637
|
+
try:
|
|
638
|
+
tmp = open(filename, "wb")
|
|
639
|
+
tmp.write(b"\0" * size) # 25MB
|
|
640
|
+
tmp.close()
|
|
641
|
+
return filename
|
|
642
|
+
except Exception:
|
|
643
|
+
pass
|
|
644
|
+
return None
|
|
645
|
+
|
|
646
|
+
def get_image(self):
|
|
647
|
+
"""Retrieve the current EnSight image.
|
|
648
|
+
|
|
649
|
+
When any of the image streaming systems is enabled, Python threads will receive the
|
|
650
|
+
most recent image and store them in this instance. The frame stored in this instance
|
|
651
|
+
can be accessed by calling this method
|
|
652
|
+
|
|
653
|
+
Returns
|
|
654
|
+
-------
|
|
655
|
+
(tuple):
|
|
656
|
+
A tuple containing a dictionary defining the image binary
|
|
657
|
+
(pixels=bytearray, width=w, height=h) and the image frame number.
|
|
658
|
+
"""
|
|
659
|
+
return self._image, self._image_number
|
|
660
|
+
|
|
661
|
+
def _start_sub_service(self):
|
|
662
|
+
"""Start a gRPC client service.
|
|
663
|
+
When the client calls one subscribe_events() or subscribe_images() with the
|
|
664
|
+
connection set to GRPC, the interface requires the client to start a gRPC server
|
|
665
|
+
that EnSight will call back to with event/image messages. This method starts
|
|
666
|
+
such a gRPC server."""
|
|
667
|
+
try:
|
|
668
|
+
if self._sub_service is not None:
|
|
669
|
+
return
|
|
670
|
+
self._sub_service = _EnSightSubServicer(parent=self)
|
|
671
|
+
self._sub_service.start()
|
|
672
|
+
except Exception:
|
|
673
|
+
self._sub_service = None
|
|
674
|
+
|
|
675
|
+
def subscribe_images(self, flip_vertical=False, use_shmem=True):
|
|
676
|
+
"""Subscribe to an image stream.
|
|
677
|
+
|
|
678
|
+
This methond makes a EnSightService::SubscribeImages() gRPC call. If
|
|
679
|
+
use_shmem is False, the transport system will be made over gRPC. It causes
|
|
680
|
+
EnSight to make a reverse gRPC connection over with gRPC calls with the
|
|
681
|
+
various images will be made. If use_shmem is True (the default), the \ref shmem will be used.
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
---------
|
|
685
|
+
flip_vertical: bool
|
|
686
|
+
If True, the image pixels will be flipped over the X axis
|
|
687
|
+
use_shmem: bool
|
|
688
|
+
If True, use the shared memory transport, otherwise use reverse gRPC"""
|
|
689
|
+
self.connect()
|
|
690
|
+
if use_shmem:
|
|
691
|
+
try:
|
|
692
|
+
# we need a shared memory file
|
|
693
|
+
self._shmem_filename = self._find_filename()
|
|
694
|
+
if self._shmem_filename is not None:
|
|
695
|
+
conn_type = ensight_pb2.SubscribeImageOptions.SHARED_MEM
|
|
696
|
+
options = dict(filename=self._shmem_filename)
|
|
697
|
+
image_options = ensight_pb2.SubscribeImageOptions(
|
|
698
|
+
prefix=self.prefix(),
|
|
699
|
+
type=conn_type,
|
|
700
|
+
options=options,
|
|
701
|
+
flip_vertical=flip_vertical,
|
|
702
|
+
chunk=False,
|
|
703
|
+
)
|
|
704
|
+
_ = self._stub.SubscribeImages(image_options, metadata=self._metadata())
|
|
705
|
+
# start the local server
|
|
706
|
+
if not self._shmem_module:
|
|
707
|
+
self._attempt_shared_mem_import()
|
|
708
|
+
if self._shmem_module:
|
|
709
|
+
self._shmem_client = self._shmem_module.stream_create(self._shmem_filename)
|
|
710
|
+
else:
|
|
711
|
+
self.command("import ensight_grpc_shmem", do_eval=False)
|
|
712
|
+
to_send = self._shmem_filename.replace("\\", "\\\\")
|
|
713
|
+
self.command(
|
|
714
|
+
f"enscl._shmem_client = ensight_grpc_shmem.stream_create('{to_send}')",
|
|
715
|
+
do_eval=False,
|
|
716
|
+
)
|
|
717
|
+
if self.command("enscl._shmem_client is not None"):
|
|
718
|
+
self._shmem_client = True
|
|
719
|
+
|
|
720
|
+
# turn on the polling thread
|
|
721
|
+
self._image_thread = threading.Thread(target=self._poll_images)
|
|
722
|
+
self._image_thread.daemon = True
|
|
723
|
+
self._image_thread.start()
|
|
724
|
+
return
|
|
725
|
+
except Exception as e:
|
|
726
|
+
print("Unable to subscribe to an image stream via shared memory: {}".format(str(e)))
|
|
727
|
+
|
|
728
|
+
self._start_sub_service()
|
|
729
|
+
conn_type = ensight_pb2.SubscribeImageOptions.GRPC
|
|
730
|
+
options = {}
|
|
731
|
+
if self._sub_service:
|
|
732
|
+
options = dict(uri=self._sub_service._uri)
|
|
733
|
+
image_options = ensight_pb2.SubscribeImageOptions(
|
|
734
|
+
prefix=self.prefix(),
|
|
735
|
+
type=conn_type,
|
|
736
|
+
options=options,
|
|
737
|
+
flip_vertical=flip_vertical,
|
|
738
|
+
chunk=True,
|
|
739
|
+
)
|
|
740
|
+
_ = self._stub.SubscribeImages(image_options, metadata=self._metadata())
|
|
741
|
+
|
|
742
|
+
def image_stream_enable(self, flip_vertical=False):
|
|
743
|
+
"""Enable a simple gRPC-based image stream from EnSight.
|
|
744
|
+
|
|
745
|
+
This method makes a EnSightService::GetImageStream() gRPC call into EnSight, returning
|
|
746
|
+
an ensightservice::ImageReply stream. The method creates a thread to hold this
|
|
747
|
+
stream open and read new image frames from it. The thread places the read images
|
|
748
|
+
in this object. An external application can retrieve the most recent one using
|
|
749
|
+
get_image().
|
|
750
|
+
|
|
751
|
+
Parameters
|
|
752
|
+
----------
|
|
753
|
+
flip_vertical: bool
|
|
754
|
+
If True, the image will be flipped over the X axis before being sent from EnSight."""
|
|
755
|
+
if self._image_stream is not None:
|
|
756
|
+
return
|
|
757
|
+
self.connect()
|
|
758
|
+
self._image_stream = self._stub.GetImageStream(
|
|
759
|
+
ensight_pb2.ImageStreamRequest(flip_vertical=flip_vertical, chunk=True),
|
|
760
|
+
metadata=self._metadata(),
|
|
761
|
+
)
|
|
762
|
+
self._image_thread = threading.Thread(target=self._poll_images)
|
|
763
|
+
self._image_thread.daemon = True
|
|
764
|
+
self._image_thread.start()
|
|
765
|
+
|
|
766
|
+
def _put_image(self, the_image):
|
|
767
|
+
"""Store an image on this instance.
|
|
768
|
+
|
|
769
|
+
This method is used by threads to store the latest image they receive
|
|
770
|
+
so it can be accessed by get_image.
|
|
771
|
+
"""
|
|
772
|
+
self._image = the_image
|
|
773
|
+
self._image_number += 1
|
|
774
|
+
|
|
775
|
+
def image_stream_is_enabled(self):
|
|
776
|
+
"""Check to see if the image stream is enabled.
|
|
777
|
+
|
|
778
|
+
If an image stream has been successfully established via image_stream_enable(),
|
|
779
|
+
then this function returns True.
|
|
780
|
+
|
|
781
|
+
Returns
|
|
782
|
+
-------
|
|
783
|
+
(bool):
|
|
784
|
+
True if a ensightservice::ImageReply steam is active
|
|
785
|
+
"""
|
|
786
|
+
return self._image_stream is not None
|
|
787
|
+
|
|
788
|
+
def _poll_images(self):
|
|
789
|
+
"""Handle image streams.
|
|
790
|
+
|
|
791
|
+
This method is called by a Python thread to read imagery via the shared memory
|
|
792
|
+
transport system or the the ensightservice::ImageReply stream.
|
|
793
|
+
"""
|
|
794
|
+
try:
|
|
795
|
+
while self._stub is not None:
|
|
796
|
+
if self._shmem_client:
|
|
797
|
+
if self._shmem_module:
|
|
798
|
+
img = self._shmem_module.stream_lock(self._shmem_client)
|
|
799
|
+
else:
|
|
800
|
+
img = self.command("ensight_grpc_shmem.stream_lock(enscl._shmem_client)")
|
|
801
|
+
if type(img) is dict:
|
|
802
|
+
the_image = dict(
|
|
803
|
+
pixels=img["pixeldata"], width=img["width"], height=img["height"]
|
|
804
|
+
)
|
|
805
|
+
self._put_image(the_image)
|
|
806
|
+
if self._shmem_module:
|
|
807
|
+
self._shmem_module.stream_unlock(self._shmem_client)
|
|
808
|
+
else:
|
|
809
|
+
self.command(
|
|
810
|
+
"ensight_grpc_shmem.stream_unlock(enscl._shmem_client)",
|
|
811
|
+
do_eval=False,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
if self._image_stream is not None:
|
|
815
|
+
img = self._image_stream.next()
|
|
816
|
+
buffer = img.pixels
|
|
817
|
+
|
|
818
|
+
while not img.final:
|
|
819
|
+
img = self._image_stream.next()
|
|
820
|
+
buffer += img.pixels
|
|
821
|
+
|
|
822
|
+
the_image = dict(pixels=buffer, width=img.width, height=img.height)
|
|
823
|
+
self._put_image(the_image)
|
|
824
|
+
except Exception:
|
|
825
|
+
# signal that the gRPC connection has broken
|
|
826
|
+
self._image_stream = None
|
|
827
|
+
self._image_thread = None
|
|
828
|
+
self._image = None
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
class _EnSightSubServicer(ensight_pb2_grpc.EnSightSubscriptionServicer):
|
|
832
|
+
"""Internal class handling reverse subscription connections.
|
|
833
|
+
The EnSight gRPC interface has a mechanism for reversing the gRPC
|
|
834
|
+
streams called Subscriptions. Image and event streams can be
|
|
835
|
+
subscribed to. In this mode, the client application starts a
|
|
836
|
+
gRPC server that implements the EnSightSubscription protocol.
|
|
837
|
+
EnSight will connect back to the client using this protocol and
|
|
838
|
+
send images/events back to the client as regular (non-stream)
|
|
839
|
+
rpc calls. This can be useful in situations where it is difficult
|
|
840
|
+
keep a long-running stream alive.
|
|
841
|
+
The EnSightSubServicer class implements a gRPC server for the client application.
|
|
842
|
+
"""
|
|
843
|
+
|
|
844
|
+
def __init__(self, parent: Optional["EnSightGRPC"] = None):
|
|
845
|
+
self._server: Optional["grpc.Server"] = None
|
|
846
|
+
self._uri: str = ""
|
|
847
|
+
self._parent = parent
|
|
848
|
+
|
|
849
|
+
def PublishEvent(self, request: Any, context: Any) -> "ensight_pb2.GenericResponse":
|
|
850
|
+
"""Publish an event to the remote server."""
|
|
851
|
+
if self._parent is not None:
|
|
852
|
+
self._parent._put_event(request)
|
|
853
|
+
return ensight_pb2.GenericResponse(str="Event Published")
|
|
854
|
+
|
|
855
|
+
def PublishImage(self, request_iterator: Any, context: Any) -> "ensight_pb2.GenericResponse":
|
|
856
|
+
"""Publish a single image (possibly in chucks) to the remote server."""
|
|
857
|
+
img: Any = request_iterator.next()
|
|
858
|
+
buffer = img.pixels
|
|
859
|
+
while not img.final:
|
|
860
|
+
img = request_iterator.next()
|
|
861
|
+
buffer += img.pixels
|
|
862
|
+
the_image = dict(pixels=buffer, width=img.width, height=img.height)
|
|
863
|
+
if self._parent is not None:
|
|
864
|
+
self._parent._put_image(the_image)
|
|
865
|
+
return ensight_pb2.GenericResponse(str="Image Published")
|
|
866
|
+
|
|
867
|
+
def start(self):
|
|
868
|
+
"""Start the gRPC server to be used for the EnSight Subscription Service."""
|
|
869
|
+
self._server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
|
870
|
+
ensight_pb2_grpc.add_EnSightSubscriptionServicer_to_server(self, self._server)
|
|
871
|
+
# Start the server on localhost with a random port
|
|
872
|
+
port = self._server.add_insecure_port("localhost:0")
|
|
873
|
+
self._uri = "localhost:" + str(port)
|
|
874
|
+
self._server.start()
|