/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include /** @todo drop once we have our string casters */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Corrade/Containers/PythonBindings.h" #include "Corrade/Containers/OptionalPythonBindings.h" #include "Magnum/PythonBindings.h" #include "Magnum/StridedArrayViewPythonBindings.h" #include "corrade/EnumOperators.h" #include "corrade/pluginmanager.h" #include "magnum/bootstrap.h" #ifdef CORRADE_TARGET_WINDOWS /* To allow people to conveniently use Python's os.path, we need to convert backslashes to forward slashes as all Corrade and Magnum APIs expect forward */ #include #endif namespace magnum { namespace { /* Adapted from pybind11's base_enum internals -- if enum_name returns ???, replace it with CUSTOM(id) */ template::type baseCustomValue> inline py::str enumWithCustomValuesName(const py::object& arg) { /* The enum_name helper is only since pybind11 2.6, before it's inline: https://github.com/pybind/pybind11/commit/5e6ec496522b313e34af3de91f6c0565f68e3552 */ /** @todo remove once support for < 2.6 is dropped */ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 py::str name = py::detail::enum_name(arg); #else py::str name = "???"; py::dict entries = arg.get_type().attr("__entries"); for(auto kv: entries) { if(py::handle(kv.second[py::int_(0)]).equal(arg)) { name = py::str(kv.first); break; } } #endif /* Haha what the hell is this comparison */ if(std::string{name} == "???") return py::str("CUSTOM({})").format(typename std::underlying_type::type(py::int_(arg)) - baseCustomValue); return name; } /* Not using the meshAttributeCustom() etc helpers as it would be too painful to pass them all, and I'd need to make my own handling of the OOB cases anyway */ template::type baseCustomValue> void enumWithCustomValues(py::enum_& enum_) { static_assert(!typename std::underlying_type::type(baseCustomValue << 1), "base custom value expected to be a single highest bit"); enum_ .def("CUSTOM", [](typename std::underlying_type::type value) { /* Assuming the base custom value is a single highest bit, the custom value should not have the same bit set (or, in other words, should be smaller) */ if(baseCustomValue & value) { PyErr_SetString(PyExc_ValueError, "custom value too large"); throw py::error_already_set{}; } return T(baseCustomValue + value); }) .def_property_readonly("is_custom", [](T value) { return typename std::underlying_type::type(value) >= baseCustomValue; }) .def_property_readonly("custom_value", [](T value) { if(typename std::underlying_type::type(value) < baseCustomValue) { PyErr_SetString(PyExc_AttributeError, "not a custom value"); throw py::error_already_set{}; } return typename std::underlying_type::type(value) - baseCustomValue; }); /* Adapted from pybind11's base_enum internals, just calling our customEnumName instead of py::detail::enum_name */ enum_.attr("__repr__") = py::cpp_function( [](const py::object& arg) -> py::str { py::handle type = /* handle_of(arg) is only since pybind11 2.6, before it's arg.get_type(): https://github.com/pybind/pybind11/commit/41aa92601ebce548290f6a9efcd66e64216bf972 */ /** @todo remove once support for < 2.6 is dropped */ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 py::type::handle_of(arg) #else arg.get_type() #endif ; py::object type_name = type.attr("__name__"); return py::str("<{}.{}: {}>") .format(std::move(type_name), enumWithCustomValuesName(arg), py::int_(arg)); }, py::name("__repr__"), py::is_method(enum_)); enum_.attr("name") = py::handle(reinterpret_cast(&PyProperty_Type))(py::cpp_function(&enumWithCustomValuesName, py::name("name"), py::is_method(enum_))); enum_.attr("__str__") = py::cpp_function( [](const py::object& arg) -> py::str { py::object type_name = /* handle_of(arg) is only since pybind11 2.6, before it's arg.get_type(): https://github.com/pybind/pybind11/commit/41aa92601ebce548290f6a9efcd66e64216bf972 */ /** @todo remove once support for < 2.6 is dropped */ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 py::type::handle_of(arg) #else arg.get_type() #endif .attr("__name__"); return pybind11::str("{}.{}").format(std::move(type_name), enumWithCustomValuesName(arg)); }, py::name("name"), py::is_method(enum_)); } template PyObject* implicitlyConvertibleToImageView(PyObject* obj, PyTypeObject*) { py::detail::make_caster> caster; if(!caster.load(obj, false)) { return nullptr; } Trade::ImageData& data = caster; if(data.isCompressed()) { PyErr_SetString(PyExc_RuntimeError, "image is compressed"); throw py::error_already_set{}; } auto r = pyCastButNotShitty(pyImageViewHolder(ImageView(data), py::reinterpret_borrow(obj))).release().ptr(); return r; } template void imageData(py::class_>& c) { /* Missing APIs: Dimensions */ /* These two are quite hacky attempts to bring the ImageData -> ImageView conversion operator functionality here. Using py::implicitly_convertible alone doesn't work as it only calls conversion constructors exposed to Python, and we can't expose such a thing to Python because ImageView is defined in the `magnum` module while this is `magnum.trade`, and that would mean a cyclic dependency. Instead, I took the guts of py::implicitly_convertible and instead of calling into Python I'm calling the C++ conversion operator directly myself. That alone is not enough, as this implicit conversion is only chosen if the target type has a Python-exposed constructor that takes a type that's implicitly convertible from the source type. Ugh. If this ever breaks with a pybind update, I'm probably going to reimplement this in a pure duck-typed fashion. I hope not tho. */ { auto tinfo = py::detail::get_type_info(typeid(ImageView)); CORRADE_INTERNAL_ASSERT(tinfo); tinfo->implicit_conversions.push_back(implicitlyConvertibleToImageView); } { auto tinfo = py::detail::get_type_info(typeid(ImageView)); CORRADE_INTERNAL_ASSERT(tinfo); tinfo->implicit_conversions.push_back(implicitlyConvertibleToImageView); } c /* There are no constructors at the moment --- expecting those types get only created by importers. (It would also need the Array type and movability figured out, postponing that to later.) */ /* Properties */ .def_property_readonly("is_compressed", &Trade::ImageData::isCompressed, "Whether the image is compressed") .def_property_readonly("storage", [](Trade::ImageData& self) { if(self.isCompressed()) { PyErr_SetString(PyExc_AttributeError, "image is compressed"); throw py::error_already_set{}; } return self.storage(); }, "Storage of pixel data") .def_property_readonly("format", [](Trade::ImageData& self) { if(self.isCompressed()) { PyErr_SetString(PyExc_AttributeError, "image is compressed"); throw py::error_already_set{}; } return self.format(); }, "Format of pixel data") .def_property_readonly("pixel_size", [](Trade::ImageData& self) { if(self.isCompressed()) { PyErr_SetString(PyExc_AttributeError, "image is compressed"); throw py::error_already_set{}; } return self.pixelSize(); }, "Pixel size (in bytes)") .def_property_readonly("size", [](Trade::ImageData& self) { return PyDimensionTraits::from(self.size()); }, "Image size") .def_property_readonly("data", [](Trade::ImageData& self) { return Containers::pyArrayViewHolder(self.data(), py::cast(self)); }, "Raw image data") .def_property_readonly("pixels", [](Trade::ImageData& self) { if(self.isCompressed()) { PyErr_SetString(PyExc_AttributeError, "image is compressed"); throw py::error_already_set{}; } return Containers::pyArrayViewHolder(Containers::PyStridedArrayView{self.pixels()}, py::cast(self)); }, "View on pixel data"); } /* For some reason having ...Args as the second (and not last) template argument does not work. So I'm listing all variants here ... which are exactly two, in fact. */ template R checkOpened(Trade::AbstractImporter& self) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } return (self.*f)(); } template R checkOpened(Trade::AbstractImporter& self, Arg1 arg1) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } return (self.*f)(arg1); } /** @todo drop this in favor of our own string caster */ template R checkOpenedString(Trade::AbstractImporter& self, const std::string& arg1) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } return (self.*f)(arg1); } template R checkOpenedBounds(Trade::AbstractImporter& self, UnsignedInt id) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } if(id >= (self.*bounds)()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return (self.*f)(id); } /** @todo drop this in favor of our own string caster */ template std::string checkOpenedBoundsReturnsString(Trade::AbstractImporter& self, R id) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } if(id >= (self.*bounds)()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return (self.*f)(id); } template(Trade::AbstractImporter::*f)(UnsignedInt), UnsignedInt(Trade::AbstractImporter::*bounds)() const> R checkOpenedBoundsResult(Trade::AbstractImporter& self, UnsignedInt id) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } if(id >= (self.*bounds)()) { 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); if(!out) { PyErr_SetString(PyExc_RuntimeError, "import failed"); throw py::error_already_set{}; } return *std::move(out); } /** @todo drop std::string in favor of our own string caster */ template(Trade::AbstractImporter::*f)(UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView)> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name) { 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{}; } /** @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); if(!out) { PyErr_SetString(PyExc_RuntimeError, "import failed"); throw py::error_already_set{}; } return *std::move(out); } template(Trade::AbstractImporter::*f)(UnsignedInt, UnsignedInt), UnsignedInt(Trade::AbstractImporter::*bounds)() const, UnsignedInt(Trade::AbstractImporter::*levelBounds)(UnsignedInt)> R checkOpenedBoundsResult(Trade::AbstractImporter& self, UnsignedInt id, UnsignedInt level) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; } if(id >= (self.*bounds)()) { PyErr_SetString(PyExc_IndexError, "ID out of bounds"); throw py::error_already_set{}; } if(level >= (self.*levelBounds)(id)) { PyErr_SetString(PyExc_IndexError, "level out of bounds"); 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); } /** @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_SetString(PyExc_IndexError, "level out of bounds"); 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 */ /** @todo drop std::string in favor of our own string caster */ template void checkImageConverterResult(Trade::AbstractImageConverter& self, const T& image, const std::string& filename) { /** @todo log redirection -- but we'd need assertions to not be part of that so when it dies, the user can still see why */ bool out = (self.*f)(image, #ifdef CORRADE_TARGET_WINDOWS /* To allow people to conveniently use Python's os.path, we need to convert backslashes to forward slashes as all Corrade and Magnum APIs expect forward */ Utility::Path::fromNativeSeparators(filename) #else filename #endif ); if(!out) { PyErr_SetString(PyExc_RuntimeError, "conversion failed"); throw py::error_already_set{}; } } Containers::Triple accessorsForMeshIndexType(const MeshIndexType type) { switch(type) { #define _c(type) \ case MeshIndexType::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) /* LCOV_EXCL_STOP */ #undef _c } CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } Containers::Triple accessorsForVertexFormat(const VertexFormat format) { switch(format) { #define _c(format) \ case VertexFormat::format: return { \ Containers::Implementation::pythonFormatString(), \ [](const char* item) { \ return py::cast(*reinterpret_cast(item)); \ }, \ [](char* item, py::handle object) { \ *reinterpret_cast(item) = py::cast(object); \ }}; /* Types (such as half-floats) that need to be cast before passed from/to pybind that doesn't understand the type directly */ #define _cc(format, castType) \ case VertexFormat::format: return { \ Containers::Implementation::pythonFormatString(), \ [](const char* item) { \ return py::cast(castType(*reinterpret_cast(item))); \ }, \ [](char* item, py::handle object) { \ *reinterpret_cast(item) = format(py::cast(object)); \ }}; /* Normalized types that need to be packed/unpacked before passed from/to pybind */ #define _cNormalized(format, unpackType) \ case VertexFormat::format ## Normalized: return { \ Containers::Implementation::pythonFormatString(), \ [](const char* item) { \ return py::cast(Math::unpack(*reinterpret_cast(item))); \ }, \ [](char* item, py::handle object) { \ *reinterpret_cast(item) = Math::pack(py::cast(object)); \ }}; /* LCOV_EXCL_START */ _c(Float) _c(Double) _c(UnsignedByte) _cNormalized(UnsignedByte, Float) _c(Byte) _cNormalized(Byte, Float) _c(UnsignedShort) _cNormalized(UnsignedShort, Float) _c(Short) _cNormalized(Short, Float) _c(UnsignedInt) _c(Int) _c(Vector2) _c(Vector2d) _cc(Vector2ub, Vector2ui) _cNormalized(Vector2ub, Vector2) _cc(Vector2b, Vector2i) _cNormalized(Vector2b, Vector2) _cc(Vector2us, Vector2ui) _cNormalized(Vector2us, Vector2) _cc(Vector2s, Vector2i) _cNormalized(Vector2s, Vector2) _c(Vector2ui) _c(Vector2i) _c(Vector3) _c(Vector3d) _cc(Vector3ub, Vector3ui) _cNormalized(Vector3ub, Vector3) _cc(Vector3b, Vector3i) _cNormalized(Vector3b, Vector3) _cc(Vector3us, Vector3ui) _cNormalized(Vector3us, Vector3) _cc(Vector3s, Vector3i) _cNormalized(Vector3s, Vector3) _c(Vector3ui) _c(Vector3i) _c(Vector4) _c(Vector4d) _cc(Vector4ub, Vector4ui) _cNormalized(Vector4ub, Vector4) _cc(Vector4b, Vector4i) _cNormalized(Vector4b, Vector4) _cc(Vector4us, Vector4ui) _cNormalized(Vector4us, Vector4) _cc(Vector4s, Vector4i) _cNormalized(Vector4s, Vector4) _c(Vector4ui) _c(Vector4i) /* LCOV_EXCL_STOP */ #undef _c #undef _cc #undef _cNormalized /** @todo handle half and matrix types */ default: return {}; } } template Containers::PyArrayViewHolder> meshIndicesView(Trade::MeshData& mesh, const Containers::StridedArrayView2D& data) { const MeshIndexType type = mesh.indexType(); const std::size_t itemsize = meshIndexTypeSize(type); const Containers::Triple formatStringGetitemSetitem = accessorsForMeshIndexType(type); /** @todo update this once there are plugins that can give back custom index 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(mesh)); } template Containers::PyArrayViewHolder> meshAttributeView(Trade::MeshData& mesh, const UnsignedInt id, const Containers::StridedArrayView2D& data) { const VertexFormat format = mesh.attributeFormat(id); const std::size_t itemsize = vertexFormatSize(format); const Containers::Triple formatStringGetitemSetitem = accessorsForVertexFormat(format); if(!formatStringGetitemSetitem.first()) { PyErr_SetString(PyExc_NotImplementedError, "access to this vertex format 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(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 this once StridedBitArrayView is exposed */ case Trade::SceneFieldType::Bit: /** @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) { m.doc() = "Data format exchange"; /* AbstractImporter depends on this */ py::module_::import("corrade.pluginmanager"); py::enum_ dataFlag{m, "DataFlags", "Data flags"}; dataFlag .value("OWNED", Trade::DataFlag::Owned) .value("EXTERNALLY_OWNED", Trade::DataFlag::ExternallyOwned) .value("MUTABLE", Trade::DataFlag::Mutable) .value("NONE", Trade::DataFlag{}); corrade::enumOperators(dataFlag); py::enum_ meshAttribute{m, "MeshAttribute", "Mesh attribute name"}; meshAttribute .value("POSITION", Trade::MeshAttribute::Position) .value("TANGENT", Trade::MeshAttribute::Tangent) .value("BITANGENT", Trade::MeshAttribute::Bitangent) .value("NORMAL", Trade::MeshAttribute::Normal) .value("TEXTURE_COORDINATES", Trade::MeshAttribute::TextureCoordinates) .value("COLOR", Trade::MeshAttribute::Color) .value("JOINT_IDS", Trade::MeshAttribute::JointIds) .value("WEIGHTS", Trade::MeshAttribute::Weights) .value("OBJECT_ID", Trade::MeshAttribute::ObjectId); enumWithCustomValues(meshAttribute); 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()) { PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); throw py::error_already_set{}; } return self.indexCount(); }, "Index count") .def_property_readonly("index_type", [](Trade::MeshData& self) { if(!self.isIndexed()) { PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); throw py::error_already_set{}; } return self.indexType(); }, "Index type") .def_property_readonly("index_offset", [](Trade::MeshData& self) { if(!self.isIndexed()) { PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); throw py::error_already_set{}; } return self.indexOffset(); }, "Index offset") .def_property_readonly("index_stride", [](Trade::MeshData& self) { if(!self.isIndexed()) { PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); throw py::error_already_set{}; } return self.indexStride(); }, "Index stride") .def_property_readonly("indices", [](Trade::MeshData& self) { if(!self.isIndexed()) { PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); throw py::error_already_set{}; } return meshIndicesView(self, self.indices()); }, "Indices") .def_property_readonly("mutable_indices", [](Trade::MeshData& self) { if(!self.isIndexed()) { PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); throw py::error_already_set{}; } if(!(self.indexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "mesh index data is not mutable"); throw py::error_already_set{}; } return meshIndicesView(self, self.mutableIndices()); }, "Mutable indices") .def_property_readonly("vertex_count", &Trade::MeshData::vertexCount, "Vertex count") /* Has to be a function instead of a property because there's an overload taking a name */ .def("attribute_count", static_cast(&Trade::MeshData::attributeCount), "Attribute array count") /** @todo direct access to MeshAttributeData, once making custom MeshData is desired */ .def("has_attribute", &Trade::MeshData::hasAttribute, "Whether the mesh has given attribute", py::arg("name")) .def("attribute_count", static_cast(&Trade::MeshData::attributeCount), "Count of given named attribute", py::arg("name")) /* IMPORTANT: due to pybind11 behavioral differences on (already EOL'd) Python 3.7 the following overloads need to have the MeshAttribute overload *before* the UnsignedInt overload, otherwise the integer overload gets picked even if an enum is passed from Python, causing massive suffering */ .def("attribute_name", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.attributeName(id); }, "Attribute name", py::arg("id")) .def("attribute_id", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { if(const Containers::Optional found = self.findAttributeId(name, id)) return *found; PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; }, "Absolute ID of a named attribute", py::arg("name"), py::arg("id") = 0) .def("attribute_id", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.attributeId(id); }, "Attribute ID in a set of attributes of the same name", py::arg("id")) .def("attribute_format", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { if(const Containers::Optional found = self.findAttributeId(name, id)) return self.attributeFormat(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; }, "Format of a named attribute", py::arg("name"), py::arg("id") = 0) .def("attribute_format", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.attributeFormat(id); }, "Attribute format", py::arg("id")) .def("attribute_offset", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { if(const Containers::Optional found = self.findAttributeId(name, id)) return self.attributeOffset(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; }, "Offset of a named attribute", py::arg("name"), py::arg("id") = 0) .def("attribute_offset", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.attributeOffset(id); }, "Attribute offset", py::arg("id")) .def("attribute_stride", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { if(const Containers::Optional found = self.findAttributeId(name, id)) return self.attributeStride(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; }, "Stride of a named attribute", py::arg("name"), py::arg("id") = 0) .def("attribute_stride", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.attributeStride(id); }, "Attribute stride", py::arg("id")) .def("attribute_array_size", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { if(const Containers::Optional found = self.findAttributeId(name, id)) return self.attributeArraySize(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; }, "Array size of a named attribute", py::arg("name"), py::arg("id") = 0) .def("attribute_array_size", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.attributeArraySize(id); }, "Attribute array size", py::arg("id")) .def("attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { const Containers::Optional found = self.findAttributeId(name, id); 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.attributeArraySize(*found) != 0) { PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); throw py::error_already_set{}; } return meshAttributeView(self, *found, self.attribute(*found)); }, "Data for given named attribute", py::arg("name"), py::arg("id") = 0) .def("attribute", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { 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.attributeArraySize(id) != 0) { PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); throw py::error_already_set{}; } return meshAttributeView(self, id, self.attribute(id)); }, "Data for given attribute", py::arg("id")) .def("mutable_attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { const Containers::Optional found = self.findAttributeId(name, id); if(!found) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "mesh vertex 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.attributeArraySize(*found) != 0) { PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); throw py::error_already_set{}; } return meshAttributeView(self, *found, self.mutableAttribute(*found)); }, "Mutable data for given named attribute", py::arg("name"), py::arg("id") = 0) .def("mutable_attribute", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "mesh vertex 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.attributeArraySize(id) != 0) { PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); throw py::error_already_set{}; } return meshAttributeView(self, id, self.mutableAttribute(id)); }, "Mutable data for given attribute", py::arg("id")); py::class_ imageData1D{m, "ImageData1D", "One-dimensional image data"}; py::class_ imageData2D{m, "ImageData2D", "Two-dimensional image data"}; py::class_ imageData3D{m, "ImageData3D", "Three-dimensional image data"}; imageData(imageData1D); imageData(imageData2D); imageData(imageData3D); py::enum_{m, "SceneMappingType", "Scene object mapping type"} .value("UNSIGNED_BYTE", Trade::SceneMappingType::UnsignedByte) .value("UNSIGNED_SHORT", Trade::SceneMappingType::UnsignedShort) .value("UNSIGNED_INT", Trade::SceneMappingType::UnsignedInt) .value("UNSIGNED_LONG", Trade::SceneMappingType::UnsignedLong); py::enum_ sceneField{m, "SceneField", "Scene field name"}; sceneField .value("PARENT", Trade::SceneField::Parent) .value("TRANSFORMATION", Trade::SceneField::Transformation) .value("TRANSLATION", Trade::SceneField::Translation) .value("ROTATION", Trade::SceneField::Rotation) .value("SCALING", Trade::SceneField::Scaling) .value("MESH", Trade::SceneField::Mesh) .value("MESH_MATERIAL", Trade::SceneField::MeshMaterial) .value("LIGHT", Trade::SceneField::Light) .value("CAMERA", Trade::SceneField::Camera) .value("SKIN", Trade::SceneField::Skin) .value("IMPORTER_STATE", Trade::SceneField::ImporterState); enumWithCustomValues(sceneField); py::enum_{m, "SceneFieldType", "Scene field type"} .value("BIT", Trade::SceneFieldType::Bit) .value("FLOAT", Trade::SceneFieldType::Float) .value("HALF", Trade::SceneFieldType::Half) .value("DOUBLE", Trade::SceneFieldType::Double) .value("UNSIGNED_BYTE", Trade::SceneFieldType::UnsignedByte) .value("BYTE", Trade::SceneFieldType::Byte) .value("UNSIGNED_SHORT", Trade::SceneFieldType::UnsignedShort) .value("SHORT", Trade::SceneFieldType::Short) .value("UNSIGNED_INT", Trade::SceneFieldType::UnsignedInt) .value("INT", Trade::SceneFieldType::Int) .value("UNSIGNED_LONG", Trade::SceneFieldType::UnsignedLong) .value("LONG", Trade::SceneFieldType::Long) .value("VECTOR2", Trade::SceneFieldType::Vector2) .value("VECTOR2H", Trade::SceneFieldType::Vector2h) .value("VECTOR2D", Trade::SceneFieldType::Vector2d) .value("VECTOR2UB", Trade::SceneFieldType::Vector2ub) .value("VECTOR2B", Trade::SceneFieldType::Vector2b) .value("VECTOR2US", Trade::SceneFieldType::Vector2us) .value("VECTOR2S", Trade::SceneFieldType::Vector2s) .value("VECTOR2UI", Trade::SceneFieldType::Vector2ui) .value("VECTOR2I", Trade::SceneFieldType::Vector2i) .value("VECTOR3", Trade::SceneFieldType::Vector3) .value("VECTOR3H", Trade::SceneFieldType::Vector3h) .value("VECTOR3D", Trade::SceneFieldType::Vector3d) .value("VECTOR3UB", Trade::SceneFieldType::Vector3ub) .value("VECTOR3B", Trade::SceneFieldType::Vector3b) .value("VECTOR3US", Trade::SceneFieldType::Vector3us) .value("VECTOR3S", Trade::SceneFieldType::Vector3s) .value("VECTOR3UI", Trade::SceneFieldType::Vector3ui) .value("VECTOR3I", Trade::SceneFieldType::Vector3i) .value("VECTOR4", Trade::SceneFieldType::Vector4) .value("VECTOR4H", Trade::SceneFieldType::Vector4h) .value("VECTOR4D", Trade::SceneFieldType::Vector4d) .value("VECTOR4UB", Trade::SceneFieldType::Vector4ub) .value("VECTOR4B", Trade::SceneFieldType::Vector4b) .value("VECTOR4US", Trade::SceneFieldType::Vector4us) .value("VECTOR4S", Trade::SceneFieldType::Vector4s) .value("VECTOR4UI", Trade::SceneFieldType::Vector4ui) .value("VECTOR4I", Trade::SceneFieldType::Vector4i) .value("MATRIX2X2", Trade::SceneFieldType::Matrix2x2) .value("MATRIX2X2H", Trade::SceneFieldType::Matrix2x2h) .value("MATRIX2X2D", Trade::SceneFieldType::Matrix2x2d) .value("MATRIX2X3", Trade::SceneFieldType::Matrix2x3) .value("MATRIX2X3H", Trade::SceneFieldType::Matrix2x3h) .value("MATRIX2X3D", Trade::SceneFieldType::Matrix2x3d) .value("MATRIX2X4", Trade::SceneFieldType::Matrix2x4) .value("MATRIX2X4H", Trade::SceneFieldType::Matrix2x4h) .value("MATRIX2X4D", Trade::SceneFieldType::Matrix2x4d) .value("MATRIX3X2", Trade::SceneFieldType::Matrix3x2) .value("MATRIX3X2H", Trade::SceneFieldType::Matrix3x2h) .value("MATRIX3X2D", Trade::SceneFieldType::Matrix3x2d) .value("MATRIX3X3", Trade::SceneFieldType::Matrix3x3) .value("MATRIX3X3H", Trade::SceneFieldType::Matrix3x3h) .value("MATRIX3X3D", Trade::SceneFieldType::Matrix3x3d) .value("MATRIX3X4", Trade::SceneFieldType::Matrix3x4) .value("MATRIX3X4H", Trade::SceneFieldType::Matrix3x4h) .value("MATRIX3X4D", Trade::SceneFieldType::Matrix3x4d) .value("MATRIX4X2", Trade::SceneFieldType::Matrix4x2) .value("MATRIX4X2H", Trade::SceneFieldType::Matrix4x2h) .value("MATRIX4X2D", Trade::SceneFieldType::Matrix4x2d) .value("MATRIX4X3", Trade::SceneFieldType::Matrix4x3) .value("MATRIX4X3H", Trade::SceneFieldType::Matrix4x3h) .value("MATRIX4X3D", Trade::SceneFieldType::Matrix4x3d) .value("MATRIX4X4", Trade::SceneFieldType::Matrix4x4) .value("MATRIX4X4H", Trade::SceneFieldType::Matrix4x4h) .value("MATRIX4X4D", Trade::SceneFieldType::Matrix4x4d) .value("RANGE1D", Trade::SceneFieldType::Range1D) .value("RANGE1DH", Trade::SceneFieldType::Range1Dh) .value("RANGE1DD", Trade::SceneFieldType::Range1Dd) .value("RANGE1DI", Trade::SceneFieldType::Range1Di) .value("RANGE2D", Trade::SceneFieldType::Range2D) .value("RANGE2DH", Trade::SceneFieldType::Range2Dh) .value("RANGE2DD", Trade::SceneFieldType::Range2Dd) .value("RANGE2DI", Trade::SceneFieldType::Range2Di) .value("RANGE3D", Trade::SceneFieldType::Range3D) .value("RANGE3DH", Trade::SceneFieldType::Range3Dh) .value("RANGE3DD", Trade::SceneFieldType::Range3Dd) .value("RANGE3DI", Trade::SceneFieldType::Range3Di) .value("COMPLEX", Trade::SceneFieldType::Complex) .value("COMPLEXD", Trade::SceneFieldType::Complexd) .value("DUAL_COMPLEX", Trade::SceneFieldType::DualComplex) .value("DUAL_COMPLEXD", Trade::SceneFieldType::DualComplexd) .value("QUATERNION", Trade::SceneFieldType::Quaternion) .value("QUATERNIOND", Trade::SceneFieldType::Quaterniond) .value("DUAL_QUATERNION", Trade::SceneFieldType::DualQuaternion) .value("DUAL_QUATERNIOND", Trade::SceneFieldType::DualQuaterniond) .value("DEG", Trade::SceneFieldType::Deg) .value("DEGH", Trade::SceneFieldType::Degh) .value("DEGD", Trade::SceneFieldType::Degd) .value("RAD", Trade::SceneFieldType::Rad) .value("RADH", Trade::SceneFieldType::Radh) .value("RADD", Trade::SceneFieldType::Radd) .value("POINTER", Trade::SceneFieldType::Pointer) .value("MUTABLE_POINTER", Trade::SceneFieldType::MutablePointer) .value("STRING_OFFSET32", Trade::SceneFieldType::StringOffset32) .value("STRING_OFFSET8", Trade::SceneFieldType::StringOffset8) .value("STRING_OFFSET16", Trade::SceneFieldType::StringOffset16) .value("STRING_OFFSET64", Trade::SceneFieldType::StringOffset64) .value("STRING_RANGE32", Trade::SceneFieldType::StringRange32) .value("STRING_RANGE8", Trade::SceneFieldType::StringRange8) .value("STRING_RANGE16", Trade::SceneFieldType::StringRange16) .value("STRING_RANGE64", Trade::SceneFieldType::StringRange64) .value("STRING_RANGE_NULL_TERMINATED32", Trade::SceneFieldType::StringRangeNullTerminated32) .value("STRING_RANGE_NULL_TERMINATED8", Trade::SceneFieldType::StringRangeNullTerminated8) .value("STRING_RANGE_NULL_TERMINATED16", Trade::SceneFieldType::StringRangeNullTerminated16) .value("STRING_RANGE_NULL_TERMINATED64", Trade::SceneFieldType::StringRangeNullTerminated64); py::enum_ sceneFieldFlag{m, "SceneFieldFlags", "Scene field flags"}; sceneFieldFlag .value("OFFSET_ONLY", Trade::SceneFieldFlag::OffsetOnly) .value("ORDERED_MAPPING", Trade::SceneFieldFlag::OrderedMapping) .value("IMPLICIT_MAPPING", Trade::SceneFieldFlag::ImplicitMapping) .value("NULL_TERMINATED_STRING", Trade::SceneFieldFlag::NullTerminatedString) .value("NONE", Trade::SceneFieldFlag{}); 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") .def_property_readonly("field_size_bound", &Trade::SceneData::fieldSizeBound, "Field size bound") .def_property_readonly("is_2d", &Trade::SceneData::is2D, "Whether the scene is two-dimensional") .def_property_readonly("is_3d", &Trade::SceneData::is3D, "Whether the scene is three-dimensional") /* IMPORTANT: due to pybind11 behavioral differences on (already EOL'd) Python 3.7 the following overloads need to have the SceneField overload *before* the UnsignedInt overload, otherwise the integer overload gets picked even if an enum is passed from Python, causing massive suffering */ .def("field_name", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.fieldName(id); }, "Field name", py::arg("id")) .def("field_flags", [](Trade::SceneData& self, Trade::SceneField fieldName) { const Containers::Optional foundField = self.findFieldId(fieldName); if(!foundField) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(*foundField))); }, "Flags of a named field", py::arg("name")) .def("field_flags", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(id))); }, "Field flags", py::arg("id")) .def("field_type", [](Trade::SceneData& self, Trade::SceneField fieldName) { const Containers::Optional foundField = self.findFieldId(fieldName); if(!foundField) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } return self.fieldType(*foundField); }, "Type of a named field", py::arg("name")) .def("field_type", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.fieldType(id); }, "Field type", py::arg("id")) .def("field_size", [](Trade::SceneData& self, Trade::SceneField fieldName) { const Containers::Optional foundField = self.findFieldId(fieldName); if(!foundField) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } return self.fieldSize(*foundField); }, "Number of entries in a named field", py::arg("name")) .def("field_size", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.fieldSize(id); }, "Number of entries in a field", py::arg("id")) .def("field_array_size", [](Trade::SceneData& self, Trade::SceneField fieldName) { const Containers::Optional foundField = self.findFieldId(fieldName); if(!foundField) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } return self.fieldArraySize(*foundField); }, "Array size of a named field", py::arg("name")) .def("field_array_size", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { PyErr_SetNone(PyExc_IndexError); throw py::error_already_set{}; } return self.fieldArraySize(id); }, "Field array size", py::arg("id")) .def("field_id", [](Trade::SceneData& self, Trade::SceneField name) { if(const Containers::Optional found = self.findFieldId(name)) return *found; PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; }, "Absolute ID of a named field", py::arg("name")) .def("has_field", &Trade::SceneData::hasField, "Whether the scene has given field") .def("field_object_offset", [](Trade::SceneData& self, Trade::SceneField fieldName, UnsignedLong object, std::size_t offset) { const Containers::Optional foundField = self.findFieldId(fieldName); if(!foundField) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } if(object >= self.mappingBound()) { PyErr_SetString(PyExc_IndexError, "object out of range"); throw py::error_already_set{}; } if(offset >= self.fieldSize(*foundField)) { PyErr_SetString(PyExc_IndexError, "offset out of range"); throw py::error_already_set{}; } const Containers::Optional found = self.findFieldObjectOffset(*foundField, object, offset); if(!found) { PyErr_SetNone(PyExc_LookupError); throw py::error_already_set{}; } return *found; }, "Offset of an object in given name field", py::arg("field_name"), py::arg("object"), py::arg("offset") = 0) .def("field_object_offset", [](Trade::SceneData& self, UnsignedInt fieldId, UnsignedLong object, std::size_t offset) { if(fieldId >= self.fieldCount()) { PyErr_SetString(PyExc_IndexError, "field out of range"); throw py::error_already_set{}; } if(object >= self.mappingBound()) { PyErr_SetString(PyExc_IndexError, "object out of range"); throw py::error_already_set{}; } if(offset >= self.fieldSize(fieldId)) { PyErr_SetString(PyExc_IndexError, "offset out of range"); throw py::error_already_set{}; } const Containers::Optional found = self.findFieldObjectOffset(fieldId, object, offset); if(!found) { PyErr_SetNone(PyExc_LookupError); throw py::error_already_set{}; } return *found; }, "Offset of an object in given field", py::arg("field_id"), py::arg("object"), py::arg("offset") = 0) .def("has_field_object", [](Trade::SceneData& self, Trade::SceneField fieldName, UnsignedLong object) { const Containers::Optional foundField = self.findFieldId(fieldName); if(!foundField) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; } if(object >= self.mappingBound()) { PyErr_SetString(PyExc_IndexError, "object out of range"); throw py::error_already_set{}; } return self.hasFieldObject(*foundField, object); }, "Whether a scene field has given object", py::arg("field_name"), py::arg("object")) .def("has_field_object", [](Trade::SceneData& self, UnsignedInt fieldId, UnsignedLong object) { if(fieldId >= self.fieldCount()) { PyErr_SetString(PyExc_IndexError, "field out of range"); throw py::error_already_set{}; } if(object >= self.mappingBound()) { PyErr_SetString(PyExc_IndexError, "object out of range"); throw py::error_already_set{}; } return self.hasFieldObject(fieldId, 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 avoid needless name differences and because in the future there *might* be pure Python importers (not now tho). */ py::enum_ importerFeatures{m, "ImporterFeatures", "Features supported by an image converter"}; importerFeatures .value("OPEN_DATA", Trade::ImporterFeature::OpenData) .value("OPEN_STATE", Trade::ImporterFeature::OpenState) .value("FILE_CALLBACK", Trade::ImporterFeature::FileCallback) .value("NONE", Trade::ImporterFeature{}); corrade::enumOperators(importerFeatures); py::enum_ importerFlags{m, "ImporterFlags", "Importer flags"}; importerFlags .value("VERBOSE", Trade::ImporterFlag::Verbose) .value("NONE", Trade::ImporterFlag{}); corrade::enumOperators(importerFlags); py::class_, PluginManager::AbstractPlugin> abstractImporter{m, "AbstractImporter", "Interface for importer plugins"}; corrade::plugin(abstractImporter); abstractImporter .def_property_readonly("features", [](Trade::AbstractImporter& self) { return Trade::ImporterFeature(Containers::enumCastUnderlyingType(self.features())); }, "Features supported by this importer") .def_property("flags", [](Trade::AbstractImporter& self) { return Trade::ImporterFlag(Containers::enumCastUnderlyingType(self.flags())); }, [](Trade::AbstractImporter& self, Trade::ImporterFlag flags) { self.setFlags(flags); }, "Importer flags") .def_property_readonly("is_opened", &Trade::AbstractImporter::isOpened, "Whether any file is opened") .def("open_data", [](Trade::AbstractImporter& self, Containers::ArrayView data) { /** @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.openData(data)) return; PyErr_SetString(PyExc_RuntimeError, "opening data failed"); throw py::error_already_set{}; }, "Open raw data", py::arg("data")) /** @todo drop std::string in favor of our own string caster */ .def("open_file", [](Trade::AbstractImporter& self, const std::string& filename) { /** @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.openFile( #ifdef CORRADE_TARGET_WINDOWS /* To allow people to conveniently use Python's os.path, we need to convert backslashes to forward slashes as all Corrade and Magnum APIs expect forward */ Utility::Path::fromNativeSeparators(filename) #else filename #endif )) return; PyErr_Format(PyExc_RuntimeError, "opening %s failed", filename.data()); throw py::error_already_set{}; }, "Open a file", py::arg("filename")) .def("close", &Trade::AbstractImporter::close, "Close currently opened file") .def_property_readonly("default_scene", checkOpened, "Default scene") .def_property_readonly("scene_count", checkOpened, "Scene count") .def_property_readonly("object_count", checkOpened, "Object count") .def("scene_for_name", checkOpenedString, "Scene ID for given name", py::arg("name")) .def("object_for_name", checkOpenedString, "Object ID for given name", py::arg("name")) .def("scene_name", checkOpenedBoundsReturnsString, "Scene name", py::arg("id")) .def("object_name", checkOpenedBoundsReturnsString, "Scene name", py::arg("id")) .def("scene", checkOpenedBoundsResult, "Scene", py::arg("id")) .def("scene", checkOpenedBoundsResultString, "Scene for given name", py::arg("name")) /** @todo drop std::string in favor of our own string caster */ .def("scene_field_for_name", [](Trade::AbstractImporter& self, const std::string& name) -> Containers::Optional { const Trade::SceneField field = self.sceneFieldForName(name); if(field == Trade::SceneField{}) return {}; return field; }, "Scene field for given name", py::arg("name")) /** @todo drop std::string in favor of our own string caster */ .def("scene_field_name", [](Trade::AbstractImporter& self, Trade::SceneField name) -> Containers::Optional { if(const Containers::String field = self.sceneFieldName(name)) return std::string{field}; return {}; }, "String name for given custom scene field", py::arg("name")) /** @todo all other data types */ .def_property_readonly("mesh_count", checkOpened, "Mesh count") .def("mesh_level_count", checkOpenedBounds, "Mesh level count", py::arg("id")) .def("mesh_for_name", checkOpenedString, "Mesh ID for given name", py::arg("name")) .def("mesh_name", checkOpenedBoundsReturnsString, "Mesh name", py::arg("id")) .def("mesh", checkOpenedBoundsResult, "Mesh", py::arg("id"), py::arg("level") = 0) .def("mesh", checkOpenedBoundsResultString, "Mesh for given name", py::arg("name"), py::arg("level") = 0) /** @todo drop std::string in favor of our own string caster */ .def("mesh_attribute_for_name", [](Trade::AbstractImporter& self, const std::string& name) -> Containers::Optional { const Trade::MeshAttribute attribute = self.meshAttributeForName(name); if(attribute == Trade::MeshAttribute{}) return {}; return attribute; }, "Mesh attribute for given name", py::arg("name")) /** @todo drop std::string in favor of our own string caster */ .def("mesh_attribute_name", [](Trade::AbstractImporter& self, Trade::MeshAttribute name) -> Containers::Optional { if(const Containers::String attribute = self.meshAttributeName(name)) return std::string{attribute}; return {}; }, "String name for given custom mesh attribute", 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") .def("image1d_level_count", checkOpenedBounds, "One-dimensional image level count", py::arg("id")) .def("image2d_level_count", checkOpenedBounds, "Two-dimensional image level count", py::arg("id")) .def("image3d_level_count", checkOpenedBounds, "Three-dimensional image level count", py::arg("id")) .def("image1d_for_name", checkOpenedString, "One-dimensional image ID for given name", py::arg("name")) .def("image2d_for_name", checkOpenedString, "Two-dimensional image ID for given name", py::arg("name")) .def("image3d_for_name", checkOpenedString, "Three-dimensional image ID for given name", py::arg("name")) .def("image1d_name", checkOpenedBoundsReturnsString, "One-dimensional image name", py::arg("id")) .def("image2d_name", checkOpenedBoundsReturnsString, "Two-dimensional image name", py::arg("id")) .def("image3d_name", checkOpenedBoundsReturnsString, "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 for given name", py::arg("name"), py::arg("level") = 0) .def("image2d", checkOpenedBoundsResult, "Two-dimensional image", py::arg("id"), py::arg("level") = 0) .def("image2d", checkOpenedBoundsResultString, "Two-dimensional image for given name", 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 for given name", py::arg("name"), py::arg("level") = 0); py::class_, PluginManager::AbstractManager> importerManager{m, "ImporterManager", "Manager for importer plugins"}; corrade::manager(importerManager); /* Image converter */ py::enum_ imageConverterFeatures{m, "ImageConverterFeatures", "Features supported by an image converter"}; imageConverterFeatures .value("CONVERT1D", Trade::ImageConverterFeature::Convert1D) .value("CONVERT2D", Trade::ImageConverterFeature::Convert2D) .value("CONVERT3D", Trade::ImageConverterFeature::Convert3D) .value("CONVERT_COMPRESSED1D", Trade::ImageConverterFeature::ConvertCompressed1D) .value("CONVERT_COMPRESSED2D", Trade::ImageConverterFeature::ConvertCompressed2D) .value("CONVERT_COMPRESSED3D", Trade::ImageConverterFeature::ConvertCompressed3D) .value("CONVERT1D_TO_FILE", Trade::ImageConverterFeature::Convert1DToFile) .value("CONVERT2D_TO_FILE", Trade::ImageConverterFeature::Convert2DToFile) .value("CONVERT3D_TO_FILE", Trade::ImageConverterFeature::Convert3DToFile) .value("CONVERT_COMPRESSED1D_TO_FILE", Trade::ImageConverterFeature::ConvertCompressed1DToFile) .value("CONVERT_COMPRESSED2D_TO_FILE", Trade::ImageConverterFeature::ConvertCompressed2DToFile) .value("CONVERT_COMPRESSED3D_TO_FILE", Trade::ImageConverterFeature::ConvertCompressed3DToFile) .value("CONVERT1D_TO_DATA", Trade::ImageConverterFeature::Convert1DToData) .value("CONVERT2D_TO_DATA", Trade::ImageConverterFeature::Convert2DToData) .value("CONVERT3D_TO_DATA", Trade::ImageConverterFeature::Convert3DToData) .value("CONVERT_COMPRESSED1D_TO_DATA", Trade::ImageConverterFeature::ConvertCompressed1DToData) .value("CONVERT_COMPRESSED2D_TO_DATA", Trade::ImageConverterFeature::ConvertCompressed2DToData) .value("CONVERT_COMPRESSED3D_TO_DATA", Trade::ImageConverterFeature::ConvertCompressed3DToData) .value("LEVELS", Trade::ImageConverterFeature::Levels) .value("NONE", Trade::ImageConverterFeature{}); corrade::enumOperators(imageConverterFeatures); py::enum_ imageConverterFlags{m, "ImageConverterFlags", "Image converter flags"}; imageConverterFlags .value("VERBOSE", Trade::ImageConverterFlag::Verbose) .value("NONE", Trade::ImageConverterFlag{}); corrade::enumOperators(imageConverterFlags); py::class_, PluginManager::AbstractPlugin> abstractImageConverter{m, "AbstractImageConverter", "Interface for image converter plugins"}; abstractImageConverter .def_property_readonly("features", [](Trade::AbstractImageConverter& self) { return Trade::ImageConverterFeature(Containers::enumCastUnderlyingType(self.features())); }, "Features supported by this converter") .def_property("flags", [](Trade::AbstractImageConverter& self) { return Trade::ImageConverterFlag(Containers::enumCastUnderlyingType(self.flags())); }, [](Trade::AbstractImageConverter& self, Trade::ImageConverterFlag flags) { self.setFlags(flags); }, "Converter flags") .def("convert_to_file", checkImageConverterResult, "Convert a 1D image to a file", py::arg("image"), py::arg("filename")) .def("convert_to_file", checkImageConverterResult, "Convert a 2D image to a file", py::arg("image"), py::arg("filename")) .def("convert_to_file", checkImageConverterResult, "Convert a 3D image to a file", py::arg("image"), py::arg("filename")); corrade::plugin(abstractImageConverter); py::class_, PluginManager::AbstractManager> imageConverterManager{m, "ImageConverterManager", "Manager for image converter plugins"}; corrade::manager(imageConverterManager); /* Scene converter */ py::enum_ sceneConverterFeatures{m, "SceneConverterFeatures", "Features supported by a scene converter"}; sceneConverterFeatures .value("CONVERT_MESH", Trade::SceneConverterFeature::ConvertMesh) .value("CONVERT_MESH_IN_PLACE", Trade::SceneConverterFeature::ConvertMeshInPlace) .value("CONVERT_MESH_TO_FILE", Trade::SceneConverterFeature::ConvertMeshToFile) .value("CONVERT_MESH_TO_DATA", Trade::SceneConverterFeature::ConvertMeshToData) .value("CONVERT_MULTIPLE", Trade::SceneConverterFeature::ConvertMultiple) .value("CONVERT_MULTIPLE_TO_FILE", Trade::SceneConverterFeature::ConvertMultipleToFile) .value("CONVERT_MULTIPLE_TO_DATA", Trade::SceneConverterFeature::ConvertMultipleToData) .value("ADD_SCENES", Trade::SceneConverterFeature::AddScenes) .value("ADD_ANIMATIONS", Trade::SceneConverterFeature::AddAnimations) .value("ADD_LIGHTS", Trade::SceneConverterFeature::AddLights) .value("ADD_CAMERAS", Trade::SceneConverterFeature::AddCameras) .value("ADD_SKINS2D", Trade::SceneConverterFeature::AddSkins2D) .value("ADD_SKINS3D", Trade::SceneConverterFeature::AddSkins3D) .value("ADD_MESHES", Trade::SceneConverterFeature::AddMeshes) .value("ADD_MATERIALS", Trade::SceneConverterFeature::AddMaterials) .value("ADD_TEXTURES", Trade::SceneConverterFeature::AddTextures) .value("ADD_IMAGES1D", Trade::SceneConverterFeature::AddImages1D) .value("ADD_IMAGES2D", Trade::SceneConverterFeature::AddImages2D) .value("ADD_IMAGES3D", Trade::SceneConverterFeature::AddImages3D) .value("ADD_COMPRESSED_IMAGES1D", Trade::SceneConverterFeature::AddCompressedImages1D) .value("ADD_COMPRESSED_IMAGES2D", Trade::SceneConverterFeature::AddCompressedImages2D) .value("ADD_COMPRESSED_IMAGES3D", Trade::SceneConverterFeature::AddCompressedImages3D) .value("MESH_LEVELS", Trade::SceneConverterFeature::MeshLevels) .value("IMAGE_LEVELS", Trade::SceneConverterFeature::ImageLevels) .value("NONE", Trade::SceneConverterFeature{}); corrade::enumOperators(sceneConverterFeatures); py::enum_ sceneConverterFlags{m, "SceneConverterFlags", "Scene converter flags"}; sceneConverterFlags .value("VERBOSE", Trade::SceneConverterFlag::Verbose) .value("NONE", Trade::SceneConverterFlag{}); corrade::enumOperators(sceneConverterFlags); py::class_, PluginManager::AbstractPlugin> abstractSceneConverter{m, "AbstractSceneConverter", "Interface for scene converter plugins"}; abstractSceneConverter .def_property_readonly("features", [](Trade::AbstractSceneConverter& self) { return Trade::SceneConverterFeature(Containers::enumCastUnderlyingType(self.features())); }, "Features supported by this converter") .def_property("flags", [](Trade::AbstractSceneConverter& self) { return Trade::SceneConverterFlag(Containers::enumCastUnderlyingType(self.flags())); }, [](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 part of that so when it dies, the user can still see why */ bool out = self.convertToFile(mesh, #ifdef CORRADE_TARGET_WINDOWS /* To allow people to conveniently use Python's os.path, we need to convert backslashes to forward slashes as all Corrade and Magnum APIs expect forward */ Utility::Path::fromNativeSeparators(filename) #else filename #endif ); if(!out) { PyErr_SetString(PyExc_RuntimeError, "conversion failed"); throw py::error_already_set{}; } }, "Convert a mesh to a file", py::arg("mesh"), py::arg("filename")); corrade::plugin(abstractSceneConverter); py::class_, PluginManager::AbstractManager> sceneConverterManager{m, "SceneConverterManager", "Manager for scene converter plugins"}; corrade::manager(sceneConverterManager); } } #ifndef MAGNUM_BUILD_STATIC /* TODO: remove declaration when https://github.com/pybind/pybind11/pull/1863 is released */ extern "C" PYBIND11_EXPORT PyObject* PyInit_trade(); PYBIND11_MODULE(trade, m) { magnum::trade(m); } #endif