From 3ed3ac699871c5929892a55f0ed4295bdcb5f7f6 Mon Sep 17 00:00:00 2001 From: Matt Williams Date: Sun, 25 Nov 2012 18:07:12 +0000 Subject: [PATCH] Start Python bindings for Raycast This is only beginning of the bindings here. It's starting to get more complicated due to the use of callbacks. To be able to define a callback function in Python which is then called by a C++ algorithm requires quite a bit of wrapping boilerplate. The class PyCallback here will wrap a Python callable and call it with the density value of the voxel. It's not very generic and at present it can't pass the sampler itself since it's not available in the Python bindings. Regardless, the new test added here (TestRaycast.py) works as expected and hopefully we will be able to build up from here. --- library/bindings/PolyVoxCore.i | 1 + library/bindings/Raycast.i | 54 ++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/TestRaycast.py | 28 ++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 library/bindings/Raycast.i create mode 100644 tests/TestRaycast.py diff --git a/library/bindings/PolyVoxCore.i b/library/bindings/PolyVoxCore.i index 529d32b8..30ea5c65 100644 --- a/library/bindings/PolyVoxCore.i +++ b/library/bindings/PolyVoxCore.i @@ -75,3 +75,4 @@ EXTRACTOR(shortname, LargeVolume) %include "SurfaceMesh.i" %include "MarchingCubesSurfaceExtractor.i" //%include "CubicSurfaceExtractor.i" +%include "Raycast.i" diff --git a/library/bindings/Raycast.i b/library/bindings/Raycast.i new file mode 100644 index 00000000..a3dc23f0 --- /dev/null +++ b/library/bindings/Raycast.i @@ -0,0 +1,54 @@ +%module Raycast +%{ +#include "Raycast.h" + +template +class PyCallback +{ +private: + PyObject *func; + PyCallback& operator=(const PyCallback&); // Not allowed +public: + PyCallback(const PyCallback& o) : func(o.func) + { + Py_XINCREF(func); + } + PyCallback(PyObject *func) : func(func) + { + Py_XINCREF(this->func); + assert(PyCallable_Check(this->func)); + } + ~PyCallback() + { + Py_XDECREF(func); + } + bool operator()(const typename VolumeType::Sampler& sampler) + { + if (!func || Py_None == func || !PyCallable_Check(func)) + { + return false; //Make this raise a Python exception + } + PyObject *args = Py_BuildValue("(l)", sampler.getVoxel().getDensity()); //TODO pass the sampler object itself in + PyObject *result = PyObject_Call(func,args,0); + Py_DECREF(args); + Py_XDECREF(result); + return (PyInt_AsLong(result) == 0) ? false : true; + } +}; + +template +PolyVox::RaycastResult raycastWithEndpointsPython(VolumeType* volData, const PolyVox::Vector3DFloat& v3dStart, const PolyVox::Vector3DFloat& v3dEnd, PyObject *callback) +{ + PyCallback newCallback(callback); + return PolyVox::raycastWithEndpoints(volData, v3dStart, v3dEnd, newCallback); +} + +%} + +%include "Raycast.h" + +template +PolyVox::RaycastResult raycastWithEndpointsPython(VolumeType* volData, const PolyVox::Vector3DFloat& v3dStart, const PolyVox::Vector3DFloat& v3dEnd, PyObject *callback); + +%template(raycastWithEndpointsSimpleVolumeDensity8) raycastWithEndpointsPython, PyCallback > >; +//%template(raycastWithEndpointsSimpleVolumeMaterial8) raycastWithEndpointsPython, PyCallback > >; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6c6fe163..c2585e41 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -51,6 +51,7 @@ REMOVE_DEFINITIONS(-DQT_GUI_LIB) #Make sure the tests don't link to the QtGui # Python tests IF(BUILD_BINDINGS) ADD_TEST(PythonSurfaceExtractorTest python ${CMAKE_CURRENT_SOURCE_DIR}/TestSurfaceExtractor.py) + ADD_TEST(PythonRaycastTest python ${CMAKE_CURRENT_SOURCE_DIR}/TestRaycast.py) ENDIF() # AmbientOcclusionGenerator tests diff --git a/tests/TestRaycast.py b/tests/TestRaycast.py new file mode 100644 index 00000000..1f45aee3 --- /dev/null +++ b/tests/TestRaycast.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import sys +sys.path.append("library/bindings/") + +import unittest +import PolyVoxCore + +def test_functor(sampler): + return sampler <= 0 + +class TestSurfaceExtractor(unittest.TestCase): + def setUp(self): + + #Create a small volume + r = PolyVoxCore.Region(PolyVoxCore.Vector3DInt32(0,0,0), PolyVoxCore.Vector3DInt32(31,31,31)) + self.vol = PolyVoxCore.SimpleVolumeDensity8(r) + #Set one single voxel to have a reasonably high density + self.vol.setVoxelAt(PolyVoxCore.Vector3DInt32(5, 5, 5), PolyVoxCore.Density8(200)) + + def test_hit_voxel(self): + self.assertEqual(PolyVoxCore.raycastWithEndpointsSimpleVolumeDensity8(self.vol, PolyVoxCore.Vector3DFloat(0,0,0), PolyVoxCore.Vector3DFloat(31,31,31), test_functor), 1) + + def test_miss_voxel(self): + self.assertEqual(PolyVoxCore.raycastWithEndpointsSimpleVolumeDensity8(self.vol, PolyVoxCore.Vector3DFloat(0,0,0), PolyVoxCore.Vector3DFloat(0,31,31), test_functor), 0) + +if __name__ == '__main__': + unittest.main()