Skip to content

Commit

Permalink
Python bindings, version 1
Browse files Browse the repository at this point in the history
  • Loading branch information
oseiskar committed Jun 2, 2024
1 parent 41f9ebf commit eaaf871
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 53 deletions.
115 changes: 62 additions & 53 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
cmake_minimum_required(VERSION 3.3)
set(LIBNAME IsoOctree)
set(MAJOR_VERSION 1)
project(${LIBNAME} VERSION "${MAJOR_VERSION}.0.0")

option(IsoOctree_PYTHON "Build python bindings" OFF)
option(IsoOctree_STATIC "Build static lib" OFF)

set(CMAKE_CXX_STANDARD 11)
if (IsoOctree_PYTHON)
set(LIBNAME "IsoOctreeNative")
else()
set(LIBNAME "IsoOctree")
endif()

file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt" VERSION_CONTENTS)
project(${LIBNAME} VERSION ${VERSION_CONTENTS})

if (IsoOctree_STATIC)
if (IsoOctree_STATIC OR IsoOctree_PYTHON)
set(IsoOctree_LIBTYPE "STATIC")
else()
set(IsoOctree_LIBTYPE "SHARED")
endif()

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

set(ISO_OCTREE_SRC SRC/api.cpp)
Expand All @@ -24,57 +30,60 @@ if(NOT MSVC)
target_compile_options(${LIBNAME} PRIVATE "-Wno-dangling-else")
endif()

include(GNUInstallDirs)
if (NOT IsoOctree_PYTHON)
include(GNUInstallDirs)

install(FILES
include/IsoOctree/IsoOctree.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LIBNAME}
COMPONENT Devel)

