From b6b7e3ca94a1bba9820cbcdbe3dd9f4715b9b544 Mon Sep 17 00:00:00 2001 From: Nikhil Mishra <128648183+nmishra-ufl@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:35:22 -0400 Subject: [PATCH 1/9] Add pybind11 wrappings for Imath::Frustum Fix for issue #428 Created a pybind11 wrapping file based on the specified template. Requires further testing, as this is my first time using pybind11. Signed-off-by: Nikhil Mishra <128648183+nmishra-ufl@users.noreply.github.com> Signed-off-by: Piotr Barejko --- src/pybind11/PyBindImath/PyBindImathFrustum | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/pybind11/PyBindImath/PyBindImathFrustum diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum b/src/pybind11/PyBindImath/PyBindImathFrustum new file mode 100644 index 00000000..d4029eba --- /dev/null +++ b/src/pybind11/PyBindImath/PyBindImathFrustum @@ -0,0 +1,55 @@ +#include "PyBindImath.h" +#include +#include +#include + +namespace PyBindImath { + +template +void register_frustum(pybind11::module& m, const char *name) +{ + pybind11::class_ c(m, name); + c.def(pybind11::init<>(), "Uninitialized by default") + .def(pybind11::init(), pybind11::arg("frustum"), "Copy constructor") + .def(pybind11::init(), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Initialize with basic frustum properties") + + .def_readwrite("nearPlane", &T::nearPlane, "The near clipping plane") + .def_readwrite("farPlane", &T::farPlane, "The far clipping plane") + .def_readwrite("fovx", &T::fovx, "The field of view in x direction") + .def_readwrite("aspect", &T::aspect, "The aspect ratio") + + .def("set", pybind11::overload_cast(&T::set), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Set frustum properties") + .def("projectionMatrix", &T::projectionMatrix, "Returns the projection matrix of the frustum") + .def("transform", &T::transform, pybind11::arg("matrix"), "Applies a transformation matrix to the frustum") + .def("intersects", [](T& self, const V& point) { + bool result = self.intersects(point); + return result; + }, pybind11::arg("point"), "Determines if the point is inside the frustum") + + .def("screenToWorld", [](T& self, const V& screenPoint) { + V worldPoint; + self.screenToWorld(screenPoint, worldPoint); + return worldPoint; + }, pybind11::arg("screenPoint"), "Convert a screen space point to world space") + + .def("worldToScreen", [](T& self, const V& worldPoint) { + V screenPoint; + self.worldToScreen(worldPoint, screenPoint); + return screenPoint; + }, pybind11::arg("worldPoint"), "Convert a world space point to screen space") + + .def("__str__", [](const T &obj) { + std::stringstream ss; + ss << obj; + return ss.str(); + }); +} + +void register_imath_frustum(pybind11::module &m) +{ + register_frustum(m, "Frustumf"); + register_frustum(m, "Frustumd"); +} + +} + From 251e46ac3ba78f07ba48cab8a08a5c83528b61b8 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Sat, 18 Jan 2025 23:57:44 -0800 Subject: [PATCH 2/9] Frustum bindings Signed-off-by: Piotr Barejko --- src/Imath/ImathFrustum.h | 2 + src/Imath/ImathMatrix.h | 2 + src/pybind11/PyBindImath/CMakeLists.txt | 9 +- src/pybind11/PyBindImath/PyBindImath.h | 4 +- src/pybind11/PyBindImath/PyBindImathFrustum | 55 ---- .../PyBindImath/PyBindImathFrustum.cpp | 252 ++++++++++++++---- .../PyBindImath/PyBindImathMatrix44.cpp | 104 ++++++++ .../PyBindImath/pybindimathmodule.cpp | 3 +- src/pybind11/test/test_frustum.py | 43 +++ 9 files changed, 367 insertions(+), 107 deletions(-) delete mode 100644 src/pybind11/PyBindImath/PyBindImathFrustum create mode 100644 src/pybind11/PyBindImath/PyBindImathMatrix44.cpp create mode 100644 src/pybind11/test/test_frustum.py diff --git a/src/Imath/ImathFrustum.h b/src/Imath/ImathFrustum.h index 90337467..a504ab38 100644 --- a/src/Imath/ImathFrustum.h +++ b/src/Imath/ImathFrustum.h @@ -39,6 +39,8 @@ IMATH_INTERNAL_NAMESPACE_HEADER_ENTER template class IMATH_EXPORT_TEMPLATE_TYPE Frustum { public: + using value_type = T; + /// @{ /// @name Constructors and Assignment /// diff --git a/src/Imath/ImathMatrix.h b/src/Imath/ImathMatrix.h index d00bcd30..f261ef1d 100644 --- a/src/Imath/ImathMatrix.h +++ b/src/Imath/ImathMatrix.h @@ -800,6 +800,8 @@ template class IMATH_EXPORT_TEMPLATE_TYPE Matrix33 template class IMATH_EXPORT_TEMPLATE_TYPE Matrix44 { public: + using value_type = T; + /// @{ /// @name Direct access to elements diff --git a/src/pybind11/PyBindImath/CMakeLists.txt b/src/pybind11/PyBindImath/CMakeLists.txt index 511abb86..06e5403f 100644 --- a/src/pybind11/PyBindImath/CMakeLists.txt +++ b/src/pybind11/PyBindImath/CMakeLists.txt @@ -13,9 +13,10 @@ set(PYBINDIMATH_SOURCES PyBindImathVec.cpp PyBindImathPlane.cpp PyBindImathLine.cpp - + PyBindImathMatrix44.cpp + # PyBindImathEuler build Error pybind test - # PyBindImathFrustum build failing + PyBindImathFrustum.cpp ) set(PYBINDIMATH_HEADERS @@ -69,12 +70,12 @@ endif() if (IMATH_INSTALL) # module - + install(TARGETS ${PYBINDIMATH_MODULE} DESTINATION ${PYTHON_INSTALL_DIR} COMPONENT python) # shared library - install(TARGETS ${PYBINDIMATH_LIBRARY} + install(TARGETS ${PYBINDIMATH_LIBRARY} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/src/pybind11/PyBindImath/PyBindImath.h b/src/pybind11/PyBindImath/PyBindImath.h index 047a297b..3d2420ac 100644 --- a/src/pybind11/PyBindImath/PyBindImath.h +++ b/src/pybind11/PyBindImath/PyBindImath.h @@ -21,8 +21,10 @@ PYBINDIMATH_EXPORT void register_imath_vec(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_box(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_plane(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_line(pybind11::module& m); +PYBINDIMATH_EXPORT void register_imath_matrix(pybind11::module& m); + // PYBINDIMATH_EXPORT void register_imath_euler(pybind11::module& m) -// PYBINDIMATH_EXPORT void register_imath_frustum(pybind11::module& m) +PYBINDIMATH_EXPORT void register_imath_frustum(pybind11::module& m); } #endif diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum b/src/pybind11/PyBindImath/PyBindImathFrustum deleted file mode 100644 index d4029eba..00000000 --- a/src/pybind11/PyBindImath/PyBindImathFrustum +++ /dev/null @@ -1,55 +0,0 @@ -#include "PyBindImath.h" -#include -#include -#include - -namespace PyBindImath { - -template -void register_frustum(pybind11::module& m, const char *name) -{ - pybind11::class_ c(m, name); - c.def(pybind11::init<>(), "Uninitialized by default") - .def(pybind11::init(), pybind11::arg("frustum"), "Copy constructor") - .def(pybind11::init(), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Initialize with basic frustum properties") - - .def_readwrite("nearPlane", &T::nearPlane, "The near clipping plane") - .def_readwrite("farPlane", &T::farPlane, "The far clipping plane") - .def_readwrite("fovx", &T::fovx, "The field of view in x direction") - .def_readwrite("aspect", &T::aspect, "The aspect ratio") - - .def("set", pybind11::overload_cast(&T::set), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Set frustum properties") - .def("projectionMatrix", &T::projectionMatrix, "Returns the projection matrix of the frustum") - .def("transform", &T::transform, pybind11::arg("matrix"), "Applies a transformation matrix to the frustum") - .def("intersects", [](T& self, const V& point) { - bool result = self.intersects(point); - return result; - }, pybind11::arg("point"), "Determines if the point is inside the frustum") - - .def("screenToWorld", [](T& self, const V& screenPoint) { - V worldPoint; - self.screenToWorld(screenPoint, worldPoint); - return worldPoint; - }, pybind11::arg("screenPoint"), "Convert a screen space point to world space") - - .def("worldToScreen", [](T& self, const V& worldPoint) { - V screenPoint; - self.worldToScreen(worldPoint, screenPoint); - return screenPoint; - }, pybind11::arg("worldPoint"), "Convert a world space point to screen space") - - .def("__str__", [](const T &obj) { - std::stringstream ss; - ss << obj; - return ss.str(); - }); -} - -void register_imath_frustum(pybind11::module &m) -{ - register_frustum(m, "Frustumf"); - register_frustum(m, "Frustumd"); -} - -} - diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp index bcfd2616..8a03f119 100644 --- a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp +++ b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp @@ -1,59 +1,219 @@ -// -// SPDX-License-Identifier: BSD-3-Clause -// Copyright Contributors to the OpenEXR Project. -// - #include "PyBindImath.h" #include -#include -#include - namespace PyBindImath { +namespace py = pybind11; +namespace { + +template +struct GetClassName {}; -template -void register_frustum(pybind11::module& m, const char *name) +template <> +struct GetClassName { + static constexpr const char* value = "Frustumf"; +}; + +template <> +struct GetClassName { + static constexpr const char* value = "Frustumd"; +}; + +template +void register_Frustum(py::module& m, const char *name) { - pybind11::class_ c(m, name); - c.def(pybind11::init<>(), "Uninitialized by default") - .def(pybind11::init(), pybind11::arg("frustum"), "Copy constructor") - .def(pybind11::init(), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Initialize with basic frustum properties") - - .def_readwrite("nearPlane", &T::nearPlane, "The near clipping plane") - .def_readwrite("farPlane", &T::farPlane, "The far clipping plane") - .def_readwrite("fovx", &T::fovx, "The field of view in x direction") - .def_readwrite("aspect", &T::aspect, "The aspect ratio") - - .def("set", pybind11::overload_cast(&T::set), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Set frustum properties") - .def("projectionMatrix", &T::projectionMatrix, "Returns the projection matrix of the frustum") - .def("transform", &T::transform, pybind11::arg("matrix"), "Applies a transformation matrix to the frustum") - .def("intersects", [](T& self, const V& point) { - bool result = self.intersects(point); - return result; - }, pybind11::arg("point"), "Determines if the point is inside the frustum") + py::class_(m, name) + .def(py::init<>(), "Frustum() default construction") + .def(py::init(), py::arg("frustum"), "Copy constructor") + .def(py::init(), "Frustum(nearPlane,farPlane,left,right,top,bottom,ortho) construction") + .def(py::init(), "Frustum(nearPlane,farPlane,fovx,fovy,aspect) construction") + + .def("set", py::overload_cast(&F::set), + "F.set(nearPlane, farPlane, left, right, top, bottom, " + "[ortho])\n" + "F.set(nearPlane, farPlane, fovx, fovy, aspect) " + " -- sets the entire state of " + "frustum F as specified. Only one of " + "fovx or fovy may be non-zero.") + .def("set", py::overload_cast(&F::set)) + + .def("modifyNearAndFar", &F::modifyNearAndFar, + "F.modifyNearAndFar(nearPlane, farPlane) -- modifies " + "the already-valid frustum F as specified") + + .def("setOrthographic", &F::setOrthographic, + "F.setOrthographic(b) -- modifies the " + "already-valid frustum F to be orthographic " + "or not") + + .def("nearPlane", &F::nearPlane, + "F.nearPlane() -- returns the coordinate of the " + "near clipping plane of frustum F") + + .def("farPlane", &F::farPlane, + "F.farPlane() -- returns the coordinate of the " + "far clipping plane of frustum F") + + // The following two functions provide backwards compatibility + // with the previous API for this class. + + .def("near", &F::nearPlane, + "F.near() -- returns the coordinate of the " + "near clipping plane of frustum F") - .def("screenToWorld", [](T& self, const V& screenPoint) { - V worldPoint; - self.screenToWorld(screenPoint, worldPoint); - return worldPoint; - }, pybind11::arg("screenPoint"), "Convert a screen space point to world space") + .def("far", &F::farPlane, + "F.far() -- returns the coordinate of the " + "far clipping plane of frustum F") + + .def("left", &F::left, + "F.left() -- returns the left coordinate of " + "the near clipping window of frustum F") + + .def("right", &F::right, + "F.right() -- returns the right coordinate of " + "the near clipping window of frustum F") + + .def("top", &F::top, + "F.top() -- returns the top coordinate of " + "the near clipping window of frustum F") + + .def("bottom", &F::bottom, + "F.bottom() -- returns the bottom coordinate " + "of the near clipping window of frustum F") + + .def("orthographic", &F::orthographic, + "F.orthographic() -- returns whether frustum " + "F is orthographic or not") + + .def("planes", [](F const& self, Imath::Plane3* planes) -> void + { + self.planes(planes); + }) + .def("planes", [](F const& self, Imath::Plane3 *p, Imath::Matrix44 const& m) -> void + { + self.planes(p, m); + }) + .def("planes", [](F const& self, Imath::Matrix44 const& m) + { + Imath::Plane3 p[6]; + self.planes(p, m); + return py::make_tuple(p[0], p[1], p[2], p[3], p[4], p[5]); + }) + .def("planes", [](F const& self) + { + Imath::Plane3 p[6]; + self.planes(p); - .def("worldToScreen", [](T& self, const V& worldPoint) { - V screenPoint; - self.worldToScreen(worldPoint, screenPoint); - return screenPoint; - }, pybind11::arg("worldPoint"), "Convert a world space point to screen space") + return py::make_tuple(p[0],p[1],p[2],p[3],p[4],p[5]); + }) + + .def("fovx", &F::fovx, + "F.fovx() -- derives and returns the " + "x field of view (in radians) for frustum F") + + .def("fovy", &F::fovy, + "F.fovy() -- derives and returns the " + "y field of view (in radians) for frustum F") + + .def("aspect", &F::aspect, + "F.aspect() -- derives and returns the " + "aspect ratio for frustum F") + + .def("projectionMatrix", &F::projectionMatrix, + "F.projectionMatrix() -- derives and returns " + "the projection matrix for frustum F") + + .def("window", &F::window, + "F.window(l,r,b,t) -- takes a rectangle in " + "the screen space (i.e., -1 <= l <= r <= 1, " + "-1 <= b <= t <= 1) of F and returns a new " + "Frustum whose near clipping-plane window " + "is that rectangle in local space") + + .def("projectScreenToRay", &F::projectScreenToRay, + "F.projectScreenToRay(V) -- returns a Line3 " + "through V, a V2 point in screen space") + + .def("projectScreenToRay", [](F const& self, py::sequence const& seq) -> Imath::Line3 { + if(seq.size() != 2) { + throw std::invalid_argument ( "projectScreenToRay expects a sequence of length 2"); + } + + Imath::Vec2 const point{py::cast(seq[0]), py::cast(seq[1])}; + return self.projectScreenToRay(point); + }) + + .def("projectPointToScreen", &F::projectPointToScreen, + "F.projectPointToScreen(V) -- returns the " + "projection of V3 V into screen space") + + .def("projectPointToScreen", [](F const& self, py::sequence const& seq) -> Imath::Vec2 { + if(seq.size() != 3) { + throw std::invalid_argument ( "projectPointToScreen expects a sequence of length 3"); + } + + Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + return self.projectPointToScreen(point); + }) - .def("__str__", [](const T &obj) { - std::stringstream ss; - ss << obj; - return ss.str(); - }); + .def("ZToDepth", &F::ZToDepth, + "F.ZToDepth(z, zMin, zMax) -- returns the " + "depth (Z in the local space of the " + "frustum F) corresponding to z (a result of " + "transformation by F's projection matrix) " + "after normalizing z to be between zMin " + "and zMax") + + .def("normalizedZToDepth", &F::normalizedZToDepth, + "F.normalizedZToDepth(z) -- returns the " + "depth (Z in the local space of the " + "frustum F) corresponding to z (a result of " + "transformation by F's projection matrix), " + "which is assumed to have been normalized " + "to [-1, 1]") + + .def("DepthToZ", &F::DepthToZ, + "F.DepthToZ(depth, zMin, zMax) -- converts " + "depth (Z in the local space of the frustum " + "F) to z (a result of transformation by F's " + "projection matrix) which is normalized to " + "[zMin, zMax]") + + .def("worldRadius", &F::worldRadius, + "F.worldRadius(V, r) -- returns the radius " + "in F's local space corresponding to the " + "point V and radius r in screen space") + + .def("worldRadius", [](F const& self, py::sequence const& seq, T radius) -> T { + if(seq.size() != 3) { + throw std::invalid_argument ( "worldRadius expects a sequence of length 3"); + } + + Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + return self.worldRadius(point, radius); + }) + + .def("screenRadius", &F::screenRadius, + "F.screenRadius(V, r) -- returns the radius " + "in screen space corresponding to " + "the point V and radius r in F's local " + "space") + + .def("screenRadius", [](F const& self, py::sequence const& seq, T radius) -> T{ + if(seq.size() != 3) { + throw std::invalid_argument ("screenRadius expects a sequence of length 3"); + } + + Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + return self.screenRadius(point, radius); + }) + ; } -void register_imath_frustum(pybind11::module &m) +} // namespace + +void register_imath_frustum(py::module &m) { - register_frustum(m, "Frustumf"); - register_frustum(m, "Frustumd"); + register_Frustum(m, "Frustumf"); + register_Frustum(m, "Frustumd"); } -} +} // PyBindImath diff --git a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp new file mode 100644 index 00000000..faabe8e3 --- /dev/null +++ b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp @@ -0,0 +1,104 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenEXR Project. +// + +#include "PyBindImath.h" +#include + +#include + +namespace PyBindImath { +namespace py = pybind11; + +namespace { + +template +struct GetClassName {}; + +template <> +struct GetClassName { + static constexpr const char* value = "Matrix44f"; +}; + +template <> +struct GetClassName { + static constexpr const char* value = "Matrix44d"; +}; + +template +struct MatrixRow { + explicit MatrixRow(T *data) : _data(data) {} + T& operator [] (std::size_t i) { return _data[i]; } + T*_data; + + static void register_class(py::module& m, std::string const& rowName) + { + // no default constructor + py::class_(m, rowName.c_str()) + .def("__len__", []{return SIZE;}) + .def("__getitem__", [](MatrixRow const& self, std::size_t index) -> T + { + // TODO: Check data boundaries + return self._data[index]; + }) + .def("__setitem__", [](MatrixRow& self, std::size_t index, T const& value) + { + // TODO: Check data boundaries + self._data[index] = value; + }) + ; + } +}; + +template +std::string Matrix44_repr(M const& v) { + std::ostringstream oss; + + oss.precision(9); + oss << std::fixed; + + oss << GetClassName::value << "((" + << v[0][0] << ", " << v[0][1] << ", " << v[0][2] << ", " << v[0][3] << "), (" + << v[1][0] << ", " << v[1][1] << ", " << v[1][2] << ", " << v[1][3] << "), (" + << v[2][0] << ", " << v[2][1] << ", " << v[2][2] << ", " << v[2][3] << "), (" + << v[3][0] << ", " << v[3][1] << ", " << v[3][2] << ", " << v[3][3] << "))"; + + return oss.str(); +} + +template > +void register_Matrix44(py::module& m) +{ + // TODO: Finish implementation of Matrix class + + std::string const matrixName = GetClassName::value; + + // Register MatrixRow + constexpr char const* rowSuffix = "Row"; + R::register_class(m, matrixName + rowSuffix); + + // Register Matrix type + py::class_(m, matrixName.c_str()) + .def(py::init<>()) + .def("__getitem__", [](M& self, std::size_t index) -> R { + T* data = self[index]; + return R{data}; + }, py::arg("i"), + "Access element at the given index.") + // set item is not required since MatrixRow is returned for set and get operations + .def("__repr__", &Matrix44_repr) + ; +} + +} // namespace + +void register_imath_matrix(py::module& m) +{ + // TODO: M22 and M33 + + register_Matrix44(m); + register_Matrix44(m); +} + +} // PyBindImath \ No newline at end of file diff --git a/src/pybind11/PyBindImath/pybindimathmodule.cpp b/src/pybind11/PyBindImath/pybindimathmodule.cpp index 79f6b2f7..884456f4 100644 --- a/src/pybind11/PyBindImath/pybindimathmodule.cpp +++ b/src/pybind11/PyBindImath/pybindimathmodule.cpp @@ -15,8 +15,9 @@ PYBIND11_MODULE(pybindimath, m) PyBindImath::register_imath_box(m); PyBindImath::register_imath_plane(m); PyBindImath::register_imath_line(m); + PyBindImath::register_imath_matrix(m); // PyBindImath::register_imath_euler(m) - // PyBindImath::register_imath_frustum(m) + PyBindImath::register_imath_frustum(m); // diff --git a/src/pybind11/test/test_frustum.py b/src/pybind11/test/test_frustum.py new file mode 100644 index 00000000..7485f72e --- /dev/null +++ b/src/pybind11/test/test_frustum.py @@ -0,0 +1,43 @@ +import sys +import os +import pytest + +import pybindimath + +@pytest.mark.parametrize("Frustum", [pybindimath.Frustumf, pybindimath.Frustumd]) +def test_frustum(Frustum): + + # The following tests do not check for the correctness of the arguments, + # but if bindings work correctly + + f = Frustum() + assert Frustum(f) is not None + f = Frustum(1, 100, 0, 1, 2, 3.0, False) + assert f.near() == pytest.approx(1.0) + assert f.far() == pytest.approx(100.0) + assert f.left() == pytest.approx(0.0) + assert f.right() == pytest.approx(1.0) + assert f.top() == pytest.approx(2.0) + assert f.bottom() == pytest.approx(3.0) + assert f.orthographic() is False + + f = Frustum(10, 1000, 0.5, 0.5, 1.0) + assert f.near() == pytest.approx(10.0) + assert f.far() == pytest.approx(1000.0) + assert f.fovx() == pytest.approx(0.5) + assert f.fovy() == pytest.approx(0.5) + assert f.aspect() == pytest.approx(1.0) + + f.modifyNearAndFar(20, 2000.0) + assert f.near() == pytest.approx(20.0) + assert f.far() == pytest.approx(2000.0) + + m = f.projectionMatrix() + assert m is not None + assert m[0][0] is not None + + assert f.projectPointToScreen((1, 2, 3)) is not None + with pytest.raises(ValueError) as e: + f.projectPointToScreen((1, 2)) + + assert str(e.value) == "projectPointToScreen expects a sequence of length 3" From 519afbbf70a540b2c00b711ad86c332f348288fe Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Sun, 19 Jan 2025 21:55:07 -0800 Subject: [PATCH 3/9] Add missing license Signed-off-by: Piotr Barejko --- src/pybind11/PyBindImath/PyBindImathFrustum.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp index 8a03f119..32b9874a 100644 --- a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp +++ b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp @@ -1,3 +1,8 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenEXR Project. +// + #include "PyBindImath.h" #include namespace PyBindImath { From 67b9f06a711b5156aef8d1fd0c9829b65c4cba77 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Sun, 19 Jan 2025 23:03:21 -0800 Subject: [PATCH 4/9] Use IMATH_NAMESPACE Signed-off-by: Piotr Barejko --- .../PyBindImath/PyBindImathFrustum.cpp | 30 +++++++++---------- .../PyBindImath/PyBindImathMatrix44.cpp | 8 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp index 32b9874a..7629006b 100644 --- a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp +++ b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp @@ -13,12 +13,12 @@ template struct GetClassName {}; template <> -struct GetClassName { +struct GetClassName { static constexpr const char* value = "Frustumf"; }; template <> -struct GetClassName { +struct GetClassName { static constexpr const char* value = "Frustumd"; }; @@ -88,23 +88,23 @@ void register_Frustum(py::module& m, const char *name) "F.orthographic() -- returns whether frustum " "F is orthographic or not") - .def("planes", [](F const& self, Imath::Plane3* planes) -> void + .def("planes", [](F const& self, IMATH_NAMESPACE::Plane3* planes) -> void { self.planes(planes); }) - .def("planes", [](F const& self, Imath::Plane3 *p, Imath::Matrix44 const& m) -> void + .def("planes", [](F const& self, IMATH_NAMESPACE::Plane3 *p, IMATH_NAMESPACE::Matrix44 const& m) -> void { self.planes(p, m); }) - .def("planes", [](F const& self, Imath::Matrix44 const& m) + .def("planes", [](F const& self, IMATH_NAMESPACE::Matrix44 const& m) { - Imath::Plane3 p[6]; + IMATH_NAMESPACE::Plane3 p[6]; self.planes(p, m); return py::make_tuple(p[0], p[1], p[2], p[3], p[4], p[5]); }) .def("planes", [](F const& self) { - Imath::Plane3 p[6]; + IMATH_NAMESPACE::Plane3 p[6]; self.planes(p); return py::make_tuple(p[0],p[1],p[2],p[3],p[4],p[5]); @@ -137,12 +137,12 @@ void register_Frustum(py::module& m, const char *name) "F.projectScreenToRay(V) -- returns a Line3 " "through V, a V2 point in screen space") - .def("projectScreenToRay", [](F const& self, py::sequence const& seq) -> Imath::Line3 { + .def("projectScreenToRay", [](F const& self, py::sequence const& seq) -> IMATH_NAMESPACE::Line3 { if(seq.size() != 2) { throw std::invalid_argument ( "projectScreenToRay expects a sequence of length 2"); } - Imath::Vec2 const point{py::cast(seq[0]), py::cast(seq[1])}; + IMATH_NAMESPACE::Vec2 const point{py::cast(seq[0]), py::cast(seq[1])}; return self.projectScreenToRay(point); }) @@ -150,12 +150,12 @@ void register_Frustum(py::module& m, const char *name) "F.projectPointToScreen(V) -- returns the " "projection of V3 V into screen space") - .def("projectPointToScreen", [](F const& self, py::sequence const& seq) -> Imath::Vec2 { + .def("projectPointToScreen", [](F const& self, py::sequence const& seq) -> IMATH_NAMESPACE::Vec2 { if(seq.size() != 3) { throw std::invalid_argument ( "projectPointToScreen expects a sequence of length 3"); } - Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + IMATH_NAMESPACE::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; return self.projectPointToScreen(point); }) @@ -192,7 +192,7 @@ void register_Frustum(py::module& m, const char *name) throw std::invalid_argument ( "worldRadius expects a sequence of length 3"); } - Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + IMATH_NAMESPACE::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; return self.worldRadius(point, radius); }) @@ -207,7 +207,7 @@ void register_Frustum(py::module& m, const char *name) throw std::invalid_argument ("screenRadius expects a sequence of length 3"); } - Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + IMATH_NAMESPACE::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; return self.screenRadius(point, radius); }) ; @@ -217,8 +217,8 @@ void register_Frustum(py::module& m, const char *name) void register_imath_frustum(py::module &m) { - register_Frustum(m, "Frustumf"); - register_Frustum(m, "Frustumd"); + register_Frustum(m, "Frustumf"); + register_Frustum(m, "Frustumd"); } } // PyBindImath diff --git a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp index faabe8e3..fb7de673 100644 --- a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp +++ b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp @@ -17,12 +17,12 @@ template struct GetClassName {}; template <> -struct GetClassName { +struct GetClassName { static constexpr const char* value = "Matrix44f"; }; template <> -struct GetClassName { +struct GetClassName { static constexpr const char* value = "Matrix44d"; }; @@ -97,8 +97,8 @@ void register_imath_matrix(py::module& m) { // TODO: M22 and M33 - register_Matrix44(m); - register_Matrix44(m); + register_Matrix44(m); + register_Matrix44(m); } } // PyBindImath \ No newline at end of file From 0f25a14a457f0d5be231db891e4a6f36f3f29643 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Mon, 20 Jan 2025 10:00:42 -0800 Subject: [PATCH 5/9] Change position of const keyword Signed-off-by: Piotr Barejko --- src/pybind11/PyBindImath/PyBindImathFrustum.cpp | 6 +++--- src/pybind11/PyBindImath/PyBindImathMatrix44.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp index 7629006b..4b33787d 100644 --- a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp +++ b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp @@ -14,16 +14,16 @@ struct GetClassName {}; template <> struct GetClassName { - static constexpr const char* value = "Frustumf"; + static constexpr char const* value = "Frustumf"; }; template <> struct GetClassName { - static constexpr const char* value = "Frustumd"; + static constexpr char const* value = "Frustumd"; }; template -void register_Frustum(py::module& m, const char *name) +void register_Frustum(py::module& m, char const* name) { py::class_(m, name) .def(py::init<>(), "Frustum() default construction") diff --git a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp index fb7de673..762803b3 100644 --- a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp +++ b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp @@ -18,12 +18,12 @@ struct GetClassName {}; template <> struct GetClassName { - static constexpr const char* value = "Matrix44f"; + static constexpr char const* value = "Matrix44f"; }; template <> struct GetClassName { - static constexpr const char* value = "Matrix44d"; + static constexpr char const* value = "Matrix44d"; }; template From dbecff331a1461e88666a624d748ad68a5fde7f9 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Mon, 20 Jan 2025 10:08:57 -0800 Subject: [PATCH 6/9] Add boundary checks for pybind11 matrix bindings Signed-off-by: Piotr Barejko --- src/pybind11/PyBindImath/PyBindImathMatrix44.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp index 762803b3..02eee9e4 100644 --- a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp +++ b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp @@ -39,12 +39,18 @@ struct MatrixRow { .def("__len__", []{return SIZE;}) .def("__getitem__", [](MatrixRow const& self, std::size_t index) -> T { - // TODO: Check data boundaries + if(index >= SIZE) + { + throw std::out_of_range("Index out of range"); + } return self._data[index]; }) .def("__setitem__", [](MatrixRow& self, std::size_t index, T const& value) { - // TODO: Check data boundaries + if(index >= SIZE) + { + throw std::out_of_range("Index out of range"); + } self._data[index] = value; }) ; @@ -82,6 +88,10 @@ void register_Matrix44(py::module& m) py::class_(m, matrixName.c_str()) .def(py::init<>()) .def("__getitem__", [](M& self, std::size_t index) -> R { + if(index >= SIZE) + { + throw std::out_of_range("Index out of range"); + } T* data = self[index]; return R{data}; }, py::arg("i"), From 3fb96bc9a2767925cfcdd6031a67b2476a30caf6 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Mon, 20 Jan 2025 19:31:08 -0800 Subject: [PATCH 7/9] Add PyBindings for Quat Signed-off-by: Piotr Barejko --- src/Imath/ImathQuat.h | 2 + src/pybind11/PyBindImath/CMakeLists.txt | 1 + src/pybind11/PyBindImath/PyBindImath.h | 1 + src/pybind11/PyBindImath/PyBindImathQuat.cpp | 204 +++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 src/pybind11/PyBindImath/PyBindImathQuat.cpp diff --git a/src/Imath/ImathQuat.h b/src/Imath/ImathQuat.h index a0972dff..a822e036 100644 --- a/src/Imath/ImathQuat.h +++ b/src/Imath/ImathQuat.h @@ -42,6 +42,8 @@ IMATH_INTERNAL_NAMESPACE_HEADER_ENTER template class IMATH_EXPORT_TEMPLATE_TYPE Quat { public: + using value_type = T; + /// @{ /// @name Direct access to elements diff --git a/src/pybind11/PyBindImath/CMakeLists.txt b/src/pybind11/PyBindImath/CMakeLists.txt index 06e5403f..52eda19a 100644 --- a/src/pybind11/PyBindImath/CMakeLists.txt +++ b/src/pybind11/PyBindImath/CMakeLists.txt @@ -14,6 +14,7 @@ set(PYBINDIMATH_SOURCES PyBindImathPlane.cpp PyBindImathLine.cpp PyBindImathMatrix44.cpp + PyBindImathQuat.cpp # PyBindImathEuler build Error pybind test PyBindImathFrustum.cpp diff --git a/src/pybind11/PyBindImath/PyBindImath.h b/src/pybind11/PyBindImath/PyBindImath.h index 3d2420ac..11bc8e95 100644 --- a/src/pybind11/PyBindImath/PyBindImath.h +++ b/src/pybind11/PyBindImath/PyBindImath.h @@ -22,6 +22,7 @@ PYBINDIMATH_EXPORT void register_imath_box(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_plane(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_line(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_matrix(pybind11::module& m); +PYBINDIMATH_EXPORT void register_imath_quat(pybind11::module& m); // PYBINDIMATH_EXPORT void register_imath_euler(pybind11::module& m) PYBINDIMATH_EXPORT void register_imath_frustum(pybind11::module& m); diff --git a/src/pybind11/PyBindImath/PyBindImathQuat.cpp b/src/pybind11/PyBindImath/PyBindImathQuat.cpp new file mode 100644 index 00000000..27938486 --- /dev/null +++ b/src/pybind11/PyBindImath/PyBindImathQuat.cpp @@ -0,0 +1,204 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenEXR Project. +// + +#include "PyBindImath.h" + +#include +#include +#include + +namespace PyBindImath { + +namespace py = pybind11; + +using IMATH_NAMESPACE::Quat; +using IMATH_NAMESPACE::Vec3; +using IMATH_NAMESPACE::Matrix33; +using IMATH_NAMESPACE::Matrix44; +using IMATH_NAMESPACE::Euler; + +using IMATH_NAMESPACE::Quatf; +using IMATH_NAMESPACE::Quatd; + +namespace { + +template +struct GetClassName {}; + +template <> +struct GetClassName { + static constexpr char const* value = "Quatf"; +}; + +template <> +struct GetClassName { + static constexpr char const* value = "Quatd"; +}; + +template +std::string Quat_str(Q const& v) +{ + std::stringstream stream; + stream << GetClassName::value << '(' + << v[0] << ", " + << v[1] << ", " + << v[2] << ", " + << v[3] << ')'; + return stream.str(); +} + +template > +std::string Quat_repr(Q const& v) +{ + static_assert(std::is_floating_point::value, "Quat_repr requires a floating-point type"); + + // Set precision based on the type + constexpr int precision = std::is_same::value ? 9 : 17; + + std::ostringstream oss; + oss << GetClassName::value << '(' + << std::setprecision(precision) << v[0] << ", " + << std::setprecision(precision) << v[1] << ", " + << std::setprecision(precision) << v[2] << ", " + << std::setprecision(precision) << v[3] << ')'; + return oss.str(); +} + +} // namespace + +template , typename E = Euler> +void register_Quat(py::module& m) +{ + auto className = GetClassName::value; + + py::class_(m, className) + .def(py::init<>(), "imath Quat initialization") + .def(py::init(), "imath Quat copy initialization") + .def(py::init(), "make Quat from components") + .def(py::init(), "make Quat from components") + .def("__init__", [](Q& self, E const& euler){new (&self) Q(euler.toQuat());}) + .def("__init__", [](Q& self, Matrix33 const& mat){new (&self) Q(E(mat).toQuat());}) + .def("__init__", [](Q& self, Matrix44 const& mat){new (&self) Q(E(mat).toQuat());}) + .def("identity",&Q::identity, + "q.identity() -- return an identity quaternion\n") + .def("invert", &Q::invert, + "q.invert() -- inverts quaternion q\n" + "(modifying q); returns q") + + .def("inverse", &Q::inverse, + "q.inverse() -- returns the inverse of\n" + "quaternion q; q is not modified\n") + + .def("normalize", &Q::normalize, + "q.normalize() -- normalizes quaternion q\n" + "(modifying q); returns q") + + .def("normalized", &Q::normalized, + "q.normalized() -- returns a normalized version\n" + "of quaternion q; q is not modified\n") + + .def("length", &Q::length) + + .def("rotateVector", &Q::rotateVector, + "q.rotateVector(orig) -- Given a vector orig,\n" + " calculate orig' = q x orig x q*\n\n" + " Assumes unit quaternions") + + .def("setAxisAngle", &Q::setAxisAngle, + "q.setAxisAngle(x,r) -- sets the value of\n" + "quaternion q so that q represents a rotation\n" + "of r radians around axis x") + + .def("setRotation", &Q::setRotation, + "q.setRotation(v,w) -- sets the value of\n" + "quaternion q so that rotating vector v by\n" + "q produces vector w") + + .def("angle", &Q::angle, + "q.angle() -- returns the rotation angle\n" + "(in radians) represented by quaternion q") + + .def("axis", &Q::axis, + "q.axis() -- returns the rotation axis\n" + "represented by quaternion q") + + .def("toMatrix33", &Q::toMatrix33, + "q.toMatrix33() -- returns a 3x3 matrix that\n" + "represents the same rotation as quaternion q") + + .def("toMatrix44", &Q::toMatrix44, + "q.toMatrix44() -- returns a 4x4 matrix that\n" + "represents the same rotation as quaternion q") + + .def("log",&Q::log) + .def("exp",&Q::exp) + .def_readwrite("v",&Q::v) + .def_readwrite("r",&Q::r) + .def("v", [](Q const& self) { return self.v; }, + "q.v() -- returns the v (vector) component\n" + "of quaternion q") + + .def("r", [](Q const& self) { return self.r; }, + "q.r() -- returns the r (scalar) component\n" + "of quaternion q") + + .def("setR", [](Q self, T r){ self.r = r; }, + "q.setR(s) -- sets the r (scalar) component\n" + "of quaternion q to s") + + .def("setV", [](Q& self, Vec3 const& v){ self.v = v; }, + "q.setV(w) -- sets the v (vector) component\n" + "of quaternion q to w") + + .def("extract", [](Q const& self, Matrix44 const& mat){ return IMATH_NAMESPACE::extractQuat(mat); }, + "q.extract(m) -- extracts the rotation component\n" + "from 4x4 matrix m and stores the result in q") + + .def("slerp", [](Q const& self, Q const& other, T t){return IMATH_NAMESPACE::slerp (self, other, t);}, + "q.slerp(p,t) -- performs sperical linear\n" + "interpolation between quaternions q and p:\n" + "q.slerp(p,0) returns q; q.slerp(p,1) returns p.\n" + "q and p must be normalized\n") + + .def("slerpShortestArc", [](Q const& self, Q const& other, T t){ return IMATH_NAMESPACE::slerpShortestArc(self, other, t); }, + "q.slerpShortestArc(p,t) -- performs spherical linear\n" + "interpolation along the shortest arc between\n" + "quaternions q and either p or -p, whichever is\n" + "closer. q and p must be normalized\n") + + .def("__str__",Quat_str) + .def("__repr__",Quat_repr) + .def(py::self * T()) + .def(py::self / T()) + + .def(py::self *= T()) + .def(py::self /= T()) + + .def(py::self * py::self) + .def(py::self / py::self) + .def(py::self + py::self) + .def(py::self - py::self) + + .def(py::self *= py::self) + .def(py::self /= py::self) + .def(py::self += py::self) + .def(py::self -= py::self) + + .def(py::self * Matrix33()) + .def(Matrix33() * py::self) + .def(py::self *= Matrix33()) + + .def(-py::self) + .def("__invert__", [](Q const& self){return ~self;}) + .def("__xor__", [](Q const& self, Q const& other){return self ^ other;}) + ; +} + +void register_imath_quat(py::module& m) +{ + +} + +} // PyBindImath From 0760e11c902a909e9c540a1162fc3331732dcda1 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Mon, 20 Jan 2025 19:31:34 -0800 Subject: [PATCH 8/9] Cosmetic changes for Matrix44 Signed-off-by: Piotr Barejko --- src/pybind11/PyBindImath/PyBindImathMatrix44.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp index 02eee9e4..9cc45615 100644 --- a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp +++ b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp @@ -11,18 +11,21 @@ namespace PyBindImath { namespace py = pybind11; +using IMATH_NAMESPACE::M44f; +using IMATH_NAMESPACE::M44d; + namespace { template struct GetClassName {}; template <> -struct GetClassName { +struct GetClassName { static constexpr char const* value = "Matrix44f"; }; template <> -struct GetClassName { +struct GetClassName { static constexpr char const* value = "Matrix44d"; }; @@ -107,8 +110,8 @@ void register_imath_matrix(py::module& m) { // TODO: M22 and M33 - register_Matrix44(m); - register_Matrix44(m); + register_Matrix44(m); + register_Matrix44(m); } } // PyBindImath \ No newline at end of file From f88a9f43f0aca47c90cc1d5219865c3c323cb4b3 Mon Sep 17 00:00:00 2001 From: Piotr Barejko Date: Mon, 20 Jan 2025 19:43:16 -0800 Subject: [PATCH 9/9] Quat compilation issues Signed-off-by: Piotr Barejko --- src/pybind11/PyBindImath/PyBindImathQuat.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/pybind11/PyBindImath/PyBindImathQuat.cpp b/src/pybind11/PyBindImath/PyBindImathQuat.cpp index 27938486..4beef29c 100644 --- a/src/pybind11/PyBindImath/PyBindImathQuat.cpp +++ b/src/pybind11/PyBindImath/PyBindImathQuat.cpp @@ -49,7 +49,7 @@ std::string Quat_str(Q const& v) return stream.str(); } -template > +template std::string Quat_repr(Q const& v) { static_assert(std::is_floating_point::value, "Quat_repr requires a floating-point type"); @@ -68,16 +68,17 @@ std::string Quat_repr(Q const& v) } // namespace -template , typename E = Euler> +template > void register_Quat(py::module& m) { auto className = GetClassName::value; py::class_(m, className) .def(py::init<>(), "imath Quat initialization") - .def(py::init(), "imath Quat copy initialization") + .def(py::init>(), "imath Quat copy initialization") + .def(py::init>(), "imath Quat copy initialization") .def(py::init(), "make Quat from components") - .def(py::init(), "make Quat from components") + .def(py::init>(), "make Quat from components") .def("__init__", [](Q& self, E const& euler){new (&self) Q(euler.toQuat());}) .def("__init__", [](Q& self, Matrix33 const& mat){new (&self) Q(E(mat).toQuat());}) .def("__init__", [](Q& self, Matrix44 const& mat){new (&self) Q(E(mat).toQuat());}) @@ -168,8 +169,8 @@ void register_Quat(py::module& m) "quaternions q and either p or -p, whichever is\n" "closer. q and p must be normalized\n") - .def("__str__",Quat_str) - .def("__repr__",Quat_repr) + .def("__str__", Quat_str) + .def("__repr__", Quat_repr) .def(py::self * T()) .def(py::self / T()) @@ -188,7 +189,6 @@ void register_Quat(py::module& m) .def(py::self * Matrix33()) .def(Matrix33() * py::self) - .def(py::self *= Matrix33()) .def(-py::self) .def("__invert__", [](Q const& self){return ~self;}) @@ -198,7 +198,8 @@ void register_Quat(py::module& m) void register_imath_quat(py::module& m) { - + register_Quat(m); + register_Quat(m); } } // PyBindImath