From 211bd5f0a8693c98561a937d0b93fe977ddcb62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 7 Feb 2023 23:23:57 +0100 Subject: [PATCH] python: mutable access to trade.MeshData raw index/vertex data. Not much useful on its own, but it's a prep for what comes next. --- doc/python/magnum.trade.rst | 6 +++ src/python/magnum/test/test_trade.py | 81 +++++++++++++++++++++++++++- src/python/magnum/trade.cpp | 28 ++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index 579745d..0d19de7 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -72,6 +72,12 @@ :dox:`Trade::MeshData::findAttributeId()`, the desired workflow is instead calling :ref:`attribute_id()` and catching an exception if not found. +.. py:property:: magnum.trade.MeshData.mutable_index_data + :raise AttributeError: If :ref:`index_data_flags` doesn't contain + :ref:`DataFlag.MUTABLE` +.. py:property:: magnum.trade.MeshData.mutable_vertex_data + :raise AttributeError: If :ref:`vertex_data_flags` doesn't contain + :ref:`DataFlag.MUTABLE` .. py:property:: magnum.trade.MeshData.index_count :raise AttributeError: If :ref:`is_indexed` is :py:`False` .. py:property:: magnum.trade.MeshData.index_type diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index 5262aef..1dc0921 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -30,7 +30,7 @@ import unittest from corrade import pluginmanager from magnum import * -from magnum import trade +from magnum import primitives, trade class ImageData(unittest.TestCase): def test(self): @@ -105,8 +105,9 @@ class MeshData(unittest.TestCase): importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) mesh = importer.mesh(0) - mesh_refcount = sys.getrefcount(mesh) self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) + self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + self.assertEqual(mesh.vertex_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) # Index properties self.assertTrue(mesh.is_indexed) @@ -155,6 +156,13 @@ class MeshData(unittest.TestCase): self.assertEqual(mesh.attribute_array_size(trade.MeshAttribute.POSITION), 0) self.assertEqual(mesh.attribute_array_size(trade.MeshAttribute.WEIGHTS), 4) + def test_index_data_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + + mesh = importer.mesh(0) + mesh_refcount = sys.getrefcount(mesh) + index_data = mesh.index_data self.assertEqual(len(index_data), 6) self.assertIs(index_data.owner, mesh) @@ -163,6 +171,21 @@ class MeshData(unittest.TestCase): del index_data self.assertEqual(sys.getrefcount(mesh), mesh_refcount) + mutable_index_data = mesh.mutable_index_data + self.assertEqual(len(mutable_index_data), 6) + self.assertIs(mutable_index_data.owner, mesh) + self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1) + + del mutable_index_data + self.assertEqual(sys.getrefcount(mesh), mesh_refcount) + + def test_vertex_data_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + + mesh = importer.mesh(0) + mesh_refcount = sys.getrefcount(mesh) + vertex_data = mesh.vertex_data self.assertEqual(len(vertex_data), 84) self.assertIs(vertex_data.owner, mesh) @@ -171,6 +194,60 @@ class MeshData(unittest.TestCase): del vertex_data self.assertEqual(sys.getrefcount(mesh), mesh_refcount) + mutable_vertex_data = mesh.mutable_vertex_data + self.assertEqual(len(mutable_vertex_data), 84) + self.assertIs(mutable_vertex_data.owner, mesh) + self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1) + + del mutable_vertex_data + self.assertEqual(sys.getrefcount(mesh), mesh_refcount) + + def test_mutable_index_data_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + + mesh = importer.mesh(0) + self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + + index_data = mesh.index_data + mutable_index_data = mesh.mutable_index_data + # Second index is 2, it's a 16-bit LE number + # TODO: ugh, report as bytes, not chars + self.assertEqual(ord(index_data[2]), 2) + self.assertEqual(ord(mutable_index_data[2]), 2) + + mutable_index_data[2] = chr(76) + self.assertEqual(ord(index_data[2]), 76) + + def test_mutable_vertex_data_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + + mesh = importer.mesh(0) + self.assertEqual(mesh.vertex_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + + vertex_data = mesh.vertex_data + mutable_vertex_data = mesh.mutable_vertex_data + # The color attribute is at offset 20, G channel is the next byte + # TODO: ugh, report as bytes, not chars + self.assertEqual(ord(vertex_data[21]), 51) + self.assertEqual(ord(mutable_vertex_data[21]), 51) + + mutable_vertex_data[21] = chr(76) + self.assertEqual(vertex_data[21], chr(76)) + + def test_data_access_not_mutable(self): + mesh = primitives.cube_solid() + # TODO split this once there's a mesh where only one or the other would + # be true (maybe with zero-copy loading of PLYs / STLs?) + self.assertEqual(mesh.index_data_flags, trade.DataFlag(0)) + self.assertEqual(mesh.vertex_data_flags, trade.DataFlag(0)) + + with self.assertRaisesRegex(AttributeError, "mesh index data is not mutable"): + mesh.mutable_index_data + with self.assertRaisesRegex(AttributeError, "mesh vertex data is not mutable"): + mesh.mutable_vertex_data + def test_nonindexed(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index c604741..8d8bf2c 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -39,6 +39,7 @@ #include "Corrade/Containers/StridedArrayViewPythonBindings.h" #include "Magnum/PythonBindings.h" +#include "corrade/EnumOperators.h" #include "corrade/pluginmanager.h" #include "magnum/bootstrap.h" @@ -306,6 +307,13 @@ void trade(py::module_& m) { /* AbstractImporter depends on this */ py::module_::import("corrade.pluginmanager"); + py::enum_ dataFlag{m, "DataFlag", "Data flag"}; + dataFlag + .value("OWNED", Trade::DataFlag::Owned) + .value("EXTERNALLY_OWNED", Trade::DataFlag::ExternallyOwned) + .value("MUTABLE", Trade::DataFlag::Mutable); + corrade::enumOperators(dataFlag); + py::enum_{m, "MeshAttribute", "Mesh attribute name"} .value("POSITION", Trade::MeshAttribute::Position) .value("TANGENT", Trade::MeshAttribute::Tangent) @@ -319,14 +327,34 @@ void trade(py::module_& m) { py::class_{m, "MeshData", "Mesh data"} .def_property_readonly("primitive", &Trade::MeshData::primitive, "Primitive") + .def_property_readonly("index_data_flags", [](Trade::MeshData& self) { + return Trade::DataFlag(Containers::enumCastUnderlyingType(self.indexDataFlags())); + }, "Index data flags") + .def_property_readonly("vertex_data_flags", [](Trade::MeshData& self) { + return Trade::DataFlag(Containers::enumCastUnderlyingType(self.vertexDataFlags())); + }, "Vertex data flags") .def_property_readonly("index_data", [](Trade::MeshData& self) { return Containers::pyArrayViewHolder(self.indexData(), py::cast(self)); }, "Raw index data") + .def_property_readonly("mutable_index_data", [](Trade::MeshData& self) { + if(!(self.indexDataFlags() & Trade::DataFlag::Mutable)) { + PyErr_SetString(PyExc_AttributeError, "mesh index data is not mutable"); + throw py::error_already_set{}; + } + return Containers::pyArrayViewHolder(self.mutableIndexData(), py::cast(self)); + }, "Mutable raw index data") /** @todo direct access to MeshAttributeData, once making custom MeshData is desired */ .def_property_readonly("vertex_data", [](Trade::MeshData& self) { return Containers::pyArrayViewHolder(self.vertexData(), py::cast(self)); }, "Raw vertex data") + .def_property_readonly("mutable_vertex_data", [](Trade::MeshData& self) { + if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) { + PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable"); + throw py::error_already_set{}; + } + return Containers::pyArrayViewHolder(self.mutableVertexData(), py::cast(self)); + }, "Mutable raw vertex data") .def_property_readonly("is_indexed", &Trade::MeshData::isIndexed, "Whether the mesh is indexed") .def_property_readonly("index_count", [](Trade::MeshData& self) { if(!self.isIndexed()) {