Repository: pyvista/pyvista

Issue:
Support for Format EnsightGold (.case)
[Here I assume ensightGold, but it probably be extended to other ensight formats]

Hi! I really love what you guys are doing with pyvista! Here I propose to add support for the ensight gold format that is widely used in CAE softwares. I would gladly submit a PR for this.

## What? 
Support for EnSightGold format. more infos about the ensight format [here](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=2ahUKEwjjkN7_gLbpAhUsxYUKHcWyAjAQFjAAegQIAxAB&url=http%3A%2F%2Fwww3.ensight.com%2FEnSight10_Docs%2FUserManual.pdf&usg=AOvVaw0WWEvL6XEvSKdnLjxWEu4O). If link is dead I referenced the ensight user manual which can be found by google search. 
Under section `EnSight Gold Casefile Format` there are specifications.

## Why?
This format is widely used by CAE softwares for the ouputs of simulations (usually meshes with scalars/vector fields). For example Ansys and STAR-CCM can export in this format and openfoam can export in .encase

## How?
In my case a working version can be achieved by:
1.-  adding  `".case": vtk.vtkGenericEnSightReader, ` to the list of supported readers, 
2.- adding ".case" as multiblock data
3.- under `composite.py`, if extesion is ".case", use ` vtk.vtkGenericEnSightReader` and set file name as `reader.SetCaseFileName(filename)`. (for read and save)


Discussion:
akaszynski: I think we should avoid placing example files in the pyvista sdist installer.  Currently, we host example files on github, though we run into issues all the time when downloading the examples from github where we host it (https://github.com/pyvista/vtk-data).

Please submit a PR for adding the file to the vtk-data repo and add it as an example file (see `tests/test_examples.py`.  Then download the example case file like as in `tests/test_filters.py` L604 for an example file.  I'd also allow the test to fail on MacOS like as in that test as the downloads seem to fail quite often on MacOS.
JevinJ: @pyvista/developers, @akaszynski, @banesullivan I believe I've fixed it, this is good up for review now. 

I don't think the currently failed tests are an issue here, at least for MacOS. The Windows error seems to be a file system error, but difficult for me to debug without being able to run plotting. 

The segfault I had previously seems to be caused by me assigning numpy arrays in update() directly without running them through np.array. I'd appreciate it if anyone knows why that would be an issue.
banesullivan: It looks like this conflicts with recent changes from #740.
akaszynski: We're planning on releasing a minor release sometime this week.  If you'd like this PR to be in for the next release please have it done by Wednesday.
LucaZampieri: Sorry for the long silence, I will try to get it done for Friday. :) 
LucaZampieri: Sorry for the lie of 2 weeks ago! :angel: (and for the slow advances of this PR)
next time I will be more diligent! :)  

LucaZampieri: Hum.. indeed the download is failing on MacOs
akaszynski: Downloads are going to fail on MacOS, and our latest testing procedure is to not test anything that requires downloading on our CI as it’s prone to failure.

