diff --git a/doc/python/magnum.scenetools.rst b/doc/python/magnum.scenetools.rst index c4fc42b..1c361f7 100644 --- a/doc/python/magnum.scenetools.rst +++ b/doc/python/magnum.scenetools.rst @@ -39,6 +39,14 @@ :raise AssertionError: If size of :p:`objects_to_keep` is different than :ref:`trade.SceneData.mapping_bound` +.. py:function:: magnum.scenetools.parents_breadth_first + :raise AssertionError: If :p:`scene` does not have + :ref:`trade.SceneField.PARENT` + +.. py:function:: magnum.scenetools.children_depth_first + :raise AssertionError: If :p:`scene` does not have + :ref:`trade.SceneField.PARENT` + .. py:function:: magnum.scenetools.absolute_field_transformations2d :raise KeyError: If :p:`field` does not exist in :p:`scene` :raise IndexError: If :p:`field_id` negative or not less than diff --git a/src/python/magnum/scenetools.cpp b/src/python/magnum/scenetools.cpp index 50880f6..83b0f33 100644 --- a/src/python/magnum/scenetools.cpp +++ b/src/python/magnum/scenetools.cpp @@ -161,6 +161,34 @@ void scenetools(py::module_& m) { return SceneTools::filterObjects(scene, objectsToKeep); }, "Filter objects in a scene", py::arg("scene"), py::arg("objects_to_keep")) + .def("parents_breadth_first", [](const Trade::SceneData& scene) { + const Containers::Optional parentFieldId = scene.findFieldId(Trade::SceneField::Parent); + if(!parentFieldId) { + PyErr_SetString(PyExc_AssertionError, "the scene has no hierarchy"); + throw py::error_already_set{}; + } + + /** @todo ugh, add type converters for Corrade arrays already */ + std::vector> out{scene.fieldSize(*parentFieldId)}; + SceneTools::parentsBreadthFirstInto(scene, + Containers::stridedArrayView(out).slice(&decltype(out)::value_type::first), + Containers::stridedArrayView(out).slice(&decltype(out)::value_type::second)); + return out; + }, "Retrieve parents in a breadth-first order", py::arg("scene")) + .def("children_depth_first", [](const Trade::SceneData& scene) { + const Containers::Optional parentFieldId = scene.findFieldId(Trade::SceneField::Parent); + if(!parentFieldId) { + PyErr_SetString(PyExc_AssertionError, "the scene has no hierarchy"); + throw py::error_already_set{}; + } + + /** @todo ugh, add type converters for Corrade arrays already */ + std::vector> out{scene.fieldSize(*parentFieldId)}; + SceneTools::childrenDepthFirstInto(scene, + Containers::stridedArrayView(out).slice(&decltype(out)::value_type::first), + Containers::stridedArrayView(out).slice(&decltype(out)::value_type::second)); + return out; + }, "Retrieve children in a depth-first order", py::arg("scene")) .def("absolute_field_transformations2d", [](const Trade::SceneData& scene, Trade::SceneField field, const Matrix3& globalTransformation) { const Containers::Optional fieldId = scene.findFieldId(field); if(!fieldId) { diff --git a/src/python/magnum/test/test_scenetools.py b/src/python/magnum/test/test_scenetools.py index 690d8e6..02dac0b 100644 --- a/src/python/magnum/test/test_scenetools.py +++ b/src/python/magnum/test/test_scenetools.py @@ -313,6 +313,41 @@ class Filter(unittest.TestCase): scenetools.filter_objects(scene, containers.BitArray.value_init(3)) class Hierarchy(unittest.TestCase): + def test_parents_breadth_first_children_depth_first(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), "scene.gltf")) + + scene = importer.scene(0) + + parents_breadth_first = scenetools.parents_breadth_first(scene) + children_depth_first = scenetools.children_depth_first(scene) + self.assertEqual(parents_breadth_first, [ + # Root objects first, parent always before all its children + (1, -1), + (2, -1), + (3, 2), + (0, 3) + ]) + self.assertEqual(children_depth_first, [ + # Object 1 has no children + (1, 0), + # Object 2 has one direct child and one grandchild, etc + (2, 2), + (3, 1), + (0, 0) + ]) + + def test_parents_breadth_first_children_depth_first_no_hierarchy(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), "scene.gltf")) + + scene = scenetools.filter_except_fields(importer.scene(0), [trade.SceneField.PARENT]) + + with self.assertRaisesRegex(AssertionError, "the scene has no hierarchy"): + scenetools.parents_breadth_first(scene) + with self.assertRaisesRegex(AssertionError, "the scene has no hierarchy"): + scenetools.children_depth_first(scene) + def test_absolute_field_transformations2d(self): # Static builds with non-static plugins cause assertions with non-owned # array deleters used by PrimitiveImporter, skip in that case