1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
"""
.. _extending_pyvista_example:
Extending PyVista
~~~~~~~~~~~~~~~~~
A :class:`pyvista.DataSet`, such as :class:`pyvista.PolyData`, can be extended
by users. For example, if the user wants to keep track of the location of the
maximum point in the (1, 0, 1) direction on the mesh.
There are two methods by which users can handle subclassing. One is directly managing
the types objects. This may require checking types during filter
operations.
The second is automatic managing of types. Users can control whether user defined
classes are nearly always used for particular types of DataSets.
.. note::
This is for advanced usage only. Automatic managing of types
will not work in all situations, in particular when a builtin dataset is directly
instantiated. See examples below.
"""
from __future__ import annotations
import numpy as np
import vtk
import pyvista
pyvista.set_plot_theme("document")
# %%
# A user defined subclass of :class:`pyvista.PolyData`, ``FooData`` is defined.
# It includes a property to keep track of the point on the mesh that is
# furthest along in the (1, 0, 1) direction.
class FooData(pyvista.PolyData):
@property
def max_point(self):
"""Returns index of point that is furthest along (1, 0, 1) direction."""
return np.argmax(np.dot(self.points, (1.0, 0.0, 1.0)))
# %%
# Directly Managing Types
# +++++++++++++++++++++++
#
# Now a ``foo_sphere`` object is created of type ``FooData``.
# The index of the point and location of the point of interest can be obtained
# directly. The sphere has a radius of 0.5, so the maximum extent in the
# direction (1, 0, 1) is :math:`0.5\sqrt{0.5}\approx0.354`
#
foo_sphere = FooData(pyvista.Sphere(theta_resolution=100, phi_resolution=100))
print("Original foo sphere:")
print(f"Type: {type(foo_sphere)}")
print(f"Maximum point index: {foo_sphere.max_point}")
print(f"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}")
# %%
# Using an inplace operation like :func:`pyvista.DataSet.rotate_y` does not
# affect the type of the object.
foo_sphere.rotate_y(90, inplace=True)
print("\nRotated foo sphere:")
print(f"Type: {type(foo_sphere)}")
print(f"Maximum point index: {foo_sphere.max_point}")
print(f"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}")
# %%
# However, filter operations can return different ``DataSet`` types including
# ones that differ from the original type. In this case, the
# :func:`decimate <pyvista.PolyDataFilters.decimate>` method returns a
# :class:`pyvista.PolyData` object.
print("\nDecimated foo sphere:")
decimated_foo_sphere = foo_sphere.decimate(0.5)
print(f"Type: {type(decimated_foo_sphere)}")
# %%
# It is now required to explicitly wrap the object into ``FooData``.
decimated_foo_sphere = FooData(foo_sphere.decimate(0.5))
print(f"Type: {type(decimated_foo_sphere)}")
print(f"Maximum point index: {decimated_foo_sphere.max_point}")
print(f"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}")
# %%
# Automatically Managing Types
# ++++++++++++++++++++++++++++
#
# The default :class:`pyvista.DataSet` type can be set using ``pyvista._wrappers``.
# In general, it is best to use this method when it is expected to primarily
# use the user defined class.
#
# In this example, all objects that would have been created as
# :class:`pyvista.PolyData` would now be created as a ``FooData`` object. Note,
# that the key is the underlying vtk object.
pyvista._wrappers['vtkPolyData'] = FooData
# %%
# It is no longer necessary to specifically wrap :class:`pyvista.PolyData`
# objects to obtain a ``FooData`` object.
foo_sphere = pyvista.Sphere(theta_resolution=100, phi_resolution=100)
print("Original foo sphere:")
print(f"Type: {type(foo_sphere)}")
print(f"Maximum point index: {foo_sphere.max_point}")
print(f"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}")
# %%
# Using an inplace operation like :func:`rotate_y <pyvista.DataSet.rotate_y>` does not
# affect the type of the object.
foo_sphere.rotate_y(90, inplace=True)
print("\nRotated foo sphere:")
print(f"Type: {type(foo_sphere)}")
print(f"Maximum point index: {foo_sphere.max_point}")
print(f"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}")
# %%
# Filter operations that return :class:`pyvista.PolyData` now return
# ``FooData``
print("\nDecimated foo sphere:")
decimated_foo_sphere = foo_sphere.decimate(0.5)
print(f"Type: {type(decimated_foo_sphere)}")
print(f"Maximum point index: {decimated_foo_sphere.max_point}")
print(f"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}")
# %%
# Users can still create a native :class:`pyvista.PolyData` object, but
# using this method may incur unintended consequences. In this case,
# it is recommended to use the directly managing types method.
poly_object = pyvista.PolyData(vtk.vtkPolyData())
print(f"Type: {type(poly_object)}")
# catch error
try:
poly_object.rotate_y(90, inplace=True)
except TypeError:
print("This operation fails")
# %%
# Usage of ``pyvista._wrappers`` may require resetting the default value
# to avoid leaking the setting into cases where it is unused.
pyvista._wrappers['vtkPolyData'] = pyvista.PolyData
# %%
# For instances where a localized usage is preferred, a tear-down method is
# recommended. One example is a ``try...finally`` block.
try:
pyvista._wrappers['vtkPolyData'] = FooData
# some operation that sometimes raises an error
finally:
pyvista._wrappers['vtkPolyData'] = pyvista.PolyData
|