Let’s add a xfail for Linux and Windows and xskip for Mac for the tests as it seems like we have to test using files.  I wish there was a more reliable way of hosting the artifacts, and I think we could use azure for this as well.  Let’s close the loop on this PR and I can cleanup the CI in a later PR.
LucaZampieri: Hey reviewers! Is there anything I can do to help with this MR?
codecov[bot]: # [Codecov](https://codecov.io/gh/pyvista/pyvista/pull/725?src=pr&el=h1) Report
> Merging [#725](https://codecov.io/gh/pyvista/pyvista/pull/725?src=pr&el=desc) into [master](https://codecov.io/gh/pyvista/pyvista/commit/f077d087e310d449bc77418392399b77fbeda78f?el=desc) will **increase** coverage by `0.26%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #725      +/-   ##
==========================================
+ Coverage   86.84%   87.10%   +0.26%     
==========================================
  Files          36       36              
  Lines        9158     9165       +7     
==========================================
+ Hits         7953     7983      +30     
+ Misses       1205     1182      -23     
```

akaszynski: Got you covered.  MacOS testing is flaky when downloading, so I'm just simply skipping these tests.

I'd like to have this in the next release, so I'm adding it to the 0.27.0 milestone.



Patch:
diff --git a/pyvista/core/common.py b/pyvista/core/common.py
index 81710611..dca5484c 100644
--- a/pyvista/core/common.py
+++ b/pyvista/core/common.py
@@ -75,7 +75,10 @@ class DataObject:
                              f' Must be one of: {valid_extensions}')
 
         reader = self._READERS[file_ext]()
-        reader.SetFileName(str(file_path))
+        if file_ext == ".case":
+            reader.SetCaseFileName(str(file_path))
+        else:
+            reader.SetFileName(str(file_path))
         reader.Update()
         return reader.GetOutputDataObject(0)
 
diff --git a/pyvista/core/composite.py b/pyvista/core/composite.py
index 96e18b30..f6698a7e 100644
--- a/pyvista/core/composite.py
+++ b/pyvista/core/composite.py
@@ -62,6 +62,7 @@ class MultiBlock(vtkMultiBlockDataSet, CompositeFilters, DataObject):
     # Bind pyvista.plotting.plot to the object
     plot = pyvista.plot
     _READERS = dict.fromkeys(['.vtm', '.vtmb'], vtk.vtkXMLMultiBlockDataReader)
+    _READERS['.case'] = vtk.vtkGenericEnSightReader
     _WRITERS = dict.fromkeys(['.vtm', '.vtmb'], vtk.vtkXMLMultiBlockDataWriter)
 
     def __init__(self, *args, **kwargs):
diff --git a/pyvista/examples/downloads.py b/pyvista/examples/downloads.py
index 0793607d..e8636c39 100644
--- a/pyvista/examples/downloads.py
+++ b/pyvista/examples/downloads.py
@@ -650,6 +650,11 @@ def download_vtk_logo():
     """Download a texture of the VTK logo."""
     return _download_and_read("vtk.png", texture=True)
 
+def download_backward_facing_step():
+    """Download an ensigh gold case of a fluid simulation."""
+    folder, _ = _download_file('EnSight.zip')
+    filename = os.path.join(folder, "foam_case_0_0_0_0.case")
+    return pyvista.read(filename)
 
 def download_gpr_data_array():
     """Download a texture of the VTK logo."""
@@ -661,3 +666,4 @@ def download_gpr_path():
     saved_file, _ = _download_file("gpr-example/path.txt")
     path = np.loadtxt(saved_file, skiprows=1)
     return pyvista.PolyData(path)
+
diff --git a/pyvista/plotting/plotting.py b/pyvista/plotting/plotting.py
index 9cc1d3de..cd8842e1 100644
--- a/pyvista/plotting/plotting.py
+++ b/pyvista/plotting/plotting.py
@@ -1524,6 +1524,7 @@ class BasePlotter(PickingHelper, WidgetHelper):
             texture = mesh._activate_texture(texture)
 
         if texture:
+
             if isinstance(texture, np.ndarray):
                 texture = numpy_to_texture(texture)
             if not isinstance(texture, (vtk.vtkTexture, vtk.vtkOpenGLTexture)):
@@ -1538,6 +1539,9 @@ class BasePlotter(PickingHelper, WidgetHelper):
                 show_scalar_bar = False
             self.mapper.SetScalarModeToUsePointFieldData()
 
+            # see https://github.com/pyvista/pyvista/issues/950
+            mesh.set_active_scalars(None)
+
         # Handle making opacity array =========================================
 
         _custom_opac = False
diff --git a/pyvista/utilities/fileio.py b/pyvista/utilities/fileio.py
index 2df12095..c88f3666 100644
--- a/pyvista/utilities/fileio.py
+++ b/pyvista/utilities/fileio.py
@@ -27,6 +27,7 @@ READERS = {
     '.vts': vtk.vtkXMLStructuredGridReader,
     '.vtm': vtk.vtkXMLMultiBlockDataReader,
     '.vtmb': vtk.vtkXMLMultiBlockDataReader,
+    '.case': vtk.vtkGenericEnSightReader,
     # Image formats:
     '.bmp': vtk.vtkBMPReader,
     '.dem': vtk.vtkDEMReader,
@@ -230,7 +231,7 @@ def read(filename, attrs=None, file_format=None):
         return pyvista.PolyData(filename)
     elif ext in '.vts': # StructuredGrid
         return pyvista.StructuredGrid(filename)
-    elif ext in ['.vtm', '.vtmb']:
+    elif ext in ['.vtm', '.vtmb', '.case']:
         return pyvista.MultiBlock(filename)
     elif ext in ['.e', '.exo']:
         return read_exodus(filename)


Test patch:
diff --git a/tests/test_composite.py b/tests/test_composite.py
index f1ecae02..0eea88f7 100644
--- a/tests/test_composite.py
+++ b/tests/test_composite.py
@@ -1,4 +1,5 @@
 import pathlib
+import platform
 
 import numpy as np
 import pytest
@@ -8,6 +9,7 @@ import pyvista
 from pyvista import PolyData, RectilinearGrid, UniformGrid, StructuredGrid, MultiBlock
 from pyvista import examples as ex
 
+skip_mac = pytest.mark.skipif(platform.system() == 'Darwin', reason="Flaky Mac tests")
 
 @pytest.fixture()
 def vtk_multi():
@@ -212,6 +214,30 @@ def test_multi_block_io(extension, binary, tmpdir, use_pathlib, ant,
     foo = pyvista.read(filename)
     assert foo.n_blocks == multi.n_blocks
 
+@skip_mac  # fails due to download examples
+@pytest.mark.parametrize('binary', [True, False])
+@pytest.mark.parametrize('extension', ['vtm', 'vtmb'])
+def test_ensight_multi_block_io(extension, binary, tmpdir, ant,
+                                sphere, uniform, airplane, globe):
+    filename = str(tmpdir.mkdir("tmpdir").join('tmp.%s' % extension))
+    # multi = ex.load_bfs()  # .case file
+    multi = ex.download_backward_facing_step()  # .case file
+    # Now check everything
+    assert multi.n_blocks == 4
+    array_names = ['v2', 'nut', 'k', 'nuTilda', 'p', 'omega', 'f', 'epsilon', 'U']
+    for block in multi:
+        assert block.array_names == array_names
+    # Save it out
+    multi.save(filename, binary)
+    foo = MultiBlock(filename)
+    assert foo.n_blocks == multi.n_blocks
+    for block in foo:
+        assert block.array_names == array_names
+    foo = pyvista.read(filename)
+    assert foo.n_blocks == multi.n_blocks
+    for block in foo:
+        assert block.array_names == array_names
+
 
 def test_invalid_arg():
     with pytest.raises(TypeError):
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 83812198..4d67e8ec 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -1,5 +1,6 @@
 import os
 import sys
+import platform
 
 import numpy as np
 import pytest
@@ -22,6 +23,7 @@ skip_py2_nobind = pytest.mark.skipif(int(sys.version[0]) < 3,
                                      reason="Python 2 doesn't support binding methods")
 
 skip_windows = pytest.mark.skipif(os.name == 'nt', reason="Flaky Windows tests")
+skip_mac = pytest.mark.skipif(platform.system() == 'Darwin', reason="Flaky Mac tests")
 
 
 @pytest.fixture(scope='module')
@@ -51,6 +53,7 @@ def test_clip_filter():
 
 
 @skip_windows
+@skip_mac
 def test_clip_by_scalars_filter():
     """This tests the clip filter on all datatypes available filters"""
     for i, dataset_in in enumerate(DATASETS):