diff --git a/src/python/magnum/CMakeLists.txt b/src/python/magnum/CMakeLists.txt index 1bd5500..3f2d565 100644 --- a/src/python/magnum/CMakeLists.txt +++ b/src/python/magnum/CMakeLists.txt @@ -83,7 +83,8 @@ endif() if(Magnum_SceneGraph_FOUND) set(magnum_scenegraph_SRCS - scenegraph.cpp) + scenegraph.cpp + scenegraph.matrix.cpp) pybind11_add_module(magnum_scenegraph ${magnum_scenegraph_SRCS}) target_include_directories(magnum_scenegraph PRIVATE diff --git a/src/python/magnum/scenegraph.cpp b/src/python/magnum/scenegraph.cpp index 2bd4152..8bf8e5d 100644 --- a/src/python/magnum/scenegraph.cpp +++ b/src/python/magnum/scenegraph.cpp @@ -24,17 +24,11 @@ */ #include -#include #include #include -#include -#include -#include -#include +#include -#include "Magnum/SceneGraph/Python.h" - -#include "magnum/bootstrap.h" +#include "magnum/scenegraph.h" namespace magnum { namespace { @@ -53,10 +47,6 @@ template struct PyDrawable: SceneGraph::PyFeatu } }; -template void scene(py::class_>& c) { - c.def(py::init(), "Constructor"); -} - template void abstractObject(py::class_, SceneGraph::PyObjectHolder>>& c) { c /* Matrix transformation APIs */ @@ -66,115 +56,6 @@ template void abstractObject(py::class_ void object(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { - c - .def(py::init_alias>*>(), - "Constructor", py::arg("parent") = nullptr) - .def(py::init_alias*>(), - "Constructor", py::arg("parent") = nullptr) - - /* Properties */ - .def_property_readonly("scene", [](SceneGraph::PyObject>& self) { - return static_cast*>(self.scene()); - }, "Scene or None if the object is not a part of any scene") - .def_property("parent", [](SceneGraph::PyObject>& self) { - return static_cast>*>(self.parent()); - }, [](SceneGraph::PyObject>& self, py::object parentobj) { - SceneGraph::Object* parent; - if(py::isinstance>>(parentobj)) - parent = py::cast>*>(parentobj); - else if(py::isinstance>(parentobj)) - parent = py::cast*>(parentobj); - else if(py::isinstance(parentobj)) - parent = nullptr; - else throw py::type_error{Utility::formatString("expected Scene, Object or None, got {}", std::string(py::str{parentobj.get_type()}))}; - - /* Decrease refcount if a parent is removed, increase it if a - parent gets added */ - if(self.parent() && !parent) py::cast(&self).dec_ref(); - else if(!self.parent() && parent) py::cast(&self).inc_ref(); - - self.setParent(parent); - }, "Parent object or None if this is the root object") - - /* Transformation APIs common to all implementations */ - .def_property("transformation", - &SceneGraph::PyObject>::transformation, - &SceneGraph::PyObject>::setTransformation, - "Object transformation") - .def("absolute_transformation", &SceneGraph::PyObject>::absoluteTransformation, - "Transformation relative to the root object") - .def("reset_transformation", [](SceneGraph::PyObject>& self) { - self.resetTransformation(); - }, "Reset the transformation") - .def("transform", [](SceneGraph::PyObject>& self, const typename Transformation::DataType& transformation) { - self.transform(transformation); - }, "Transform the object") - .def("transform_local", [](SceneGraph::PyObject>& self, const typename Transformation::DataType& transformation) { - self.transformLocal(transformation); - }, "Transform the object as a local transformation") - .def("translate", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { - self.translate(vector); - }, "Translate the object") - .def("translate_local", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { - self.translateLocal(vector); - }, "Translate the object as a local transformation"); -} - -template void object2D(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { - c - .def("rotate", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotate(static_cast>(angle)); - }, "Rotate the object") - .def("rotate_local", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateLocal(static_cast>(angle)); - }, "Rotate the object as a local transformation"); -} - -template void object3D(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { - c - .def("rotate", [](SceneGraph::PyObject>& self, const Radd angle, const Math::Vector3& normalizedAxis) { - self.rotate(static_cast>(angle), normalizedAxis); - }, "Rotate the object as a local transformation", py::arg("angle"), py::arg("normalized_axis")) - .def("rotate_local", [](SceneGraph::PyObject>& self, const Radd angle, const Math::Vector3& normalizedAxis) { - self.rotateLocal(static_cast>(angle), normalizedAxis); - }, "Rotate the object as a local transformation", py::arg("angle"), py::arg("normalized_axis")) - .def("rotate_x", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateX(static_cast>(angle)); - }, "Rotate the object around X axis") - .def("rotate_x_local", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateXLocal(static_cast>(angle)); - }, "Rotate the object around X axis as a local transformation") - .def("rotate_y", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateY(static_cast>(angle)); - }, "Rotate the object around Y axis") - .def("rotate_y_local", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateYLocal(static_cast>(angle)); - }, "Rotate the object around Y axis as a local transformation") - .def("rotate_z", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateZ(static_cast>(angle)); - }, "Rotate the object around Z axis") - .def("rotate_z_local", [](SceneGraph::PyObject>& self, const Radd angle) { - self.rotateZLocal(static_cast>(angle)); - }, "Rotate the object around Z axis as a local transformation"); -} - -template void objectScale(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { - c - .def("scale", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { - self.scale(vector); - }, "Scale the object") - .def("scale_local", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { - self.scaleLocal(vector); - }, "Scale the object as a local transformation") - .def("reflect", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { - self.reflect(vector); - }, "Reflect the object") - .def("reflect_local", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { - self.reflectLocal(vector); - }, "Reflect the object as a local transformation"); -} - template void featureGroup(py::class_>& c) { c .def(py::init(), "Constructor") @@ -249,28 +130,6 @@ void scenegraph(py::module& m) { abstractObject(abstractObject3D); } - /* 2D/3D matrix-based implementation */ - { - py::module matrix = m.def_submodule("matrix"); - matrix.doc() = "General matrix-based scene graph implementation"; - - py::class_> scene2D_{matrix, "Scene2D", "Two-dimensional scene with matrix-based transformation implementation"}; - scene(scene2D_); - - py::class_> scene3D_{matrix, "Scene3D", "Three-dimensional scene with matrix-based transformation implementation"}; - scene(scene3D_); - - py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject2D, SceneGraph::PyObjectHolder>> object2D_{matrix, "Object2D", "Two-dimensional object with matrix-based transformation implementation"}; - object(object2D_); - object2D(object2D_); - objectScale(object2D_); - - py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject3D, SceneGraph::PyObjectHolder>> object3D_{matrix, "Object3D", "Three-dimensional object with matrix-based transformation implementation"}; - object(object3D_); - object3D(object3D_); - objectScale(object3D_); - } - /* Drawables, camera */ { py::enum_{m, "AspectRatioPolicy", "Camera aspect ratio policy"} @@ -308,5 +167,5 @@ PYBIND11_MODULE(scenegraph, m) { m.doc() = "Scene graph library"; magnum::scenegraph(m); + magnum::scenegraphMatrix(m); } - diff --git a/src/python/magnum/scenegraph.h b/src/python/magnum/scenegraph.h new file mode 100644 index 0000000..dcb4353 --- /dev/null +++ b/src/python/magnum/scenegraph.h @@ -0,0 +1,156 @@ +#ifndef magnum_scenegraph_h +#define magnum_scenegraph_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include + +#include "Magnum/SceneGraph/Python.h" + +#include "magnum/bootstrap.h" + +namespace magnum { + +template void scene(py::class_>& c) { + c.def(py::init(), "Constructor"); +} + +template void object(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { + c + .def(py::init_alias>*>(), + "Constructor", py::arg("parent") = nullptr) + .def(py::init_alias*>(), + "Constructor", py::arg("parent") = nullptr) + + /* Properties */ + .def_property_readonly("scene", [](SceneGraph::PyObject>& self) { + return static_cast*>(self.scene()); + }, "Scene or None if the object is not a part of any scene") + .def_property("parent", [](SceneGraph::PyObject>& self) { + return static_cast>*>(self.parent()); + }, [](SceneGraph::PyObject>& self, py::object parentobj) { + SceneGraph::Object* parent; + if(py::isinstance>>(parentobj)) + parent = py::cast>*>(parentobj); + else if(py::isinstance>(parentobj)) + parent = py::cast*>(parentobj); + else if(py::isinstance(parentobj)) + parent = nullptr; + else throw py::type_error{Utility::formatString("expected Scene, Object or None, got {}", std::string(py::str{parentobj.get_type()}))}; + + /* Decrease refcount if a parent is removed, increase it if a + parent gets added */ + if(self.parent() && !parent) py::cast(&self).dec_ref(); + else if(!self.parent() && parent) py::cast(&self).inc_ref(); + + self.setParent(parent); + }, "Parent object or None if this is the root object") + + /* Transformation APIs common to all implementations */ + .def_property("transformation", + &SceneGraph::PyObject>::transformation, + &SceneGraph::PyObject>::setTransformation, + "Object transformation") + .def("absolute_transformation", &SceneGraph::PyObject>::absoluteTransformation, + "Transformation relative to the root object") + .def("reset_transformation", [](SceneGraph::PyObject>& self) { + self.resetTransformation(); + }, "Reset the transformation") + .def("transform", [](SceneGraph::PyObject>& self, const typename Transformation::DataType& transformation) { + self.transform(transformation); + }, "Transform the object") + .def("transform_local", [](SceneGraph::PyObject>& self, const typename Transformation::DataType& transformation) { + self.transformLocal(transformation); + }, "Transform the object as a local transformation") + .def("translate", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { + self.translate(vector); + }, "Translate the object") + .def("translate_local", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { + self.translateLocal(vector); + }, "Translate the object as a local transformation"); +} + +template void object2D(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { + c + .def("rotate", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotate(static_cast>(angle)); + }, "Rotate the object") + .def("rotate_local", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateLocal(static_cast>(angle)); + }, "Rotate the object as a local transformation"); +} + +template void object3D(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { + c + .def("rotate", [](SceneGraph::PyObject>& self, const Radd angle, const Math::Vector3& normalizedAxis) { + self.rotate(static_cast>(angle), normalizedAxis); + }, "Rotate the object as a local transformation", py::arg("angle"), py::arg("normalized_axis")) + .def("rotate_local", [](SceneGraph::PyObject>& self, const Radd angle, const Math::Vector3& normalizedAxis) { + self.rotateLocal(static_cast>(angle), normalizedAxis); + }, "Rotate the object as a local transformation", py::arg("angle"), py::arg("normalized_axis")) + .def("rotate_x", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateX(static_cast>(angle)); + }, "Rotate the object around X axis") + .def("rotate_x_local", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateXLocal(static_cast>(angle)); + }, "Rotate the object around X axis as a local transformation") + .def("rotate_y", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateY(static_cast>(angle)); + }, "Rotate the object around Y axis") + .def("rotate_y_local", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateYLocal(static_cast>(angle)); + }, "Rotate the object around Y axis as a local transformation") + .def("rotate_z", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateZ(static_cast>(angle)); + }, "Rotate the object around Z axis") + .def("rotate_z_local", [](SceneGraph::PyObject>& self, const Radd angle) { + self.rotateZLocal(static_cast>(angle)); + }, "Rotate the object around Z axis as a local transformation"); +} + +template void objectScale(py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject, SceneGraph::PyObjectHolder>>& c) { + c + .def("scale", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { + self.scale(vector); + }, "Scale the object") + .def("scale_local", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { + self.scaleLocal(vector); + }, "Scale the object as a local transformation") + .def("reflect", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { + self.reflect(vector); + }, "Reflect the object") + .def("reflect_local", [](SceneGraph::PyObject>& self, const VectorTypeFor& vector) { + self.reflectLocal(vector); + }, "Reflect the object as a local transformation"); +} + +void scenegraphMatrix(py::module& m); + +} + +#endif diff --git a/src/python/magnum/scenegraph.matrix.cpp b/src/python/magnum/scenegraph.matrix.cpp new file mode 100644 index 0000000..7f84d96 --- /dev/null +++ b/src/python/magnum/scenegraph.matrix.cpp @@ -0,0 +1,54 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "scenegraph.h" + +namespace magnum { + +void scenegraphMatrix(py::module& m) { + py::module matrix = m.def_submodule("matrix"); + matrix.doc() = "General matrix-based scene graph implementation"; + + py::class_> scene2D_{matrix, "Scene2D", "Two-dimensional scene with matrix-based transformation implementation"}; + scene(scene2D_); + + py::class_> scene3D_{matrix, "Scene3D", "Three-dimensional scene with matrix-based transformation implementation"}; + scene(scene3D_); + + py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject2D, SceneGraph::PyObjectHolder>> object2D_{matrix, "Object2D", "Two-dimensional object with matrix-based transformation implementation"}; + object(object2D_); + object2D(object2D_); + objectScale(object2D_); + + py::class_, SceneGraph::PyObject>, SceneGraph::AbstractObject3D, SceneGraph::PyObjectHolder>> object3D_{matrix, "Object3D", "Three-dimensional object with matrix-based transformation implementation"}; + object(object3D_); + object3D(object3D_); + objectScale(object3D_); +} + +} diff --git a/src/python/magnum/test/test_scenegraph.py b/src/python/magnum/test/test_scenegraph.py index 9d40e71..fa8423c 100644 --- a/src/python/magnum/test/test_scenegraph.py +++ b/src/python/magnum/test/test_scenegraph.py @@ -116,25 +116,6 @@ class Object(unittest.TestCase): with self.assertRaisesRegex(TypeError, "expected Scene, Object or None, got "): a.parent = "noo" - def test_transformation(self): - scene = Scene3D() - - a = Object3D(scene) - a.rotate_local(Deg(35.0), Vector3.x_axis()) - self.assertEqual(a.transformation, Matrix4.rotation_x(Deg(35.0))) - self.assertEqual(a.absolute_transformation(), Matrix4.rotation_x(Deg(35.0))) - - b = Object3D(a) - b.translate((3.0, 4.0, 5.0)) - self.assertEqual(b.transformation, Matrix4.translation((3.0, 4.0, 5.0))) - self.assertEqual(b.absolute_transformation(), - Matrix4.rotation_x(Deg(35.0))@ - Matrix4.translation((3.0, 4.0, 5.0))) - - c = Object3D(scene) - self.assertEqual(c.transformation, Matrix4.identity_init()) - self.assertEqual(c.absolute_transformation(), Matrix4.identity_init()) - def test_drawable(self): object = Object3D() object_refcount = sys.getrefcount(object) diff --git a/src/python/magnum/test/test_scenegraph_matrix.py b/src/python/magnum/test/test_scenegraph_matrix.py new file mode 100644 index 0000000..e476ac3 --- /dev/null +++ b/src/python/magnum/test/test_scenegraph_matrix.py @@ -0,0 +1,51 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 +# Vladimír Vondruš +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +import sys +import unittest + +from magnum import * +from magnum import scenegraph +from magnum.scenegraph.matrix import Object3D, Scene3D + +class Object(unittest.TestCase): + def test_transformation(self): + scene = Scene3D() + + a = Object3D(scene) + a.rotate_local(Deg(35.0), Vector3.x_axis()) + self.assertEqual(a.transformation, Matrix4.rotation_x(Deg(35.0))) + self.assertEqual(a.absolute_transformation(), Matrix4.rotation_x(Deg(35.0))) + + b = Object3D(a) + b.translate((3.0, 4.0, 5.0)) + self.assertEqual(b.transformation, Matrix4.translation((3.0, 4.0, 5.0))) + self.assertEqual(b.absolute_transformation(), + Matrix4.rotation_x(Deg(35.0))@ + Matrix4.translation((3.0, 4.0, 5.0))) + + c = Object3D(scene) + self.assertEqual(c.transformation, Matrix4.identity_init()) + self.assertEqual(c.absolute_transformation(), Matrix4.identity_init())