diff --git a/examples/00-load/README.txt b/examples/00-load/README.txt
index 1f3cbf90..621de4ac 100644
--- a/examples/00-load/README.txt
+++ b/examples/00-load/README.txt
@@ -1,5 +1,5 @@
-Loading and Creating Meshes
----------------------------
+Mesh Generation
+---------------
 
 These examples demo how to read various file types into PyVista mesh objects,
 create meshes from NumPy arrays, and how to create primitive geometric objects
diff --git a/examples/00-load/create-point-cloud.py b/examples/00-load/create-point-cloud.py
new file mode 100644
index 00000000..1d453a41
--- /dev/null
+++ b/examples/00-load/create-point-cloud.py
@@ -0,0 +1,117 @@
+"""
+.. _create_point_cloud:
+
+Create Point Cloud
+~~~~~~~~~~~~~~~~~~
+
+Create a :class:`pyvista.PolyData` object from a point cloud of vertices and
+scalar arrays for those points.
+
+"""
+
+import numpy as np
+import pyvista as pv
+from pyvista import examples
+
+
+###############################################################################
+# Point clouds are generally constructed in the :class:`pyvista.PolyData` class
+# and can easiy have scalar/vector data arrays associated with the point
+# cloud. In this example, we'll work a bit backwards using a point cloud that
+# that is available from our ``examples`` module. This however is no different
+# than creating a PyVista mesh with your own NumPy arrays of vertice locations.
+
+# Define some helpers - ignore these and use your own data!
+def generate_points(subset=0.02):
+    """A helper to make a 3D NumPy array of points (n_points by 3)"""
+    dataset = examples.download_lidar()
+    ids = np.random.randint(low=0, high=dataset.n_points-1,
+                            size=int(dataset.n_points * subset))
+    return dataset.points[ids]
+
+
+points = generate_points()
+# Print firts 5 rows to prove its a numpy array (n_points by 3)
+# Columns are (X Y Z)
+print(points[0:5, :])
+
+###############################################################################
+# Now that you have a NumPy array of points/vertices either from our sample
+# data or your own project, creating a PyVista mesh of those points is simply:
+point_cloud = pv.PolyData(points)
+print(point_cloud)
+
+###############################################################################
+# And we can even do a sanity check
+print(np.allclose(points, point_cloud.points))
+
+###############################################################################
+# And now that we have a PyVista mesh, we can plot it. Note that we add an
+# option to use eye dome lighting - this is a shading technique to improve
+# depth perception with point clouds (learn more in :ref:`ref_edl`).
+point_cloud.plot(eye_dome_lighting=True)
+
+###############################################################################
+# Now what if you have data attributes (scalar/vector arrays) that you'd like
+# to associate with every node of your mesh? You can easily add NumPy data
+# arrays that have a length equal to the number of points in the mesh along the
+# first axis. For example, lets add a few arrays to this new ``point_cloud``
+# mesh.
+#
+# Make an array of scalar values with the same length as the points array.
+# Each element in this array will correspond to points at the same index:
+
+# Make data array using z-component of points array
+data = points[:,-1]
+
+# Add that data to the mesh with the name "uniform dist"
+point_cloud["elevation"] = data
+
+###############################################################################
+# And now we can plot the point cloud with that random data. PyVista is smart
+# enough to plot the scalar array you added by default. Note that this time,
+# we specify to render every point as its own sphere.
+point_cloud.plot(render_points_as_spheres=True)
+
+###############################################################################
+# That data is kind of boring, right? You can also add data arrays with
+# more than one scalar value - perhaps a vector with three elements? Let's
+# make a little function that will compute vectors for every node in the point
+# cloud and add those vectors to the mesh.
+#
+# This time, we're going to create a totally new, random point cloud.
+
+# Create random XYZ points
+points = np.random.rand(100, 3)
+# Make PolyData
+point_cloud = pv.PolyData(points)
+
+
+def compute_vectors(mesh):
+    origin = mesh.center
+    vectors = mesh.points - origin
+    vectors = vectors / np.linalg.norm(vectors, axis=1)[:, None]
+    return vectors
+
+vectors = compute_vectors(point_cloud)
+print(vectors[0:5, :])
+
+###############################################################################
+
+point_cloud['vectors'] = vectors
+
+###############################################################################
+# Now we can make arrows using those vectors using the glyph filter
+# (see :ref:`glyph_example` for more details).
+
+arrows = point_cloud.glyph(orient='vectors', scale=False, factor=0.15,)
+
+# Display the arrows
+plotter = pv.Plotter()
+plotter.add_mesh(point_cloud, color='maroon', point_size=10.,
+                 render_points_as_spheres=True)
+plotter.add_mesh(arrows, color='lightblue')
+# plotter.add_point_labels([point_cloud.center,], ['Center',],
+#                          point_color='yellow', point_size=20)
+plotter.show_grid()
+plotter.show()
diff --git a/examples/00-load/create-poly.py b/examples/00-load/create-poly.py
index 3d61ac8a..dec2094c 100644
--- a/examples/00-load/create-poly.py
+++ b/examples/00-load/create-poly.py
@@ -4,7 +4,8 @@
 Create PolyData
 ~~~~~~~~~~~~~~~
 
