Browse Source

python: expose trade.SceneData basics together with importer APIs.

No data access yet, that'll come later.
next
Vladimír Vondruš 3 years ago
parent
commit
bbd7ccedb1
  1. 82
      doc/python/magnum.trade.rst
  2. 71
      src/python/magnum/test/scene.gltf
  3. 231
      src/python/magnum/test/test_trade.py
  4. 335
      src/python/magnum/trade.cpp

82
doc/python/magnum.trade.rst

@ -183,6 +183,63 @@
:raise AttributeError: If :ref:`vertex_data_flags` doesn't contain
:ref:`DataFlag.MUTABLE`
.. py:enum:: magnum.trade.SceneField
The equivalent to C++ :dox:`Trade::sceneFieldCustom()` is creating an enum
value using a ``CUSTOM()`` named constructor. The ``is_custom``
property then matches :dox:`Trade::isSceneFieldCustom()` and you can
retrieve the custom ID again with a ``custom_value`` property.
..
>>> from magnum import trade
.. code:: pycon
>>> attribute = trade.SceneField.CUSTOM(17)
>>> attribute.name
'CUSTOM(17)'
>>> attribute.is_custom
True
>>> attribute.custom_value
17
.. py:function:: magnum.trade.SceneData.field_name
:raise IndexError: If :p:`id` is negative or not less than
:ref:`field_count`
.. py:function:: magnum.trade.SceneData.field_flags
:raise IndexError: If :p:`id` is negative or not less than
:ref:`field_count`
:raise KeyError: If :p:`name` does not exist
.. py:function:: magnum.trade.SceneData.field_type
:raise IndexError: If :p:`id` is negative or not less than
:ref:`field_count`
:raise KeyError: If :p:`name` does not exist
.. py:function:: magnum.trade.SceneData.field_size
:raise IndexError: If :p:`id` is negative or not less than
:ref:`field_count`
:raise KeyError: If :p:`name` does not exist
.. py:function:: magnum.trade.SceneData.field_array_size
:raise IndexError: If :p:`id` is negative or not less than
:ref:`field_count`
:raise KeyError: If :p:`name` does not exist
.. py:function:: magnum.trade.SceneData.field_id
:raise KeyError: If :p:`name` does not exist
.. py:function:: magnum.trade.SceneData.field_object_offset
:raise IndexError: If :p:`field_id` is negative or not less than
:ref:`field_count`
:raise KeyError: If :p:`field_name` does not exist
:raise IndexError: If :p:`object` is negative or not less than
:ref:`mapping_bound`
:raise IndexError: If :p:`offset` is negative or larger than
:ref:`field_size()` for given field
:raise LookupError: If :p:`object` is not found
.. py:function:: magnum.trade.SceneData.has_field_object
:raise IndexError: If :p:`field_id` is negative or not less than
:ref:`field_count`
:raise KeyError: If :p:`field_name` does not exist
:raise IndexError: If :p:`object` is negative or not less than
:ref:`mapping_bound`
.. py:class:: magnum.trade.ImporterManager
:summary: Manager for :ref:`AbstractImporter` plugin instances
@ -218,6 +275,31 @@
:dox:`Trade::AbstractImporter::openFile()`, which expects forward slashes
as directory separators on all platforms.
.. py:property:: magnum.trade.AbstractImporter.default_scene
:raise AssertionError: If no file is opened
.. py:property:: magnum.trade.AbstractImporter.scene_count
:raise AssertionError: If no file is opened
.. py:property:: magnum.trade.AbstractImporter.object_count
:raise AssertionError: If no file is opened
.. py:function:: magnum.trade.AbstractImporter.scene_for_name
:raise AssertionError: If no file is opened
.. py:function:: magnum.trade.AbstractImporter.object_for_name
:raise AssertionError: If no file is opened
.. py:function:: magnum.trade.AbstractImporter.scene_name
:raise AssertionError: If no file is opened
:raise IndexError: If :p:`id` is negative or not less than :ref:`scene_count`
.. py:function:: magnum.trade.AbstractImporter.object_name
:raise AssertionError: If no file is opened
:raise IndexError: If :p:`id` is negative or not less than :ref:`object_count`
.. TODO this needs distinction by parameter names, at least
.. py:function:: magnum.trade.AbstractImporter.scene
:raise AssertionError: If no file is opened
:raise RuntimeError: If scene import fails
:raise IndexError: If :p:`id` is negative or not less than :ref:`scene`
:raise KeyError: If :p:`name` was not found
.. py:property:: magnum.trade.AbstractImporter.mesh_count
:raise AssertionError: If no file is opened
.. py:function:: magnum.trade.AbstractImporter.mesh_level_count

