Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions ansys/dpf/core/meshed_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,19 @@ def __str__(self):
# self._message = skin.get_output(0, types.meshed_region)
# return MeshedRegion(self._channel, skin, self._model, name)

def _as_vtk(self, as_linear=True):
def _as_vtk(self, as_linear=True, include_ids=False):
"""Convert DPF mesh to a pyvista unstructured grid"""
from ansys.dpf.core.vtk_helper import dpf_mesh_to_vtk
nodes = self.nodes.coordinates_field.data
etypes = self.elements.element_types_field.data
conn = self.elements.connectivities_field.data
grid = dpf_mesh_to_vtk(nodes, etypes, conn, as_linear)
grid['node_ids'] = self.nodes.scoping.ids
grid['element_ids'] = self.elements.scoping.ids

# consider adding this when scoping request is faster
if include_ids:
grid['node_ids'] = self.nodes.scoping.ids
grid['element_ids'] = self.elements.scoping.ids

return grid

@property
Expand Down Expand Up @@ -290,7 +294,8 @@ def plot(self, field_or_fields_container=None, notebook=None,
off_screen, show_axes, **kwargs)

# otherwise, simply plot self
return pl.plot_mesh(notebook)
kwargs['notebook'] = notebook
return pl.plot_mesh(**kwargs)


class Node:
Expand Down Expand Up @@ -642,13 +647,13 @@ def mapping_id_to_index(self):
self._mapping_id_to_index = self._build_mapping_id_to_index()
return self._mapping_id_to_index

def map_scoping(self, scope):
def map_scoping(self, external_scope):
"""Return the indices to map the scoping of these elements to
the scoping of a field.

Parameters
----------
scope : scoping.Scoping
external_scope : scoping.Scoping
Scoping to map to.

Returns
Expand All @@ -657,6 +662,9 @@ def map_scoping(self, scope):
List of indices to map from the external scope to the
scoping of these nodes.

mask : numpy.ndarray
Members of the external scope that are in the node scoping.

Examples
--------
Return the indices that map a field to a nodes collection.
Expand All @@ -667,9 +675,10 @@ def map_scoping(self, scope):
>>> nodes = model.metadata.meshed_region.nodes
>>> disp = model.results.displacements()
>>> field = disp.outputs.field_containers()[0]
>>> ind = nodes.map_scoping(field.scoping)
>>> ind
>>> ind, mask = nodes.map_scoping(field.scoping)
>>> ind, mask
array([ 508, 509, 909, ..., 3472, 3471, 3469])
array([True, True, True, ..., True, True, True])

These indices can then be used to remap ``nodes.coordinates`` to
match the order of the field data. That way the field data matches the
Expand All @@ -678,10 +687,12 @@ def map_scoping(self, scope):
>>> mapped_nodes = nodes.coordinates[ind]

"""
if scope.location in ['Elemental', 'NodalElemental']:
if external_scope.location in ['Elemental', 'NodalElemental']:
raise ValueError('Input scope location must be "Nodal"')
return np.array(list(map(self.mapping_id_to_index.get, scope.ids)))

arr = np.array(list(map(self.mapping_id_to_index.get, external_scope.ids)))
mask = arr != None
ind = arr[mask].astype(np.int)
return ind, mask

class Elements():
"""Elements belonging to a ``meshed_region``.
Expand Down Expand Up @@ -899,13 +910,13 @@ def mapping_id_to_index(self) -> dict:
self._mapping_id_to_index = self._build_mapping_id_to_index()
return self._mapping_id_to_index

def map_scoping(self, scope):
def map_scoping(self, external_scope):
"""Return the indices to map the scoping of these elements to
the scoping of a field.

Parameters
----------
scope : scoping.Scoping
external_scope : scoping.Scoping
Scoping to map to.

Returns
Expand All @@ -914,6 +925,9 @@ def map_scoping(self, scope):
List of indices to map from the external scope to the
scoping of these elements.

mask : numpy.ndarray
Members of the external scope that are in the element scoping.

