diff --git a/ansys/dpf/core/meshed_region.py b/ansys/dpf/core/meshed_region.py index 931e61af5e8..61c116c480e 100644 --- a/ansys/dpf/core/meshed_region.py +++ b/ansys/dpf/core/meshed_region.py @@ -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 @@ -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: @@ -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 @@ -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. @@ -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 @@ -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``. @@ -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 @@ -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. @@ -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, @@ -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 diff --git a/ansys/dpf/core/plotter.py b/ansys/dpf/core/plotter.py index 8d00f22f6c0..324d434f031 100644 --- a/ansys/dpf/core/plotter.py +++ b/ansys/dpf/core/plotter.py @@ -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 @@ -30,18 +30,25 @@ 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 -------- @@ -49,7 +56,7 @@ def plot_chart(self, fields_container): >>> 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) @@ -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)") @@ -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, @@ -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) @@ -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() - diff --git a/ansys/dpf/core/vtk_helper.py b/ansys/dpf/core/vtk_helper.py index 4890c5a3a51..ad1836b3dd1 100644 --- a/ansys/dpf/core/vtk_helper.py +++ b/ansys/dpf/core/vtk_helper.py @@ -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] diff --git a/examples/01-static-transient/00-basic_transient.py b/examples/01-static-transient/00-basic_transient.py index 29675d8f8d6..b1088062764 100644 --- a/examples/01-static-transient/00-basic_transient.py +++ b/examples/01-static-transient/00-basic_transient.py @@ -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], diff --git a/tests/test_meshregion.py b/tests/test_meshregion.py index 78ba82a5fba..3e24910b01e 100644 --- a/tests/test_meshregion.py +++ b/tests/test_meshregion.py @@ -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)