From 513fbc547d3f1c7272bfb4eebc52ede53b143246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 17 Mar 2023 12:31:46 +0100 Subject: [PATCH] python: expose trade.TextureData and related importer APIs. --- doc/python/magnum.trade.rst | 16 ++++++++ src/python/magnum/test/image.gltf | 15 ------- src/python/magnum/test/test_trade.py | 60 ++++++++++++++++++++++++++-- src/python/magnum/test/texture.gltf | 36 +++++++++++++++++ src/python/magnum/trade.cpp | 30 ++++++++++++++ 5 files changed, 139 insertions(+), 18 deletions(-) delete mode 100644 src/python/magnum/test/image.gltf create mode 100644 src/python/magnum/test/texture.gltf diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index a30e05a..a0647b4 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -402,6 +402,22 @@ :raise IndexError: If :p:`id` is negative or not less than :ref:`mesh_count` :raise KeyError: If :p:`name` was not found +.. py:property:: magnum.trade.AbstractImporter.texture_count + :raise AssertionError: If no file is opened +.. py:function:: magnum.trade.AbstractImporter.texture_for_name + :raise AssertionError: If no file is opened +.. py:function:: magnum.trade.AbstractImporter.texture_name + :raise AssertionError: If no file is opened + :raise IndexError: If :p:`id` is negative or not less than :ref:`texture_count` + +.. TODO this needs distinction by parameter names, at least + +.. py:function:: magnum.trade.AbstractImporter.texture + :raise AssertionError: If no file is opened + :raise RuntimeError: If texture import fails + :raise IndexError: If :p:`id` is negative or not less than :ref:`texture_count` + :raise KeyError: If :p:`name` was not found + .. py:property:: magnum.trade.AbstractImporter.image1d_count :raise AssertionError: If no file is opened .. py:property:: magnum.trade.AbstractImporter.image2d_count diff --git a/src/python/magnum/test/image.gltf b/src/python/magnum/test/image.gltf deleted file mode 100644 index 4190e09..0000000 --- a/src/python/magnum/test/image.gltf +++ /dev/null @@ -1,15 +0,0 @@ -{ - "asset": { - "version": "2.0" - }, - "images": [ - { - "uri": "nonexistent.foo", - "name": "A broken image" - }, - { - "uri": "rgb.png", - "name": "A named image" - } - ] -} diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index f910aea..ce7e77c 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -66,6 +66,8 @@ class ImageData(unittest.TestCase): image.pixel_size with self.assertRaisesRegex(AttributeError, "image is compressed"): image.pixels + with self.assertRaisesRegex(AttributeError, "image is compressed"): + image.mutable_pixels def test_convert_view(self): # The only way to get an image instance is through a manager @@ -1044,6 +1046,19 @@ class SceneData(unittest.TestCase): with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): scene.mutable_field(string_field) +class SceneData(unittest.TestCase): + def test(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) + + texture = importer.texture("A texture") + self.assertEqual(texture.type, trade.TextureType.TEXTURE2D) + self.assertEqual(texture.minification_filter, SamplerFilter.NEAREST) + self.assertEqual(texture.magnification_filter, SamplerFilter.LINEAR) + self.assertEqual(texture.mipmap_filter, SamplerMipmap.NEAREST) + self.assertEqual(texture.wrapping, (SamplerWrapping.MIRRORED_REPEAT, SamplerWrapping.CLAMP_TO_EDGE, SamplerWrapping.REPEAT)) + self.assertEqual(texture.image, 1) + class Importer(unittest.TestCase): def test_manager(self): manager = trade.ImporterManager() @@ -1232,6 +1247,17 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(AssertionError, "no file opened"): importer.mesh('') + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.texture_count + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.texture_for_name('') + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.texture_name(0) + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.texture(0) + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.texture('') + with self.assertRaisesRegex(AssertionError, "no file opened"): importer.image1d_count with self.assertRaisesRegex(AssertionError, "no file opened"): @@ -1287,6 +1313,11 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(IndexError, "ID out of bounds"): importer.mesh(0) + with self.assertRaises(IndexError): + importer.texture_name(0) + with self.assertRaises(IndexError): + importer.texture(0) + with self.assertRaises(IndexError): importer.image1d_level_count(0) with self.assertRaises(IndexError): @@ -1438,6 +1469,29 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "import failed"): importer.mesh('A broken mesh') + def test_texture_by_name(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) + + texture = importer.texture("A texture") + self.assertEqual(texture.image, 1) + + def test_texture_by_name_not_found(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) + + with self.assertRaises(KeyError): + importer.texture('Nonexistent') + + def test_texture_failed(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) + + with self.assertRaisesRegex(RuntimeError, "import failed"): + importer.texture(1) + with self.assertRaisesRegex(RuntimeError, "import failed"): + importer.texture("A broken texture") + def test_image2d(self): manager = trade.ImporterManager() manager_refcount = sys.getrefcount(manager) @@ -1471,14 +1525,14 @@ class Importer(unittest.TestCase): def test_image2d_by_name(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') - importer.open_file(os.path.join(os.path.dirname(__file__), 'image.gltf')) + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) image = importer.image2d('A named image') self.assertEqual(image.size, Vector2i(3, 2)) def test_image2d_by_name_not_found(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') - importer.open_file(os.path.join(os.path.dirname(__file__), 'image.gltf')) + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) with self.assertRaises(KeyError): importer.image2d('Nonexistent') @@ -1494,7 +1548,7 @@ class Importer(unittest.TestCase): def test_image2d_failed(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') - importer.open_file(os.path.join(os.path.dirname(__file__), 'image.gltf')) + importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) with self.assertRaisesRegex(RuntimeError, "import failed"): importer.image2d(0) diff --git a/src/python/magnum/test/texture.gltf b/src/python/magnum/test/texture.gltf new file mode 100644 index 0000000..c5837b0 --- /dev/null +++ b/src/python/magnum/test/texture.gltf @@ -0,0 +1,36 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "nonexistent.foo", + "name": "A broken image" + }, + { + "uri": "rgb.png", + "name": "A named image" + } + ], + "samplers": [ + { + "wrapS": 33648, + "wrapT": 33071, + "minFilter": 9984, + "magFilter": 9729 + } + ], + "textures": [ + { + "source": 0 + }, + { + "name": "A broken texture" + }, + { + "name": "A texture", + "sampler": 0, + "source": 1 + } + ] +} diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 3b9804f..751c97b 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "Corrade/Containers/PythonBindings.h" #include "Corrade/Containers/OptionalPythonBindings.h" @@ -1453,6 +1454,29 @@ void trade(py::module_& m) { return sceneFieldView(self, id, self.mutableField(id)); }, "Mutable data for given field", py::arg("name")); + py::enum_{m, "TextureType", "Texture type"} + .value("TEXTURE1D", Trade::TextureType::Texture1D) + .value("TEXTURE1D_ARRAY", Trade::TextureType::Texture1DArray) + .value("TEXTURE2D", Trade::TextureType::Texture2D) + .value("TEXTURE2D_ARRAY", Trade::TextureType::Texture2DArray) + .value("TEXTURE3D", Trade::TextureType::Texture3D) + .value("CUBE_MAP", Trade::TextureType::CubeMap) + .value("CUBE_MAP_ARRAY", Trade::TextureType::CubeMapArray); + + py::class_{m, "TextureData", "Texture data"} + .def_property_readonly("type", &Trade::TextureData::type, "Texture type") + .def_property_readonly("minification_filter", &Trade::TextureData::minificationFilter, "Minification filter") + .def_property_readonly("magnification_filter", &Trade::TextureData::magnificationFilter, "Magnification filter") + .def_property_readonly("mipmap_filter", &Trade::TextureData::mipmapFilter, "Mipmap filter") + .def_property_readonly("wrapping", [](Trade::TextureData& self) { + return std::make_tuple( + self.wrapping()[0], + self.wrapping()[1], + self.wrapping()[2] + ); + }, "Wrapping") + .def_property_readonly("image", &Trade::TextureData::image, "Image ID"); + /* Importer. Skipping file callbacks and openState as those operate with void*. Leaving the name as AbstractImporter (instead of Importer) to avoid needless name differences and because in the future there *might* @@ -1555,6 +1579,12 @@ void trade(py::module_& m) { return {}; }, "String name for given custom mesh attribute", py::arg("name")) + .def_property_readonly("texture_count", checkOpened, "Texture count") + .def("texture_for_name", checkOpenedString, "Texture ID for given name", py::arg("name")) + .def("texture_name", checkOpenedBoundsReturnsString, "Texture name", py::arg("id")) + .def("texture", checkOpenedBoundsResult, "Texture", py::arg("id")) + .def("texture", checkOpenedBoundsResultString, "Texture for given name", py::arg("name")) + .def_property_readonly("image1d_count", checkOpened, "One-dimensional image count") .def_property_readonly("image2d_count", checkOpened, "Two-dimensional image count") .def_property_readonly("image3d_count", checkOpened, "Three-dimensional image count")