Examples
--------
Return the indices that map a field to an elements collection.
Expand All @@ -924,7 +938,7 @@ def map_scoping(self, scope):
>>> elements = model.metadata.meshed_region.elements
>>> vol = model.results.volume()
>>> field = vol.outputs.field_containers()[0]
>>> ind = elements.map_scoping(field.scoping)
>>> ind, mask = elements.map_scoping(field.scoping)
>>> ind
[66039
11284,
Expand All @@ -942,6 +956,9 @@ def map_scoping(self, scope):
>>> mapped_data = field.data[ind]

"""
if scope.location in ['Nodal', 'NodalElemental']:
raise ValueError('Input scope location must be "Elemental"')
return np.array(list(map(self.mapping_id_to_index.get, scope.ids)))
if external_scope.location in ['Nodal', 'NodalElemental']:
raise ValueError('Input scope location must be "Nodal"')
arr = np.array(list(map(self.mapping_id_to_index.get, external_scope.ids)))
mask = arr != None
ind = arr[mask].astype(np.int)
return ind, mask
36 changes: 21 additions & 15 deletions ansys/dpf/core/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Plotter:
def __init__(self, mesh):
self._mesh = mesh

def plot_mesh(self, notebook=None):
def plot_mesh(self, **kwargs):
"""Plot the mesh using pyvista.

Parameters
Expand All @@ -30,26 +30,33 @@ def plot_mesh(self, notebook=None):
external to the notebook with an interactive window. When
``True``, always plot within a notebook.

**kwargs : optional
Additional keyword arguments for the plotter. See
``help(pyvista.plot)`` for additional keyword arguments.
"""
return self._mesh.grid.plot(notebook=notebook)

kwargs.setdefault('color', 'w')
kwargs.setdefault('show_edges', True)
return self._mesh.grid.plot(**kwargs)

def plot_chart(self, fields_container):
"""Plot the minimum/maximum result values over time
if the time_freq_support contains several time_steps
(for example: transient analysis)
"""Plot the minimum/maximum result values over time.

This is a valid method if the time_freq_support contains
several time_steps (for example, a transient analysis)

Parameters
----------
field_container
dpf.core.FieldsContainer that must contains a result for each time step of the time_freq_support.
field_container : dpf.core.FieldsContainer
A fields container that must contains a result for each
time step of the time_freq_support.

Examples
--------
>>> from ansys.dpf import core
>>> model = core.Model('file.rst')
>>> stress = model.results.stress()
>>> scoping = core.Scoping()
>>> scoping.ids = list(range(1, len(model.metadata.time_freq_support.frequencies) + 1))
>>> scoping.ids = range(1, len(model.metadata.time_freq_support.frequencies) + 1)
>>> stress.inputs.time_scoping.connect(scoping)
>>> fc = stress.outputs.fields_container()
>>> plotter = core.plotter.Plotter(model.metadata.meshed_region)
Expand All @@ -65,8 +72,8 @@ def plot_chart(self, fields_container):
minmaxOp.inputs.connect(normOp.outputs)
fieldMin = minmaxOp.outputs.field_min()
fieldMax = minmaxOp.outputs.field_max()
pyplot.plot(time_field.data,fieldMax.data,'r',label='Maximum')
pyplot.plot(time_field.data,fieldMin.data,'b',label='Minimum')
pyplot.plot(time_field.data, fieldMax.data, 'r', label='Maximum')
pyplot.plot(time_field.data, fieldMin.data, 'b', label='Minimum')
unit = tfq.frequencies.unit
if unit == "Hz":
pyplot.xlabel("frequencies (Hz)")
Expand All @@ -76,7 +83,7 @@ def plot_chart(self, fields_container):
pyplot.xlabel(unit)
substr = fields_container[0].name.split("_")
pyplot.ylabel(substr[0] + fieldMin.unit)
pyplot.title( substr[0] + ": min/max values over time")
pyplot.title(substr[0] + ": min/max values over time")
return pyplot.legend()

def plot_contour(self, field_or_fields_container, notebook=None,
Expand Down Expand Up @@ -181,8 +188,8 @@ def plot_contour(self, field_or_fields_container, notebook=None,
overall_data = np.full(len(mesh_location), np.nan)

for field in fields_container:
ind = mesh_location.map_scoping(field.scoping)
overall_data[ind] = field.data
ind, mask = mesh_location.map_scoping(field.scoping)
overall_data[ind] = field.data[mask]

# create the plotter and add the meshes
plotter = pv.Plotter(notebook=notebook, off_screen=off_screen)
Expand Down Expand Up @@ -233,4 +240,3 @@ def _plot_contour_using_vtk_file(self, fields_container, notebook=None):
plotter.add_mesh(grid, scalars=val, stitle=field_name, show_edges=True)
plotter.add_axes()
plotter.show()

9 changes: 9 additions & 0 deletions ansys/dpf/core/vtk_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ def compute_offset():
cells[cell_pos + 7] = cells[cell_pos + 3]
cells[cell_pos + 8] = cells[cell_pos + 4]

anstri6_mask = etypes == 4 # kAnsTri6 = 4
if np.any(anstri6_mask):
if offset is None:
offset = compute_offset()
cell_pos = offset[anstri6_mask]
cells[cell_pos + 4] = cells[cell_pos + 1]
cells[cell_pos + 5] = cells[cell_pos + 2]
cells[cell_pos + 6] = cells[cell_pos + 3]

else:
vtk_cell_type = VTK_MAPPING[etypes]

Expand Down
2 changes: 1 addition & 1 deletion examples/01-static-transient/00-basic_transient.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
# to match the order of nodes in the mesh.

nodes = meshed_region.nodes
ind = nodes.map_scoping(field.scoping)
ind, mask = nodes.map_scoping(field.scoping)

# show that the order of the remapped node scoping matches the field scoping
print('Scoping matches:', np.allclose(np.array(nodes.scoping.ids)[ind],
Expand Down
2 changes: 1 addition & 1 deletion tests/test_meshregion.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_get_mesh_from_model(simple_bar_model):

def test_vtk_grid_from_model(simple_bar_model):
mesh = simple_bar_model.metadata.meshed_region
grid = mesh.grid
grid = mesh._as_vtk(include_ids=True)
assert np.allclose(grid['element_ids'], mesh.elements.scoping.ids)
assert np.allclose(grid['node_ids'], mesh.nodes.scoping.ids)
assert all(grid.celltypes == vtk.VTK_HEXAHEDRON)
Expand Down