Skip to content

Commit 918c515

Browse files
authored
Add vtk_helpers for DPF to VTK translation (#1581)
* Add dpf_field_to_vtk in vtk_helper.py Signed-off-by: paul.profizi <[email protected]> * Refactor vtk_helper.py Signed-off-by: paul.profizi <[email protected]> * Add tests Signed-off-by: paul.profizi <[email protected]> * Add dpf_meshes_to_vtk in vtk_helper.py Signed-off-by: paul.profizi <[email protected]> * Add dpf_fieldscontainer_to_vtk in vtk_helper.py Signed-off-by: paul.profizi <[email protected]> * Add retro criterion on tests Signed-off-by: paul.profizi <[email protected]> * Sort field naming by label_space Signed-off-by: paul.profizi <[email protected]> * Sort field naming by label_space Signed-off-by: paul.profizi <[email protected]> * Add optional mesh arguments to fields translation methods Signed-off-by: paul.profizi <[email protected]> * Fix for Docker and test on all server types Signed-off-by: paul.profizi <[email protected]> * Fix error logic when field has no mesh Signed-off-by: paul.profizi <[email protected]> * Fix test for ansys-grpc-dpf Signed-off-by: paul.profizi <[email protected]> * Add dpf_property_field_to_vtk Signed-off-by: paul.profizi <[email protected]> * Fix retro-compatibility Signed-off-by: paul.profizi <[email protected]> * Fix version_requires undefined Signed-off-by: paul.profizi <[email protected]> * Fix requirement on server version for dpf_property_field_to_vtk Signed-off-by: paul.profizi <[email protected]> --------- Signed-off-by: paul.profizi <[email protected]>
1 parent 24523e3 commit 918c515

File tree

2 files changed

+453
-11
lines changed

2 files changed

+453
-11
lines changed

src/ansys/dpf/core/vtk_helper.py

Lines changed: 291 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import numpy as np
22
import pyvista as pv
3-
import ansys.dpf.core as dpf
4-
from ansys.dpf.core import errors
3+
from typing import Union
54
from vtk import (
65
VTK_HEXAHEDRON,
76
VTK_LINE,
@@ -22,6 +21,10 @@
2221
VTK_WEDGE,
2322
vtkVersion,
2423
)
24+
25+
import ansys.dpf.core as dpf
26+
from ansys.dpf.core import errors
27+
from ansys.dpf.core.check_version import server_meet_version_and_raise
2528
from ansys.dpf.core.elements import element_types
2629

2730
VTK9 = vtkVersion().GetVTKMajorVersion() >= 9
@@ -124,7 +127,7 @@ def __init__(
124127
ModuleNotFoundError.__init__(self, msg)
125128

126129

127-
def dpf_mesh_to_vtk_op(mesh, nodes, as_linear):
130+
def dpf_mesh_to_vtk_op(mesh, nodes=None, as_linear=True):
128131
"""Return a pyvista unstructured grid given DPF node and element
129132
definitions from operators (server > 6.2)
130133
@@ -134,7 +137,7 @@ def dpf_mesh_to_vtk_op(mesh, nodes, as_linear):
134137
Meshed Region to export to pyVista format
135138
136139
nodes : dpf.Field
137-
Field containing the nodes of the mesh.
140+
Field containing the node coordinates of the mesh.
138141
139142
as_linear : bool
140143
Export quadratic surface elements as linear.
@@ -333,25 +336,28 @@ def compute_offset():
333336
return pv.UnstructuredGrid(offset, cells, vtk_cell_type, node_coordinates)
334337

335338

336-
def dpf_mesh_to_vtk(mesh, nodes=None, as_linear=True):
337-
"""Return a pyvista unstructured grid given DPF node and element
338-
definitions.
339+
def dpf_mesh_to_vtk(
340+
mesh: dpf.MeshedRegion,
341+
nodes: Union[dpf.Field, None] = None,
342+
as_linear: bool = True
343+
) -> pv.UnstructuredGrid:
344+
"""Return a pyvista UnstructuredGrid given a pydpf MeshedRegion.
339345
340346
Parameters
341347
----------
342348
mesh : dpf.MeshedRegion
343-
Meshed Region to export to pyVista format
349+
Meshed Region to export to pyVista format.
344350
345351
nodes : dpf.Field, optional
346-
Field containing the nodes of the mesh.
352+
Field containing the node coordinates of the mesh (useful to get a deformed geometry).
347353
348354
as_linear : bool, optional
349355
Export quadratic surface elements as linear.
350356
351357
Returns
352358
-------
353-
grid : pyvista.UnstructuredGrid
354-
Unstructured grid of the DPF mesh.
359+
grid:
360+
UnstructuredGrid corresponding to the DPF mesh.
355361
"""
356362
try:
357363
return dpf_mesh_to_vtk_op(mesh, nodes, as_linear)
@@ -363,3 +369,277 @@ def vtk_update_coordinates(vtk_grid, coordinates_array):
363369
from copy import copy
364370

365371
vtk_grid.points = copy(coordinates_array)
372+
373+
374+
def dpf_meshes_to_vtk(
375+
meshes_container: dpf.MeshesContainer,
376+
nodes: Union[dpf.FieldsContainer, None] = None,
377+
as_linear: bool = True
378+
) -> pv.UnstructuredGrid:
379+
"""Return a pyvista UnstructuredGrid given a pydpf MeshedRegion.
380+
381+
Parameters
382+
----------
383+
meshes_container:
384+
MeshesContainer to export to pyVista format.
385+
386+
nodes:
387+
FieldsContainer containing the node coordinates for each mesh
388+
(useful to get a deformed geometry). The labels must match a field to a mesh.
389+
390+
as_linear : bool, optional
391+
Export quadratic surface elements as linear.
392+
393+
Returns
394+
-------
395+
grid:
396+
UnstructuredGrid corresponding to the DPF meshes.
397+
"""
398+
grids = []
399+
for i, mesh in enumerate(meshes_container):
400+
nodes_i = None
401+
if nodes:
402+
nodes_i = nodes[i]
403+
grids.append(dpf_mesh_to_vtk(mesh, nodes_i, as_linear))
404+
return pv.MultiBlock(grids).combine()
405+
406+
407+
def dpf_field_to_vtk(
408+
field: dpf.Field,
409+
meshed_region: Union[dpf.MeshedRegion, None] = None,
410+
nodes: Union[dpf.Field, None] = None,
411+
as_linear: bool = True
412+
) -> pv.UnstructuredGrid:
413+
"""Return a pyvista UnstructuredGrid given a DPF Field.
414+
415+
Parameters
416+
----------
417+
field:
418+
Field to export to pyVista format.
419+
420+
meshed_region:
421+
Mesh to associate to the field.
422+
Useful for fluid results where the field is not automatically associated to its mesh.
423+
424+
nodes:
425+
Field containing the node coordinates of the mesh (useful to get a deformed geometry).
426+
427+
as_linear:
428+
Export quadratic surface elements as linear.
429+
430+
Returns
431+
-------
432+
grid:
433+
UnstructuredGrid corresponding to the DPF Field.
434+
"""
435+
# Check Field location
436+
supported_locations = [
437+
dpf.locations.nodal, dpf.locations.elemental, dpf.locations.faces, dpf.locations.overall
438+
]
439+
if field.location not in supported_locations:
440+
raise ValueError(
441+
f"Supported field locations for translation to VTK are: {supported_locations}."
442+
)
443+
444+
# Associate the provided mesh with the field
445+
if meshed_region:
446+
field.meshed_region = meshed_region
447+
else:
448+
try:
449+
meshed_region = field.meshed_region
450+
except errors.DPFServerException as e:
451+
if "the field doesn't have this support type" in str(e):
452+
raise ValueError("The field does not have a meshed_region.")
453+
else:
454+
raise e
455+
except RuntimeError as e:
456+
if "The field's support is not a mesh" in str(e):
457+
raise ValueError("The field does not have a meshed_region.")
458+
else:
459+
raise e
460+
461+
# Initialize the bare UnstructuredGrid
462+
if meshed_region.nodes.n_nodes == 0:
463+
raise ValueError("The field does not have a meshed_region.")
464+
grid = dpf_mesh_to_vtk(mesh=meshed_region, nodes=nodes, as_linear=as_linear)
465+
466+
# Map Field.data to the VTK mesh
467+
overall_data = _map_field_to_mesh(field=field, meshed_region=meshed_region)
468+
469+
# Update the UnstructuredGrid
470+
if field.location == dpf.locations.nodal:
471+
grid.point_data[field.name] = overall_data
472+
else:
473+
grid.cell_data[field.name] = overall_data
474+
return grid
475+
476+
477+
def dpf_fieldscontainer_to_vtk(
478+
fields_container: dpf.FieldsContainer,
479+
meshes_container: Union[dpf.MeshesContainer, None] = None,
480+
nodes: Union[dpf.Field, None] = None,
481+
as_linear: bool = True
482+
) -> pv.UnstructuredGrid:
483+
"""Return a pyvista UnstructuredGrid given a DPF FieldsContainer.
484+
485+
If the fields have different mesh supports, a global merged mesh support is created.
486+
487+
Parameters
488+
----------
489+
fields_container:
490+
FieldsContainer to export to pyVista format.
491+
492+
meshes_container:
493+
MeshesContainer with meshes to associate to the fields in the FieldsContainer.
494+
Useful for fluid results where the fields are not automatically associated to their mesh.
495+
496+
nodes:
497+
Field containing the node coordinates of the mesh (useful to get a deformed geometry).
498+
499+
as_linear:
500+
Export quadratic surface elements as linear.
501+
502+
Returns
503+
-------
504+
grid:
505+
UnstructuredGrid corresponding to the DPF Field.
506+
"""
507+
# Check Field location
508+
supported_locations = [
509+
dpf.locations.nodal, dpf.locations.elemental, dpf.locations.faces, dpf.locations.overall
510+
]
511+
if fields_container[0].location not in supported_locations:
512+
raise ValueError(
513+
f"Supported field locations for translation to VTK are: {supported_locations}."
514+
)
515+
516+
# Associate the meshes in meshes_container to the corresponding fields if provided
517+
if meshes_container:
518+
for i, mesh in enumerate(meshes_container):
519+
label_space = meshes_container.get_label_space(i)
520+
fields_container.get_field(
521+
label_space_or_index=label_space
522+
).meshed_region = meshes_container.get_mesh(label_space_or_index=label_space)
523+
524+
# Initialize the bare UnstructuredGrid
525+
# Loop on the fields to check if merging supports is necessary
526+
meshes = []
527+
for field in fields_container:
528+
if field.meshed_region not in meshes:
529+
meshes.append(field.meshed_region)
530+
if len(meshes)>1:
531+
# Merge the meshed_regions
532+
merge_op = dpf.operators.utility.merge_meshes(server=fields_container._server)
533+
for i, mesh in enumerate(meshes):
534+
merge_op.connect(i, mesh)
535+
meshed_region = merge_op.eval()
536+
else:
537+
meshed_region = meshes[0]
538+
if meshed_region.nodes.n_nodes == 0:
539+
raise ValueError("The meshed_region of the fields contains no nodes.")
540+
grid = dpf_mesh_to_vtk(mesh=meshed_region, nodes=nodes, as_linear=as_linear)
541+
542+
for i, field in enumerate(fields_container):
543+
# Map Field.data to the VTK mesh
544+
overall_data = _map_field_to_mesh(field=field, meshed_region=meshed_region)
545+
label_space = fields_container.get_label_space(i)
546+
label_space = dict([(k, label_space[k]) for k in sorted(label_space.keys())])
547+
field.name = field.name+f" {label_space}"
548+
# Update the UnstructuredGrid
549+
if field.location == dpf.locations.nodal:
550+
grid.point_data[field.name] = overall_data
551+
else:
552+
grid.cell_data[field.name] = overall_data
553+
554+
return grid
555+
556+
557+
def _map_field_to_mesh(
558+
field: Union[dpf.Field, dpf.PropertyField],
559+
meshed_region: dpf.MeshedRegion
560+
) -> np.ndarray:
561+
"""Return an NumPy array of 'Field.data' mapped to the mesh on the field's location."""
562+
location = field.location
563+
if location == dpf.locations.nodal:
564+
mesh_location = meshed_region.nodes
565+
elif location == dpf.locations.elemental:
566+
mesh_location = meshed_region.elements
567+
elif location == dpf.locations.faces:
568+
mesh_location = meshed_region.faces
569+
if len(mesh_location) == 0:
570+
raise ValueError("No faces found to plot on")
571+
elif location == dpf.locations.overall:
572+
mesh_location = meshed_region.elements
573+
else:
574+
raise ValueError("Only elemental, nodal or faces location are supported for plotting.")
575+
component_count = field.component_count
576+
if component_count > 1:
577+
overall_data = np.full((len(mesh_location), component_count), np.nan)
578+
else:
579+
overall_data = np.full(len(mesh_location), np.nan)
580+
if location != dpf.locations.overall:
581+
ind, mask = mesh_location.map_scoping(field.scoping)
582+
overall_data[ind] = field.data[mask]
583+
else:
584+
overall_data[:] = field.data[0]
585+
return overall_data
586+
587+
588+
def dpf_property_field_to_vtk(
589+
property_field: dpf.PropertyField,
590+
meshed_region: dpf.MeshedRegion,
591+
nodes: Union[dpf.Field, None] = None,
592+
as_linear: bool = True
593+
) -> pv.UnstructuredGrid:
594+
"""Return a pyvista UnstructuredGrid given a DPF PropertyField.
595+
596+
..note:
597+
Available starting with DPF 2024.2.pre1.
598+
599+
Parameters
600+
----------
601+
property_field:
602+
PropertyField to export to pyVista format.
603+
604+
meshed_region:
605+
Mesh to associate to the property field.
606+
607+
nodes:
608+
Field containing the node coordinates of the mesh (useful to get a deformed geometry).
609+
610+
as_linear:
611+
Export quadratic surface elements as linear.
612+
613+
Returns
614+
-------
615+
grid:
616+
UnstructuredGrid corresponding to the DPF PropertyField.
617+
"""
618+
server_meet_version_and_raise(
619+
required_version="8.1",
620+
server=meshed_region._server,
621+
msg="Use of dpf_property_field_to_vtk requires DPF 2024.2.pre1 or above."
622+
)
623+
# Check Field location
624+
supported_locations = [
625+
dpf.locations.nodal, dpf.locations.elemental, dpf.locations.faces, dpf.locations.overall
626+
]
627+
if property_field.location not in supported_locations:
628+
raise ValueError(
629+
f"Supported field locations for translation to VTK are: {supported_locations}."
630+
)
631+
632+
# Initialize the bare UnstructuredGrid
633+
if meshed_region.nodes.n_nodes == 0:
634+
raise ValueError("The property field does not have a meshed_region.")
635+
grid = dpf_mesh_to_vtk(mesh=meshed_region, nodes=nodes, as_linear=as_linear)
636+
637+
# Map Field.data to the VTK mesh
638+
overall_data = _map_field_to_mesh(field=property_field, meshed_region=meshed_region)
639+
640+
# Update the UnstructuredGrid
641+
if property_field.location == dpf.locations.nodal:
642+
grid.point_data[property_field.name] = overall_data
643+
else:
644+
grid.cell_data[property_field.name] = overall_data
645+
return grid

0 commit comments

Comments
 (0)