install(FILES
include/IsoOctree/IsoOctree.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LIBNAME}
COMPONENT Devel)
# CMake installation boilerplate
install(TARGETS ${LIBNAME}
EXPORT ${LIBNAME}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# CMake installation boilerplate
install(TARGETS ${LIBNAME}
EXPORT ${LIBNAME}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
set_property(TARGET ${LIBNAME} PROPERTY VERSION ${PROJECT_VERSION})
set_property(TARGET ${LIBNAME} PROPERTY SOVERSION ${MAJOR_VERSION})
set_property(TARGET ${LIBNAME} PROPERTY INTERFACE_${LIBNAME}_MAJOR_VERSION ${MAJOR_VERSION})
set_property(TARGET ${LIBNAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${LIBNAME}_MAJOR_VERSION)

set_property(TARGET ${LIBNAME} PROPERTY VERSION ${PROJECT_VERSION})
set_property(TARGET ${LIBNAME} PROPERTY SOVERSION ${MAJOR_VERSION})
set_property(TARGET ${LIBNAME} PROPERTY INTERFACE_${LIBNAME}_MAJOR_VERSION ${MAJOR_VERSION})
set_property(TARGET ${LIBNAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${LIBNAME}_MAJOR_VERSION)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
export(EXPORT ${LIBNAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Targets.cmake"
NAMESPACE ${LIBNAME}::
)
configure_file(cmake/${LIBNAME}Config.cmake
"${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Config.cmake"
COPYONLY
)

export(EXPORT ${LIBNAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Targets.cmake"
NAMESPACE ${LIBNAME}::
)
configure_file(cmake/${LIBNAME}Config.cmake
"${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}Config.cmake"
COPYONLY
)
set(ConfigPackageLocation lib/cmake/${LIBNAME})
install(EXPORT ${LIBNAME}Targets
FILE
${LIBNAME}Targets.cmake
NAMESPACE
${LIBNAME}::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
cmake/${LIBNAME}Config.cmake
"${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}ConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
COMPONENT
Devel
)

set(ConfigPackageLocation lib/cmake/${LIBNAME})
install(EXPORT ${LIBNAME}Targets
FILE
${LIBNAME}Targets.cmake
NAMESPACE
${LIBNAME}::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
cmake/${LIBNAME}Config.cmake
"${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/${LIBNAME}ConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
COMPONENT
Devel
)
endif()
1 change: 1 addition & 0 deletions VERSION.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.1.0
22 changes: 22 additions & 0 deletions build_and_test_python_library.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
set -eux

source venv/bin/activate
rm -rf dist
rm -rf python/target

mkdir -p python/target
cd python/target
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j8
cd ../..

python python/setup.py bdist_wheel
deactivate

cd python/target
python -m venv venv_test
source venv_test/bin/activate
pip install ../../dist/*.whl
cd ..
python example.py
2 changes: 2 additions & 0 deletions build_library_and_example.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#!/bin/bash
set -eux

rm -rf target
mkdir -p target
cd target
cmake -DCMAKE_INSTALL_PREFIX=/tmp/IsoOctreeInstall -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
make -j8 install
cd ..

cd example
rm -rf target
mkdir -p target
cd target
cmake -DCMAKE_PREFIX_PATH=/tmp/IsoOctreeInstall ..
Expand Down
14 changes: 14 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.12)
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../VERSION.txt" VERSION_CONTENTS)
project(IsoOctree VERSION ${VERSION_CONTENTS})

option(IsoOctree_PYTHON "Build python bindings" OFF)
set(IsoOctree_PYTHON ON)

add_subdirectory(pybind11)
add_subdirectory(.. ${CMAKE_BINARY_DIR}/IsoOctreeNative_build)

pybind11_add_module(IsoOctree bindings.cpp)
target_include_directories(IsoOctree PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include)
target_link_libraries(IsoOctree PRIVATE IsoOctreeNative)
133 changes: 133 additions & 0 deletions python/bindings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include <memory>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/functional.h>
#include <pybind11/stl.h>
#include "IsoOctree/IsoOctree.hpp"
#include <iostream>

#define DEF_READONLY(klass, name, doc) def_readonly(#name, &klass::name, doc)
#define DEF_COPY_READONLY(klass, name, doc) def_property_readonly(\
#name,\
[](const klass &self) { return self.name; },\
py::return_value_policy::copy, doc)


namespace py = pybind11;

namespace {
using Real = double;
using Octree = isoOctree::Octree<Real>;
using Voxel = Octree::Voxel;
using Point3D = isoOctree::Point3D<Real>;
using CornerValues = Octree::CornerValues;
using RootInfo = Octree::RootInfo;
using MeshInfo = isoOctree::MeshInfo<Real>;

using ShouldExpandCallback = std::function<bool(const Voxel &, const CornerValues &corners)>;
using GetValueCallback = std::function<float(const Point3D &)>;

class CallbackTraverser : public Octree::Traverser {
private:
RootInfo rootInfo;
ShouldExpandCallback shouldExpandCallback;
GetValueCallback getValueCallback;

public:
CallbackTraverser(
const RootInfo &rootInfo,
const ShouldExpandCallback &shouldExpandCallback,
const GetValueCallback &getValueCallback)
:
rootInfo(rootInfo),
shouldExpandCallback(shouldExpandCallback),
getValueCallback(getValueCallback)
{}

const RootInfo &root() final { return rootInfo; }
bool shouldExpand(const Voxel &voxel, const CornerValues &corners) final {
return shouldExpandCallback(voxel, corners);
}

float isoValue(const Point3D &point) final {
return getValueCallback(point);
}
};
}

PYBIND11_MODULE(IsoOctree, m) {
m.doc() = "IsoOctree Python wrapper";

py::class_<Voxel>(m, "Voxel")

.DEF_READONLY(Voxel, index,
"Level-specific octree node index triplet (i,j,k), each in the range [0, 2^depth-1]")

.DEF_READONLY(Voxel, depth,
"Octree level: 0 is root, 1 is the first level, etc.")

.DEF_COPY_READONLY(Voxel, minCorner,
"Voxel minimum coordinate corner in world coordinates")

.DEF_READONLY(Voxel, width,
"Voxel cube width");

py::class_<MeshInfo, std::unique_ptr<MeshInfo>>(m, "MeshInfo")

.def_property_readonly("vertices",
[](MeshInfo &self) {
return py::array_t<Real>(
std::vector<ptrdiff_t> { static_cast<long>(self.vertices.size()), 3 },
reinterpret_cast<const Real*>(self.vertices.data())
);
},
"Vertices numpy array of shape (N, 3)")

.def_property_readonly("triangles",
[](MeshInfo &self) {
return py::array_t<isoOctree::MeshIndex>(
std::vector<ptrdiff_t> { static_cast<long>(self.triangles.size()), 3 },
reinterpret_cast<const isoOctree::MeshIndex*>(self.triangles.data())
);
},
"Faces (all triangles) as an index array of shape (M, 3), each element\n"
"is an index to the rows in vertices, and thus in the range (0, N-1)");

m.def("buildMesh",
[](
GetValueCallback isoFunction,
ShouldExpandCallback expandFunction,
Point3D center,
Real width,
int maxDepth)
{
Octree::RootInfo rootInfo;
rootInfo.center = center;
rootInfo.width = width;
rootInfo.maxDepth = maxDepth;
CallbackTraverser traverser(rootInfo, expandFunction, isoFunction);
std::unique_ptr<isoOctree::MeshInfo<Real>> output = std::make_unique<isoOctree::MeshInfo<Real>>();
isoOctree::buildMesh(traverser, *output);
return std::move(output);
},
py::arg("isoFunction"),
py::arg("expandFunction"),
py::kw_only(),
py::arg("center") = Point3D { 0, 0, 0 },
py::arg("width") = 1.0,
py::arg("maxDepth") = 10,
"Mesh an implicit function using the Iso-Octree algorithm \n"
"\n"
"Applied to a cubical region [cx-w/2, cx+w/2] x [cy-w/2, cy+w/2] x [cz-w/2, cz+w/2]\n"
"called the (octree) root voxel.\n"
"\n"
"Arguments:\n"
"\t isoFunction: Function f that defines the surface (f < 0 is inside)\n"
"\t expandFunction: Function that decides whether to expand a Voxel\n"
"\t center: Center (cx, cy, cz) of the root voxel\n"
"\t width: Width of the root voxel w\n"
"\t maxDepth: Maximum octree depth d. The smallest possible voxel dimension is w/2^d\n"
"\n"
"Returns: a MeshInfo object");
}

39 changes: 39 additions & 0 deletions python/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import IsoOctree

def getValue(p):
x, y, z = p
return x*x*x*x - 5*x*x + y*y*y*y - 5*y*y + z*z*z*z - 5*z*z + 11.8

def shouldExpand(node, corners):
BASE_DEPTH = 3
if node.depth < BASE_DEPTH: return True

nNonNegative = 0
nNegative = 0
for v in corners:
if v < 0.0: nNegative += 1
else: nNonNegative += 1

if nNonNegative == 0 or nNegative == 0: return False

# Note: this logic is rather flawed for adaptive refinement,
# but results to leaf nodes at different level, which is a good
# test to the IsoOctree algorithm
return nNonNegative != nNegative

def writeMeshAsObj(mesh, filename):
with open(filename, 'wt') as f:
for v in mesh.vertices:
f.write('v %f %f %f\n' % (v[0], v[1], v[2]))
for t in mesh.triangles:
f.write('f %d %d %d\n' % (t[0]+1, t[1]+1, t[2]+1))

if __name__ == '__main__':
mesh = IsoOctree.buildMesh(
getValue,
shouldExpand,
center = [0.1, 0.2, 0.3],
width = 6.0,
maxDepth = 6)

writeMeshAsObj(mesh, 'example.obj')
Loading

0 comments on commit eaaf871

Please sign in to comment.