-Creating a PolyData object from NumPy arrays
+Creating a PolyData (triangulated surface) object from NumPy arrays of the
+vertices and faces.
 
 """
 
diff --git a/examples/01-filter/README.txt b/examples/01-filter/README.txt
index df06ffdb..b6b92486 100644
--- a/examples/01-filter/README.txt
+++ b/examples/01-filter/README.txt
@@ -1,8 +1,8 @@
 Filtering
 ---------
 
-These examples show case various mesh analysis and filtering rountines presentat
-in the :ref:`filters_ref` module. Explore these demos to perfrom tasks such as:
+These examples show case various mesh analysis and filtering routines present
+in the :ref:`filters_ref` module. Explore these demos to perform tasks such as:
 
 * Slicing and cutting meshes
 * Computing mesh properties like volume, area, and surface normals
diff --git a/examples/02-plot/plot-glyphs.py b/examples/01-filter/glyphs.py
similarity index 86%
rename from examples/02-plot/plot-glyphs.py
rename to examples/01-filter/glyphs.py
index 1ca6d9f3..0875211b 100644
--- a/examples/02-plot/plot-glyphs.py
+++ b/examples/01-filter/glyphs.py
@@ -53,13 +53,13 @@ vectors = np.vstack(
 sphere.vectors = vectors * 0.3
 
 # plot just the arrows
-sphere.arrows.plot()
+sphere.arrows.plot(scalars='GlyphScale')
 
 ###############################################################################
 
 # plot the arrows and the sphere
 p = pv.Plotter()
-p.add_mesh(sphere.arrows, lighting=False, stitle="Vector Magnitude")
+p.add_mesh(sphere.arrows, scalars='GlyphScale', lighting=False, stitle="Vector Magnitude")
 p.add_mesh(sphere, color="grey", ambient=0.6, opacity=0.5, show_edges=False)
 p.show()
 
@@ -70,15 +70,14 @@ p.show()
 #
 # Sometimes you might not want glyphs for every node in the input dataset. In
 # this case, you can choose to build glyphs for a subset of the input dataset
-# by using a percentage of the points. Using this percentage, a uniform
-# distribuiton is used to select points from the input dataset and use them for
-# glyphing.
+# by using a merging tolerance. Here we specify a merging tolerance of five
+# percent which equats to five perfect of the bounding box's length.
 
 # Example dataset with normals
 mesh = examples.load_random_hills()
 
 # create a subset of arrows using the glyph filter
-arrows = mesh.glyph(scale="Normals", orient="Normals", subset=0.05)
+arrows = mesh.glyph(scale="Normals", orient="Normals", tolerance=0.05)
 
 p = pv.Plotter()
 p.add_mesh(arrows, color="black")
diff --git a/examples/02-plot/README.txt b/examples/02-plot/README.txt
index 75256a45..dad91c17 100644
--- a/examples/02-plot/README.txt
+++ b/examples/02-plot/README.txt
@@ -1,9 +1,9 @@
-Plotting Examples
------------------
+Plotting
+--------
 
-These examples show case many of the possibilites for altering how you display
+These examples show case many of the possibilities for altering how you display
 spatial data. Explore these examples to learn how to leverage our powerful 3D
-plotting routines to perfrom tasks like:
+plotting routines to perform tasks like:
 
 * Color mapping scalar values with ``matplotlib`` colormaps
 * Creating animations as GIFs or movie files
diff --git a/examples/02-plot/plot-clear.py b/examples/02-plot/clear.py
similarity index 100%
rename from examples/02-plot/plot-clear.py
rename to examples/02-plot/clear.py
diff --git a/examples/02-plot/plot-cmap.py b/examples/02-plot/cmap.py
similarity index 73%
rename from examples/02-plot/plot-cmap.py
rename to examples/02-plot/cmap.py
index 5b2bb19d..a2e487cf 100644
--- a/examples/02-plot/plot-cmap.py
+++ b/examples/02-plot/cmap.py
@@ -1,8 +1,9 @@
 """
-Custom Colormaps
+Colormap Choices
 ~~~~~~~~~~~~~~~~
 
