Repository: pyvista/pyvista

Issue:
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)


Discussion:

Patch:
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',


Test patch:
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():