Repository: napari/napari

Issue:
ClippingPlanes uses vispy `x,y,z` convention and not napari's `z,y,x`
## 🐛 Bug

Experimental clipping planes API uses a different convention than our default.

## To Reproduce

Steps to reproduce the behavior:

1. Execute the code below:

```python3

import napari
from napari.layers.utils.plane import ClippingPlaneList
import numpy as np

shape = np.array((10, 50, 100))
image = np.arange(np.prod(shape)).reshape(shape)

center = shape / 2  # does not work
# center = np.flip(shape) / 2  # does work

cpl = ClippingPlaneList.from_bounding_box(center, dimensions=[5] * 3)

napari.view_image(image, experimental_clipping_planes=cpl, ndisplay=3)

napari.run()
```

## Expected behavior

I expected it to display a cube in the center of the canvas. It works when we flip the coordinates.

## Environment
```
napari: 0.4.11.dev374+gac3d85f5.d20211207
Platform: Linux-5.11.0-40-generic-x86_64-with-glibc2.17
System: Ubuntu 20.04.3 LTS
Python: 3.8.10 (default, Jun 4 2021, 15:09:15) [GCC 7.5.0]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.21.0
SciPy: 1.7.1
Dask: 2021.06.2
VisPy: 0.9.4

OpenGL:
- GL version: 4.6.0 NVIDIA 460.73.01
- MAX_TEXTURE_SIZE: 32768

Screens:
- screen 1: resolution 3440x1440, scale 1.0

Plugins:
- console: 0.0.4
- napari-ome-zarr: 0.3.0
- napari-tracks-reader: 0.1.3
```


Discussion:
Nice reproducible example @JoOkuma! Explains why I was seeing unintuitive behaviour the one time I played with this. I attributed it to me not thinking hard enough about the problem. The opposite was true! :sweat_smile: 

CC @brisvag @alisterburt 
I also noticed that the clipping planes do not take the layer's `scale` into account. I haven't checked the others transformations. To make it work the planes need to be specified in the transformed (final) space.
This was caused by the change from data-space to world-space for the coordinates of the clipping planes, and forgetting to update things here in napari. Shouldn't be too hard to fix!

> I also noticed that the clipping planes do not take the layer's scale into account.

This is on purpose. The idea is that later on, we should eb able to use a single `Plane` evented model to clip any number of objects in space, and it should Just Work. This means that the clipping has to happen in the post-transformed world coordinates. See #3148 for some thoughts on the future of this. In short, clipping planes should probably not be a layer attribute.

Resolving patch:
diff --git a/napari/_vispy/layers/base.py b/napari/_vispy/layers/base.py
--- a/napari/_vispy/layers/base.py
+++ b/napari/_vispy/layers/base.py
@@ -156,7 +156,8 @@ def _on_matrix_change(self):
     def _on_experimental_clipping_planes_change(self):
         if hasattr(self.node, 'clipping_planes'):
             self.node.clipping_planes = (
-                self.layer.experimental_clipping_planes.as_array()
+                # invert axes because vispy uses xyz but napari zyx
+                self.layer.experimental_clipping_planes.as_array()[..., ::-1]
             )
 
     def reset(self):


Test patch:
diff --git a/napari/_vispy/_tests/test_image_rendering.py b/napari/_vispy/_tests/test_image_rendering.py
--- a/napari/_vispy/_tests/test_image_rendering.py
+++ b/napari/_vispy/_tests/test_image_rendering.py
@@ -1,6 +1,8 @@
 import numpy as np
 
 from napari._tests.utils import skip_on_win_ci
+from napari._vispy.layers.image import VispyImageLayer
+from napari.layers.image import Image
 
 
 def test_image_rendering(make_napari_viewer):
@@ -56,3 +58,24 @@ def test_visibility_consistency(qtbot, make_napari_viewer):
     layer.visible = True
     screen2 = viewer.screenshot(flash=False).astype('float')
     assert np.max(np.abs(screen2 - screen1)) < 5
+
+
+def test_clipping_planes_dims():
+    """
+    Ensure that dims are correctly set on clipping planes
+    (vispy uses xyz, napary zyx)
+    """
+    clipping_planes = {
+        'position': (1, 2, 3),
+        'normal': (1, 2, 3),
+    }
+    image_layer = Image(
+        np.zeros((2, 2, 2)), experimental_clipping_planes=clipping_planes
+    )
+    vispy_layer = VispyImageLayer(image_layer)
+    napari_clip = image_layer.experimental_clipping_planes.as_array()
+    # needed to get volume node
+    image_layer._ndisplay = 3
+    vispy_layer._on_display_change()
+    vispy_clip = vispy_layer.node.clipping_planes
+    assert np.all(napari_clip == vispy_clip[..., ::-1])