-Use a custom built colormap when plotting scalar values.
+Use a Matplotlib, Colorcet, cmocean, or custom colormap when plotting scalar
+values.
 """
 
 from pyvista import examples
@@ -10,16 +11,17 @@ import pyvista as pv
 import matplotlib.pyplot as plt
 
 ###############################################################################
-# Any colormap built for ``matplotlib`` or ``colorcet`` is fully compatible
-# with PyVista. Colormaps are typically specified by passing the string name of
-# the ``matplotlib`` or ``colorcet`` colormap to the plotting routine via the
-# ``cmap`` argument.
+# Any colormap built for ``matplotlib``, ``colorcet``, or ``cmocean`` is fully
+# compatible with PyVista. Colormaps are typically specified by passing the
+# string name of the colormap to the plotting routine via the ``cmap``
+# argument.
 #
-# See `Matplotlib's complete list of available colormaps`_ and
-# `Colorcet's complete list`_.
+# See `Matplotlib's complete list of available colormaps`_,
+# `Colorcet's complete list`_, and `cmocean's complete list`_.
 #
 # .. _Matplotlib's complete list of available colormaps: https://matplotlib.org/tutorials/colors/colormaps.html
 # .. _Colorcet's complete list: http://colorcet.pyviz.org/user_guide/index.html
+# .. _cmocean's complete list: https://matplotlib.org/cmocean/
 #
 # To get started using a custom colormap, download some data with scalars to
 # plot.
@@ -48,6 +50,10 @@ mesh.plot(cmap=cmap, cpos="xy")
 #
 # The "hot" version washes out detail at the high end, as if the image is
 # overexposed, while "fire" makes detail visible throughout the data range.
+#
+# Please note that in order to use Colorcet's colormaps including "fire", you
+# must have Colorcet installed in your Python environment:
+# ``pip install colorcet``
 
 p = pv.Plotter(shape=(2, 2), border=False)
 p.subplot(0, 0)
diff --git a/examples/02-plot/plot-edges.py b/examples/02-plot/edges.py
similarity index 100%
rename from examples/02-plot/plot-edges.py
rename to examples/02-plot/edges.py
diff --git a/examples/02-plot/plot-edl.py b/examples/02-plot/edl.py
similarity index 99%
rename from examples/02-plot/plot-edl.py
rename to examples/02-plot/edl.py
index dc8ac57d..dba32e3d 100644
--- a/examples/02-plot/plot-edl.py
+++ b/examples/02-plot/edl.py
@@ -1,4 +1,6 @@
 """
+.. _ref_edl:
+
 Eye Dome Lighting
 ~~~~~~~~~~~~~~~~~
 
diff --git a/examples/02-plot/plot-gif.py b/examples/02-plot/gif.py
similarity index 100%
rename from examples/02-plot/plot-gif.py
rename to examples/02-plot/gif.py
diff --git a/examples/02-plot/plot-labels.py b/examples/02-plot/labels.py
similarity index 100%
rename from examples/02-plot/plot-labels.py
rename to examples/02-plot/labels.py
diff --git a/examples/02-plot/plot-linked.py b/examples/02-plot/linked.py
similarity index 100%
rename from examples/02-plot/plot-linked.py
rename to examples/02-plot/linked.py
diff --git a/examples/02-plot/plot-logo.py.old b/examples/02-plot/logo.py.old
similarity index 100%
rename from examples/02-plot/plot-logo.py.old
rename to examples/02-plot/logo.py.old
diff --git a/examples/02-plot/plot-movie.py b/examples/02-plot/movie.py
similarity index 100%
rename from examples/02-plot/plot-movie.py
rename to examples/02-plot/movie.py
diff --git a/examples/02-plot/plot-multi-window.py b/examples/02-plot/multi-window.py
similarity index 100%
rename from examples/02-plot/plot-multi-window.py
rename to examples/02-plot/multi-window.py
diff --git a/examples/02-plot/plot-opacity.py b/examples/02-plot/opacity.py
similarity index 100%
rename from examples/02-plot/plot-opacity.py
rename to examples/02-plot/opacity.py
diff --git a/examples/02-plot/plot-orbit.py b/examples/02-plot/orbit.py
similarity index 100%
rename from examples/02-plot/plot-orbit.py
rename to examples/02-plot/orbit.py
diff --git a/examples/02-plot/plot-scalar-bars.py b/examples/02-plot/scalar-bars.py
similarity index 100%
rename from examples/02-plot/plot-scalar-bars.py
rename to examples/02-plot/scalar-bars.py
diff --git a/examples/02-plot/plot-screenshot.py b/examples/02-plot/screenshot.py
similarity index 100%
rename from examples/02-plot/plot-screenshot.py
rename to examples/02-plot/screenshot.py
diff --git a/examples/02-plot/plot-shading.py b/examples/02-plot/shading.py
similarity index 100%
rename from examples/02-plot/plot-shading.py
rename to examples/02-plot/shading.py
diff --git a/examples/02-plot/plot-texture.py b/examples/02-plot/texture.py
similarity index 100%
rename from examples/02-plot/plot-texture.py
rename to examples/02-plot/texture.py
diff --git a/examples/02-plot/plot-themes.py b/examples/02-plot/themes.py
similarity index 100%
rename from examples/02-plot/plot-themes.py
rename to examples/02-plot/themes.py
diff --git a/examples/02-plot/plot-volume.py b/examples/02-plot/volume.py
similarity index 90%
rename from examples/02-plot/plot-volume.py
rename to examples/02-plot/volume.py
index b26d5b3e..5b5bf9b7 100644
--- a/examples/02-plot/plot-volume.py
+++ b/examples/02-plot/volume.py
@@ -68,5 +68,8 @@ p.show()
 frog = examples.download_frog()
 
 p = pv.Plotter()
