ansys-pyensight-core 0.8.12__py3-none-any.whl → 0.9.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.

Potentially problematic release.


This version of ansys-pyensight-core might be problematic. Click here for more details.

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