diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index 3f593ec..0d904b7 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -210,6 +210,40 @@ >>> attribute.custom_value 17 +.. py:class:: magnum.trade.SceneData + + :TODO: remove this line once m.css stops ignoring first caption on a page + + `Index and attribute data access`_ + ================================== + + The class makes use of Python's dynamic nature and provides direct access + to index and attribute data in their concrete types via :ref:`mapping()` + and :ref:`field()`. The returned views point to the underlying scene data, + element access coverts to a type corresponding to a particular + :ref:`SceneFieldType` and for performance-oriented access the view + implements a buffer protocol with a corresponding type annotation: + + .. + >>> import os + >>> from magnum import trade + >>> importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + >>> importer.open_file('../../src/python/magnum/test/scene.gltf') + + .. code:: pycon + + >>> scene = importer.scene(0) + >>> list(scene.mapping(trade.SceneField.TRANSLATION)) + [1, 3, 0] + >>> list(scene.field(trade.SceneField.TRANSLATION)) + [Vector(1, 2, 3), Vector(4, 5, 6), Vector(7, 8, 9)] + >>> np.array(scene.field(trade.SceneField.TRANSLATION), copy=False)[1] + array([4., 5., 6.], dtype=float32) + + Depending on the value of :ref:`data_flags` it's also possible to access + the data in a mutable way via :ref:`mutable_mapping()` and + :ref:`mutable_field()`. + .. py:function:: magnum.trade.SceneData.field_name :raise IndexError: If :p:`id` is negative or not less than :ref:`field_count` diff --git a/src/Magnum/StridedArrayViewPythonBindings.h b/src/Magnum/StridedArrayViewPythonBindings.h index c5925e2..6394781 100644 --- a/src/Magnum/StridedArrayViewPythonBindings.h +++ b/src/Magnum/StridedArrayViewPythonBindings.h @@ -61,6 +61,49 @@ _c(Vector4us, "4H") _c(Vector4s, "4h") _c(Vector4ui, "4I") _c(Vector4i, "4i") + +_c(Matrix2x2, "4f") +_c(Matrix2x2d, "4d") +_c(Matrix2x3, "6f") +_c(Matrix2x3d, "6d") +_c(Matrix2x4, "8f") +_c(Matrix2x4d, "8d") +_c(Matrix3x2, "6f") +_c(Matrix3x2d, "6d") +_c(Matrix3x3, "9f") +_c(Matrix3x3d, "9d") +_c(Matrix3x4, "12f") +_c(Matrix3x4d, "12d") +_c(Matrix4x2, "8f") +_c(Matrix4x2d, "8d") +_c(Matrix4x3, "12f") +_c(Matrix4x3d, "12d") +_c(Matrix4x4, "16f") +_c(Matrix4x4d, "16d") + +_c(Range1D, "2f") +_c(Range1Dd, "2d") +_c(Range1Di, "2i") +_c(Range2D, "4f") +_c(Range2Dd, "4d") +_c(Range2Di, "4i") +_c(Range3D, "6f") +_c(Range3Dd, "6d") +_c(Range3Di, "6i") + +_c(Complex, "2f") +_c(Complexd, "2d") +_c(DualComplex, "4f") +_c(DualComplexd, "4f") +_c(Quaternion, "4f") +_c(Quaterniond, "4d") +_c(DualQuaternion, "8f") +_c(DualQuaterniond, "8d") + +_c(Deg, "f") +_c(Degd, "d") +_c(Rad, "f") +_c(Radd, "d") #undef _c } diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index e8f3331..b0993c5 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -700,6 +700,188 @@ class SceneData(unittest.TestCase): # some fields self.assertEqual(scene.field_array_size(trade.SceneField.TRANSLATION), 0) + def test_mapping_access(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) + translation_id = scene.field_id(trade.SceneField.TRANSLATION) + + translations = scene.mapping(translation_id) + self.assertEqual(translations.size, (3, )) + self.assertEqual(translations.stride, (4, )) + self.assertEqual(translations.format, 'I') + self.assertEqual(list(translations), [1, 3, 0]) + self.assertIs(translations.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del translations + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + cameras = scene.mapping(trade.SceneField.CAMERA) + self.assertEqual(cameras.size, (2, )) + self.assertEqual(cameras.stride, (4, )) + self.assertEqual(cameras.format, 'I') + self.assertEqual(list(cameras), [2, 3]) + self.assertIs(cameras.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del cameras + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + mutable_translations = scene.mutable_mapping(translation_id) + self.assertEqual(mutable_translations.size, (3, )) + self.assertEqual(mutable_translations.stride, (4, )) + self.assertEqual(mutable_translations.format, 'I') + self.assertEqual(list(mutable_translations), [1, 3, 0]) + self.assertIs(mutable_translations.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del mutable_translations + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + mutable_cameras = scene.mutable_mapping(trade.SceneField.CAMERA) + self.assertEqual(mutable_cameras.size, (2, )) + self.assertEqual(mutable_cameras.stride, (4, )) + self.assertEqual(mutable_cameras.format, 'I') + self.assertEqual(list(mutable_cameras), [2, 3]) + self.assertIs(mutable_cameras.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del mutable_cameras + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + def test_field_access(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) + translation_id = scene.field_id(trade.SceneField.TRANSLATION) + + translations = scene.field(translation_id) + self.assertEqual(translations.size, (3, )) + self.assertEqual(translations.stride, (12, )) + self.assertEqual(translations.format, '3f') + self.assertEqual(list(translations), [ + Vector3(1, 2, 3), + Vector3(4, 5, 6), + Vector3(7, 8, 9) + ]) + self.assertIs(translations.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del translations + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + cameras = scene.field(trade.SceneField.CAMERA) + self.assertEqual(cameras.size, (2, )) + self.assertEqual(cameras.stride, (4, )) + self.assertEqual(cameras.format, 'I') + self.assertEqual(list(cameras), [1, 0]) + self.assertIs(cameras.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del cameras + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + mutable_translations = scene.mutable_field(translation_id) + self.assertEqual(mutable_translations.size, (3, )) + self.assertEqual(mutable_translations.stride, (12, )) + self.assertEqual(mutable_translations.format, '3f') + self.assertEqual(list(mutable_translations), [ + Vector3(1, 2, 3), + Vector3(4, 5, 6), + Vector3(7, 8, 9) + ]) + self.assertIs(mutable_translations.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del mutable_translations + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + mutable_cameras = scene.mutable_field(trade.SceneField.CAMERA) + self.assertEqual(mutable_cameras.size, (2, )) + self.assertEqual(mutable_cameras.stride, (4, )) + self.assertEqual(mutable_cameras.format, 'I') + self.assertEqual(list(mutable_cameras), [1, 0]) + self.assertIs(mutable_cameras.owner, scene) + self.assertEqual(sys.getrefcount(scene), scene_refcount + 1) + + del mutable_cameras + self.assertEqual(sys.getrefcount(scene), scene_refcount) + + def test_mutable_mapping_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) + + scene = importer.scene(0) + self.assertEqual(scene.data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + translation_id = scene.field_id(trade.SceneField.TRANSLATION) + + translations = scene.mapping(translation_id) + mutable_translations = scene.mutable_mapping(translation_id) + self.assertEqual(translations[1], 3) + self.assertEqual(mutable_translations[1], 3) + + mutable_translations[1] = 776 + self.assertEqual(translations[1], 776) + + cameras = scene.mapping(trade.SceneField.CAMERA) + mutable_cameras = scene.mutable_mapping(trade.SceneField.CAMERA) + self.assertEqual(cameras[1], 3) + self.assertEqual(mutable_cameras[1], 3) + + mutable_cameras[1] = 13378 + self.assertEqual(cameras[1], 13378) + + def test_mutable_field_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) + + scene = importer.scene(0) + self.assertEqual(scene.data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + translation_id = scene.field_id(trade.SceneField.TRANSLATION) + + translations = scene.field(translation_id) + mutable_translations = scene.mutable_field(translation_id) + self.assertEqual(translations[1], Vector3(4, 5, 6)) + self.assertEqual(mutable_translations[1], Vector3(4, 5, 6)) + + mutable_translations[1] *= 0.5 + self.assertEqual(translations[1], Vector3(2, 2.5, 3)) + + cameras = scene.field(trade.SceneField.CAMERA) + mutable_cameras = scene.mutable_field(trade.SceneField.CAMERA) + self.assertEqual(cameras[1], 0) + self.assertEqual(mutable_cameras[1], 0) + + mutable_cameras[1] = 13378 + self.assertEqual(cameras[1], 13378) + + def test_pointer_field_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) + + scene = importer.scene(0) + self.assertEqual(scene.data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + + pointer = scene.field(trade.SceneField.IMPORTER_STATE) + mutable_pointer = scene.mutable_field(trade.SceneField.IMPORTER_STATE) + self.assertEqual(pointer.format, 'P') + self.assertEqual(mutable_pointer.format, 'P') + self.assertNotEqual(pointer[1], 0x0) + self.assertEqual(mutable_pointer[1], pointer[1]) + + mutable_pointer[1] = 0xdeadbeef + self.assertEqual(pointer[1], 0xdeadbeef) + + def test_data_access_not_mutable(self): + pass + # TODO implement once there's a way to get immutable SceneData, either + # by "deserializing" a binary blob or via some SceneTools API + def test_field_oob(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) @@ -721,6 +903,14 @@ class SceneData(unittest.TestCase): scene.has_field_object(scene.field_count, 0) with self.assertRaisesRegex(IndexError, "field out of range"): scene.field_object_offset(scene.field_count, 0) + with self.assertRaises(IndexError): + scene.mapping(scene.field_count) + with self.assertRaises(IndexError): + scene.mutable_mapping(scene.field_count) + with self.assertRaises(IndexError): + scene.field(scene.field_count) + with self.assertRaises(IndexError): + scene.mutable_field(scene.field_count) # Access by nonexistent field name with self.assertRaises(KeyError): @@ -737,6 +927,14 @@ class SceneData(unittest.TestCase): scene.has_field_object(trade.SceneField.SCALING, 0) with self.assertRaises(KeyError): scene.field_object_offset(trade.SceneField.SCALING, 0) + with self.assertRaises(KeyError): + scene.mapping(trade.SceneField.SCALING) + with self.assertRaises(KeyError): + scene.mutable_mapping(trade.SceneField.SCALING) + with self.assertRaises(KeyError): + scene.field(trade.SceneField.SCALING) + with self.assertRaises(KeyError): + scene.mutable_field(trade.SceneField.SCALING) # OOB object ID with self.assertRaisesRegex(IndexError, "object out of range"): @@ -766,6 +964,29 @@ class SceneData(unittest.TestCase): with self.assertRaisesRegex(IndexError, "offset out of range"): scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT) + 1) + def test_field_access_array(self): + pass + # TODO implement once there's some importer that gives back arrays + # (gltf? not sure) + + def test_field_access_unsupported_type(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) + string_field = importer.scene_field_for_name('aString') + self.assertIsNotNone(string_field) + + scene = importer.scene(0) + string_field_id = scene.field_id(string_field) + + with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + scene.field(string_field_id) + with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + scene.mutable_field(string_field_id) + with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + scene.field(string_field) + with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + scene.mutable_field(string_field) + class Importer(unittest.TestCase): def test(self): manager = trade.ImporterManager() diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 7d2be8d..aba9209 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -29,8 +29,13 @@ #include /** @todo drop once we have our string casters */ #include #include +#include +#include #include #include +#include +#include +#include #include #include #include @@ -584,6 +589,201 @@ template Containers::PyArrayViewHolder{data.template transposed<0, 1>()[0], formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(mesh)); } +Containers::Triple accessorsForSceneMappingType(const Trade::SceneMappingType type) { + switch(type) { + #define _c(type) \ + case Trade::SceneMappingType::type: return { \ + Containers::Implementation::pythonFormatString(), \ + [](const char* item) { \ + return py::cast(*reinterpret_cast(item)); \ + }, \ + [](char* item, py::handle object) { \ + *reinterpret_cast(item) = py::cast(object); \ + }}; + /* LCOV_EXCL_START */ + _c(UnsignedByte) + _c(UnsignedShort) + _c(UnsignedInt) + _c(UnsignedLong) + /* LCOV_EXCL_STOP */ + #undef _c + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Triple accessorsForSceneFieldType(const Trade::SceneFieldType type) { + switch(type) { + #define _ctf(typeEnum, type, format) \ + case Trade::SceneFieldType::typeEnum: return { \ + format, \ + [](const char* item) { \ + return py::cast(*reinterpret_cast(item)); \ + }, \ + [](char* item, py::handle object) { \ + *reinterpret_cast(item) = py::cast(object); \ + }}; + #define _c(type) _ctf(type, type, Containers::Implementation::pythonFormatString()) + /* Types (such as half-floats) that need to be cast before passed + from/to pybind that doesn't understand the type directly */ + #define _cc(type, castType) \ + case Trade::SceneFieldType::type: return { \ + Containers::Implementation::pythonFormatString(), \ + [](const char* item) { \ + return py::cast(castType(*reinterpret_cast(item))); \ + }, \ + [](char* item, py::handle object) { \ + *reinterpret_cast(item) = type(py::cast(object)); \ + }}; + /* LCOV_EXCL_START */ + _c(Float) + _c(Double) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + + _c(Vector2) + _c(Vector2d) + _cc(Vector2ub, Vector2ui) + _cc(Vector2b, Vector2i) + _cc(Vector2us, Vector2ui) + _cc(Vector2s, Vector2i) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3d) + _cc(Vector3ub, Vector3ui) + _cc(Vector3b, Vector3i) + _cc(Vector3us, Vector3ui) + _cc(Vector3s, Vector3i) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4d) + _cc(Vector4ub, Vector4ui) + _cc(Vector4b, Vector4i) + _cc(Vector4us, Vector4ui) + _cc(Vector4s, Vector4i) + _c(Vector4ui) + _c(Vector4i) + + _c(Matrix2x2) + _c(Matrix2x2d) + _c(Matrix2x3) + _c(Matrix2x3d) + _c(Matrix2x4) + _c(Matrix2x4d) + _c(Matrix3x2) + _c(Matrix3x2d) + _c(Matrix3x3) + _c(Matrix3x3d) + _c(Matrix3x4) + _c(Matrix3x4d) + _c(Matrix4x2) + _c(Matrix4x2d) + _c(Matrix4x3) + _c(Matrix4x3d) + _c(Matrix4x4) + _c(Matrix4x4d) + + _c(Range1D) + _c(Range1Dd) + _c(Range1Di) + _c(Range2D) + _c(Range2Dd) + _c(Range2Di) + _c(Range3D) + _c(Range3Dd) + _c(Range3Di) + + _c(Complex) + _c(Complexd) + _c(DualComplex) + _c(DualComplexd) + _c(Quaternion) + _c(Quaterniond) + _c(DualQuaternion) + _c(DualQuaterniond) + + _c(Deg) + _c(Degd) + _c(Rad) + _c(Radd) + + /* I see very little reason for accessing these from Python but + nevertheless, one never knows when it will be useful */ + /** @todo passing them through as void* makes them a capsule object in + Python, is that useful for anything? and the P type is useless for + numpy ("'P' is not a valid PEP 3118 buffer format string") */ + _ctf(Pointer, std::size_t, "P") + _ctf(MutablePointer, std::size_t, "P") + /* LCOV_EXCL_STOP */ + #undef _c + #undef _cc + + /** @todo handle these once there's something to test with */ + case Trade::SceneFieldType::Half: + case Trade::SceneFieldType::Vector2h: + case Trade::SceneFieldType::Vector3h: + case Trade::SceneFieldType::Vector4h: + case Trade::SceneFieldType::Matrix2x2h: + case Trade::SceneFieldType::Matrix2x3h: + case Trade::SceneFieldType::Matrix2x4h: + case Trade::SceneFieldType::Matrix3x2h: + case Trade::SceneFieldType::Matrix3x3h: + case Trade::SceneFieldType::Matrix3x4h: + case Trade::SceneFieldType::Matrix4x2h: + case Trade::SceneFieldType::Matrix4x3h: + case Trade::SceneFieldType::Matrix4x4h: + case Trade::SceneFieldType::Range1Dh: + case Trade::SceneFieldType::Range2Dh: + case Trade::SceneFieldType::Range3Dh: + case Trade::SceneFieldType::Degh: + case Trade::SceneFieldType::Radh: + /** @todo handle these once StringIterable is exposed */ + case Trade::SceneFieldType::StringOffset8: + case Trade::SceneFieldType::StringOffset16: + case Trade::SceneFieldType::StringOffset32: + case Trade::SceneFieldType::StringOffset64: + case Trade::SceneFieldType::StringRange8: + case Trade::SceneFieldType::StringRange16: + case Trade::SceneFieldType::StringRange32: + case Trade::SceneFieldType::StringRange64: + case Trade::SceneFieldType::StringRangeNullTerminated8: + case Trade::SceneFieldType::StringRangeNullTerminated16: + case Trade::SceneFieldType::StringRangeNullTerminated32: + case Trade::SceneFieldType::StringRangeNullTerminated64: + return {}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +template Containers::PyArrayViewHolder> sceneMappingView(Trade::SceneData& scene, const Containers::StridedArrayView2D& data) { + const Trade::SceneMappingType type = scene.mappingType(); + const std::size_t itemsize = Trade::sceneMappingTypeSize(type); + const Containers::Triple formatStringGetitemSetitem = accessorsForSceneMappingType(type); + /* We support all mapping types */ + CORRADE_INTERNAL_ASSERT(formatStringGetitemSetitem.first()); + return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<1, T>{data.template transposed<0, 1>()[0], formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(scene)); +} + +template Containers::PyArrayViewHolder> sceneFieldView(Trade::SceneData& scene, const UnsignedInt id, const Containers::StridedArrayView2D& data) { + const Trade::SceneFieldType type = scene.fieldType(id); + const std::size_t itemsize = Trade::sceneFieldTypeSize(type); + const Containers::Triple formatStringGetitemSetitem = accessorsForSceneFieldType(type); + if(!formatStringGetitemSetitem.first()) { + PyErr_SetString(PyExc_NotImplementedError, "access to this scene field type is not implemented yet, sorry"); + throw py::error_already_set{}; + } + return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<1, T>{data.template transposed<0, 1>()[0], formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(scene)); +} + } void trade(py::module_& m) { @@ -982,6 +1182,11 @@ void trade(py::module_& m) { corrade::enumOperators(sceneFieldFlag); py::class_{m, "SceneData", "Scene data"} + .def_property_readonly("data_flags", [](Trade::SceneData& self) { + return Trade::DataFlag(Containers::enumCastUnderlyingType(self.dataFlags())); + }, "Data flags") + /** @todo expose raw data at all? compared to meshes there's no use + case like being able to pass raw memory to the GPU ... yet */ .def_property_readonly("mapping_type", &Trade::SceneData::mappingType, "Type used for object mapping") .def_property_readonly("mapping_bound", &Trade::SceneData::mappingBound, "Object mapping bound") .def_property_readonly("field_count", &Trade::SceneData::fieldCount, "Field count") @@ -1131,7 +1336,107 @@ void trade(py::module_& m) { throw py::error_already_set{}; } return self.hasFieldObject(fieldId, object); - }, "Whether a scene field has given object", py::arg("field_id"), py::arg("object")); + }, "Whether a scene field has given object", py::arg("field_id"), py::arg("object")) + .def("mapping", [](Trade::SceneData& self, Trade::SceneField name) { + const Containers::Optional found = self.findFieldId(name); + if(!found) { + PyErr_SetNone(PyExc_KeyError); + throw py::error_already_set{}; + } + return sceneMappingView(self, self.mapping(*found)); + }, "Object mapping data for given named field", py::arg("name")) + .def("mapping", [](Trade::SceneData& self, UnsignedInt id) { + if(id >= self.fieldCount()) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + return sceneMappingView(self, self.mapping(id)); + }, "Object mapping data for given field", py::arg("name")) + .def("mutable_mapping", [](Trade::SceneData& self, Trade::SceneField name) { + const Containers::Optional found = self.findFieldId(name); + if(!found) { + PyErr_SetNone(PyExc_KeyError); + throw py::error_already_set{}; + } + if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { + PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); + throw py::error_already_set{}; + } + return sceneMappingView(self, self.mutableMapping(*found)); + }, "Mutable object mapping data for given named field", py::arg("name")) + .def("mutable_mapping", [](Trade::SceneData& self, UnsignedInt id) { + if(id >= self.fieldCount()) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { + PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); + throw py::error_already_set{}; + } + return sceneMappingView(self, self.mutableMapping(id)); + }, "Mutable object mapping data for given field", py::arg("name")) + .def("field", [](Trade::SceneData& self, Trade::SceneField name) { + const Containers::Optional found = self.findFieldId(name); + if(!found) { + PyErr_SetNone(PyExc_KeyError); + throw py::error_already_set{}; + } + /** @todo handle arrays (return a 2D view, and especially annotate + the return type properly in the docs) */ + if(self.fieldArraySize(*found) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); + throw py::error_already_set{}; + } + return sceneFieldView(self, *found, self.field(*found)); + }, "Data for given named field", py::arg("name")) + .def("field", [](Trade::SceneData& self, UnsignedInt id) { + if(id >= self.fieldCount()) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + /** @todo handle arrays (return a 2D view, and especially annotate + the return type properly in the docs) */ + if(self.fieldArraySize(id) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); + throw py::error_already_set{}; + } + return sceneFieldView(self, id, self.field(id)); + }, "Data for given field", py::arg("name")) + .def("mutable_field", [](Trade::SceneData& self, Trade::SceneField name) { + const Containers::Optional found = self.findFieldId(name); + if(!found) { + PyErr_SetNone(PyExc_KeyError); + throw py::error_already_set{}; + } + if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { + PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); + throw py::error_already_set{}; + } + /** @todo handle arrays (return a 2D view, and especially annotate + the return type properly in the docs) */ + if(self.fieldArraySize(*found) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); + throw py::error_already_set{}; + } + return sceneFieldView(self, *found, self.mutableField(*found)); + }, "Mutable data for given named field", py::arg("name")) + .def("mutable_field", [](Trade::SceneData& self, UnsignedInt id) { + if(id >= self.fieldCount()) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { + PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); + throw py::error_already_set{}; + } + /** @todo handle arrays (return a 2D view, and especially annotate + the return type properly in the docs) */ + if(self.fieldArraySize(id) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); + throw py::error_already_set{}; + } + return sceneFieldView(self, id, self.mutableField(id)); + }, "Mutable data for given field", py::arg("name")); /* Importer. Skipping file callbacks and openState as those operate with void*. Leaving the name as AbstractImporter (instead of Importer) to