-p.add_volume(frog, cmap="viridis", opacity="sigmoid_5")
+p.add_volume(frog, cmap="viridis", opacity="sigmoid_6")
+p.camera_position = [(929., 1067., -278.9),
+                     (249.5, 234.5, 101.25),
+                     (-0.2048, -0.2632, -0.9427)]
 p.show()
diff --git a/pyvista/core/filters.py b/pyvista/core/filters.py
index d3dc7360..68ec70e9 100644
--- a/pyvista/core/filters.py
+++ b/pyvista/core/filters.py
@@ -95,11 +95,11 @@ class DataSetFilters(object):
         # run the clip
         if isinstance(dataset, vtk.vtkPolyData):
             alg = vtk.vtkClipPolyData()
-        elif isinstance(dataset, vtk.vtkImageData):
-            alg = vtk.vtkClipVolume()
-            alg.SetMixed3DCellGeneration(True)
+        # elif isinstance(dataset, vtk.vtkImageData):
+        #     alg = vtk.vtkClipVolume()
+        #     alg.SetMixed3DCellGeneration(True)
         else:
-            alg = vtk.vtkClipDataSet()
+            alg = vtk.vtkTableBasedClipDataSet()
         alg.SetInputDataObject(dataset) # Use the grid as the data we desire to cut
         alg.SetClipFunction(plane) # the the cutter to use the plane we made
         alg.SetInsideOut(invert) # invert the clip if needed
@@ -768,7 +768,7 @@ class DataSetFilters(object):
 
 
     def glyph(dataset, orient=True, scale=True, factor=1.0, geom=None,
-              subset=None):
+              tolerance=0.0, absolute=False):
         """
         Copies a geometric representation (called a glyph) to every
         point in the input dataset.  The glyph may be oriented along
@@ -789,23 +789,27 @@ class DataSetFilters(object):
         geom : vtk.vtkDataSet
             The geometry to use for the glyph
 
-        subset : float, optional
-            Take a percentage subset of the mesh's points. Float value is
-            percent subset between 0 and 1.
+        tolerance : float, optional
+            Specify tolerance in terms of fraction of bounding box length.
+            Float value is between 0 and 1. Default is 0.0. If ``absolute``
+            is ``True`` then the tolerance can be an absolute distance.
+
+        absolute : bool, optional
+            Control if ``tolerance`` is an absolute distance or a fraction.
         """
-        if subset is not None:
-            if subset <= 0.0 or subset > 1.0:
-                raise RuntimeError('subset must be a percentage between 0 and 1.')
-            ids = np.random.random_integers(low=0, high=dataset.n_points-1,
-                                    size=int(dataset.n_points * subset))
-            small = pyvista.PolyData(dataset.points[ids])
-            for name in dataset.point_arrays.keys():
-                small.point_arrays[name] = dataset.point_arrays[name][ids]
-            dataset = small
+        # Clean the points before glyphing
+        small = pyvista.PolyData(dataset.points)
+        small.point_arrays.update(dataset.point_arrays)
+        dataset = small.clean(point_merging=True, merge_tol=tolerance,
+                              lines_to_points=False, polys_to_lines=False,
+                              strips_to_polys=False, inplace=False,
+                              absolute=absolute)
+        # Make glyphing geometry
         if geom is None:
             arrow = vtk.vtkArrowSource()
             arrow.Update()
             geom = arrow.GetOutput()
+        # Run the algorithm
         alg = vtk.vtkGlyph3D()
         alg.SetSourceData(geom)
         if isinstance(scale, str):
@@ -817,6 +821,8 @@ class DataSetFilters(object):
                     alg.SetScaleModeToScaleByVector()
                 else:
                     alg.SetScaleModeToScaleByScalar()
+        else:
+            alg.SetScaleModeToDataScalingOff()
         if isinstance(orient, str):
             dataset.active_vectors_name = orient
             orient = True
