Browse Source

python: expose scenetools.filter_field_entries().

next
Vladimír Vondruš 3 years ago
parent
commit
f83c2cb1aa
  1. 8
      doc/python/magnum.scenetools.rst
  2. 76
      src/python/magnum/scenetools.cpp
  3. 106
      src/python/magnum/test/test_scenetools.py

8
doc/python/magnum.scenetools.rst

@ -27,6 +27,14 @@
:raise AssertionError: If size of :p:`fields_to_keep` is different than
:ref:`trade.SceneData.field_count`
.. py:function:: magnum.scenetools.filter_field_entries
:raise AssertionError: If any field in :p:`entries_to_keep` does not exist
in :p:`scene`
:raise AssertionError: If any field in :p:`entries_to_keep` is listed more
than once
:raise AssertionError: If size of any array in :p:`entries_to_keep` does
not match :ref:`trade.SceneData.field_size()` for given field
.. 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

76
src/python/magnum/scenetools.cpp

@ -26,8 +26,10 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> /* for std::vector */
#include <Corrade/Containers/ArrayViewStl.h>
#include <Corrade/Containers/BitArray.h>
#include <Corrade/Containers/BitArrayView.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Magnum/Math/Matrix3.h>
#include <Magnum/Math/Matrix4.h>
#include <Magnum/SceneTools/Filter.h>
@ -74,6 +76,80 @@ void scenetools(py::module_& m) {
py::object sceneOwner = pyObjectHolderFor<Trade::PyDataHolder>(scene).owner;
return Trade::pyDataHolder(SceneTools::filterExceptFields(scene, fields), sceneOwner.is_none() ? py::cast(scene) : std::move(sceneOwner));
}, "Filter a scene to contain everything the selected subset of named fields", py::arg("scene"), py::arg("fields"))
/** @todo ew, especially the cast .. i hope they have compatible
layout, not like std::tuple */
/* The enum-based overloads NEEDS to be before the integer overload,
otherwise pybind happily uses the enums as integer values!!! */
.def("filter_field_entries", [](const Trade::SceneData& scene, const std::vector<std::pair<Trade::SceneField, Containers::BitArrayView>> entriesToKeepStl) {
const auto entriesToKeep = Containers::arrayCast<const Containers::Pair<Trade::SceneField, Containers::BitArrayView>>(Containers::arrayView(entriesToKeepStl));
Containers::BitArray usedFields{ValueInit, scene.fieldCount()};
for(std::size_t i = 0; i != entriesToKeep.size(); ++i) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(entriesToKeep[i].first());
if(!fieldId) {
PyErr_Format(PyExc_AssertionError, "field at index %zu not found", i, scene.fieldCount());
throw py::error_already_set{};
}
if(usedFields[*fieldId]) {
PyErr_Format(PyExc_AssertionError, "field at index %zu listed more than once", i);
throw py::error_already_set{};
}
usedFields.set(*fieldId);
const Containers::BitArrayView mask = entriesToKeep[i].second();
if(mask.size() != scene.fieldSize(*fieldId)) {
PyErr_Format(PyExc_AssertionError, "expected %zu bits for field at index %zu but got %zu", scene.fieldSize(*fieldId), i, mask.size());
throw py::error_already_set{};
}
const Trade::SceneFieldType fieldType = scene.fieldType(*fieldId);
if(Trade::Implementation::isSceneFieldTypeString(fieldType)) {
PyErr_SetString(PyExc_NotImplementedError, "filtering string fields is not implemented yet, sorry");
throw py::error_already_set{};
}
if(fieldType == Trade::SceneFieldType::Bit) {
PyErr_SetString(PyExc_NotImplementedError, "filtering bit fields is not implemented yet, sorry");
throw py::error_already_set{};
}
}
/** @todo check field sharing as well to avoid an assertion --
make an internal helper in SceneTools or some such, it makes no
sense to duplicate the whole logic here */
return SceneTools::filterFieldEntries(scene, entriesToKeep);
}, "Filter individual entries of named fields in a scene", py::arg("scene"), py::arg("entries_to_keep"))
.def("filter_field_entries", [](const Trade::SceneData& scene, const std::vector<std::pair<UnsignedInt, Containers::BitArrayView>> entriesToKeepStl) {
const auto entriesToKeep = Containers::arrayCast<const Containers::Pair<UnsignedInt, Containers::BitArrayView>>(Containers::arrayView(entriesToKeepStl));
Containers::BitArray usedFields{ValueInit, scene.fieldCount()};
for(std::size_t i = 0; i != entriesToKeep.size(); ++i) {
const UnsignedInt fieldId = entriesToKeep[i].first();
if(fieldId >= scene.fieldCount()) {
PyErr_Format(PyExc_AssertionError, "index %u out of range for %u fields", fieldId, scene.fieldCount());
throw py::error_already_set{};
}
if(usedFields[fieldId]) {
PyErr_Format(PyExc_AssertionError, "field %u listed more than once", fieldId);
throw py::error_already_set{};
}
usedFields.set(fieldId);
const Containers::BitArrayView mask = entriesToKeep[i].second();
if(mask.size() != scene.fieldSize(fieldId)) {
PyErr_Format(PyExc_AssertionError, "expected %zu bits for field %u but got %zu", scene.fieldSize(fieldId), fieldId, mask.size());
throw py::error_already_set{};
}
const Trade::SceneFieldType fieldType = scene.fieldType(fieldId);
if(Trade::Implementation::isSceneFieldTypeString(fieldType)) {
PyErr_SetString(PyExc_NotImplementedError, "filtering string fields is not implemented yet, sorry");
throw py::error_already_set{};
}
if(fieldType == Trade::SceneFieldType::Bit) {
PyErr_SetString(PyExc_NotImplementedError, "filtering bit fields is not implemented yet, sorry");
throw py::error_already_set{};
}
}
/** @todo check field sharing as well to avoid an assertion --
make an internal helper in SceneTools or some such, it makes no
sense to duplicate the whole logic here */
return SceneTools::filterFieldEntries(scene, entriesToKeep);
}, "Filter individual entries of fields in a scene", py::arg("scene"), py::arg("entries_to_keep"))
.def("absolute_field_transformations2d", [](const Trade::SceneData& scene, Trade::SceneField field, const Matrix3& globalTransformation) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(field);
if(!fieldId) {

106
src/python/magnum/test/test_scenetools.py

@ -23,6 +23,7 @@
# DEALINGS IN THE SOFTWARE.
#
import os
import sys
import unittest
@ -168,6 +169,111 @@ class Filter(unittest.TestCase):
del filtered2
self.assertEqual(sys.getrefcount(scene), scene_refcount)
def test_field_entries(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), "scene.gltf"))
scene = importer.scene(0)
scene_refcount = sys.getrefcount(scene)
self.assertEqual(scene.field_count, 8)
self.assertEqual(scene.field_size(trade.SceneField.PARENT), 4)
self.assertEqual(scene.field_size(trade.SceneField.IMPORTER_STATE), 4)
self.assertEqual(scene.field_size(trade.SceneField.TRANSFORMATION), 4)
self.assertEqual(scene.field_size(trade.SceneField.CAMERA), 2)
# Remove two parents (and importer state, which is linked), one camera
# and all but one transformation
parents_to_keep = containers.BitArray.direct_init(scene.field_size(trade.SceneField.PARENT), True)
parents_to_keep[0] = False
parents_to_keep[2] = False
transformations_to_keep = containers.BitArray.direct_init(scene.field_size(trade.SceneField.TRANSFORMATION), False)
transformations_to_keep[3] = True
cameras_to_keep = containers.BitArray.direct_init(scene.field_size(trade.SceneField.CAMERA), True)
cameras_to_keep[1] = False
filtered1 = scenetools.filter_field_entries(scene, [
(trade.SceneField.PARENT, parents_to_keep),
(trade.SceneField.IMPORTER_STATE, parents_to_keep),
(trade.SceneField.TRANSFORMATION, transformations_to_keep),
(trade.SceneField.CAMERA, cameras_to_keep)
])
filtered2 = scenetools.filter_field_entries(scene, [
(scene.field_id(trade.SceneField.PARENT), parents_to_keep),
(scene.field_id(trade.SceneField.IMPORTER_STATE), parents_to_keep),
(scene.field_id(trade.SceneField.TRANSFORMATION), transformations_to_keep),
(scene.field_id(trade.SceneField.CAMERA), cameras_to_keep)
])
self.assertEqual(filtered1.field_count, 8)
self.assertEqual(filtered2.field_count, 8)
self.assertEqual(filtered1.field_size(trade.SceneField.PARENT), 2)
self.assertEqual(filtered2.field_size(trade.SceneField.PARENT), 2)
self.assertEqual(filtered1.field_size(trade.SceneField.TRANSFORMATION), 1)
self.assertEqual(filtered2.field_size(trade.SceneField.TRANSFORMATION), 1)
self.assertEqual(filtered1.field_size(trade.SceneField.CAMERA), 1)
self.assertEqual(filtered2.field_size(trade.SceneField.CAMERA), 1)
# The original scene isn't referenced by these, it's a full copy
self.assertEqual(sys.getrefcount(scene), scene_refcount)
def test_field_entries_invalid(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), "scene.gltf"))
scene = importer.scene(0)
scene_refcount = sys.getrefcount(scene)
self.assertEqual(scene.field_count, 8)
with self.assertRaisesRegex(AssertionError, "index 8 out of range for 8 fields"):
scenetools.filter_field_entries(scene, [
(8, containers.BitArrayView())
])
with self.assertRaisesRegex(AssertionError, "field at index 1 not found"):
scenetools.filter_field_entries(scene, [
(trade.SceneField.CAMERA, containers.BitArray.value_init(2)),
(trade.SceneField.LIGHT, containers.BitArrayView())
])
with self.assertRaisesRegex(AssertionError, "field at index 2 listed more than once"):
scenetools.filter_field_entries(scene, [
(trade.SceneField.CAMERA, containers.BitArray.value_init(2)),
(trade.SceneField.TRANSLATION, containers.BitArray.value_init(3)),
(trade.SceneField.CAMERA, containers.BitArray.value_init(2))
])
with self.assertRaisesRegex(AssertionError, "field 4 listed more than once"):
scenetools.filter_field_entries(scene, [
(scene.field_id(trade.SceneField.CAMERA), containers.BitArray.value_init(2)),
(scene.field_id(trade.SceneField.TRANSLATION), containers.BitArray.value_init(3)),
(scene.field_id(trade.SceneField.CAMERA), containers.BitArray.value_init(2))
])
with self.assertRaisesRegex(AssertionError, "expected 3 bits for field 3 but got 4"):
scenetools.filter_field_entries(scene, [
(scene.field_id(trade.SceneField.TRANSLATION), containers.BitArray.value_init(4))
])
with self.assertRaisesRegex(AssertionError, "expected 3 bits for field at index 1 but got 4"):
scenetools.filter_field_entries(scene, [
(trade.SceneField.CAMERA, containers.BitArray.value_init(2)),
(trade.SceneField.TRANSLATION, containers.BitArray.value_init(4))
])
with self.assertRaisesRegex(NotImplementedError, "filtering string fields is not implemented yet, sorry"):
scenetools.filter_field_entries(scene, [
(scene.field_id(importer.scene_field_for_name('aString')), containers.BitArray.value_init(1))
])
with self.assertRaisesRegex(NotImplementedError, "filtering string fields is not implemented yet, sorry"):
scenetools.filter_field_entries(scene, [
(importer.scene_field_for_name('aString'), containers.BitArray.value_init(1))
])
with self.assertRaisesRegex(NotImplementedError, "filtering bit fields is not implemented yet, sorry"):
scenetools.filter_field_entries(scene, [
(scene.field_id(importer.scene_field_for_name('yes')), containers.BitArray.value_init(2))
])
with self.assertRaisesRegex(NotImplementedError, "filtering bit fields is not implemented yet, sorry"):
scenetools.filter_field_entries(scene, [
(importer.scene_field_for_name('yes'), containers.BitArray.value_init(2))
])
class Hierarchy(unittest.TestCase):
def test_absolute_field_transformations2d(self):
# Static builds with non-static plugins cause assertions with non-owned

Loading…
Cancel
Save