71
src/python/magnum/test/scene.gltf

@ -0,0 +1,71 @@
{
"asset": {
"version": "2.0"
},
"cameras": [
{
"type": "orthographic",
"orthographic": {
"xmag": 1.0,
"ymag": 1.0,
"znear": 0.1,
"zfar": 100.0
}
},
{
"type": "perspective",
"perspective": {
"yfov": 0.6108652,
"znear": 0.1,
"zfar": 100.0
}
}
],
"nodes": [
{
"translation": [7, 8, 9]
},
{
"name": "Translated node",
"translation": [1, 2, 3]
},
{
"name": "Camera node",
"matrix": [
1, 0, 0, 0,
0, 3, 0, 0,
0, 0, 2, 0,
0, 0, 0, 1
],
"camera": 1,
"extras": {
"aNumber": 5,
"aString": "hello!"
},
"children": [3]
},
{
"camera": 0,
"translation": [4, 5, 6],
"children": [0]
},
{
"name": "A broken node",
"mesh": 666
}
],
"scenes": [
{
"name": "A scene",
"nodes": [1, 2]
},
{
"name": "A default scene that's empty"
},
{
"name": "A broken scene",
"nodes": [4]
}
],
"scene": 1
}

231
src/python/magnum/test/test_trade.py

@ -574,6 +574,162 @@ class MeshData(unittest.TestCase):
with self.assertRaisesRegex(NotImplementedError, "access to this vertex format is not implemented yet, sorry"):
mesh.mutable_attribute(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE"))
class SceneData(unittest.TestCase):
def test_custom_field(self):
# Creating a custom attribute
a = trade.SceneField.CUSTOM(17)
self.assertTrue(a.is_custom)
if hasattr(a, 'value'): # only since pybind11 2.6.2
self.assertEqual(a.value, 0x80000000 + 17)
self.assertEqual(a.custom_value, 17)
self.assertEqual(a.name, "CUSTOM(17)")
self.assertEqual(str(a), "SceneField.CUSTOM(17)")
self.assertEqual(repr(a), "<SceneField.CUSTOM(17): 2147483665>")
# Lowest possible custom value, test that it's correctly recognized as
# custom by all APIs
zero = trade.SceneField.CUSTOM(0)
self.assertTrue(zero.is_custom)
if hasattr(zero, 'value'): # only since pybind11 2.6.2
self.assertEqual(zero.value, 0x80000000)
self.assertEqual(zero.custom_value, 0)
self.assertEqual(zero.name, "CUSTOM(0)")
self.assertEqual(str(zero), "SceneField.CUSTOM(0)")
self.assertEqual(repr(zero), "<SceneField.CUSTOM(0): 2147483648>")
# Largest possible custom value
largest = trade.SceneField.CUSTOM(0x7fffffff)
self.assertTrue(largest.is_custom)
if hasattr(largest, 'value'): # only since pybind11 2.6.2
self.assertEqual(largest.value, 0xffffffff)
self.assertEqual(largest.custom_value, 0x7fffffff)
# Creating a custom attribute with a value that won't fit
with self.assertRaisesRegex(ValueError, "custom value too large"):
trade.SceneField.CUSTOM(0x80000000)
# Accessing properties on builtin values should still work as expected
b = trade.SceneField.SKIN
self.assertFalse(b.is_custom)
if hasattr(b, 'value'): # only since pybind11 2.6.2
self.assertEqual(b.value, 10)
with self.assertRaisesRegex(AttributeError, "not a custom value"):
b.custom_value
self.assertEqual(b.name, "SKIN")
self.assertEqual(str(b), "SceneField.SKIN")
self.assertEqual(repr(b), "<SceneField.SKIN: 10>")
def test(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
scene = importer.scene(0)
self.assertEqual(scene.mapping_type, trade.SceneMappingType.UNSIGNED_INT)
self.assertEqual(scene.mapping_bound, 4)
self.assertEqual(scene.field_count, 7)
# TODO add some array extras once supported to have this different from
# the mapping bound
self.assertEqual(scene.field_size_bound, 4)
self.assertFalse(scene.is_2d)
self.assertTrue(scene.is_3d)
# Field properties by ID
self.assertEqual(scene.field_name(2), trade.SceneField.TRANSFORMATION)
self.assertEqual(scene.field_name(6), trade.SceneField.CUSTOM(1))
# TODO some field flags in glTF please?
self.assertEqual(scene.field_flags(2), trade.SceneFieldFlag(0))
self.assertEqual(scene.field_type(2), trade.SceneFieldType.MATRIX4X4)
self.assertEqual(scene.field_size(3), 3)
# TODO add some array extras once supported to have this non-zero for
# some fields
self.assertEqual(scene.field_array_size(2), 0)
self.assertTrue(scene.has_field_object(2, 3))
self.assertFalse(scene.has_field_object(4, 1))
self.assertEqual(scene.field_object_offset(2, 3), 2)
self.assertEqual(scene.field_object_offset(2, 3, 1), 2)
# Field properties by name
self.assertEqual(scene.field_id(trade.SceneField.CUSTOM(0)), 5)
self.assertTrue(scene.has_field(trade.SceneField.IMPORTER_STATE))
self.assertFalse(scene.has_field(trade.SceneField.SKIN))
self.assertTrue(scene.has_field_object(trade.SceneField.TRANSFORMATION, 3))
self.assertFalse(scene.has_field_object(trade.SceneField.CAMERA, 1))
self.assertEqual(scene.field_object_offset(trade.SceneField.TRANSFORMATION, 3), 2)
self.assertEqual(scene.field_object_offset(trade.SceneField.TRANSFORMATION, 3, 1), 2)
# TODO some field flags in glTF please?
self.assertEqual(scene.field_flags(trade.SceneField.PARENT), trade.SceneFieldFlag(0))
self.assertEqual(scene.field_type(6), trade.SceneFieldType.STRING_OFFSET32)
self.assertEqual(scene.field_size(trade.SceneField.CUSTOM(0)), 1)
# TODO add some array extras once supported to have this non-zero for
# some fields
self.assertEqual(scene.field_array_size(trade.SceneField.TRANSLATION), 0)
def test_field_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
scene = importer.scene(0)
# Access by OOB field ID
with self.assertRaises(IndexError):
scene.field_name(scene.field_count)
with self.assertRaises(IndexError):
scene.field_flags(scene.field_count)
with self.assertRaises(IndexError):
scene.field_type(scene.field_count)
with self.assertRaises(IndexError):
scene.field_size(scene.field_count)
with self.assertRaises(IndexError):
scene.field_array_size(scene.field_count)
with self.assertRaisesRegex(IndexError, "field out of range"):
scene.has_field_object(scene.field_count, 0)
with self.assertRaisesRegex(IndexError, "field out of range"):
scene.field_object_offset(scene.field_count, 0)
# Access by nonexistent field name
with self.assertRaises(KeyError):
scene.field_id(trade.SceneField.SCALING)
with self.assertRaises(KeyError):
scene.field_flags(trade.SceneField.SCALING)
with self.assertRaises(KeyError):
scene.field_type(trade.SceneField.SCALING)
with self.assertRaises(KeyError):
scene.field_size(trade.SceneField.SCALING)
with self.assertRaises(KeyError):
scene.field_array_size(trade.SceneField.SCALING)
with self.assertRaises(KeyError):
scene.has_field_object(trade.SceneField.SCALING, 0)
with self.assertRaises(KeyError):
scene.field_object_offset(trade.SceneField.SCALING, 0)
# OOB object ID
with self.assertRaisesRegex(IndexError, "object out of range"):
scene.has_field_object(0, 4) # PARENT
with self.assertRaisesRegex(IndexError, "object out of range"):
scene.has_field_object(trade.SceneField.PARENT, 4)
with self.assertRaisesRegex(IndexError, "object out of range"):
scene.field_object_offset(0, 4) # PARENT
with self.assertRaisesRegex(IndexError, "object out of range"):
scene.field_object_offset(trade.SceneField.PARENT, 4)
# Lookup error
with self.assertRaises(LookupError):
scene.field_object_offset(4, 1) # CAMERA
with self.assertRaises(LookupError):
scene.field_object_offset(trade.SceneField.CAMERA, 1)
# Lookup error due to field offset being at the end
with self.assertRaises(LookupError):
scene.field_object_offset(0, 1, scene.field_size(0)) # PARENT
with self.assertRaises(LookupError):
scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT))
# OOB field offset (offset == size is allowed, tested above)
with self.assertRaisesRegex(IndexError, "offset out of range"):
scene.field_object_offset(0, 1, scene.field_size(0) + 1) # PARENT
with self.assertRaisesRegex(IndexError, "offset out of range"):
scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT) + 1)
class Importer(unittest.TestCase):
def test(self):
manager = trade.ImporterManager()
@ -592,6 +748,25 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('StbImageImporter')
self.assertFalse(importer.is_opened)
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.default_scene
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.scene_count
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.object_count
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.scene_for_name('')
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.object_for_name('')
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.scene_name(0)
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.object_name(0)
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.scene(0)
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.scene('')
with self.assertRaisesRegex(AssertionError, "no file opened"):
importer.mesh_count
with self.assertRaisesRegex(AssertionError, "no file opened"):
@ -646,6 +821,13 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('StbImageImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png'))
with self.assertRaises(IndexError):
importer.scene_name(0)
with self.assertRaises(IndexError):
importer.object_name(0)
with self.assertRaises(IndexError):
importer.scene(0)
with self.assertRaises(IndexError):
importer.mesh_level_count(0)
with self.assertRaises(IndexError):
@ -684,6 +866,55 @@ class Importer(unittest.TestCase):
with self.assertRaisesRegex(RuntimeError, "opening data failed"):
importer.open_data(b'')
def test_scene(self):
# importer refcounting tested in image2d
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# Asking for custom scene field names should work even if not opened,
# returns None
self.assertIsNone(importer.scene_field_name(trade.SceneField.CUSTOM(1)))
self.assertIsNone(importer.scene_field_for_name('aString'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
self.assertEqual(importer.default_scene, 1)
self.assertEqual(importer.scene_count, 3)
self.assertEqual(importer.scene_name(1), "A default scene that's empty")
self.assertEqual(importer.scene_for_name("A default scene that's empty"), 1)
self.assertEqual(importer.object_count, 5)
self.assertEqual(importer.object_name(2), "Camera node")
self.assertEqual(importer.object_for_name("Camera node"), 2)
# It should work after opening
self.assertEqual(importer.scene_field_name(trade.SceneField.CUSTOM(1)), 'aString')
self.assertEqual(importer.scene_field_for_name('aString'), trade.SceneField.CUSTOM(1))
scene = importer.scene(0)
self.assertEqual(scene.field_count, 7)
self.assertTrue(scene.has_field(importer.scene_field_for_name('aString')))
def test_scene_by_name(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
scene = importer.scene("A scene")
self.assertEqual(scene.field_count, 7)
def test_scebne_by_name_not_found(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
with self.assertRaises(KeyError):
importer.scene('Nonexistent')
def test_scene_failed(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
with self.assertRaisesRegex(RuntimeError, "import failed"):
importer.scene(2)
with self.assertRaisesRegex(RuntimeError, "import failed"):
importer.scene("A broken scene")
def test_mesh(self):
# importer refcounting tested in image2d
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')

335
src/python/magnum/trade.cpp

@ -36,6 +36,7 @@
#include <Magnum/Trade/AbstractSceneConverter.h>
#include <Magnum/Trade/ImageData.h>
#include <Magnum/Trade/MeshData.h>
#include <Magnum/Trade/SceneData.h>
#include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/OptionalPythonBindings.h"
@ -321,6 +322,29 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
return *std::move(out);
}
/** @todo drop std::string in favor of our own string caster */
template<class R, Containers::Optional<R>(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<R> out = (self.*f)(id);
if(!out) {
PyErr_SetString(PyExc_RuntimeError, "import failed");
throw py::error_already_set{};
}
return *std::move(out);
}
template<class R, Containers::Optional<R>(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()) {
@ -793,6 +817,294 @@ void trade(py::module_& m) {
imageData(imageData2D);
imageData(imageData3D);
py::enum_<Trade::SceneMappingType>{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_<Trade::SceneField> 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<Trade::SceneField, Trade::Implementation::SceneFieldCustom>(sceneField);
py::enum_<Trade::SceneFieldType>{m, "SceneFieldType", "Scene field type"}
.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_<Trade::SceneFieldFlag> sceneFieldFlag{m, "SceneFieldFlag", "Scene field flag"};
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);
corrade::enumOperators(sceneFieldFlag);
py::class_<Trade::SceneData>{m, "SceneData", "Scene data"}
.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 yet-uninvestigated pybind11 platform-specific
behavioral differences 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<UnsignedInt> 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<UnsignedInt> 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<UnsignedInt> 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<UnsignedInt> 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<UnsignedInt> 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<UnsignedInt> 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<std::size_t> 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<std::size_t> 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<UnsignedInt> 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"));
/* 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*
@ -830,6 +1142,29 @@ void trade(py::module_& m) {
}, "Open a file", py::arg("filename"))
.def("close", &Trade::AbstractImporter::close, "Close currently opened file")
.def_property_readonly("default_scene", checkOpened<Int, &Trade::AbstractImporter::defaultScene>, "Default scene")
.def_property_readonly("scene_count", checkOpened<UnsignedInt, &Trade::AbstractImporter::sceneCount>, "Scene count")
.def_property_readonly("object_count", checkOpened<UnsignedLong, &Trade::AbstractImporter::objectCount>, "Object count")
.def("scene_for_name", checkOpenedString<Int, &Trade::AbstractImporter::sceneForName>, "Scene ID for given name", py::arg("name"))
.def("object_for_name", checkOpenedString<Long, &Trade::AbstractImporter::objectForName>, "Object ID for given name", py::arg("name"))
.def("scene_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::sceneName, &Trade::AbstractImporter::sceneCount>, "Scene name", py::arg("id"))
.def("object_name", checkOpenedBoundsReturnsString<UnsignedLong, &Trade::AbstractImporter::objectName, &Trade::AbstractImporter::objectCount>, "Scene name", py::arg("id"))
.def("scene", checkOpenedBoundsResult<Trade::SceneData, &Trade::AbstractImporter::scene, &Trade::AbstractImporter::sceneCount>, "Scene", py::arg("id"))
.def("scene", checkOpenedBoundsResultString<Trade::SceneData, &Trade::AbstractImporter::scene, &Trade::AbstractImporter::sceneForName>, "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<Trade::SceneField> {
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<std::string> {
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<UnsignedInt, &Trade::AbstractImporter::meshCount>, "Mesh count")
.def("mesh_level_count", checkOpenedBounds<UnsignedInt, &Trade::AbstractImporter::meshLevelCount, &Trade::AbstractImporter::meshCount>, "Mesh level count", py::arg("id"))

Loading…
Cancel
Save