{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Extending PyVista {#extending_pyvista_example}\n\nA `pyvista.DataSet`{.interpreted-text role=\"class\"}, such as\n`pyvista.PolyData`{.interpreted-text role=\"class\"}, can be extended by\nusers. For example, if the user wants to keep track of the location of\nthe maximum point in the (1, 0, 1) direction on the mesh.\n\nThere are two methods by which users can handle subclassing. One is\ndirectly managing the types objects. This may require checking types\nduring filter operations.\n\nThe second is automatic managing of types. Users can control whether\nuser defined classes are nearly always used for particular types of\nDataSets.\n\n::: note\n::: title\nNote\n:::\n\nThis is for advanced usage only. Automatic managing of types will not\nwork in all situations, in particular when a builtin dataset is directly\ninstantiated. See examples below.\n:::\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import numpy as np\nimport vtk\n\nimport pyvista\n\npyvista.set_plot_theme(\"document\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "A user defined subclass of `pyvista.PolyData`{.interpreted-text\nrole=\"class\"}, `FooData` is defined. It includes a property to keep\ntrack of the point on the mesh that is furthest along in the (1, 0, 1)\ndirection.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "class FooData(pyvista.PolyData):\n    @property\n    def max_point(self):\n        \"\"\"Returns index of point that is furthest along (1, 0, 1) direction.\"\"\"\n        return np.argmax(np.dot(self.points, (1.0, 0.0, 1.0)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Directly Managing Types\n\nNow a `foo_sphere` object is created of type `FooData`. The index of the\npoint and location of the point of interest can be obtained directly.\nThe sphere has a radius of 0.5, so the maximum extent in the direction\n(1, 0, 1) is $0.5\\sqrt{0.5}\\approx0.354$\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "foo_sphere = FooData(pyvista.Sphere(theta_resolution=100, phi_resolution=100))\nprint(\"Original foo sphere:\")\nprint(f\"Type: {type(foo_sphere)}\")\nprint(f\"Maximum point index: {foo_sphere.max_point}\")\nprint(f\"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Using an inplace operation like\n`pyvista.DataSet.rotate_y`{.interpreted-text role=\"func\"} does not\naffect the type of the object.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "foo_sphere.rotate_y(90, inplace=True)\nprint(\"\\nRotated foo sphere:\")\nprint(f\"Type: {type(foo_sphere)}\")\nprint(f\"Maximum point index: {foo_sphere.max_point}\")\nprint(f\"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "However, filter operations can return different `DataSet` types\nincluding ones that differ from the original type. In this case, the\n`decimate <pyvista.PolyDataFilters.decimate>`{.interpreted-text\nrole=\"func\"} method returns a `pyvista.PolyData`{.interpreted-text\nrole=\"class\"} object.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "print(\"\\nDecimated foo sphere:\")\ndecimated_foo_sphere = foo_sphere.decimate(0.5)\nprint(f\"Type: {type(decimated_foo_sphere)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "It is now required to explicitly wrap the object into `FooData`.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "decimated_foo_sphere = FooData(foo_sphere.decimate(0.5))\nprint(f\"Type: {type(decimated_foo_sphere)}\")\nprint(f\"Maximum point index: {decimated_foo_sphere.max_point}\")\nprint(f\"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Automatically Managing Types\n\nThe default `pyvista.DataSet`{.interpreted-text role=\"class\"} type can\nbe set using `pyvista._wrappers`. In general, it is best to use this\nmethod when it is expected to primarily use the user defined class.\n\nIn this example, all objects that would have been created as\n`pyvista.PolyData`{.interpreted-text role=\"class\"} would now be created\nas a `FooData` object. Note, that the key is the underlying vtk object.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pyvista._wrappers['vtkPolyData'] = FooData"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "It is no longer necessary to specifically wrap\n`pyvista.PolyData`{.interpreted-text role=\"class\"} objects to obtain a\n`FooData` object.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "foo_sphere = pyvista.Sphere(theta_resolution=100, phi_resolution=100)\nprint(\"Original foo sphere:\")\nprint(f\"Type: {type(foo_sphere)}\")\nprint(f\"Maximum point index: {foo_sphere.max_point}\")\nprint(f\"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Using an inplace operation like\n`rotate_y <pyvista.DataSet.rotate_y>`{.interpreted-text role=\"func\"}\ndoes not affect the type of the object.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "foo_sphere.rotate_y(90, inplace=True)\nprint(\"\\nRotated foo sphere:\")\nprint(f\"Type: {type(foo_sphere)}\")\nprint(f\"Maximum point index: {foo_sphere.max_point}\")\nprint(f\"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Filter operations that return `pyvista.PolyData`{.interpreted-text\nrole=\"class\"} now return `FooData`\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "print(\"\\nDecimated foo sphere:\")\ndecimated_foo_sphere = foo_sphere.decimate(0.5)\nprint(f\"Type: {type(decimated_foo_sphere)}\")\nprint(f\"Maximum point index: {decimated_foo_sphere.max_point}\")\nprint(f\"Location of maximum point: {foo_sphere.points[foo_sphere.max_point, :]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Users can still create a native `pyvista.PolyData`{.interpreted-text\nrole=\"class\"} object, but using this method may incur unintended\nconsequences. In this case, it is recommended to use the directly\nmanaging types method.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "poly_object = pyvista.PolyData(vtk.vtkPolyData())\nprint(f\"Type: {type(poly_object)}\")\n# catch error\ntry:\n    poly_object.rotate_y(90, inplace=True)\nexcept TypeError:\n    print(\"This operation fails\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Usage of `pyvista._wrappers` may require resetting the default value to\navoid leaking the setting into cases where it is unused.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pyvista._wrappers['vtkPolyData'] = pyvista.PolyData"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "For instances where a localized usage is preferred, a tear-down method\nis recommended. One example is a `try...finally` block.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "try:\n    pyvista._wrappers['vtkPolyData'] = FooData\n    # some operation that sometimes raises an error\nfinally:\n    pyvista._wrappers['vtkPolyData'] = pyvista.PolyData"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}