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.
Files changed (37) hide show
  1. ansys/pyensight/core/__init__.py +41 -0
  2. ansys/pyensight/core/common.py +341 -0
  3. ansys/pyensight/core/deep_pixel_view.html +98 -0
  4. ansys/pyensight/core/dockerlauncher.py +1124 -0
  5. ansys/pyensight/core/dvs.py +872 -0
  6. ansys/pyensight/core/enscontext.py +345 -0
  7. ansys/pyensight/core/enshell_grpc.py +641 -0
  8. ansys/pyensight/core/ensight_grpc.py +874 -0
  9. ansys/pyensight/core/ensobj.py +515 -0
  10. ansys/pyensight/core/launch_ensight.py +296 -0
  11. ansys/pyensight/core/launcher.py +388 -0
  12. ansys/pyensight/core/libuserd.py +2110 -0
  13. ansys/pyensight/core/listobj.py +280 -0
  14. ansys/pyensight/core/locallauncher.py +579 -0
  15. ansys/pyensight/core/py.typed +0 -0
  16. ansys/pyensight/core/renderable.py +880 -0
  17. ansys/pyensight/core/session.py +1923 -0
  18. ansys/pyensight/core/sgeo_poll.html +24 -0
  19. ansys/pyensight/core/utils/__init__.py +21 -0
  20. ansys/pyensight/core/utils/adr.py +111 -0
  21. ansys/pyensight/core/utils/dsg_server.py +1220 -0
  22. ansys/pyensight/core/utils/export.py +606 -0
  23. ansys/pyensight/core/utils/omniverse.py +769 -0
  24. ansys/pyensight/core/utils/omniverse_cli.py +614 -0
  25. ansys/pyensight/core/utils/omniverse_dsg_server.py +1196 -0
  26. ansys/pyensight/core/utils/omniverse_glb_server.py +848 -0
  27. ansys/pyensight/core/utils/parts.py +1221 -0
  28. ansys/pyensight/core/utils/query.py +487 -0
  29. ansys/pyensight/core/utils/readers.py +300 -0
  30. ansys/pyensight/core/utils/resources/Materials/000_sky.exr +0 -0
  31. ansys/pyensight/core/utils/support.py +128 -0
  32. ansys/pyensight/core/utils/variables.py +2019 -0
  33. ansys/pyensight/core/utils/views.py +674 -0
  34. ansys_pyensight_core-0.11.0.dist-info/METADATA +309 -0
  35. ansys_pyensight_core-0.11.0.dist-info/RECORD +37 -0
  36. ansys_pyensight_core-0.11.0.dist-info/WHEEL +4 -0
  37. ansys_pyensight_core-0.11.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,606 @@
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
+ import glob
24
+ import os
25
+ import tempfile
26
+ from types import ModuleType
27
+ from typing import Any, List, Optional, Union
28
+ import uuid
29
+
30
+ from PIL import Image
31
+ import numpy
32
+
33
+ try:
34
+ import ensight
35
+ import enve
36
+ except ImportError:
37
+ from ansys.api.pyensight import ensight_api
38
+
39
+
40
+ class Export:
41
+ """Provides the ``ensight.utils.export`` interface.
42
+
43
+ The methods in this class implement simplified interfaces to common
44
+ image and animation export operations.
45
+
46
+ This class is instantiated as ``ensight.utils.export`` in EnSight Python
47
+ and as ``Session.ensight.utils.export`` in PyEnSight. The constructor is
48
+ passed the interface, which serves as the ``ensight`` module for either
49
+ case. As a result, the methods can be accessed as ``ensight.utils.export.image()``
50
+ in EnSight Python or ``session.ensight.utils.export.animation()`` in PyEnSight.
51
+
52
+ Parameters
53
+ ----------
54
+ interface :
55
+ Entity that provides the ``ensight`` namespace. In the case of
56
+ EnSight Python, the ``ensight`` module is passed. In the case
57
+ of PyEnSight, ``Session.ensight`` is passed.
58
+ """
59
+
60
+ def __init__(self, interface: Union["ensight_api.ensight", "ensight"]):
61
+ self._ensight = interface
62
+
63
+ def _remote_support_check(self):
64
+ """Determine if ``ensight.utils.export`` exists on the remote system.
65
+
66
+ Before trying to use this module, use this method to determine if this
67
+ module is available in the EnSight instance.
68
+
69
+ Raises
70
+ ------
71
+ RuntimeError if the module is not present.
72
+ """
73
+ # if a module, then we are inside EnSight
74
+ if isinstance(self._ensight, ModuleType): # pragma: no cover
75
+ return # pragma: no cover
76
+ try:
77
+ _ = self._ensight._session.cmd("dir(ensight.utils.export)")
78
+ except RuntimeError: # pragma: no cover
79
+ import ansys.pyensight.core # pragma: no cover
80
+
81
+ raise RuntimeError( # pragma: no cover
82
+ f"Remote EnSight session must have PyEnsight version \
83
+ {ansys.pyensight.core.DEFAULT_ANSYS_VERSION} or higher installed to use this API."
84
+ )
85
+
86
+ TIFFTAG_IMAGEDESCRIPTION: int = 0x010E
87
+
88
+ def image(
89
+ self,
90
+ filename: str,
91
+ width: Optional[int] = None,
92
+ height: Optional[int] = None,
93
+ passes: int = 4,
94
+ enhanced: bool = False,
95
+ raytrace: bool = False,
96
+ ) -> None:
97
+ """Render an image of the current EnSight scene.
98
+
99
+ Parameters
100
+ ----------
101
+ filename : str
102
+ Name of the local file to save the image to.
103
+ width : int, optional
104
+ Width of the image in pixels. The default is ``None``, in which case
105
+ ```ensight.objs.core.WINDOWSIZE[0]`` is used.
106
+ height : int, optional
107
+ Height of the image in pixels. The default is ``None``, in which case
108
+ ``ensight.objs.core.WINDOWSIZE[1]`` is used.
109
+ passes : int, optional
110
+ Number of antialiasing passes. The default is ``4``.
111
+ enhanced : bool, optional
112
+ Whether to save the image to the filename specified in the TIFF format.
113
+ The default is ``False``. The TIFF format includes additional channels
114
+ for the per-pixel object and variable information.
115
+ raytrace : bool, optional
116
+ Whether to render the image with the raytracing engine. The default is ``False``.
117
+
118
+ Examples
119
+ --------
120
+ >>> s = LocalLauncher().start()
121
+ >>> s.load_data(f"{s.cei_home}/ensight{s.cei_suffix}/data/cube/cube.case")
122
+ >>> s.ensight.utils.export.image("example.png")
123
+
124
+ """
125
+ self._remote_support_check()
126
+
127
+ win_size = self._ensight.objs.core.WINDOWSIZE
128
+ if width is None:
129
+ width = win_size[0]
130
+ if height is None:
131
+ height = win_size[1]
132
+
133
+ if isinstance(self._ensight, ModuleType): # pragma: no cover
134
+ raw_image = self._image_remote(
135
+ width, height, passes, enhanced, raytrace
136
+ ) # pragma: no cover
137
+ else:
138
+ cmd = f"ensight.utils.export._image_remote({width}, {height}, {passes}, "
139
+ cmd += f"{enhanced}, {raytrace})"
140
+ raw_image = self._ensight._session.cmd(cmd)
141
+
142
+ pil_image = self._dict_to_pil(raw_image)
143
+ if enhanced:
144
+ tiffinfo_dir = {self.TIFFTAG_IMAGEDESCRIPTION: raw_image["metadata"]}
145
+ pil_image[0].save(
146
+ filename,
147
+ save_all=True,
148
+ append_images=[pil_image[1], pil_image[2]],
149
+ tiffinfo=tiffinfo_dir,
150
+ )
151
+ else:
152
+ pil_image[0].save(filename)
153
+
154
+ def _dict_to_pil(self, data: dict) -> list:
155
+ """Convert the contents of the dictionary into a PIL image.
156
+
157
+ Parameters
158
+ ----------
159
+ data : dict
160
+ Dictionary representation of the contents of the ``enve`` object.
161
+
162
+ Returns
163
+ -------
164
+ list
165
+ List of one or three image objects, [RGB {, pick, variable}].
166
+ """
167
+ images = [
168
+ Image.fromarray(self._numpy_from_dict(data["pixeldata"])).transpose(
169
+ Image.FLIP_TOP_BOTTOM
170
+ )
171
+ ]
172
+ if data.get("variabledata", None) and data.get("pickdata", None):
173
+ images.append(
174
+ Image.fromarray(self._numpy_from_dict(data["pickdata"])).transpose(
175
+ Image.FLIP_TOP_BOTTOM
176
+ )
177
+ )
178
+ images.append(
179
+ Image.fromarray(self._numpy_from_dict(data["variabledata"])).transpose(
180
+ Image.FLIP_TOP_BOTTOM
181
+ )
182
+ )
183
+ return images
184
+
185
+ @staticmethod
186
+ def _numpy_to_dict(array: Any) -> Optional[dict]:
187
+ """Convert a numpy array into a dictionary.
188
+
189
+ Parameters
190
+ ----------
191
+ array:
192
+ Numpy array or None.
193
+
194
+ Returns
195
+ -------
196
+ ``None`` or a dictionary that can be serialized.
197
+ """
198
+ if array is None:
199
+ return None
200
+ return dict(shape=array.shape, dtype=array.dtype.str, data=array.tobytes())
201
+
202
+ @staticmethod
203
+ def _numpy_from_dict(obj: Optional[dict]) -> Any:
204
+ """Convert a dictionary into a numpy array.
205
+
206
+ Parameters
207
+ ----------
208
+ obj:
209
+ Dictionary generated by ``_numpy_to_dict`` or ``None``.
210
+
211
+ Returns
212
+ -------
213
+ ``None`` or a numpy array.
214
+ """
215
+ if obj is None:
216
+ return None
217
+ return numpy.frombuffer(obj["data"], dtype=obj["dtype"]).reshape(obj["shape"])
218
+
219
+ def _image_remote(
220
+ self, width: int, height: int, passes: int, enhanced: bool, raytrace: bool
221
+ ) -> dict:
222
+ """EnSight-side implementation.
223
+
224
+ Parameters
225
+ ----------
226
+ width : int
227
+ Width of the image in pixels.
228
+ height : int
229
+ Height of the image in pixels.
230
+ passes : int
231
+ Number of antialiasing passes.
232
+ enhanced : bool
233
+ Whether to returned the image as a "deep pixel" TIFF image file.
234
+ raytrace :
235
+ Whether to render the image with the raytracing engine.
236
+
237
+ Returns
238
+ -------
239
+ dict
240
+ Dictionary of the various channels.
241
+ """
242
+ if not raytrace:
243
+ img = ensight.render(x=width, y=height, num_samples=passes, enhanced=enhanced)
244
+ else:
245
+ with tempfile.TemporaryDirectory() as tmpdirname:
246
+ tmpfilename = os.path.join(tmpdirname, str(uuid.uuid1()))
247
+ ensight.file.image_format("png")
248
+ ensight.file.image_file(tmpfilename)
249
+ ensight.file.image_window_size("user_defined")
250
+ ensight.file.image_window_xy(width, height)
251
+ ensight.file.image_rend_offscreen("ON")
252
+ ensight.file.image_numpasses(passes)
253
+ ensight.file.image_stereo("current")
254
+ ensight.file.image_screen_tiling(1, 1)
255
+ ensight.file.raytracer_options("fgoverlay 1 imagedenoise 1 quality 5")
256
+ ensight.file.image_raytrace_it("ON")
257
+ ensight.file.save_image()
258
+ img = enve.image()
259
+ img.load(f"{tmpfilename}.png")
260
+ # get the channels from the enve.image instance
261
+ output = dict(width=width, height=height, metadata=img.metadata)
262
+ # extract the channels from the image
263
+ output["pixeldata"] = self._numpy_to_dict(img.pixeldata)
264
+ output["variabledata"] = self._numpy_to_dict(img.variabledata)
265
+ output["pickdata"] = self._numpy_to_dict(img.pickdata)
266
+ return output
267
+
268
+ ANIM_TYPE_SOLUTIONTIME: int = 0
269
+ ANIM_TYPE_ANIMATEDTRACES: int = 1
270
+ ANIM_TYPE_FLIPBOOK: int = 2
271
+ ANIM_TYPE_KEYFRAME: int = 3
272
+
273
+ def animation(
274
+ self,
275
+ filename: str,
276
+ width: Optional[int] = None,
277
+ height: Optional[int] = None,
278
+ passes: int = 4,
279
+ anim_type: int = ANIM_TYPE_SOLUTIONTIME,
280
+ frames: Optional[int] = None,
281
+ starting_frame: int = 0,
282
+ frames_per_second: float = 60.0,
283
+ format_options: Optional[str] = "",
284
+ raytrace: bool = False,
285
+ ) -> None:
286
+ """Generate an MPEG4 animation file.
287
+
288
+ An MPEG4 animation file can be generated from temporal data, flipbooks, keyframes,
289
+ or animated traces.
290
+
291
+ Parameters
292
+ ----------
293
+ filename : str
294
+ Name for the MPEG4 file to save to local disk.
295
+ width : int, optional
296
+ Width of the image in pixels. The default is ``None``, in which case
297
+ ``ensight.objs.core.WINDOWSIZE[0]`` is used.
298
+ height : int, optional
299
+ Height of the image in pixels. The default is ``None``, in which case
300
+ ``ensight.objs.core.WINDOWSIZE[1]`` is used.
301
+ passes : int, optional
302
+ Number of antialiasing passes. The default is ``4``.
303
+ anim_type : int, optional
304
+ Type of the animation to render. The default is ``0``, in which case
305
+ ``"ANIM_TYPE_SOLUTIONTIME"`` is used. This table provides descriptions
306
+ by each option number and name:
307
+
308
+ =========================== ========================================
309
+ Name Animation type
310
+ =========================== ========================================
311
+ 0: ANIM_TYPE_SOLUTIONTIME Animation over all solution times
312
+ 1: ANIM_TYPE_ANIMATEDTRACES Records animated rotations and traces
313
+ 2: ANIM_TYPE_FLIPBOOK Records current flipbook animation
314
+ 3: ANIM_TYPE_KEYFRAME Records current kKeyframe animation
315
+ =========================== ========================================
316
+
317
+ frames : int, optional
318
+ Number of frames to save. The default is ``None``. The default for
319
+ all but ``ANIM_TYPE_ANIMATEDTRACES`` covers all timesteps, flipbook
320
+ pages, or keyframe steps. If ``ANIM_TYPE_ANIMATEDTRACES`` is specified,
321
+ this keyword is required.
322
+ starting_frame : int, optional
323
+ Keyword for saving a subset of the complete collection of frames.
324
+ The default is ``0``.
325
+ frames_per_second : float, optional
326
+ Number of frames per second for playback in the saved animation.
327
+ The default is ``60.0``.
328
+ format_options : str, optional
329
+ More specific options for the MPEG4 encoder. The default is ``""``.
330
+ raytrace : bool, optional
331
+ Whether to render the image with the raytracing engine. The default is ``False``.
332
+
333
+ Examples
334
+ --------
335
+ >>> s = LocalLauncher().start()
336
+ >>> data = f"{s.cei_home}/ensight{s.cei_suffix}gui/demos/Crash Queries.ens"
337
+ >>> s.ensight.objs.ensxml_restore_file(data)
338
+ >>> quality = "Quality Best Type 1"
339
+ >>> s.ensight.utils.export.animation("local_file.mp4", format_options=quality)
340
+
341
+ """
342
+ self._remote_support_check()
343
+
344
+ win_size = self._ensight.objs.core.WINDOWSIZE
345
+ if width is None:
346
+ width = win_size[0]
347
+ if height is None:
348
+ height = win_size[1]
349
+
350
+ if format_options is None:
351
+ format_options = "Quality High Type 1"
352
+
353
+ num_frames: int = 0
354
+ if frames is None:
355
+ if anim_type == self.ANIM_TYPE_SOLUTIONTIME:
356
+ num_timesteps = self._ensight.objs.core.TIMESTEP_LIMITS[1]
357
+ num_frames = num_timesteps - starting_frame
358
+ elif anim_type == self.ANIM_TYPE_ANIMATEDTRACES:
359
+ raise RuntimeError("frames is a required keyword with ANIMATEDTRACES animations")
360
+ elif anim_type == self.ANIM_TYPE_FLIPBOOK:
361
+ num_flip_pages = len(self._ensight.objs.core.FLIPBOOKS[0].PAGE_DETAILS)
362
+ num_frames = num_flip_pages - starting_frame
363
+ elif anim_type == self.ANIM_TYPE_KEYFRAME:
364
+ num_keyframe_pages = self._ensight.objs.core.KEYFRAMEDATA["totalFrames"]
365
+ num_frames = num_keyframe_pages - starting_frame
366
+ else:
367
+ num_frames = frames
368
+
369
+ if num_frames < 1: # pragma: no cover
370
+ raise RuntimeError( # pragma: no cover
371
+ "No frames selected. Perhaps a static dataset SOLUTIONTIME request \
372
+ or no FLIPBOOK/KEYFRAME defined."
373
+ )
374
+
375
+ if isinstance(self._ensight, ModuleType): # pragma: no cover
376
+ raw_mpeg4 = self._animation_remote( # pragma: no cover
377
+ width,
378
+ height,
379
+ passes,
380
+ anim_type,
381
+ starting_frame,
382
+ num_frames,
383
+ frames_per_second,
384
+ format_options,
385
+ raytrace,
386
+ )
387
+ else:
388
+ cmd = f"ensight.utils.export._animation_remote({width}, {height}, {passes}, "
389
+ cmd += f"{anim_type}, {starting_frame}, {num_frames}, "
390
+ cmd += f"{frames_per_second}, '{format_options}', {raytrace})"
391
+ raw_mpeg4 = self._ensight._session.cmd(cmd)
392
+
393
+ with open(filename, "wb") as fp:
394
+ fp.write(raw_mpeg4)
395
+
396
+ def _animation_remote(
397
+ self,
398
+ width: int,
399
+ height: int,
400
+ passes: int,
401
+ anim_type: int,
402
+ start: int,
403
+ frames: int,
404
+ fps: float,
405
+ options: str,
406
+ raytrace: bool,
407
+ ) -> bytes:
408
+ """EnSight-side implementation.
409
+
410
+ Parameters
411
+ ----------
412
+ width : int
413
+ Width of the image in pixels.
414
+ height : int
415
+ Height of the image in pixels.
416
+ passes : int
417
+ Number of antialiasing passes.
418
+ anim_type : int
419
+ Type of animation to save.
420
+ start : int
421
+ First frame number to save.
422
+ frames : int
423
+ Number of frames to save.
424
+ fps : float
425
+ Output framerate.
426
+ options : str
427
+ MPEG4 configuration options.
428
+ raytrace : bool
429
+ Whether to render the image with the raytracing engine.
430
+
431
+ Returns
432
+ -------
433
+ bytes
434
+ MPEG4 stream in bytes.
435
+ """
436
+
437
+ with tempfile.TemporaryDirectory() as tmpdirname:
438
+ tmpfilename = os.path.join(tmpdirname, str(uuid.uuid1()) + ".mp4")
439
+ self._ensight.file.animation_rend_offscreen("ON")
440
+ self._ensight.file.animation_screen_tiling(1, 1)
441
+ self._ensight.file.animation_format("mpeg4")
442
+ if options:
443
+ self._ensight.file.animation_format_options(options)
444
+ self._ensight.file.animation_frame_rate(fps)
445
+ self._ensight.file.animation_rend_offscreen("ON")
446
+ self._ensight.file.animation_numpasses(passes)
447
+ self._ensight.file.animation_stereo("mono")
448
+ self._ensight.file.animation_screen_tiling(1, 1)
449
+ self._ensight.file.animation_file(tmpfilename)
450
+ self._ensight.file.animation_window_size("user_defined")
451
+ self._ensight.file.animation_window_xy(width, height)
452
+ self._ensight.file.animation_frames(frames)
453
+ self._ensight.file.animation_start_number(start)
454
+ self._ensight.file.animation_multiple_images("OFF")
455
+ if raytrace:
456
+ self._ensight.file.animation_raytrace_it("ON")
457
+ else:
458
+ self._ensight.file.animation_raytrace_it("OFF")
459
+ self._ensight.file.animation_raytrace_ext("OFF")
460
+
461
+ self._ensight.file.animation_play_time("OFF")
462
+ self._ensight.file.animation_play_flipbook("OFF")
463
+ self._ensight.file.animation_play_keyframe("OFF")
464
+
465
+ self._ensight.file.animation_reset_time("OFF")
466
+ self._ensight.file.animation_reset_traces("OFF")
467
+ self._ensight.file.animation_reset_flipbook("OFF")
468
+ self._ensight.file.animation_reset_keyframe("OFF")
469
+
470
+ if anim_type == self.ANIM_TYPE_SOLUTIONTIME:
471
+ # playing over time
472
+ self._ensight.file.animation_play_time("ON")
473
+ self._ensight.file.animation_reset_time("ON")
474
+ elif anim_type == self.ANIM_TYPE_ANIMATEDTRACES:
475
+ # recording particle traces/etc
476
+ self._ensight.file.animation_reset_traces("ON")
477
+ elif anim_type == self.ANIM_TYPE_KEYFRAME:
478
+ self._ensight.file.animation_reset_keyframe("ON")
479
+ self._ensight.file.animation_play_keyframe("ON")
480
+ elif anim_type == self.ANIM_TYPE_FLIPBOOK:
481
+ self._ensight.file.animation_play_flipbook("ON")
482
+ self._ensight.file.animation_reset_flipbook("ON")
483
+
484
+ self._ensight.file.save_animation()
485
+
486
+ with open(tmpfilename, "rb") as fp:
487
+ mp4_data = fp.read()
488
+
489
+ return mp4_data
490
+
491
+ GEOM_EXPORT_GLTF = "gltf2"
492
+ GEOM_EXPORT_AVZ = "avz"
493
+ GEOM_EXPORT_PLY = "ply"
494
+ GEOM_EXPORT_STL = "stl"
495
+
496
+ extension_map = {
497
+ GEOM_EXPORT_GLTF: ".glb",
498
+ GEOM_EXPORT_AVZ: ".avz",
499
+ GEOM_EXPORT_PLY: ".ply",
500
+ GEOM_EXPORT_STL: ".stl",
501
+ }
502
+
503
+ def _geometry_remote( # pragma: no cover
504
+ self, format: str, starting_timestep: int, frames: int, delta_timestep: int
505
+ ) -> List[bytes]:
506
+ """EnSight-side implementation.
507
+
508
+ Parameters
509
+ ----------
510
+ format : str
511
+ The format to export
512
+ starting_timestep: int
513
+ The first timestep to export. If None, defaults to the current timestep
514
+ frames: int
515
+ Number of timesteps to save. If None, defaults from the current timestep to the last
516
+ delta_timestep: int
517
+ The delta timestep to use when exporting
518
+
519
+ Returns
520
+ -------
521
+ bytes
522
+ Geometry export in bytes
523
+ """
524
+ rawdata = None
525
+ extension = self.extension_map.get(format)
526
+ rawdata_list = []
527
+ if not extension:
528
+ raise RuntimeError("The geometry export format provided is not supported.")
529
+ with tempfile.TemporaryDirectory() as tmpdirname:
530
+ self._ensight.part.select_all()
531
+ self._ensight.savegeom.format(format)
532
+ self._ensight.savegeom.begin_step(starting_timestep)
533
+ # frames is 1-indexed, so I need to decrease of 1
534
+ self._ensight.savegeom.end_step(starting_timestep + frames - 1)
535
+ self._ensight.savegeom.step_by(delta_timestep)
536
+ tmpfilename = os.path.join(tmpdirname, str(uuid.uuid1()))
537
+ self._ensight.savegeom.save_geometric_entities(tmpfilename)
538
+ files = glob.glob(f"{tmpfilename}*{extension}")
539
+ for export_file in files:
540
+ with open(export_file, "rb") as tmpfile:
541
+ rawdata = tmpfile.read()
542
+ rawdata_list.append(rawdata)
543
+ return rawdata_list
544
+
545
+ def geometry(
546
+ self,
547
+ filename: str,
548
+ format: str = GEOM_EXPORT_GLTF,
549
+ starting_timestep: Optional[int] = None,
550
+ frames: Optional[int] = 1,
551
+ delta_timestep: Optional[int] = None,
552
+ ) -> None:
553
+ """Export a geometry file.
554
+
555
+ Parameters
556
+ ----------
557
+ filename: str
558
+ The location where to export the geometry
559
+ format : str
560
+ The format to export
561
+ starting_timestep: int
562
+ The first timestep to export. If None, defaults to the current timestep
563
+ frames: int
564
+ Number of timesteps to save. If None, defaults from the current timestep to the last
565
+ delta_timestep: int
566
+ The delta timestep to use when exporting
567
+
568
+ Examples
569
+ --------
570
+ >>> s = LocalLauncher().start()
571
+ >>> data = f"{s.cei_home}/ensight{s.cei_suffix}gui/demos/Crash Queries.ens"
572
+ >>> s.ensight.objs.ensxml_restore_file(data)
573
+ >>> s.ensight.utils.export.geometry("local_file.glb", format=s.ensight.utils.export.GEOM_EXPORT_GLTF)
574
+ """
575
+ if starting_timestep is None:
576
+ starting_timestep = int(self._ensight.objs.core.TIMESTEP)
577
+ if frames is None or frames == -1:
578
+ # Timesteps are 0-indexed so frames need to be increased of 1
579
+ frames = int(self._ensight.objs.core.TIMESTEP_LIMITS[1]) + 1
580
+ if not delta_timestep:
581
+ delta_timestep = 1
582
+ self._remote_support_check()
583
+ raw_data_list = None
584
+ if isinstance(self._ensight, ModuleType): # pragma: no cover
585
+ raw_data_list = self._geometry_remote( # pragma: no cover
586
+ format,
587
+ starting_timestep=starting_timestep,
588
+ frames=frames,
589
+ delta_timestep=delta_timestep,
590
+ )
591
+ else:
592
+ self._ensight._session.ensight_version_check("2024 R2")
593
+ cmd = f"ensight.utils.export._geometry_remote('{format}', {starting_timestep}, {frames}, {delta_timestep})"
594
+ raw_data_list = self._ensight._session.cmd(cmd)
595
+ if raw_data_list: # pragma: no cover
596
+ if len(raw_data_list) == 1:
597
+ with open(filename, "wb") as fp:
598
+ fp.write(raw_data_list[0])
599
+ else:
600
+ for idx, raw_data in enumerate(raw_data_list):
601
+ filename_base, extension = os.path.splitext(filename)
602
+ _filename = f"{filename_base}{str(idx).zfill(3)}{extension}"
603
+ with open(_filename, "wb") as fp:
604
+ fp.write(raw_data)
605
+ else: # pragma: no cover
606
+ raise IOError("Export was not successful") # pragma: no cover