diff --git a/pyvista/core/pointset.py b/pyvista/core/pointset.py
index a0be1225..536df44b 100644
--- a/pyvista/core/pointset.py
+++ b/pyvista/core/pointset.py
@@ -1267,7 +1267,8 @@ class PolyData(vtkPolyData, PointSet):
             return mesh
 
     def clean(self, point_merging=True, merge_tol=None, lines_to_points=True,
-              polys_to_lines=True, strips_to_polys=True, inplace=False):
+              polys_to_lines=True, strips_to_polys=True, inplace=False,
+              absolute=True, **kwargs):
         """
         Cleans mesh by merging duplicate points, remove unused
         points, and/or remove degenerate cells.
@@ -1279,7 +1280,9 @@ class PolyData(vtkPolyData, PointSet):
 
         merge_tol : float, optional
             Set merging tolarance.  When enabled merging is set to
-            absolute distance
+            absolute distance. If ``absolute`` is False, then the merging
+            tolerance is a fraction of the bounding box legnth. The alias
+            ``tolerance`` is also excepted.
 
         lines_to_points : bool, optional
             Turn on/off conversion of degenerate lines to points.  Enabled by
@@ -1295,18 +1298,26 @@ class PolyData(vtkPolyData, PointSet):
         inplace : bool, optional
             Updates mesh in-place while returning nothing.  Default True.
 
+        absolute : bool, optional
+            Control if ``merge_tol`` is an absolute distance or a fraction.
+
         Returns
         -------
         mesh : pyvista.PolyData
             Cleaned mesh.  None when inplace=True
         """
+        if merge_tol is None:
+            merge_tol = kwargs.pop('tolerance', None)
         clean = vtk.vtkCleanPolyData()
         clean.SetConvertLinesToPoints(lines_to_points)
         clean.SetConvertPolysToLines(polys_to_lines)
         clean.SetConvertStripsToPolys(strips_to_polys)
-        if merge_tol:
-            clean.ToleranceIsAbsoluteOn()
-            clean.SetAbsoluteTolerance(merge_tol)
+        if isinstance(merge_tol, (int, float)):
+            if absolute:
+                clean.ToleranceIsAbsoluteOn()
+                clean.SetAbsoluteTolerance(merge_tol)
+            else:
+                clean.SetTolerance(merge_tol)
         clean.SetInputData(self)
         clean.Update()
 
diff --git a/pyvista/plotting/plotting.py b/pyvista/plotting/plotting.py
index 28d4b8cd..1173e03e 100644
--- a/pyvista/plotting/plotting.py
+++ b/pyvista/plotting/plotting.py
@@ -70,10 +70,14 @@ class BasePlotter(object):
         return object.__new__(cls)
 
     def __init__(self, shape=(1, 1), border=None, border_color='k',
-                 border_width=1.0):
+                 border_width=1.0, title=None):
         """ Initialize base plotter """
         self.image_transparent_background = rcParams['transparent_background']
 
+        if title is None:
+            title = rcParams['title']
+        self.title = str(title)
+
         # by default add border for multiple plots
         if border is None:
             if shape != (1, 1):
@@ -605,7 +609,7 @@ class BasePlotter(object):
                 mesh = grid.extract_surface()
                 ind = mesh.point_arrays['vtkOriginalPointIds']
                 # remap scalars
-                if scalars is not None:
+                if isinstance(scalars, np.ndarray):
                     scalars = scalars[ind]
 
             mesh.compute_normals(cell_normals=False, inplace=True)
@@ -841,7 +845,7 @@ class BasePlotter(object):
                    opacity='linear', n_colors=256, cmap=None, flip_scalars=False,
                    reset_camera=None, name=None, ambient=0.0, categories=False,
                    loc=None, backface_culling=False, multi_colors=False,
-                   blending='additive', mapper='fixed_point', rng=None,
+                   blending='composite', mapper='fixed_point', rng=None,
                    stitle=None, scalar_bar_args=None,
                    show_scalar_bar=None, **kwargs):
         """
@@ -3201,8 +3205,12 @@ class Plotter(BasePlotter):
                     renderer.camera_position = cpos
             self._first_time = False
 
+        if title is None:
+            title = self.title
+
         if title:
             self.ren_win.SetWindowName(title)
+            self.title = title
 
         # if full_screen:
         if full_screen:
diff --git a/pyvista/plotting/qt_plotting.py b/pyvista/plotting/qt_plotting.py
index 59c2de85..2c8d260f 100644
--- a/pyvista/plotting/qt_plotting.py
+++ b/pyvista/plotting/qt_plotting.py
@@ -1,7 +1,6 @@
 import logging
 import os
 import time
-from threading import Thread
 
 import numpy as np
 import scooby
@@ -47,8 +46,17 @@ class QFileDialog(object):
     pass
 
 
+def pyqtSlot(*args, **kwargs):
+    """dummy function for environments without pyqt5"""
+    return lambda *x: None
+
+
+class QMainWindow(object):
+    pass
+
+
 try:
-    from PyQt5.QtCore import pyqtSignal
+    from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer
     from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
     from PyQt5 import QtGui
     from PyQt5 import QtCore
@@ -276,7 +284,7 @@ class QtInteractor(QVTKRenderWindowInteractor, BasePlotter):
         if not has_pyqt:
             raise AssertionError('Requires PyQt5')
         QVTKRenderWindowInteractor.__init__(self, parent)
-        BasePlotter.__init__(self, shape=shape)
+        BasePlotter.__init__(self, shape=shape, title=title)
         self.parent = parent
 
         # Create and start the interactive renderer
@@ -287,7 +295,7 @@ class QtInteractor(QVTKRenderWindowInteractor, BasePlotter):
 
         self.background_color = rcParams['background']
 
-        if title:
+        if self.title:
             self.setWindowTitle(title)
 
         self.iren.RemoveObservers('MouseMoveEvent')  # slows window update?
@@ -340,6 +348,8 @@ class BackgroundPlotter(QtInteractor):
 
             from IPython.external.qt_for_kernel import QtGui
             QtGui.QApplication.instance()
+        else:
+            ipython = None
 
         # run within python
         if app is None:
@@ -349,7 +359,7 @@ class BackgroundPlotter(QtInteractor):
                 app = QApplication([''])
 
         self.app = app
-        self.app_window = QMainWindow()
+        self.app_window = MainWindow()
 
         self.frame = QFrame()
         self.frame.setFrameStyle(QFrame.NoFrame)
@@ -450,22 +460,17 @@ class BackgroundPlotter(QtInteractor):
         ensures the render window stays updated without consuming too
         many resources.
         """
