diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index d5dba1c..42b282b 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -106,10 +106,14 @@ .. py:function:: magnum.trade.AbstractImporter.mesh_name :raise AssertionError: If no file is opened :raise IndexError: If :p:`id` is negative or not less than :ref:`mesh_count` + +.. TODO this needs distinction by parameter names, at least + .. py:function:: magnum.trade.AbstractImporter.mesh :raise AssertionError: If no file is opened :raise RuntimeError: If mesh import fails :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.image1d_count :raise AssertionError: If no file is opened @@ -151,21 +155,26 @@ :raise IndexError: If :p:`id` is negative or not less than :ref:`image3d_count` +.. TODO this needs distinction by parameter names, at least + .. py:function:: magnum.trade.AbstractImporter.image1d :raise AssertionError: If no file is opened :raise RuntimeError: If image import fails :raise IndexError: If :p:`id` is negative or not less than :ref:`image1d_count` + :raise KeyError: If :p:`name` was not found .. py:function:: magnum.trade.AbstractImporter.image2d :raise AssertionError: If no file is opened :raise RuntimeError: If image import fails :raise IndexError: If :p:`id` is negative or not less than :ref:`image2d_count` + :raise KeyError: If :p:`name` was not found .. py:function:: magnum.trade.AbstractImporter.image3d :raise AssertionError: If no file is opened :raise RuntimeError: If image import fails :raise IndexError: If :p:`id` is negative or not less than :ref:`image3d_count` + :raise KeyError: If :p:`name` was not found .. py:class:: magnum.trade.ImageConverterManager :summary: Manager for :ref:`AbstractImageConverter` plugin instances diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index caa00bd..25faa06 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -227,6 +227,27 @@ class Importer(unittest.TestCase): with self.assertRaises(IndexError): importer.mesh(0, 1) + def test_mesh_by_name(self): + importer = trade.ImporterManager().load_and_instantiate('CgltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb')) + + mesh = importer.mesh('Non-indexed mesh') + self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) + + def test_mesh_by_name_not_found(self): + importer = trade.ImporterManager().load_and_instantiate('CgltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb')) + + with self.assertRaises(KeyError): + importer.mesh('Nonexistent') + + def test_mesh_by_name_level_oob(self): + importer = trade.ImporterManager().load_and_instantiate('CgltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb')) + + with self.assertRaises(IndexError): + importer.mesh('Non-indexed mesh', 1) + def test_image2d(self): manager = trade.ImporterManager() manager_refcount = sys.getrefcount(manager) @@ -250,6 +271,8 @@ class Importer(unittest.TestCase): del importer self.assertEqual(sys.getrefcount(manager), manager_refcount) + # TODO image by name (in some gltf?) + def test_image_level_oob(self): # importer refcounting tested in image2d importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 0d474cd..cc790d3 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -242,6 +242,34 @@ template(Trade::AbstractImporter::*f)(UnsignedI return *std::move(out); } +/** @todo drop std::string in favor of our own string caster */ +template(Trade::AbstractImporter::*f)(UnsignedInt, UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView), UnsignedInt(Trade::AbstractImporter::*levelBounds)(UnsignedInt)> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name, UnsignedInt level) { + if(!self.isOpened()) { + PyErr_SetString(PyExc_AssertionError, "no file opened"); + throw py::error_already_set{}; + } + + const Int id = (self.*indexForName)(name); + if(id == -1) { + PyErr_SetNone(PyExc_KeyError); + throw py::error_already_set{}; + } + + if(level >= (self.*levelBounds)(id)) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + + /** @todo log redirection -- but we'd need assertions to not be part of + that so when it dies, the user can still see why */ + Containers::Optional out = (self.*f)(id, level); + if(!out) { + PyErr_SetString(PyExc_RuntimeError, "import failed"); + throw py::error_already_set{}; + } + + return *std::move(out); +} /* Can't be named just checkResult() because the AbstractSceneConverter overload would confuse GCC 4.8 */ @@ -325,8 +353,8 @@ void trade(py::module_& m) { .def("mesh_for_name", checkOpenedString, "Mesh ID for given name") .def("mesh_name", checkOpenedBoundsReturnsString<&Trade::AbstractImporter::meshName, &Trade::AbstractImporter::meshCount>, "Mesh name", py::arg("id")) .def("mesh", checkOpenedBoundsResult, "Mesh", py::arg("id"), py::arg("level") = 0) + .def("mesh", checkOpenedBoundsResultString, "Mesh", py::arg("name"), py::arg("level") = 0) /** @todo mesh_attribute_for_name / mesh_attribute_name */ - /** @todo access by name, not just name -> id resolving */ .def_property_readonly("image1d_count", checkOpened, "One-dimensional image count") .def_property_readonly("image2d_count", checkOpened, "Two-dimensional image count") @@ -341,8 +369,11 @@ void trade(py::module_& m) { .def("image2d_name", checkOpenedBoundsReturnsString<&Trade::AbstractImporter::image2DName, &Trade::AbstractImporter::image2DCount>, "Two-dimensional image name", py::arg("id")) .def("image3d_name", checkOpenedBoundsReturnsString< &Trade::AbstractImporter::image3DName, &Trade::AbstractImporter::image3DCount>, "Three-dimensional image name", py::arg("id")) .def("image1d", checkOpenedBoundsResult, "One-dimensional image", py::arg("id"), py::arg("level") = 0) + .def("image1d", checkOpenedBoundsResultString, "One-dimensional image", py::arg("name"), py::arg("level") = 0) .def("image2d", checkOpenedBoundsResult, "Two-dimensional image", py::arg("id"), py::arg("level") = 0) - .def("image3d", checkOpenedBoundsResult, "Three-dimensional image", py::arg("id"), py::arg("level") = 0); + .def("image2d", checkOpenedBoundsResultString, "Two-dimensional image", py::arg("name"), py::arg("level") = 0) + .def("image3d", checkOpenedBoundsResult, "Three-dimensional image", py::arg("id"), py::arg("level") = 0) + .def("image3d", checkOpenedBoundsResultString, "Threee-dimensional image", py::arg("name"), py::arg("level") = 0); py::class_, PluginManager::AbstractManager> importerManager{m, "ImporterManager", "Manager for importer plugins"}; corrade::manager(importerManager);