*************** Python bindings *************** Introduction ============ PolyVox itself is a C++ library but in order to make it useful to as many people as possible, we also provide bindings to a number of other languages. These bindings are all generated by `SWIG `_ and provide a bridge to a compiled PolyVox library (*DLL* or *.so*) via an interafce which is native to the language. This allows you in a Python script to simpy call ``import PolyVoxCore`` and get access to the whole library's functionality. The Python bindings are available for Python 3. Comparison with C++ ------------------- All the bindings available for PolyVox (so both the Python bindings and any future supported bindings such as for C♯) follow the PolyVox C++ API quite closely. This means that many PolyVox code examples written in C++ are mostly applicable also to Python. Classes and functions are named the same and take the same set of arguments. The main place this falls down is with templated C++ classes. Since C++ templates are essentially a code-generation system built into the C++ compiler there is no way for a user of the Python bindings to request, at run-time, a specific piece of code to be generated. The way we work around this is by, as part of the bindings generation process, pre-compiling a number of different versions of each templated class. For example, in C++ a 3D vector containing 32-bit integers would be declared as .. code-block:: c++ PolyVox::Vector3D my_vec(0,1,4); but in Python it would be accessed as .. code-block:: python my_vec = PolyVoxCore.Vector3Dint32_t(0,1,4) As a rule, any time in C++ where you see a template instantiation with ``<>``, just remove the angle brackets and it will yield the Python class name. The choice of which C++ templates to instantiate is made by the developers of PolyVox in order to try to cover the main use-cases that library users would have. If, however, you want to add your own versions, this can be done by editing the SWIG interface files and recompiling PolyVox. Buildings the bindings ====================== The bindings are built as part of the standard PolyVox build process. For details on this please follow the instructions at :doc:`install`. The important things to note there are the requirements for building the bindings: *Python development libraries* and *SWIG*. During the CMake phase of building, it should tell you whether or not the bindings will be built. Compiling the whole PolyVox library should then give you two files inside the ``build/library/bindings`` directory: ``PolyVoxCore.py`` This is the main entry point to the library. ``_PolyVoxCore.so`` (or ``.dll`` on Windows) This contains the compiled code of the PolyVox library. This file has a link dependency the main ``libPolyVoxCore.so`` library as well as the Python shared library. Using the bindings ================== As discussed above, the Python API is very similar to the C++ one but none-the-less we'll go through an example to see how it works. All the code in this section is taken from ``PythonExample.py`` found in the source distribution of PolyVox in the ``examples/Python`` folder. Seting up the volume -------------------- The first this we do is import the ``PolyVoxCore`` module. We rename it as ``pv`` to make our life easier. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 29 We create a ``Region`` from two vectors defining the bounds of the area - a volume 64×64×64. Remember that ``pv.Vector3Dint32_t`` refers to a 3D :polyvox:`PolyVox::Vector` templated on a 32-bit integer. The second line creates a :polyvox:`SimpleVolume` of the same size as the :polyvox:`Region` where each voxel in the volume is defined an unsigned 8-bit integer. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 31-33 We're going to fill our volume with a sphere and so we start by finding out where the centre of the volume is and defining the radius of our desired shape. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 35-37 Then we actually loop over each of the dimensions of the volume such that inside the loop, ``x``, ``y`` and ``z`` refer to the current location. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 38-41 All we do inside the loop is set all the voxels inside the sphere to have a value of ``255`` and all those outside to have a value of ``0``. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 42-53 Getting the mesh data --------------------- Extracting the surface mesh for the volume is a two-step process. First we tell PolyVox to generate the appropriate mesh, then we have to convert the PolyVox mesh data to something that our rendering library can understand. First we define the sort of mesh that we want. For this example we want a mesh with information on the position, material and normal of each vertex. Once we have out mesh object ready to be filled, we pass it to our surface extractor of choice. PolyVox comes with a number of different surface extractors but for our example here, we want a cubic mesh. You should also note that the ungainly looking ``CubicSurfaceExtractorWithNormalsSimpleVolumeuint8`` refers to the C++ class ``CubicSurfaceExtractorWithNormals>``. The ``execute()`` call is when PolyVox actually goes off and generates the requested mesh based on the data contained in the volume. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 55-58 Up until this point, the Python code has been totally generic with respect to your choice of rendering engine. For this example we will be using `PyOpenGL `_ as it provides a nice pythonic API for many OpenGL functions. Regardless of which rendering engine you are using, you will need to be able to wrangle the PolyVox mesh output into something you can insert into your engine. In our case, we want two lists: 1. All the vertices along with their normals 2. The vertex indices which describes how the vertices are put together to make triangles. PyOpenGL undersands `NumPy `_ arrays and so we are going to copy our vertex data into two of these. :polyvox:`SurfaceMesh` provides two useful functions here, ``getIndices()`` and ``getVertices()``, the Python versions of which return Python tuples. The indices we can pass directly to NumPy as long as we make sure we specify the correct type for the data inside. For the vertices, we want to rearange the data so that OpenGL can read it more efficiently. To this end we explicitly retrieve the vertex positions and normals for each vertex and place them such that the vertex ``x``, ``y`` and ``z`` positions are placed contiguously in memory followed by the normal vector's ``x``, ``y`` and ``z`` values. .. literalinclude:: ../examples/Python/PythonExample.py :language: python :lines: 62-69 From this point on in the example, PolyVox is no longer used directly and all the code is standard PyOpenGL. I won't go through every line here but the source code in ``PythonExample.py`` is commented and should be sufficient to understand how things work.