-        self.render_trigger.connect(self.ren_win.Render)
-        twait = rate**-1
-
-        def render():
-            while self.active:
-                time.sleep(twait)
-                self._render()
-
-        self.render_thread = Thread(target=render)
-        self.render_thread.start()
+        twait = (rate**-1) * 1000.0
+        self.render_timer = QTimer(parent=self.app_window)
+        self.render_timer.timeout.connect(self._render)
+        self.app_window.signal_close.connect(self.render_timer.stop)
+        self.render_timer.start(twait)
 
     def closeEvent(self, event):
         self.active = False
-        self.app.quit()
         self.close()
 
+
     def add_actor(self, actor, reset_camera=None, name=None, loc=None, culling=False):
         actor, prop = super(BackgroundPlotter, self).add_actor(actor,
                                                                reset_camera,
@@ -528,9 +533,11 @@ class BackgroundPlotter(QtInteractor):
             return self.renderer.disable_eye_dome_lighting()
         return self.renderer.enable_eye_dome_lighting()
 
+    @pyqtSlot()
     def _render(self):
         super(BackgroundPlotter, self)._render()
         self.update_app_icon()
+        self.ren_win.Render() # force rendering
         return
 
     @property
@@ -548,3 +555,14 @@ class BackgroundPlotter(QtInteractor):
 
     def __del__(self):  # pragma: no cover
         self.close()
+
+
+class MainWindow(QMainWindow):
+    signal_close = pyqtSignal()
+
+    def __init__(self, parent=None):
+        super(MainWindow, self).__init__(parent)
+
+    def closeEvent(self, event):
+        self.signal_close.emit()
+        event.accept()
diff --git a/pyvista/plotting/theme.py b/pyvista/plotting/theme.py
index eca106c9..3435ddda 100644
--- a/pyvista/plotting/theme.py
+++ b/pyvista/plotting/theme.py
@@ -50,7 +50,8 @@ rcParams = {
     'interactive' : False,
     'render_points_as_spheres' : False,
     'use_panel' : True,
-    'transparent_background' : False
+    'transparent_background' : False,
+    'title' : 'PyVista',
 }
 
 DEFAULT_THEME = dict(rcParams)
@@ -95,14 +96,16 @@ def parse_color(color):
         return string_to_rgb(color)
     elif len(color) == 3:
         return color
+    elif len(color) == 4:
+        return color[:3]
     else:
         raise Exception("""
-    Invalid color input
+    Invalid color input: ({})
     Must ba string, rgb list, or hex color string.  For example:
         color='white'
         color='w'
         color=[1, 1, 1]
-        color='#FFFFFF'""")
+        color='#FFFFFF'""".format(color))
 
 
 
diff --git a/pyvista/plotting/tools.py b/pyvista/plotting/tools.py
index a9d14af3..513710dc 100644
--- a/pyvista/plotting/tools.py
+++ b/pyvista/plotting/tools.py
@@ -63,6 +63,7 @@ def create_axes_orientation_box(line_width=1, text_scale=0.366667,
         axes_actor.GetCubeProperty().SetOpacity(0)
 
         cube = pyvista.Cube()
+        cube.clear_arrays() # remove normals
         face_colors = np.array([x_face_color,
                                 x_face_color,
                                 y_face_color,
diff --git a/setup.py b/setup.py
index a36537ba..ad4a9cb2 100644
--- a/setup.py
+++ b/setup.py
@@ -42,7 +42,7 @@ setup(
               'pyvista.plotting', 'pyvista.utilities'],
     version=__version__,
     description='Easier Pythonic interface to VTK',
-    long_description=open(readme_file).read(),
+    long_description=io_open(readme_file, encoding="utf-8").read(),
     author='PyVista Developers',
     author_email='info@pyvista.org',
     license='MIT',

Change default window title
Can we change the default render title from "Visualization Toolkit ..." to "PyVista [something]"?

This would help brand screen grabs a bit better.

Currently it looks like:

<img width="624" alt="Screen Shot 2019-07-01 at 9 58 29 PM" src="https://user-images.githubusercontent.com/22067021/60481821-5a6c3800-9c4c-11e9-88a8-80b368e624f9.png">



It was done here: https://github.com/mne-tools/mne-python/pull/6475#issue-290127885 by @GuillaumeFavelier 

![59834537-73b9de00-9348-11e9-9f62-be5ce84512c6](https://user-images.githubusercontent.com/22067021/60481804-445e7780-9c4c-11e9-8d9b-ab11745ee46d.png)

diff --git a/tests/test_filters.py b/tests/test_filters.py
index e17f7a7d..e2bee58a 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -294,7 +294,7 @@ def test_glyph():
     sphere.point_arrays['arr'] = np.ones(sphere.n_points)
     result = sphere.glyph(scale='arr')
     result = sphere.glyph(scale='arr', orient='Normals', factor=0.1)
-    result = sphere.glyph(scale='arr', orient='Normals', factor=0.1, subset=0.5)
+    result = sphere.glyph(scale='arr', orient='Normals', factor=0.1, tolerance=0.1)
 
 
 def test_split_and_connectivity():

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-memprof pytest-qt nbval
apt-get update apt-get install -y libgl1-mesa-dev xvfb
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
attrs==24.2.0
backcall==0.2.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
cmocean==2.0
codecov==2.1.13
colorcet==3.1.0
comm==0.1.4
coverage==7.2.7
cycler==0.11.0
debugpy==1.7.0
decorator==5.1.1
entrypoints==0.4
exceptiongroup==1.2.2
fastjsonschema==2.21.1
fonttools==4.38.0
idna==3.10
imageio==2.31.2
imageio-ffmpeg==0.5.1
importlib-metadata==6.7.0
importlib-resources==5.12.0
iniconfig==2.0.0
ipykernel==6.16.2
ipython==7.34.0
ipywidgets==8.1.5
jedi==0.19.2
jsonschema==4.17.3
jupyter_client==7.4.9
jupyter_core==4.12.0
jupyterlab_widgets==3.0.13
kiwisolver==1.4.5
matplotlib==3.5.3
matplotlib-inline==0.1.6
nbformat==5.8.0
nbval==0.11.0
nest-asyncio==1.6.0
numpy==1.21.6
packaging==24.0
parso==0.8.4
pexpect==4.9.0
pickleshare==0.7.5
Pillow==9.5.0
pkgutil_resolve_name==1.3.10
pluggy==1.2.0
prompt_toolkit==3.0.48
psutil==7.0.0
ptyprocess==0.7.0
Pygments==2.17.2
pyparsing==3.1.4
PyQt5==5.11.3
PyQt5_sip==4.19.19
pyrsistent==0.19.3
pytest==7.4.4
pytest-cov==4.1.0
pytest-memprof==0.2.0
pytest-qt==4.2.0
python-dateutil==2.9.0.post0
-e git+https://github.com/pyvista/pyvista.git@f9c19b33d50b6e6c13d290eb899d2e908896e3c5#egg=pyvista
pyzmq==26.2.1
requests==2.31.0
scooby==0.7.3
six==1.17.0
tomli==2.0.1
tornado==6.2
traitlets==5.9.0
typing_extensions==4.7.1
urllib3==2.0.7
vtk==9.3.1
wcwidth==0.2.13
widgetsnbextension==4.0.13
zipp==3.15.0

name: pyvista
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - attrs==24.2.0
      - backcall==0.2.0
      - charset-normalizer==3.4.1
      - cmocean==2.0
      - codecov==2.1.13
      - colorcet==3.1.0
      - comm==0.1.4
      - coverage==7.2.7
      - cycler==0.11.0
      - debugpy==1.7.0
      - decorator==5.1.1
      - entrypoints==0.4
      - exceptiongroup==1.2.2
      - fastjsonschema==2.21.1
      - fonttools==4.38.0
      - idna==3.10
      - imageio==2.31.2
      - imageio-ffmpeg==0.5.1
      - importlib-metadata==6.7.0
      - importlib-resources==5.12.0
      - iniconfig==2.0.0
      - ipykernel==6.16.2
      - ipython==7.34.0
      - ipywidgets==8.1.5
      - jedi==0.19.2
      - jsonschema==4.17.3
      - jupyter-client==7.4.9
      - jupyter-core==4.12.0
      - jupyterlab-widgets==3.0.13
      - kiwisolver==1.4.5
      - matplotlib==3.5.3
      - matplotlib-inline==0.1.6
      - nbformat==5.8.0
      - nbval==0.11.0
      - nest-asyncio==1.6.0
      - numpy==1.21.6
      - packaging==24.0
      - parso==0.8.4
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pillow==9.5.0
      - pkgutil-resolve-name==1.3.10
      - pluggy==1.2.0
      - prompt-toolkit==3.0.48
      - psutil==7.0.0
      - ptyprocess==0.7.0
      - pygments==2.17.2
      - pyparsing==3.1.4
      - pyqt5==5.11.3
      - pyqt5-sip==4.19.19
      - pyrsistent==0.19.3
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-memprof==0.2.0
      - pytest-qt==4.2.0
      - python-dateutil==2.9.0.post0
      - pyzmq==26.2.1
      - requests==2.31.0
      - scooby==0.7.3
      - six==1.17.0
      - tomli==2.0.1
      - tornado==6.2
      - traitlets==5.9.0
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - vtk==9.3.1
      - wcwidth==0.2.13
      - widgetsnbextension==4.0.13
      - zipp==3.15.0
prefix: /opt/conda/envs/pyvista

tests/test_filters.py::test_glyph
tests/test_filters.py::test_threshold tests/test_filters.py::test_threshold_percent tests/test_filters.py::test_outline tests/test_filters.py::test_split_and_connectivity tests/test_filters.py::test_delaunay_3d tests/test_filters.py::test_smooth
tests/test_filters.py::test_clip_filter tests/test_filters.py::test_clip_box tests/test_filters.py::test_slice_filter tests/test_filters.py::test_slice_orthogonal_filter tests/test_filters.py::test_slice_along_axis tests/test_filters.py::test_outline_corners tests/test_filters.py::test_extract_geometry tests/test_filters.py::test_wireframe tests/test_filters.py::test_contour tests/test_filters.py::test_elevation tests/test_filters.py::test_texture_map_to_plane tests/test_filters.py::test_compute_cell_sizes tests/test_filters.py::test_cell_centers tests/test_filters.py::test_warp_by_scalar tests/test_filters.py::test_cell_data_to_point_data tests/test_filters.py::test_point_data_to_cell_data tests/test_filters.py::test_triangulate tests/test_filters.py::test_resample tests/test_filters.py::test_streamlines tests/test_filters.py::test_plot_over_line tests/test_filters.py::test_slice_along_line tests/test_filters.py::test_interpolate tests/test_filters.py::test_select_enclosed_points tests/test_filters.py::test_decimate_boundary
examples/00-load/create-point-cloud.py examples/02-plot/plot-shading.py examples/02-plot/plot-volume.py examples/02-plot/plot-labels.py examples/02-plot/plot-edges.py examples/02-plot/plot-scalar-bars.py examples/02-plot/plot-clear.py examples/02-plot/plot-gif.py examples/02-plot/plot-edl.py pyvista/core/filters.py examples/02-plot/README.txt examples/00-load/create-poly.py examples/02-plot/plot-texture.py examples/01-filter/README.txt examples/02-plot/plot-cmap.py examples/02-plot/plot-linked.py examples/02-plot/plot-movie.py examples/02-plot/plot-orbit.py setup.py examples/02-plot/plot-opacity.py examples/02-plot/plot-glyphs.py examples/02-plot/plot-multi-window.py examples/02-plot/plot-themes.py pyvista/core/pointset.py examples/02-plot/plot-logo.py.old examples/00-load/README.txt pyvista/plotting/theme.py examples/02-plot/plot-screenshot.py pyvista/plotting/tools.py pyvista/plotting/plotting.py pyvista/plotting/qt_plotting.py
examples/02-plot/gif.py examples/02-plot/logo.py.old examples/02-plot/opacity.py examples/00-load/create-point-cloud.py examples/01-filter/glyphs.py examples/02-plot/edl.py examples/02-plot/texture.py examples/02-plot/labels.py examples/02-plot/clear.py examples/02-plot/linked.py examples/02-plot/edges.py examples/02-plot/scalar-bars.py pyvista/core/filters.py examples/02-plot/README.txt examples/00-load/create-poly.py examples/01-filter/README.txt setup.py examples/02-plot/volume.py examples/02-plot/screenshot.py examples/02-plot/themes.py pyvista/core/pointset.py examples/02-plot/movie.py examples/02-plot/orbit.py examples/02-plot/multi-window.py examples/00-load/README.txt pyvista/plotting/theme.py pyvista/plotting/tools.py examples/02-plot/shading.py pyvista/plotting/plotting.py examples/02-plot/cmap.py pyvista/plotting/qt_plotting.py