{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# VTK\\'s Next Generation API\n\nThis requires a pre-release version of VTK:\n\n``` bash\npip install --extra-index-url https://wheels.vtk.org vtk==9.3.20240629.dev0\n```\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import magpylib as magpy\nimport numpy as np\n\n# for factory overrides\nfrom pyvista.examples.downloads import _download_archive_file_or_folder\nfrom vtkmodules.util.data_model import *  # noqa: F403\nfrom vtkmodules.util.execution_model import select_ports\nfrom vtkmodules.util.numpy_support import vtk_to_numpy\nfrom vtkmodules.vtkCommonCore import vtkIdList\nfrom vtkmodules.vtkCommonDataModel import vtkDataObjectTreeIterator, vtkImageData\nfrom vtkmodules.vtkFiltersExtraction import vtkExtractBlockUsingDataAssembly\nfrom vtkmodules.vtkFiltersFlowPaths import vtkStreamTracer\nfrom vtkmodules.vtkFiltersSources import vtkSphereSource\nfrom vtkmodules.vtkIOParallelXML import vtkXMLPartitionedDataSetCollectionWriter\n\n# Utility function to save simulation input/output datasets to filesystem\nfrom vtkmodules.vtkIOXML import vtkXMLImageDataWriter, vtkXMLPolyDataWriter\nfrom vtkmodules.vtkRenderingCore import (\n    vtkActor,\n    vtkCompositePolyDataMapper,\n    vtkLightKit,\n    vtkPolyDataMapper,\n    vtkRenderer,\n    vtkRenderWindow,\n    vtkRenderWindowInteractor,\n)\n\n\ndef build_magnetic_coils(mesh, current=1000):\n    magpy_coils = magpy.Collection()\n\n    # Extract blocks under the \"coils\" node.\n    coil_extractor = vtkExtractBlockUsingDataAssembly(\n        assembly_name=\"Assembly\", selector=\"//coils\", input_data=mesh\n    )\n    coil_extractor.Update()\n\n    # Build a magpy current source for every coil.\n    coil_iter = vtkDataObjectTreeIterator(data_set=coil_extractor.output)\n    coil_iter.visit_only_leaves = True\n    coil_iter.InitTraversal()\n    while not coil_iter.IsDoneWithTraversal():\n        line = vtkIdList()\n        coil = coil_iter.current_data_object\n        coil.lines.InitTraversal()\n        while coil.lines.GetNextCell(line):\n            vertices = [coil.points[line.GetId(i)] for i in range(line.GetNumberOfIds())]\n            magpy_coils.add(magpy.current.Polyline(vertices=vertices, current=current))\n        coil_iter.GoToNextItem()\n\n    return magpy_coils\n\n\ndef save_dataset(dataset, file_name) -> None:\n    if file_name.endswith(\".vti\"):\n        writer = vtkXMLImageDataWriter()\n    elif file_name.endswith(\".vtp\"):\n        writer = vtkXMLPolyDataWriter()\n    elif file_name.endswith(\".vtpc\"):\n        writer = vtkXMLPartitionedDataSetCollectionWriter()\n    writer.input_data_object = dataset\n    writer.file_name = file_name\n    writer.Write()\n\n\n# Creates a render window interactor, connects it to a render window.\n# Switch the interactor style such that left mouse click and drag orbit the camera\n# around the camera's focal point.\ninteractor = vtkRenderWindowInteractor()\ninteractor.interactor_style.SetCurrentStyleToTrackballCamera()\n\nwindow = vtkRenderWindow(size=(1280, 720), interactor=interactor)\n\nrenderer = vtkRenderer(automatic_light_creation=False, background=(1.0, 1.0, 1.0))\nwindow.AddRenderer(renderer)\n\n# Uses light kit for better lit scenes than the default in VTK.\nlight_kit = vtkLightKit()\nlight_kit.AddLightsToRenderer(renderer)\n\nimport pathlib"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Load input mesh from a vtkPartitionedDataSetCollection file\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from vtkmodules.vtkIOXML import vtkXMLPartitionedDataSetCollectionReader\n\npath = _download_archive_file_or_folder(\"reactor.zip\", target_file=\"\")\n\nreader = vtkXMLPartitionedDataSetCollectionReader()\nreader.file_name = pathlib.Path(path + \"/reactor/\" + \"mesh.vtpc\")\nreader.Update()\nreactor = reader.output\n\nactor = vtkActor()\nactor.mapper = (reactor >> vtkCompositePolyDataMapper()).last\n# Make the toroid translucent so we can look at objects inside it.\nactor.property.opacity = 0.2\nrenderer.AddActor(actor)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Construct magpy coil objects for each coil in the reactor mesh.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "coils = build_magnetic_coils(reactor, current=1000)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Compute B, H in a 32x32x32 grid\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "grid = vtkImageData(extent=(-16, 16, -16, 16, -16, 16), spacing=(0.1, 0.1, 0.1))\ngrid_points = vtk_to_numpy(grid.points.data)\nb = coils.getB(grid_points) * 1000\ngrid.point_data.set_array(\"B (mT)\", b)\nh = coils.getH(grid_points)\ngrid.point_data.set_array(\"H (A/m)\", h)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Show coils\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "magpy.show(coils, arrow=True)\nsave_dataset(grid, \"data/solution.vti\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Compute streamlines of B field induced by toroidal coils.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "trace_streamlines = vtkStreamTracer(\n    integrator_type=vtkStreamTracer.RUNGE_KUTTA45,\n    integration_direction=vtkStreamTracer.BOTH,\n    initial_integration_step=0.2,\n    maximum_propagation=3.2,\n)\ntrace_streamlines.SetInputArrayToProcess(0, 0, 0, 0, \"B (mT)\")\n\ncreate_sphere = vtkSphereSource(theta_resolution=16)\n\ngrid >> select_ports(0, trace_streamlines)\ncreate_sphere >> select_ports(1, trace_streamlines)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Visualize streamlines\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from vtkmodules.vtkFiltersCore import vtkTubeFilter\n\nactor = vtkActor()\nactor.mapper = (\n    trace_streamlines >> vtkTubeFilter(number_of_sides=3, radius=0.00383538) >> vtkPolyDataMapper()\n).last\nrenderer.AddActor(actor)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Animate the disk position such that it oscillates between y=-1 and y=1.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from itertools import cycle\n\n\nclass vtkTimerCallback:  # noqa: N801\n    def __init__(self, sphere, window, nsteps=10) -> None:\n        half_nsteps = int(nsteps / 2)\n        self.radii = cycle(\n            np.append(np.linspace(0, 0.8, half_nsteps), np.linspace(0.8, 0, half_nsteps))\n        )\n        self.sphere = sphere\n        self.window = window\n\n    def execute(self, obj, event) -> None:\n        self.sphere.radius = next(self.radii)\n        self.window.Render()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Sign up to receive TimerEvent\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cb = vtkTimerCallback(create_sphere, window, nsteps=250)\ninteractor.RemoveObservers(\"TimerEvent\")\ninteractor.AddObserver(\"TimerEvent\", cb.execute)\ncb.timerId = interactor.CreateRepeatingTimer(2)\n\nrenderer.ResetCamera()\nwindow.Render()\ninteractor.Start()"
      ]
    }
  ],
  "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.11"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}