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

@@ -1,411 +1,430 @@
1
- """ensight_grpc module
2
-
3
- This package defines the EnSightGRPC class which provides a simpler
4
- interface to the EnSight gRPC interface, including event streams.
5
-
6
- """
7
- import threading
8
- from typing import Any, Callable, List, Optional, Tuple, Union
9
- import uuid
10
-
11
- from ansys.api.pyensight.v0 import ensight_pb2, ensight_pb2_grpc
12
- import grpc
13
-
14
-
15
- class EnSightGRPC(object):
16
- """Wrapper around a gRPC connection to an EnSight instance
17
-
18
- This class provides an asynchronous interface to the EnSight
19
- core gRPC interface. It can handle remote event
20
- streams, providing a much simpler interface to the EnSight
21
- application. The default is to make a connection to an EnSight
22
- gRPC server on port 12345 on the loopback host.
23
-
24
- Parameters
25
- ----------
26
- host: str, optional
27
- Hostname where there EnSight gRPC server is running.
28
- port: int, optional
29
- Port to make the gRPC connection to
30
- secret_key: str, optional
31
- Connection secret key
32
- """
33
-
34
- def __init__(self, host: str = "127.0.0.1", port: int = 12345, secret_key: str = ""):
35
- self._host = host
36
- self._port = port
37
- self._channel = None
38
- self._stub = None
39
- self._dsg_stub = None
40
- self._security_token = secret_key
41
- self._session_name: str = ""
42
- # Streaming APIs
43
- # Event (strings)
44
- self._event_stream = None
45
- self._event_thread: Optional[threading.Thread] = None
46
- self._events: List[Any] = list()
47
- # Callback for events (self._events not used)
48
- self._event_callback: Optional[Callable] = None
49
- self._prefix: Optional[str] = None
50
-
51
- @property
52
- def host(self) -> str:
53
- """The gRPC server (EnSight) hostname"""
54
- return self._host
55
-
56
- def port(self) -> int:
57
- """The gRPC server (EnSight) port number"""
58
- return self._port
59
-
60
- @property
61
- def security_token(self) -> str:
62
- """The gRPC server (EnSight) secret key
63
-
64
- EnSight supports a security token in either numeric (-security {int}) or
65
- string (ENSIGHT_SECURITY_TOKEN environmental variable) form. If EnSight
66
- is using a security token, all gRPC calls must include this token. This
67
- call sets the token for all grPC calls made by this class.
68
- """
69
- return self._security_token
70
-
71
- @security_token.setter
72
- def security_token(self, name: str) -> None:
73
- self._security_token = name
74
-
75
- @property
76
- def session_name(self) -> str:
77
- """The gRPC server session name
78
-
79
- EnSight gRPC calls can include the session name via 'session_name' metadata.
80
- A client session may provide a session name via this property.
81
- """
82
- return self._session_name
83
-
84
- @session_name.setter
85
- def session_name(self, name: str) -> None:
86
- self._session_name = name
87
-
88
- def shutdown(self, stop_ensight: bool = False, force: bool = False) -> None:
89
- """Close down the gRPC connection
90
-
91
- Disconnect all connections to the gRPC server. If stop_ensight is True, send the
92
- 'Exit' command to the EnSight gRPC server.
93
-
94
- Parameters
95
- ----------
96
- stop_ensight: bool, optional
97
- if True, send an 'Exit' command to the gRPC server.
98
- force: bool, optional
99
- if stop_ensight and force are true, stop EnSight aggressively
100
- """
101
- if self.is_connected():
102
- # if requested, send 'Exit'
103
- if stop_ensight:
104
- # the gRPC ExitRequest is exactly that, a request in some
105
- # cases the operation needs to be forced
106
- if force:
107
- try:
108
- self.command("ensight.exit(0)", do_eval=False)
109
- except IOError:
110
- # we expect this as the exit can result in the gRPC call failing
111
- pass
112
- else:
113
- if self._stub:
114
- _ = self._stub.Exit(ensight_pb2.ExitRequest(), metadata=self._metadata())
115
- # clean up control objects
116
- self._stub = None
117
- self._dsg_stub = None
118
- if self._channel:
119
- self._channel.close()
120
- self._channel = None
121
-
122
- def is_connected(self) -> bool:
123
- """Check to see if the gRPC connection is live
124
-
125
- Returns
126
- -------
127
- True if the connection is active.
128
- """
129
- return self._channel is not None
130
-
131
- def connect(self, timeout: float = 15.0) -> None:
132
- """Establish the gRPC connection to EnSight
133
-
134
- Attempt to connect to an EnSight gRPC server using the host and port
135
- established by the constructor. Note on failure, this function just
136
- returns, but is_connected() will return False.
137
-
138
- Parameters
139
- ----------
140
- timeout: float
141
- how long to wait for the connection to timeout
142
- """
143
- if self.is_connected():
144
- return
145
- # set up the channel
146
- self._channel = grpc.insecure_channel(
147
- "{}:{}".format(self._host, self._port),
148
- options=[
149
- ("grpc.max_receive_message_length", -1),
150
- ("grpc.max_send_message_length", -1),
151
- ("grpc.testing.fixed_reconnect_backoff_ms", 1100),
152
- ],
153
- )
154
- try:
155
- grpc.channel_ready_future(self._channel).result(timeout=timeout)
156
- except grpc.FutureTimeoutError:
157
- self._channel = None
158
- return
159
- # hook up the stub interface
160
- self._stub = ensight_pb2_grpc.EnSightServiceStub(self._channel)
161
-
162
- def _metadata(self) -> List[Tuple[bytes, Union[str, bytes]]]:
163
- """Compute the gRPC stream metadata
164
-
165
- Compute the list to be passed to the gRPC calls for things like security
166
- and the session name.
167
-
168
- """
169
- ret: List[Tuple[bytes, Union[str, bytes]]] = list()
170
- s: Union[str, bytes]
171
- if self._security_token:
172
- s = self._security_token
173
- if type(s) == str:
174
- s = s.encode("utf-8")
175
- ret.append((b"shared_secret", s))
176
- if self.session_name:
177
- s = self.session_name.encode("utf-8")
178
- ret.append((b"session_name", s))
179
- return ret
180
-
181
- def render(
182
- self,
183
- width: int = 640,
184
- height: int = 480,
185
- aa: int = 1,
186
- png: bool = True,
187
- highlighting: bool = False,
188
- ) -> bytes:
189
- """Generate a rendering of the current EnSight scene
190
-
191
- Render the current scene at a specific size and using a specific number of anti-aliasing
192
- passes. The return value can be a byte array (width*height*3) bytes or a PNG image.
193
-
194
- Parameters
195
- ----------
196
- width: int, optional
197
- width of the image to render
198
- height: int, optional
199
- height of the image to render
200
- aa: int, optional
201
- number of antialiasing passes to use in generating the image
202
- png: bool, optional
203
- if True, the return value is a PNG image bytestream. Otherwise, it is a simple
204
- bytes object with width*height*3 values.
205
- highlighting: bool, optional
206
- if True, selection highlighting will be included in the image.
207
-
208
- Returns
209
- -------
210
- bytes
211
- bytes object representation of the rendered image
212
-
213
- Raises
214
- ------
215
- IOError if the operation fails
216
- """
217
- self.connect()
218
- ret_type = ensight_pb2.RenderRequest.IMAGE_RAW
219
- if png:
220
- ret_type = ensight_pb2.RenderRequest.IMAGE_PNG
221
- response: Any
222
- try:
223
- if self._stub:
224
- response = self._stub.RenderImage(
225
- ensight_pb2.RenderRequest(
226
- type=ret_type,
227
- image_width=width,
228
- image_height=height,
229
- image_aa_passes=aa,
230
- include_highlighting=highlighting,
231
- ),
232
- metadata=self._metadata(),
233
- )
234
- except Exception:
235
- raise IOError("gRPC connection dropped")
236
- return response.value
237
-
238
- def geometry(self) -> bytes:
239
- """Return the current scene geometry in glTF format
240
-
241
- Package up the geometry currently being viewed in the EnSight session as
242
- a glTF stream. Return this stream as an array of byte. Note: no
243
- intermediate files are utilized.
244
-
245
- Note: currently there is a limitation of glTF files to 2GB
246
-
247
- Returns
248
- -------
249
- bytes object representation of the glTF file
250
-
251
- Raises
252
- ------
253
- IOError if the operation fails
254
- """
255
- self.connect()
256
- response: Any
257
- try:
258
- if self._stub:
259
- response = self._stub.GetGeometry(
260
- ensight_pb2.GeometryRequest(type=ensight_pb2.GeometryRequest.GEOMETRY_GLB),
261
- metadata=self._metadata(),
262
- )
263
- except Exception:
264
- raise IOError("gRPC connection dropped")
265
- return response.value
266
-
267
- def command(self, command_string: str, do_eval: bool = True, json: bool = False) -> Any:
268
- """Send a Python command string to be executed in EnSight
269
-
270
- The string will be run or evaluated in the EnSight Python interpreter via the
271
- EnSightService::RunPython() gRPC all. If an exception or other error occurs, this
272
- function will throw a RuntimeError. If do_eval is False, the return value will be None,
273
- otherwise it will be the returned string (eval() will not be performed). If json is True,
274
- the return value will be a JSON representation of the report execution result.
275
-
276
- Parameters
277
- ----------
278
- command_string: str
279
- The string to execute
280
- do_eval: bool, optional
281
- If True, a return value will be computed and returned
282
- json: bool, optional
283
- If True and do_eval is True, the return value will be a JSON representation of
284
- the evaluated value.
285
-
286
- Returns
287
- -------
288
- Any
289
- None, a string ready for Python eval() or a JSON string.
290
-
291
- Raises
292
- ------
293
- RuntimeError if the operation fails.
294
- IOError if the communication fails.
295
- """
296
- self.connect()
297
- flags = ensight_pb2.PythonRequest.EXEC_RETURN_PYTHON
298
- response: Any
299
- if json:
300
- flags = ensight_pb2.PythonRequest.EXEC_RETURN_JSON
301
- if not do_eval:
302
- flags = ensight_pb2.PythonRequest.EXEC_NO_RESULT
303
- try:
304
- if self._stub:
305
- response = self._stub.RunPython(
306
- ensight_pb2.PythonRequest(type=flags, command=command_string),
307
- metadata=self._metadata(),
308
- )
309
- except Exception:
310
- raise IOError("gRPC connection dropped")
311
- if response.error < 0:
312
- raise RuntimeError(response.value)
313
- if flags == ensight_pb2.PythonRequest.EXEC_NO_RESULT:
314
- return None
315
- # This was moved externally so pre-processing could be performed
316
- # elif flags == ensight_pb2.PythonRequest.EXEC_RETURN_PYTHON:
317
- # return eval(response.value)
318
- return response.value
319
-
320
- def prefix(self) -> str:
321
- """Return the unique prefix for this instance.
322
-
323
- Some EnSight gRPC APIs require a unique prefix so that EnSight can handle
324
- multiple, simultaneous remote connections. This method will generate a GUID-based
325
- prefix.
326
-
327
- Returns
328
- -------
329
- str
330
- A unique (for this session) prefix string of the form: grpc://{uuid}/
331
- """
332
- # prefix URIs will have the format: "grpc://{uuid}/{callbackname}?enum={}&uid={}"
333
- if self._prefix is None:
334
- self._prefix = "grpc://" + str(uuid.uuid1()) + "/"
335
- return self._prefix
336
-
337
- def event_stream_enable(self, callback: Optional[Callable] = None) -> None:
338
- """Enable a simple gRPC-based event stream from EnSight
339
-
340
- This method makes a EnSightService::GetEventStream() gRPC call into EnSight, returning
341
- an ensightservice::EventReply stream. The method creates a thread to hold this
342
- stream open and read new events from it. The thread adds the event strings to
343
- a list of events stored on this instance. If callback is not None, the object
344
- will be called with the event string, otherwise they can be retrieved using get_event().
345
- """
346
- if self._event_stream is not None:
347
- return
348
- self._event_callback = callback
349
- self.connect()
350
- if self._stub:
351
- self._event_stream = self._stub.GetEventStream(
352
- ensight_pb2.EventStreamRequest(prefix=self.prefix()),
353
- metadata=self._metadata(),
354
- )
355
- self._event_thread = threading.Thread(target=self._poll_events)
356
- self._event_thread.daemon = True
357
- self._event_thread.start()
358
-
359
- def event_stream_is_enabled(self) -> bool:
360
- """Check to see if the event stream is enabled
361
-
362
- If an event stream has been successfully established via
363
- event_stream_enable(), then this function returns True.
364
-
365
- Returns
366
- -------
367
- True if a ensightservice::EventReply steam is active
368
- """
369
- return self._event_stream is not None
370
-
371
- def get_event(self) -> Optional[str]:
372
- """Retrieve and remove the oldest ensightservice::EventReply string
373
-
374
- When any of the event streaming systems is enabled, Python threads will receive the
375
- event records and store them in this instance in an ordered fashion. This method
376
- retrieves the oldest ensightservice::EventReply string in the queue.
377
-
378
- Returns
379
- -------
380
- None or the oldest event string in the queue.
381
- """
382
- try:
383
- return self._events.pop(0)
384
- except IndexError:
385
- return None
386
-
387
- def _put_event(self, evt: "ensight_pb2.EventReply") -> None:
388
- """Add an event record to the event queue on this instance
389
-
390
- This method is used by threads to make the events they receive available to
391
- calling applications via get_event().
392
- """
393
- if self._event_callback:
394
- self._event_callback(evt.tag)
395
- return
396
- self._events.append(evt.tag)
397
-
398
- def _poll_events(self) -> None:
399
- """Internal method to handle event streams
400
-
401
- This method is called by a Python thread to read events via the established
402
- ensightservice::EventReply stream.
403
- """
404
- try:
405
- while self._stub is not None:
406
- evt = self._event_stream.next()
407
- self._put_event(evt)
408
- except Exception:
409
- # signal that the gRPC connection has broken
410
- self._event_stream = None
411
- self._event_thread = None
1
+ """ensight_grpc module
2
+
3
+ This package defines the EnSightGRPC class which provides a simpler
4
+ interface to the EnSight gRPC interface, including event streams.
5
+
6
+ """
7
+ import threading
8
+ from typing import Any, Callable, List, Optional, Tuple, Union
9
+ import uuid
10
+
11
+ from ansys.api.pyensight.v0 import dynamic_scene_graph_pb2_grpc, ensight_pb2, ensight_pb2_grpc
12
+ import grpc
13
+
14
+
15
+ class EnSightGRPC(object):
16
+ """Wrapper around a gRPC connection to an EnSight instance
17
+
18
+ This class provides an asynchronous interface to the EnSight
19
+ core gRPC interface. It can handle remote event
20
+ streams, providing a much simpler interface to the EnSight
21
+ application. The default is to make a connection to an EnSight
22
+ gRPC server on port 12345 on the loopback host.
23
+
24
+ Parameters
25
+ ----------
26
+ host: str, optional
27
+ Hostname where there EnSight gRPC server is running.
28
+ port: int, optional
29
+ Port to make the gRPC connection to
30
+ secret_key: str, optional
31
+ Connection secret key
32
+ """
33
+
34
+ def __init__(self, host: str = "127.0.0.1", port: int = 12345, secret_key: str = ""):
35
+ self._host = host
36
+ self._port = port
37
+ self._channel = None
38
+ self._stub = None
39
+ self._dsg_stub = None
40
+ self._security_token = secret_key
41
+ self._session_name: str = ""
42
+ # Streaming APIs
43
+ # Event (strings)
44
+ self._event_stream = None
45
+ self._event_thread: Optional[threading.Thread] = None
46
+ self._events: List[Any] = list()
47
+ # Callback for events (self._events not used)
48
+ self._event_callback: Optional[Callable] = None
49
+ self._prefix: Optional[str] = None
50
+
51
+ @property
52
+ def host(self) -> str:
53
+ """The gRPC server (EnSight) hostname"""
54
+ return self._host
55
+
56
+ def port(self) -> int:
57
+ """The gRPC server (EnSight) port number"""
58
+ return self._port
59
+
60
+ @property
61
+ def security_token(self) -> str:
62
+ """The gRPC server (EnSight) secret key
63
+
64
+ EnSight supports a security token in either numeric (-security {int}) or
65
+ string (ENSIGHT_SECURITY_TOKEN environmental variable) form. If EnSight
66
+ is using a security token, all gRPC calls must include this token. This
67
+ call sets the token for all grPC calls made by this class.
68
+ """
69
+ return self._security_token
70
+
71
+ @security_token.setter
72
+ def security_token(self, name: str) -> None:
73
+ self._security_token = name
74
+
75
+ @property
76
+ def session_name(self) -> str:
77
+ """The gRPC server session name
78
+
79
+ EnSight gRPC calls can include the session name via 'session_name' metadata.
80
+ A client session may provide a session name via this property.
81
+ """
82
+ return self._session_name
83
+
84
+ @session_name.setter
85
+ def session_name(self, name: str) -> None:
86
+ self._session_name = name
87
+
88
+ def shutdown(self, stop_ensight: bool = False, force: bool = False) -> None:
89
+ """Close down the gRPC connection
90
+
91
+ Disconnect all connections to the gRPC server. If stop_ensight is True, send the
92
+ 'Exit' command to the EnSight gRPC server.
93
+
94
+ Parameters
95
+ ----------
96
+ stop_ensight: bool, optional
97
+ if True, send an 'Exit' command to the gRPC server.
98
+ force: bool, optional
99
+ if stop_ensight and force are true, stop EnSight aggressively
100
+ """
101
+ if self.is_connected():
102
+ # if requested, send 'Exit'
103
+ if stop_ensight:
104
+ # the gRPC ExitRequest is exactly that, a request in some
105
+ # cases the operation needs to be forced
106
+ if force:
107
+ try:
108
+ self.command("ensight.exit(0)", do_eval=False)
109
+ except IOError:
110
+ # we expect this as the exit can result in the gRPC call failing
111
+ pass
112
+ else:
113
+ if self._stub:
114
+ _ = self._stub.Exit(ensight_pb2.ExitRequest(), metadata=self._metadata())
115
+ # clean up control objects
116
+ self._stub = None
117
+ self._dsg_stub = None
118
+ if self._channel:
119
+ self._channel.close()
120
+ self._channel = None
121
+
122
+ def is_connected(self) -> bool:
123
+ """Check to see if the gRPC connection is live
124
+
125
+ Returns
126
+ -------
127
+ True if the connection is active.
128
+ """
129
+ return self._channel is not None
130
+
131
+ def connect(self, timeout: float = 15.0) -> None:
132
+ """Establish the gRPC connection to EnSight
133
+
134
+ Attempt to connect to an EnSight gRPC server using the host and port
135
+ established by the constructor. Note on failure, this function just
136
+ returns, but is_connected() will return False.
137
+
138
+ Parameters
139
+ ----------
140
+ timeout: float
141
+ how long to wait for the connection to timeout
142
+ """
143
+ if self.is_connected():
144
+ return
145
+ # set up the channel
146
+ self._channel = grpc.insecure_channel(
147
+ "{}:{}".format(self._host, self._port),
148
+ options=[
149
+ ("grpc.max_receive_message_length", -1),
150
+ ("grpc.max_send_message_length", -1),
151
+ ("grpc.testing.fixed_reconnect_backoff_ms", 1100),
152
+ ],
153
+ )
154
+ try:
155
+ grpc.channel_ready_future(self._channel).result(timeout=timeout)
156
+ except grpc.FutureTimeoutError:
157
+ self._channel = None
158
+ return
159
+ # hook up the stub interface
160
+ self._stub = ensight_pb2_grpc.EnSightServiceStub(self._channel)
161
+ self._dsg_stub = dynamic_scene_graph_pb2_grpc.DynamicSceneGraphServiceStub(self._channel)
162
+
163
+ def _metadata(self) -> List[Tuple[bytes, Union[str, bytes]]]:
164
+ """Compute the gRPC stream metadata
165
+
166
+ Compute the list to be passed to the gRPC calls for things like security
167
+ and the session name.
168
+
169
+ """
170
+ ret: List[Tuple[bytes, Union[str, bytes]]] = list()
171
+ s: Union[str, bytes]
172
+ if self._security_token:
173
+ s = self._security_token
174
+ if type(s) == str:
175
+ s = s.encode("utf-8")
176
+ ret.append((b"shared_secret", s))
177
+ if self.session_name:
178
+ s = self.session_name.encode("utf-8")
179
+ ret.append((b"session_name", s))
180
+ return ret
181
+
182
+ def render(
183
+ self,
184
+ width: int = 640,
185
+ height: int = 480,
186
+ aa: int = 1,
187
+ png: bool = True,
188
+ highlighting: bool = False,
189
+ ) -> bytes:
190
+ """Generate a rendering of the current EnSight scene
191
+
192
+ Render the current scene at a specific size and using a specific number of anti-aliasing
193
+ passes. The return value can be a byte array (width*height*3) bytes or a PNG image.
194
+
195
+ Parameters
196
+ ----------
197
+ width: int, optional
198
+ width of the image to render
199
+ height: int, optional
200
+ height of the image to render
201
+ aa: int, optional
202
+ number of antialiasing passes to use in generating the image
203
+ png: bool, optional
204
+ if True, the return value is a PNG image bytestream. Otherwise, it is a simple
205
+ bytes object with width*height*3 values.
206
+ highlighting: bool, optional
207
+ if True, selection highlighting will be included in the image.
208
+
209
+ Returns
210
+ -------
211
+ bytes
212
+ bytes object representation of the rendered image
213
+
214
+ Raises
215
+ ------
216
+ IOError if the operation fails
217
+ """
218
+ self.connect()
219
+ ret_type = ensight_pb2.RenderRequest.IMAGE_RAW
220
+ if png:
221
+ ret_type = ensight_pb2.RenderRequest.IMAGE_PNG
222
+ response: Any
223
+ try:
224
+ if self._stub:
225
+ response = self._stub.RenderImage(
226
+ ensight_pb2.RenderRequest(
227
+ type=ret_type,
228
+ image_width=width,
229
+ image_height=height,
230
+ image_aa_passes=aa,
231
+ include_highlighting=highlighting,
232
+ ),
233
+ metadata=self._metadata(),
234
+ )
235
+ except Exception:
236
+ raise IOError("gRPC connection dropped")
237
+ return response.value
238
+
239
+ def geometry(self) -> bytes:
240
+ """Return the current scene geometry in glTF format
241
+
242
+ Package up the geometry currently being viewed in the EnSight session as
243
+ a glTF stream. Return this stream as an array of byte. Note: no
244
+ intermediate files are utilized.
245
+
246
+ Note: currently there is a limitation of glTF files to 2GB
247
+
248
+ Returns
249
+ -------
250
+ bytes object representation of the glTF file
251
+
252
+ Raises
253
+ ------
254
+ IOError if the operation fails
255
+ """
256
+ self.connect()
257
+ response: Any
258
+ try:
259
+ if self._stub:
260
+ response = self._stub.GetGeometry(
261
+ ensight_pb2.GeometryRequest(type=ensight_pb2.GeometryRequest.GEOMETRY_GLB),
262
+ metadata=self._metadata(),
263
+ )
264
+ except Exception:
265
+ raise IOError("gRPC connection dropped")
266
+ return response.value
267
+
268
+ def command(self, command_string: str, do_eval: bool = True, json: bool = False) -> Any:
269
+ """Send a Python command string to be executed in EnSight
270
+
271
+ The string will be run or evaluated in the EnSight Python interpreter via the
272
+ EnSightService::RunPython() gRPC all. If an exception or other error occurs, this
273
+ function will throw a RuntimeError. If do_eval is False, the return value will be None,
274
+ otherwise it will be the returned string (eval() will not be performed). If json is True,
275
+ the return value will be a JSON representation of the report execution result.
276
+
277
+ Parameters
278
+ ----------
279
+ command_string: str
280
+ The string to execute
281
+ do_eval: bool, optional
282
+ If True, a return value will be computed and returned
283
+ json: bool, optional
284
+ If True and do_eval is True, the return value will be a JSON representation of
285
+ the evaluated value.
286
+
287
+ Returns
288
+ -------
289
+ Any
290
+ None, a string ready for Python eval() or a JSON string.
291
+
292
+ Raises
293
+ ------
294
+ RuntimeError if the operation fails.
295
+ IOError if the communication fails.
296
+ """
297
+ self.connect()
298
+ flags = ensight_pb2.PythonRequest.EXEC_RETURN_PYTHON
299
+ response: Any
300
+ if json:
301
+ flags = ensight_pb2.PythonRequest.EXEC_RETURN_JSON
302
+ if not do_eval:
303
+ flags = ensight_pb2.PythonRequest.EXEC_NO_RESULT
304
+ try:
305
+ if self._stub:
306
+ response = self._stub.RunPython(
307
+ ensight_pb2.PythonRequest(type=flags, command=command_string),
308
+ metadata=self._metadata(),
309
+ )
310
+ except Exception:
311
+ raise IOError("gRPC connection dropped")
312
+ if response.error < 0:
313
+ raise RuntimeError(response.value)
314
+ if flags == ensight_pb2.PythonRequest.EXEC_NO_RESULT:
315
+ return None
316
+ # This was moved externally so pre-processing could be performed
317
+ # elif flags == ensight_pb2.PythonRequest.EXEC_RETURN_PYTHON:
318
+ # return eval(response.value)
319
+ return response.value
320
+
321
+ def prefix(self) -> str:
322
+ """Return the unique prefix for this instance.
323
+
324
+ Some EnSight gRPC APIs require a unique prefix so that EnSight can handle
325
+ multiple, simultaneous remote connections. This method will generate a GUID-based
326
+ prefix.
327
+
328
+ Returns
329
+ -------
330
+ str
331
+ A unique (for this session) prefix string of the form: grpc://{uuid}/
332
+ """
333
+ # prefix URIs will have the format: "grpc://{uuid}/{callbackname}?enum={}&uid={}"
334
+ if self._prefix is None:
335
+ self._prefix = "grpc://" + str(uuid.uuid1()) + "/"
336
+ return self._prefix
337
+
338
+ def event_stream_enable(self, callback: Optional[Callable] = None) -> None:
339
+ """Enable a simple gRPC-based event stream from EnSight
340
+
341
+ This method makes a EnSightService::GetEventStream() gRPC call into EnSight, returning
342
+ an ensightservice::EventReply stream. The method creates a thread to hold this
343
+ stream open and read new events from it. The thread adds the event strings to
344
+ a list of events stored on this instance. If callback is not None, the object
345
+ will be called with the event string, otherwise they can be retrieved using get_event().
346
+ """
347
+ if self._event_stream is not None:
348
+ return
349
+ self._event_callback = callback
350
+ self.connect()
351
+ if self._stub:
352
+ self._event_stream = self._stub.GetEventStream(
353
+ ensight_pb2.EventStreamRequest(prefix=self.prefix()),
354
+ metadata=self._metadata(),
355
+ )
356
+ self._event_thread = threading.Thread(target=self._poll_events)
357
+ self._event_thread.daemon = True
358
+ self._event_thread.start()
359
+
360
+ def event_stream_is_enabled(self) -> bool:
361
+ """Check to see if the event stream is enabled
362
+
363
+ If an event stream has been successfully established via
364
+ event_stream_enable(), then this function returns True.
365
+
366
+ Returns
367
+ -------
368
+ True if a ensightservice::EventReply steam is active
369
+ """
370
+ return self._event_stream is not None
371
+
372
+ def dynamic_scene_graph_stream(self, client_cmds):
373
+ """Open up a dynamic scene graph stream
374
+
375
+ Make a DynamicSceneGraphService::GetSceneStream() rpc call and return
376
+ a ensightservice::SceneUpdateCommand stream instance.
377
+
378
+ Parameters
379
+ ----------
380
+ client_cmds
381
+ iterator that produces ensightservice::SceneClientCommand objects
382
+
383
+ Returns
384
+ -------
385
+ ensightservice::SceneUpdateCommand stream instance
386
+ """
387
+ self.connect()
388
+ return self._dsg_stub.GetSceneStream(client_cmds, metadata=self._metadata())
389
+
390
+ def get_event(self) -> Optional[str]:
391
+ """Retrieve and remove the oldest ensightservice::EventReply string
392
+
393
+ When any of the event streaming systems is enabled, Python threads will receive the
394
+ event records and store them in this instance in an ordered fashion. This method
395
+ retrieves the oldest ensightservice::EventReply string in the queue.
396
+
397
+ Returns
398
+ -------
399
+ None or the oldest event string in the queue.
400
+ """
401
+ try:
402
+ return self._events.pop(0)
403
+ except IndexError:
404
+ return None
405
+
406
+ def _put_event(self, evt: "ensight_pb2.EventReply") -> None:
407
+ """Add an event record to the event queue on this instance
408
+
409
+ This method is used by threads to make the events they receive available to
410
+ calling applications via get_event().
411
+ """
412
+ if self._event_callback:
413
+ self._event_callback(evt.tag)
414
+ return
415
+ self._events.append(evt.tag)
416
+
417
+ def _poll_events(self) -> None:
418
+ """Internal method to handle event streams
419
+
420
+ This method is called by a Python thread to read events via the established
421
+ ensightservice::EventReply stream.
422
+ """
423
+ try:
424
+ while self._stub is not None:
425
+ evt = self._event_stream.next()
426
+ self._put_event(evt)
427
+ except Exception:
428
+ # signal that the gRPC connection has broken
429
+ self._event_stream = None
430
+ self._event_thread = None