From cdfd79571f5857a622c7a752521f978dbc567a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Mar 2023 21:44:11 +0100 Subject: [PATCH] python: expose mesh-to-mesh and in-place mesh conversion. Needs to fetch MeshOptimizer on the CIs for testing this as there's no other plugin with these features right now. --- doc/python/magnum.trade.rst | 8 +++- package/ci/appveyor-desktop-gles.bat | 1 + package/ci/appveyor-desktop.bat | 4 ++ package/ci/appveyor.yml | 6 +++ package/ci/circleci.yml | 28 +++++++++++++- package/ci/unix-desktop-gles.sh | 1 + package/ci/unix-desktop.sh | 1 + src/python/magnum/test/test_trade.py | 58 +++++++++++++++++++++++++++- src/python/magnum/trade.cpp | 18 +++++++++ 9 files changed, 121 insertions(+), 4 deletions(-) diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index ad43a12..1a51564 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -500,8 +500,14 @@ raising an exception. See particular function documentation for detailed behavior. +.. py:function:: magnum.trade.AbstractSceneConverter.convert + :raise RuntimeError: If conversion fails + +.. py:function:: magnum.trade.AbstractSceneConverter.convert_in_place + :raise RuntimeError: If conversion fails + .. py:function:: magnum.trade.AbstractSceneConverter.convert_to_file - :raise RuntimeError: If scene conversion fails + :raise RuntimeError: If conversion fails For compatibility with :ref:`os.path`, on Windows this function converts all backslashes in :p:`filename` to forward slashes before passing it to diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index 1df4ee0..3a354b3 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -78,6 +78,7 @@ cmake .. ^ -DMAGNUM_BUILD_STATIC=%BUILD_STATIC% ^ -DMAGNUM_WITH_DDSIMPORTER=ON ^ -DMAGNUM_WITH_GLTFIMPORTER=ON ^ + -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=ON ^ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON ^ -DMAGNUM_WITH_STBIMAGECONVERTER=ON ^ -DMAGNUM_WITH_STBIMAGEIMPORTER=ON ^ diff --git a/package/ci/appveyor-desktop.bat b/package/ci/appveyor-desktop.bat index 81eaf49..663b61e 100644 --- a/package/ci/appveyor-desktop.bat +++ b/package/ci/appveyor-desktop.bat @@ -12,6 +12,9 @@ rem currently disabled -- https://github.com/catchorg/Catch2/issues/1113 if "%COMPILER%" == "msvc-clang" if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2022" set COMPILER_EXTRA=-DCMAKE_CXX_COMPILER="C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/Llvm/bin/clang-cl.exe" -DCMAKE_LINKER="C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/Llvm/bin/lld-link.exe" -DCMAKE_CXX_FLAGS="-m64 /EHsc" if "%COMPILER%" == "msvc-clang" if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2019" set COMPILER_EXTRA=-DCMAKE_CXX_COMPILER="C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/Llvm/bin/clang-cl.exe" -DCMAKE_LINKER="C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/Llvm/bin/lld-link.exe" -DCMAKE_CXX_FLAGS="-m64 /EHsc" +set EXCEPT_MSVC2017=ON +IF "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" set EXCEPT_MSVC2017=OFF + rem Build pybind11. Downloaded in the appveyor.yml script. cd pybind11-%PYBIND% || exit /b mkdir -p build && cd build || exit /b @@ -85,6 +88,7 @@ cmake .. ^ -DMAGNUM_BUILD_STATIC=%BUILD_STATIC% ^ -DMAGNUM_WITH_DDSIMPORTER=ON ^ -DMAGNUM_WITH_GLTFIMPORTER=ON ^ + -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=%EXCEPT_MSVC2017% ^ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON ^ -DMAGNUM_WITH_STBIMAGECONVERTER=ON ^ -DMAGNUM_WITH_STBIMAGEIMPORTER=ON ^ diff --git a/package/ci/appveyor.yml b/package/ci/appveyor.yml index dce87ec..faaa37d 100644 --- a/package/ci/appveyor.yml +++ b/package/ci/appveyor.yml @@ -123,6 +123,12 @@ install: - IF "%TARGET%" == "desktop" 7z x glfw-3.2.1.bin.WIN64.zip && ren glfw-3.2.1.bin.WIN64 glfw && mkdir deps && mkdir deps\lib && mkdir deps\bin && mkdir deps\include && xcopy /e glfw\include\* deps\include\ - IF "%TARGET%" == "desktop" IF "%COMPILER:~0,4%" == "msvc" copy glfw\lib-vc2015\glfw3.dll deps\bin\ && copy glfw\lib-vc2015\glfw3dll.lib deps\lib\glfw3.lib +# meshoptimizer for MSVC 2022, 2019 and clang-cl; MinGW. MSVC 2017 doesn't work +# with the 2019 build unfortunately, and can't build it because of +# https://github.com/actions/runner-images/issues/3294 +- IF "%COMPILER:~0,4%" == "msvc" appveyor DownloadFile https://ci.magnum.graphics/meshoptimizer-0.18-windows-2019-debug.zip && 7z x meshoptimizer-0.18-windows-2019-debug.zip -o%APPVEYOR_BUILD_FOLDER%\deps +- IF "%COMPILER%" == "mingw" appveyor DownloadFile https://ci.magnum.graphics/meshoptimizer-0.18-windows-mingw.zip && 7z x meshoptimizer-0.18-windows-mingw.zip -o%APPVEYOR_BUILD_FOLDER%\deps + build_script: - IF "%TARGET%" == "desktop" IF "%COMPILER:~0,4%" == "msvc" call package\ci\appveyor-desktop.bat - IF "%TARGET%" == "desktop-gles" call package\ci\appveyor-desktop-gles.bat diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index fbe5408..fcc0a60 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -128,6 +128,23 @@ commands: wget https://ci.magnum.graphics/swiftshader-gles-r5464.a6940c8e6e-<< parameters.build >>.zip unzip swiftshader-gles-r5464.a6940c8e6e-<< parameters.build >>.zip + install-meshoptimizer: + steps: + - run: + name: Install meshoptimizer + # few commits after 0.14 with a fix for old Apple Clang + command: | + export MESHOPTIMIZER_VERSION=97c52415c6d29f297a76482ddde22f739292446d + mkdir -p $HOME/meshoptimizer && cd $HOME/meshoptimizer + wget -nc --no-check-certificate https://github.com/zeux/meshoptimizer/archive/$MESHOPTIMIZER_VERSION.tar.gz + tar --strip-components=1 -xzf $MESHOPTIMIZER_VERSION.tar.gz + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX=$HOME/deps \ + -G Ninja + ninja install + build: parameters: script: @@ -172,6 +189,7 @@ jobs: - install-cmake-3_4 - install-python-3_6 - install-pybind11 + - install-meshoptimizer - build: script: unix-desktop.sh - lcov @@ -193,6 +211,7 @@ jobs: - install-cmake-3_4 - install-python-3_6 - install-pybind11 + - install-meshoptimizer - install-swiftshader-gles: build: ubuntu-16.04 - build: @@ -216,6 +235,7 @@ jobs: - install-cmake-3_4 - install-python-3_6 - install-pybind11 + - install-meshoptimizer - install-swiftshader-gles: build: ubuntu-16.04 - build: @@ -240,6 +260,7 @@ jobs: - install-cmake-3_4 - install-python-3_6 - install-pybind11 + - install-meshoptimizer - build: script: unix-desktop.sh - lcov @@ -251,7 +272,8 @@ jobs: PLATFORM_GL_API: CGL steps: - install-base-macos: - extra: sdl2 glfw numpy pybind11 + extra: sdl2 glfw wget numpy pybind11 + - install-meshoptimizer - build: script: unix-desktop.sh - lcov @@ -268,6 +290,7 @@ jobs: extra: sdl2 glfw wget numpy pybind11 - install-swiftshader-gles: build: macos-10.15 + - install-meshoptimizer - build: script: unix-desktop-gles.sh - lcov @@ -281,7 +304,8 @@ jobs: PLATFORM_GL_API: CGL steps: - install-base-macos: - extra: sdl2 glfw numpy pybind11 + extra: sdl2 glfw wget numpy pybind11 + - install-meshoptimizer - build: script: unix-desktop.sh - lcov diff --git a/package/ci/unix-desktop-gles.sh b/package/ci/unix-desktop-gles.sh index 5275293..8f98ff5 100755 --- a/package/ci/unix-desktop-gles.sh +++ b/package/ci/unix-desktop-gles.sh @@ -65,6 +65,7 @@ cmake .. \ -DMAGNUM_BUILD_STATIC=$BUILD_STATIC \ -DMAGNUM_WITH_DDSIMPORTER=ON \ -DMAGNUM_WITH_GLTFIMPORTER=ON \ + -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=ON \ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON \ -DMAGNUM_WITH_STBIMAGECONVERTER=ON \ -DMAGNUM_WITH_STBIMAGEIMPORTER=ON \ diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index 72c3979..e97eb2e 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -69,6 +69,7 @@ cmake .. \ -DMAGNUM_BUILD_STATIC=$BUILD_STATIC \ -DMAGNUM_WITH_DDSIMPORTER=ON \ -DMAGNUM_WITH_GLTFIMPORTER=ON \ + -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=ON \ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON \ -DMAGNUM_WITH_STBIMAGECONVERTER=ON \ -DMAGNUM_WITH_STBIMAGEIMPORTER=ON \ diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index 0285cfd..1363560 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -1466,6 +1466,62 @@ class SceneConverter(unittest.TestCase): self.assertEqual(converter.flags, trade.SceneConverterFlags.VERBOSE) def test_mesh(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + mesh = importer.mesh('Indexed mesh') + + converter_manager = converter = trade.SceneConverterManager() + if 'MeshOptimizerSceneConverter' not in converter_manager.plugin_list: + self.skipTest("MeshOptimizerSceneConverter plugin not available") + + converter = trade.SceneConverterManager().load_and_instantiate('MeshOptimizerSceneConverter') + + converted_mesh = converter.convert(mesh) + self.assertEqual(converted_mesh.index_count, mesh.index_count) + + def test_mesh_failed(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + mesh = importer.mesh('Non-indexed mesh') + + converter_manager = converter = trade.SceneConverterManager() + if 'MeshOptimizerSceneConverter' not in converter_manager.plugin_list: + self.skipTest("MeshOptimizerSceneConverter plugin not available") + + converter = trade.SceneConverterManager().load_and_instantiate('MeshOptimizerSceneConverter') + + with self.assertRaisesRegex(RuntimeError, "conversion failed"): + converted_mesh = converter.convert(mesh) + + def test_mesh_in_place(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + mesh = importer.mesh('Indexed mesh') + + converter_manager = converter = trade.SceneConverterManager() + if 'MeshOptimizerSceneConverter' not in converter_manager.plugin_list: + self.skipTest("MeshOptimizerSceneConverter plugin not available") + + converter = trade.SceneConverterManager().load_and_instantiate('MeshOptimizerSceneConverter') + + converter.convert_in_place(mesh) + self.assertEqual(mesh.index_count, 3) + + def test_mesh_in_place_failed(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + mesh = importer.mesh('Non-indexed mesh') + + converter_manager = converter = trade.SceneConverterManager() + if 'MeshOptimizerSceneConverter' not in converter_manager.plugin_list: + self.skipTest("MeshOptimizerSceneConverter plugin not available") + + converter = trade.SceneConverterManager().load_and_instantiate('MeshOptimizerSceneConverter') + + with self.assertRaisesRegex(RuntimeError, "conversion failed"): + converter.convert_in_place(mesh) + + def test_mesh_to_file(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) mesh = importer.mesh(1) @@ -1476,7 +1532,7 @@ class SceneConverter(unittest.TestCase): converter.convert_to_file(mesh, os.path.join(tmp, "mesh.ply")) self.assertTrue(os.path.exists(os.path.join(tmp, "mesh.ply"))) - def test_mesh_failed(self): + def test_mesh_to_file_failed(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) mesh = importer.mesh(1) diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 1cfebc5..262077f 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -1640,6 +1640,24 @@ void trade(py::module_& m) { }, [](Trade::AbstractSceneConverter& self, Trade::SceneConverterFlag flags) { self.setFlags(flags); }, "Converter flags") + .def("convert", [](Trade::AbstractSceneConverter& self, const Trade::MeshData& mesh) { + /** @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.convert(mesh); + if(!out) { + PyErr_SetString(PyExc_RuntimeError, "conversion failed"); + throw py::error_already_set{}; + } + return out; + }, "Convert a mesh", py::arg("mesh")) + .def("convert_in_place", [](Trade::AbstractSceneConverter& self, Trade::MeshData& mesh) { + /** @todo log redirection -- but we'd need assertions to not be + part of that so when it dies, the user can still see why */ + if(!self.convertInPlace(mesh)) { + PyErr_SetString(PyExc_RuntimeError, "conversion failed"); + throw py::error_already_set{}; + } + }, "Convert a mesh", py::arg("mesh")) /** @todo drop std::string in favor of our own string caster */ .def("convert_to_file", [](Trade::AbstractSceneConverter& self, const Trade::MeshData& mesh, const std::string& filename) { /** @todo log redirection -- but we'd need assertions to not be