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,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
|