diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index 7d43880..fba56df 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -258,6 +258,310 @@ :raise AttributeError: If :ref:`vertex_data_flags` doesn't contain :ref:`DataFlags.MUTABLE` +.. py:enum:: magnum.trade.MaterialLayer + + The equivalent to C++ :dox:`Trade::materialLayerName()` is the ``string`` + property, as ``name`` is reserved for the Python enum name. + + .. + >>> from magnum import trade + + .. code:: pycon + + >>> layer = trade.MaterialLayer.CLEAR_COAT + >>> layer.name + 'CLEAR_COAT' + >>> layer.string + 'ClearCoat' + +.. py:enum:: magnum.trade.MaterialAttribute + + The equivalent to C++ :dox:`Trade::materialAttributeName()` is the + ``string`` property, as ``name`` is reserved for the Python enum name. + + .. + >>> from magnum import trade + + .. code:: pycon + + >>> attribute = trade.MaterialAttribute.BASE_COLOR_TEXTURE_MATRIX + >>> attribute.name + 'BASE_COLOR_TEXTURE_MATRIX' + >>> attribute.string + 'BaseColorTextureMatrix' + +.. py:enum:: magnum.trade.MaterialTextureSwizzle + + The ``component_count`` property matches :dox:`Trade::materialTextureSwizzleComponentCount()`. + + .. + >>> from magnum import trade + + .. code:: pycon + + >>> trade.MaterialTextureSwizzle.GA.component_count + 2 + +.. py:class:: magnum.trade.MaterialData + + :TODO: remove this line once m.css stops ignoring first caption on a page + + `Attribute data access`_ + ======================== + + The class makes use of Python's dynamic nature and provides direct access + to attribute data in their concrete types via :ref:`attribute()`: + + .. + >>> import os + >>> from magnum import trade + >>> importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + >>> importer.open_file('../../src/python/magnum/test/material.gltf') + + .. code:: pycon + + >>> material = importer.material(0) + >>> material.attribute(trade.MaterialAttribute.BASE_COLOR) + Vector(0.3, 0.4, 0.5, 0.8) + >>> material.attribute(trade.MaterialAttribute.DOUBLE_SIDED) + True + +.. py:function:: magnum.trade.MaterialData.attribute_data_offset(self, layer: int) + :raise IndexError: If :p:`layer` is negative or *greater* than + :ref:`layer_count` +.. py:function:: magnum.trade.MaterialData.layer_id(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.layer_id(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.layer_name(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + +.. py:function:: magnum.trade.MaterialData.layer_factor(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` +.. py:function:: magnum.trade.MaterialData.layer_factor(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.layer_factor(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + +.. py:function:: magnum.trade.MaterialData.layer_factor_texture(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` + +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_swizzle(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_swizzle(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_swizzle(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` + +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_matrix(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_matrix(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_matrix(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` + +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_coordinates(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_coordinates(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_coordinates(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` + +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_layer(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_layer(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.layer_factor_texture_layer(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :ref:`MaterialAttribute.LAYER_FACTOR_TEXTURE` isn't + present in :p:`layer` + +.. py:function:: magnum.trade.MaterialData.attribute_count(self, layer: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` +.. py:function:: magnum.trade.MaterialData.attribute_count(self, layer: str) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.attribute_count(self, layer: magnum.trade.MaterialLayer) + :raise KeyError: If :p:`layer` doesn't exist + +.. py:function:: magnum.trade.MaterialData.has_attribute(self, layer: int, name: str) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` +.. py:function:: magnum.trade.MaterialData.has_attribute(self, layer: int, name: magnum.trade.MaterialAttribute) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` +.. py:function:: magnum.trade.MaterialData.has_attribute(self, layer: str, name: str) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.has_attribute(self, layer: str, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.has_attribute(self, layer: magnum.trade.MaterialLayer, name: str) + :raise KeyError: If :p:`layer` doesn't exist +.. py:function:: magnum.trade.MaterialData.has_attribute(self, layer: magnum.trade.MaterialLayer, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + +.. py:function:: magnum.trade.MaterialData.attribute_id(self, layer: int, name: str) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_id(self, layer: int, name: magnum.trade.MaterialAttribute) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_id(self, layer: str, name: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_id(self, layer: str, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_id(self, layer: magnum.trade.MaterialLayer, name: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_id(self, layer: magnum.trade.MaterialLayer, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_id(self, name: str) + :raise KeyError: If :p:`name` isn't present in the base material +.. py:function:: magnum.trade.MaterialData.attribute_id(self, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`name` isn't present in the base material + +.. py:function:: magnum.trade.MaterialData.attribute_name(self, layer: int, id: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_name(self, layer: str, id: int) + :raise KeyError: If :p:`layer` doesn't exist + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_name(self, layer: magnum.trade.MaterialLayer, id: int) + :raise KeyError: If :p:`layer` doesn't exist + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_name(self, id: int) + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` + +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: int, id: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: int, name: str) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: int, name: magnum.trade.MaterialAttribute) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: str, id: int) + :raise KeyError: If :p:`layer` doesn't exist + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: str, name: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: str, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: magnum.trade.MaterialLayer, id: int) + :raise KeyError: If :p:`layer` doesn't exist + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: magnum.trade.MaterialLayer, name: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, layer: magnum.trade.MaterialLayer, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, id: int) + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` +.. py:function:: magnum.trade.MaterialData.attribute_type(self, name: str) + :raise KeyError: If :p:`name` isn't present in the base material +.. py:function:: magnum.trade.MaterialData.attribute_type(self, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`name` isn't present in the base material + +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: int, id: int) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: int, name: str) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: int, name: magnum.trade.MaterialAttribute) + :raise IndexError: If :p:`layer` is negative or not less than + :ref:`layer_count` + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: str, id: int) + :raise KeyError: If :p:`layer` doesn't exist + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: str, name: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: str, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: magnum.trade.MaterialLayer, id: int) + :raise KeyError: If :p:`layer` doesn't exist + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` for :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: magnum.trade.MaterialLayer, name: str) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, layer: magnum.trade.MaterialLayer, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`layer` doesn't exist + :raise KeyError: If :p:`name` isn't present in :p:`layer` +.. py:function:: magnum.trade.MaterialData.attribute(self, id: int) + :raise IndexError: If :p:`id` is negative or not less than + :ref:`attribute_count()` +.. py:function:: magnum.trade.MaterialData.attribute(self, name: str) + :raise KeyError: If :p:`name` isn't present in the base material +.. py:function:: magnum.trade.MaterialData.attribute(self, name: magnum.trade.MaterialAttribute) + :raise KeyError: If :p:`name` isn't present in the base material + .. py:enum:: magnum.trade.SceneField The equivalent to C++ :dox:`Trade::sceneFieldCustom()` is creating an enum @@ -466,6 +770,23 @@ :raise IndexError: If :p:`level` is negative or not less than :ref:`mesh_level_count()` for this mesh +.. py:property:: magnum.trade.AbstractImporter.material_count + :raise AssertionError: If no file is opened +.. py:function:: magnum.trade.AbstractImporter.material_for_name + :raise AssertionError: If no file is opened +.. py:function:: magnum.trade.AbstractImporter.material_name + :raise AssertionError: If no file is opened + :raise IndexError: If :p:`id` is negative or not less than :ref:`material_count` + +.. py:function:: magnum.trade.AbstractImporter.material(self, id: int) + :raise AssertionError: If no file is opened + :raise RuntimeError: If material import fails + :raise IndexError: If :p:`id` is negative or not less than :ref:`material_count` +.. py:function:: magnum.trade.AbstractImporter.material(self, name: str) + :raise AssertionError: If no file is opened + :raise RuntimeError: If material import fails + :raise KeyError: If :p:`name` was not found + .. py:property:: magnum.trade.AbstractImporter.texture_count :raise AssertionError: If no file is opened .. py:function:: magnum.trade.AbstractImporter.texture_for_name @@ -765,6 +1086,9 @@ .. py:property:: magnum.trade.AbstractSceneConverter.mesh_count :raise AssertionError: If no conversion is in progress +.. py:property:: magnum.trade.AbstractSceneConverter.material_count + :raise AssertionError: If no conversion is in progress + .. py:property:: magnum.trade.AbstractSceneConverter.scene_count :raise AssertionError: If no conversion is in progress @@ -783,6 +1107,12 @@ :raise AssertionError: If no conversion is in progress :raise RuntimeError: If adding the data fails +.. py:function:: magnum.trade.AbstractSceneConverter.add(self, material: magnum.trade.MaterialData, name: str) + :raise AssertionError: If :ref:`SceneConverterFeatures.ADD_MATERIALS` is + not supported + :raise AssertionError: If no conversion is in progress + :raise RuntimeError: If adding the data fails + .. py:function:: magnum.trade.AbstractSceneConverter.add(self, image: magnum.trade.ImageData2D, name: str) :raise AssertionError: If :ref:`ImageData2D.is_compressed` is :py:`False` and :ref:`SceneConverterFeatures.ADD_IMAGES2D` is not supported diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index 59d546c..4702136 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -150,6 +150,9 @@ Changelog to index and attribute data, together with :ref:`VertexFormat`, :ref:`trade.DataFlags`, :ref:`trade.AbstractImporter.mesh_attribute_name()` and :ref:`trade.AbstractImporter.mesh_attribute_for_name()` +- Exposed the whole interface of :ref:`trade.MaterialData` including typed + access to attribute data, together with + :ref:`trade.AbstractImporter.material()` and related importer APIs - Exposed the whole interface of :ref:`trade.SceneData` including typed access to mapping and field data, together with :ref:`trade.AbstractImporter.scene()` and related importer APIs diff --git a/src/Magnum/Trade/PythonBindings.h b/src/Magnum/Trade/PythonBindings.h index 588dfc9..be81afc 100644 --- a/src/Magnum/Trade/PythonBindings.h +++ b/src/Magnum/Trade/PythonBindings.h @@ -29,6 +29,7 @@ #include #include "Magnum/Trade/Data.h" +#include "Magnum/Trade/MaterialData.h" /* :( */ #include "Magnum/Trade/MeshData.h" /* :( */ namespace Magnum { namespace Trade { @@ -39,6 +40,11 @@ namespace Implementation { template inline bool pyDataFlagsNeedOwner(const T& data) { return !(data.dataFlags() & (DataFlag::Owned|DataFlag::Global)); } +inline bool pyDataFlagsNeedOwner(const Trade::MaterialData& data) { + return + !(data.attributeDataFlags() & (DataFlag::Owned|DataFlag::Global)) || + !(data.layerDataFlags() & (DataFlag::Owned|DataFlag::Global)); +} inline bool pyDataFlagsNeedOwner(const Trade::MeshData& data) { return !(data.indexDataFlags() & (DataFlag::Owned|DataFlag::Global)) || diff --git a/src/python/magnum/test/material.gltf b/src/python/magnum/test/material.gltf new file mode 100644 index 0000000..69725b0 --- /dev/null +++ b/src/python/magnum/test/material.gltf @@ -0,0 +1,68 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "rgb.png" + } + ], + "materials": [ + { + "name": "A material with a layer", + "alphaCutoff": 0.369, + "alphaMode": "MASK", + "doubleSided": true, + "pbrMetallicRoughness": { + "baseColorFactor": [0.3, 0.4, 0.5, 0.8] + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 0.7, + "clearcoatTexture": { + "index": 2, + "texCoord": 13, + "extensions": { + "KHR_texture_transform": { + "offset": [0.25, 0.5] + } + } + }, + "clearcoatRoughnessTexture": { + "index": 1 + } + } + } + }, + { + "name": "Empty material" + }, + { + "name": "Material with an empty layer", + "extensions": { + "KHR_materials_clearcoat": {} + } + }, + { + "name": "A broken material", + "alphaMode": false + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ] +} diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index f798a0e..3afada0 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -201,6 +201,409 @@ class ImageData(unittest.TestCase): with self.assertRaisesRegex(NotImplementedError, "access to PixelFormat.DEPTH32F_STENCIL8UI is not implemented yet, sorry"): image.pixels +class MaterialData(unittest.TestCase): + def test_layer_properties(self): + self.assertEqual(trade.MaterialLayer.CLEAR_COAT.string, "ClearCoat") + + def test_attribute_properties(self): + self.assertEqual(trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE.string, "LayerFactorTextureSwizzle") + + def test_texture_swizzle_properties(self): + self.assertEqual(trade.MaterialTextureSwizzle.GB.component_count, 2) + + def test(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + # This adds extra Diffuse attributes for BaseColor, don't want + importer.configuration['phongMaterialFallback'] = False + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + material = importer.material(0) + material_empty = importer.material(1) + + self.assertEqual(material.attribute_data_flags, trade.DataFlags.OWNED|trade.DataFlags.MUTABLE) + self.assertEqual(material.layer_data_flags, trade.DataFlags.OWNED|trade.DataFlags.MUTABLE) + self.assertEqual(material.types, trade.MaterialTypes.PBR_METALLIC_ROUGHNESS|trade.MaterialTypes.PBR_CLEAR_COAT) + + self.assertEqual(material.layer_count, 2) + self.assertEqual(material.attribute_data_offset(1), 3) + self.assertEqual(material.attribute_data_offset(2), 11) + self.assertTrue(material.has_layer("ClearCoat")) + self.assertFalse(material_empty.has_layer("ClearCoat")) + self.assertTrue(material.has_layer(trade.MaterialLayer.CLEAR_COAT)) + self.assertFalse(material_empty.has_layer(trade.MaterialLayer.CLEAR_COAT)) + self.assertEqual(material.layer_id("ClearCoat"), 1) + self.assertEqual(material.layer_id(trade.MaterialLayer.CLEAR_COAT), 1) + self.assertEqual(material.layer_name(1), "ClearCoat") + self.assertAlmostEqual(material.layer_factor(1), 0.7) + self.assertAlmostEqual(material.layer_factor("ClearCoat"), 0.7) + self.assertAlmostEqual(material.layer_factor(trade.MaterialLayer.CLEAR_COAT), 0.7) + self.assertEqual(material.layer_factor_texture(1), 2) + self.assertEqual(material.layer_factor_texture("ClearCoat"), 2) + self.assertEqual(material.layer_factor_texture(trade.MaterialLayer.CLEAR_COAT), 2) + # TODO test with something where the swizzle isn't default to verify + # it's querying the right layer + self.assertEqual(material.layer_factor_texture_swizzle(1), trade.MaterialTextureSwizzle.R) + self.assertEqual(material.layer_factor_texture_swizzle("ClearCoat"), trade.MaterialTextureSwizzle.R) + self.assertEqual(material.layer_factor_texture_swizzle(trade.MaterialLayer.CLEAR_COAT), trade.MaterialTextureSwizzle.R) + self.assertEqual(material.layer_factor_texture_matrix(1), Matrix3.translation((0.25, -0.5))) + self.assertEqual(material.layer_factor_texture_matrix("ClearCoat"), Matrix3.translation((0.25, -0.5))) + self.assertEqual(material.layer_factor_texture_matrix(trade.MaterialLayer.CLEAR_COAT), Matrix3.translation((0.25, -0.5))) + self.assertEqual(material.layer_factor_texture_coordinates(1), 13) + self.assertEqual(material.layer_factor_texture_coordinates("ClearCoat"), 13) + self.assertEqual(material.layer_factor_texture_coordinates(trade.MaterialLayer.CLEAR_COAT), 13) + # TODO test with something where the layer isn't 0, KHR_image_ktx is + # too annoying + self.assertEqual(material.layer_factor_texture_layer(1), 0) + self.assertEqual(material.layer_factor_texture_layer("ClearCoat"), 0) + self.assertEqual(material.layer_factor_texture_layer(trade.MaterialLayer.CLEAR_COAT), 0) + + self.assertEqual(material.attribute_count(1), 8) + self.assertEqual(material.attribute_count("ClearCoat"), 8) + self.assertEqual(material.attribute_count(trade.MaterialLayer.CLEAR_COAT), 8) + self.assertEqual(material.attribute_count(1), 8) + self.assertEqual(material.attribute_count(), 3) + + self.assertTrue(material.has_attribute(1, "LayerFactorTexture")) + self.assertFalse(material.has_attribute(1, "LayerFactorTextureSwizzle")) + self.assertTrue(material.has_attribute(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE)) + self.assertFalse(material.has_attribute(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE)) + self.assertTrue(material.has_attribute("ClearCoat", "LayerFactorTexture")) + self.assertFalse(material.has_attribute("ClearCoat", "LayerFactorTextureSwizzle")) + self.assertTrue(material.has_attribute("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE)) + self.assertFalse(material.has_attribute("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE)) + self.assertTrue(material.has_attribute(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTexture")) + self.assertFalse(material.has_attribute(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTextureSwizzle")) + self.assertTrue(material.has_attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE)) + self.assertFalse(material.has_attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE)) + self.assertTrue(material.has_attribute("DoubleSided")) + self.assertFalse(material.has_attribute("BaseColorTexture")) + self.assertTrue(material.has_attribute(trade.MaterialAttribute.DOUBLE_SIDED)) + self.assertFalse(material.has_attribute(trade.MaterialAttribute.BASE_COLOR_TEXTURE)) + + self.assertEqual(material.attribute_id(1, "RoughnessTexture"), 6) + self.assertEqual(material.attribute_id(1, trade.MaterialAttribute.ROUGHNESS_TEXTURE), 6) + self.assertEqual(material.attribute_id("ClearCoat", "RoughnessTexture"), 6) + self.assertEqual(material.attribute_id("ClearCoat", trade.MaterialAttribute.ROUGHNESS_TEXTURE), 6) + self.assertEqual(material.attribute_id(trade.MaterialLayer.CLEAR_COAT, "RoughnessTexture"), 6) + self.assertEqual(material.attribute_id(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.ROUGHNESS_TEXTURE), 6) + self.assertEqual(material.attribute_id("DoubleSided"), 2) + self.assertEqual(material.attribute_id(trade.MaterialAttribute.DOUBLE_SIDED), 2) + + self.assertEqual(material.attribute_name(1, 6), "RoughnessTexture") + self.assertEqual(material.attribute_name("ClearCoat", 6), "RoughnessTexture") + self.assertEqual(material.attribute_name(trade.MaterialLayer.CLEAR_COAT, 6), "RoughnessTexture") + self.assertEqual(material.attribute_name(2), "DoubleSided") + + self.assertEqual(material.attribute_type(1, 4), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type(1, "LayerFactorTextureMatrix"), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_MATRIX), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type("ClearCoat", 4), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type("ClearCoat", "LayerFactorTextureMatrix"), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_MATRIX), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type(trade.MaterialLayer.CLEAR_COAT, 4), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTextureMatrix"), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_MATRIX), trade.MaterialAttributeType.MATRIX3X3) + self.assertEqual(material.attribute_type(2), trade.MaterialAttributeType.BOOL) + self.assertEqual(material.attribute_type("DoubleSided"), trade.MaterialAttributeType.BOOL) + self.assertEqual(material.attribute_type(trade.MaterialAttribute.DOUBLE_SIDED), trade.MaterialAttributeType.BOOL) + + self.assertEqual(material.attribute(1, 3), 13) + self.assertEqual(material.attribute(1, "LayerFactorTextureCoordinates"), 13) + self.assertEqual(material.attribute(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_COORDINATES), 13) + self.assertEqual(material.attribute("ClearCoat", 3), 13) + self.assertEqual(material.attribute("ClearCoat", "LayerFactorTextureCoordinates"), 13) + self.assertEqual(material.attribute("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_COORDINATES), 13) + self.assertEqual(material.attribute(trade.MaterialLayer.CLEAR_COAT, 3), 13) + self.assertEqual(material.attribute(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTextureCoordinates"), 13) + self.assertEqual(material.attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_COORDINATES), 13) + self.assertEqual(material.attribute(2), True) + self.assertEqual(material.attribute("DoubleSided"), True) + self.assertEqual(material.attribute(trade.MaterialAttribute.DOUBLE_SIDED), True) + + self.assertTrue(material.is_double_sided) + self.assertEqual(material.alpha_mode, trade.MaterialAlphaMode.MASK) + self.assertEqual(material.alpha_mask, 0.36899998784065247) + + def test_attribute_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + material = importer.material(0) + + # Boolean, scalar, vector, matrix + self.assertEqual(material.attribute(trade.MaterialAttribute.DOUBLE_SIDED), True) + self.assertEqual(material.attribute(trade.MaterialAttribute.ALPHA_MASK), 0.36899998784065247) + self.assertEqual(material.attribute(trade.MaterialAttribute.BASE_COLOR), Vector4(0.3, 0.4, 0.5, 0.8)) + self.assertEqual(material.attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_MATRIX), Matrix3( + (1.0, 0.0, 0.0), + (0.0, 1.0, 0.0), + (0.25, -0.5, 1.0))) + + # Texture swizzle, string + self.assertEqual(material.attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.ROUGHNESS_TEXTURE_SWIZZLE), trade.MaterialTextureSwizzle.G) + self.assertEqual(material.attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_NAME), "ClearCoat") + + def test_attribute_access_unsupported_format(self): + # TODO test this once Assimp or Ufbx or serialized data loading exists + # to have a Buffer or a Pointer attribute + pass + + def test_layer_oob(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + # This adds extra Diffuse attributes for BaseColor, don't want + importer.configuration['phongMaterialFallback'] = False + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + material = importer.material(0) + material_empty = importer.material(1) + material_empty_layer = importer.material(2) + + # TODO these are all printed with '' around except for an IndexError, + # why? Is that implicit behavior of the KeyError? Ugh?? + # https://stackoverflow.com/a/24999035 + with self.assertRaisesRegex(IndexError, "index 3 out of range for 2 layers"): + # Passing 2 works + material.attribute_data_offset(3) + with self.assertRaisesRegex(KeyError, "FlearFoat not found among 2 layers"): + material.layer_id("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_id(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_name(2) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_factor(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.layer_factor("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_factor(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_factor_texture(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.layer_factor_texture("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_factor_texture(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_factor_texture_swizzle(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.layer_factor_texture_swizzle("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_factor_texture_swizzle(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_factor_texture_matrix(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.layer_factor_texture_matrix("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_factor_texture_matrix(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_factor_texture_coordinates(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.layer_factor_texture_coordinates("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_factor_texture_coordinates(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.layer_factor_texture_layer(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.layer_factor_texture_layer("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.layer_factor_texture_layer(trade.MaterialLayer.CLEAR_COAT) + + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_count(2) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_count("FlearFoat") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_count(trade.MaterialLayer.CLEAR_COAT) + + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.has_attribute(2, "LayerFactor") + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.has_attribute(2, trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.has_attribute("FlearFoat", "LayerFactor") + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.has_attribute("FlearFoat", trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.has_attribute(trade.MaterialLayer.CLEAR_COAT, "LayerFactor") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.has_attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR) + + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_id(2, "LayerFactor") + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_id(2, trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_id("FlearFoat", "LayerFactor") + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_id("FlearFoat", trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_id(trade.MaterialLayer.CLEAR_COAT, "LayerFactor") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_id(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR) + + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_name(2, 0) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_name("FlearFoat", 0) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_name(trade.MaterialLayer.CLEAR_COAT, 0) + + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_type(2, 0) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_type(2, "LayerFactor") + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute_type(2, trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_type("FlearFoat", 0) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_type("FlearFoat", "LayerFactor") + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute_type("FlearFoat", trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_type(trade.MaterialLayer.CLEAR_COAT, 0) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_type(trade.MaterialLayer.CLEAR_COAT, "LayerFactor") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute_type(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR) + + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute(2, 0) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute(2, "LayerFactor") + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 layers"): + material.attribute(2, trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute("FlearFoat", 0) + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute("FlearFoat", "LayerFactor") + with self.assertRaisesRegex(KeyError, "name FlearFoat not found among 2 layers"): + material.attribute("FlearFoat", trade.MaterialAttribute.LAYER_FACTOR) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute(trade.MaterialLayer.CLEAR_COAT, 0) + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute(trade.MaterialLayer.CLEAR_COAT, "LayerFactor") + with self.assertRaisesRegex(KeyError, "MaterialLayer.CLEAR_COAT not found among 1 layers"): + material_empty.attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR) + + def test_attribute_oob(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + # This adds extra Diffuse attributes for BaseColor, don't want + importer.configuration['phongMaterialFallback'] = False + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + material = importer.material(0) + material_empty_layer = importer.material(2) + + # TODO these are all printed with '' around except for an IndexError, + # why? Is that implicit behavior of the KeyError? Ugh?? + # https://stackoverflow.com/a/24999035 + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer 0"): + material.layer_factor_texture(0) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer ClearCoat"): + material_empty_layer.layer_factor_texture("ClearCoat") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in MaterialLayer.CLEAR_COAT"): + material_empty_layer.layer_factor_texture(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer 0"): + material.layer_factor_texture_swizzle(0) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer ClearCoat"): + material_empty_layer.layer_factor_texture_swizzle("ClearCoat") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in MaterialLayer.CLEAR_COAT"): + material_empty_layer.layer_factor_texture_swizzle(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer 0"): + material.layer_factor_texture_matrix(0) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer ClearCoat"): + material_empty_layer.layer_factor_texture_matrix("ClearCoat") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in MaterialLayer.CLEAR_COAT"): + material_empty_layer.layer_factor_texture_matrix(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer 0"): + material.layer_factor_texture_coordinates(0) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer ClearCoat"): + material_empty_layer.layer_factor_texture_coordinates("ClearCoat") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in MaterialLayer.CLEAR_COAT"): + material_empty_layer.layer_factor_texture_coordinates(trade.MaterialLayer.CLEAR_COAT) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer 0"): + material.layer_factor_texture_layer(0) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in layer ClearCoat"): + material_empty_layer.layer_factor_texture_layer("ClearCoat") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE not found in MaterialLayer.CLEAR_COAT"): + material_empty_layer.layer_factor_texture_layer(trade.MaterialLayer.CLEAR_COAT) + + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in layer 1"): + material.attribute_id(1, "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in layer 1"): + material.attribute_id(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in layer ClearCoat"): + material.attribute_id("ClearCoat", "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in layer ClearCoat"): + material.attribute_id("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in MaterialLayer.CLEAR_COAT"): + material.attribute_id(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in MaterialLayer.CLEAR_COAT"): + material.attribute_id(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "attribute DiffuseTexure not found in the base material"): + material.attribute_id("DiffuseTexure") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.DIFFUSE_TEXTURE not found in the base material"): + material.attribute_id(trade.MaterialAttribute.DIFFUSE_TEXTURE) + + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in layer 1"): + material.attribute_name(1, 8) + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in layer ClearCoat"): + material.attribute_name("ClearCoat", 8) + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in MaterialLayer.CLEAR_COAT"): + material.attribute_name(trade.MaterialLayer.CLEAR_COAT, 8) + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 attributes in the base material"): + material.attribute_name(3) + + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in layer 1"): + material.attribute_type(1, 8) + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in layer ClearCoat"): + material.attribute_type("ClearCoat", 8) + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in MaterialLayer.CLEAR_COAT"): + material.attribute_type(trade.MaterialLayer.CLEAR_COAT, 8) + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in layer 1"): + material.attribute_type(1, "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in layer ClearCoat"): + material.attribute_type("ClearCoat", "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in MaterialLayer.CLEAR_COAT"): + material.attribute_type(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in layer 1"): + material.attribute_type(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in layer ClearCoat"): + material.attribute_type("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in MaterialLayer.CLEAR_COAT"): + material.attribute_type(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 attributes in the base material"): + material.attribute_type(3) + with self.assertRaisesRegex(KeyError, "attribute DiffuseTexure not found in the base material"): + material.attribute_type("DiffuseTexure") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.DIFFUSE_TEXTURE not found in the base material"): + material.attribute_type(trade.MaterialAttribute.DIFFUSE_TEXTURE) + + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in layer 1"): + material.attribute(1, 8) + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in layer ClearCoat"): + material.attribute("ClearCoat", 8) + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 attributes in MaterialLayer.CLEAR_COAT"): + material.attribute(trade.MaterialLayer.CLEAR_COAT, 8) + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in layer 1"): + material.attribute(1, "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in layer ClearCoat"): + material.attribute("ClearCoat", "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "attribute LayerFactorTextureSwizzle not found in MaterialLayer.CLEAR_COAT"): + material.attribute(trade.MaterialLayer.CLEAR_COAT, "LayerFactorTextureSwizzle") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in layer 1"): + material.attribute(1, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in layer ClearCoat"): + material.attribute("ClearCoat", trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(KeyError, "MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE not found in MaterialLayer.CLEAR_COAT"): + material.attribute(trade.MaterialLayer.CLEAR_COAT, trade.MaterialAttribute.LAYER_FACTOR_TEXTURE_SWIZZLE) + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 attributes in the base material"): + material.attribute(3) + with self.assertRaisesRegex(KeyError, "attribute DiffuseTexure not found in the base material"): + material.attribute("DiffuseTexure") + with self.assertRaisesRegex(KeyError, "MaterialAttribute.DIFFUSE_TEXTURE not found in the base material"): + material.attribute(trade.MaterialAttribute.DIFFUSE_TEXTURE) + class MeshData(unittest.TestCase): def test_custom_attribute(self): # Creating a custom attribute @@ -1353,6 +1756,17 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(AssertionError, "no file opened"): importer.mesh('') + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.material_count + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.material_for_name('') + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.material_name(0) + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.material(0) + with self.assertRaisesRegex(AssertionError, "no file opened"): + importer.material('') + with self.assertRaisesRegex(AssertionError, "no file opened"): importer.texture_count with self.assertRaisesRegex(AssertionError, "no file opened"): @@ -1408,6 +1822,9 @@ class Importer(unittest.TestCase): mesh_importer = trade.ImporterManager().load_and_instantiate('GltfImporter') mesh_importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + material_importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + material_importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + scene_importer = trade.ImporterManager().load_and_instantiate('GltfImporter') scene_importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) @@ -1425,6 +1842,11 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"): mesh_importer.mesh(5) + with self.assertRaisesRegex(IndexError, "index 4 out of range for 4 entries"): + material_importer.material_name(4) + with self.assertRaisesRegex(IndexError, "index 4 out of range for 4 entries"): + material_importer.material(4) + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"): texture_importer.texture_name(3) with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"): @@ -1587,6 +2009,40 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "import failed"): importer.mesh('A broken mesh') + def test_material(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + self.assertEqual(importer.material_count, 4) + self.assertEqual(importer.material_name(2), 'Material with an empty layer') + self.assertEqual(importer.material_for_name('Material with an empty layer'), 2) + + material = importer.material(2) + self.assertEqual(material.layer_count, 2) + + def test_material_by_name(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + material = importer.material("Material with an empty layer") + self.assertEqual(material.layer_count, 2) + + def test_material_by_name_not_found(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + with self.assertRaisesRegex(KeyError, "name Nonexistent not found among 4 entries"): + importer.material('Nonexistent') + + def test_material_failed(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + + with self.assertRaisesRegex(RuntimeError, "import failed"): + importer.material(3) + with self.assertRaisesRegex(RuntimeError, "import failed"): + importer.material("A broken material") + def test_texture(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') @@ -2035,6 +2491,54 @@ class SceneConverter(unittest.TestCase): # TODO implement once there's a converter that doesn't support meshes pass + def test_batch_add_material(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + material = importer.material("Material with an empty layer") + + converter = trade.SceneConverterManager().load_and_instantiate('GltfSceneConverter') + + with tempfile.TemporaryDirectory() as tmp: + filename = os.path.join(tmp, "file.gltf") + converter.begin_file(filename) + self.assertEqual(converter.material_count, 0) + + converter.add(material, "Material with an empty layer") + self.assertEqual(converter.material_count, 1) + + converter.end_file() + + with open(filename, 'r') as f: + self.assertIn("Material with an empty layer", f.read()) + + def test_batch_add_material_failed(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + # References a texture, which means conversion will fail due to the + # texture not being added before + material = importer.material(0) + + converter = trade.SceneConverterManager().load_and_instantiate('GltfSceneConverter') + + with tempfile.TemporaryDirectory() as tmp: + converter.begin_file(os.path.join(tmp, "file.gltf")) + + with self.assertRaisesRegex(RuntimeError, "adding the material failed"): + converter.add(material) + + def test_batch_add_material_not_supported(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + material = importer.material("Material with an empty layer") + + converter = trade.SceneConverterManager().load_and_instantiate('StanfordSceneConverter') + + with tempfile.TemporaryDirectory() as tmp: + converter.begin_file(os.path.join(tmp, "file.ply")) + + with self.assertRaisesRegex(AssertionError, "material conversion not supported"): + converter.add(material) + def test_batch_add_scene(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) @@ -2325,6 +2829,8 @@ class SceneConverter(unittest.TestCase): mesh = importer.mesh('Custom mesh attribute') importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) scene = importer.scene("A default scene that's empty") + importer.open_file(os.path.join(os.path.dirname(__file__), 'material.gltf')) + material = importer.material("Material with an empty layer") importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) @@ -2341,6 +2847,10 @@ class SceneConverter(unittest.TestCase): converter.add(mesh) with self.assertRaisesRegex(AssertionError, "no conversion in progress"): converter.set_mesh_attribute_name(trade.MeshAttribute.CUSTOM(1), 'foobar') + with self.assertRaisesRegex(AssertionError, "no conversion in progress"): + converter.material_count + with self.assertRaisesRegex(AssertionError, "no conversion in progress"): + converter.add(material) with self.assertRaisesRegex(AssertionError, "no conversion in progress"): converter.scene_count with self.assertRaisesRegex(AssertionError, "no conversion in progress"): diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index e8dddeb..6f6c973 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -538,6 +539,58 @@ Containers::Triple(layer, id)); + #define _c(type) _ct(type, type) + /* LCOV_EXCL_START */ + _ct(Bool, bool) + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + /* LCOV_EXCL_STOP */ + _ct(TextureSwizzle, Trade::MaterialTextureSwizzle) + #undef _c + #undef _ct + + /** @todo drop std::string in favor of our own string caster */ + case Trade::MaterialAttributeType::String: + return py::cast(std::string{material.attribute(layer, id)}); + + case Trade::MaterialAttributeType::Pointer: + case Trade::MaterialAttributeType::MutablePointer: + case Trade::MaterialAttributeType::Buffer: + PyErr_Format(PyExc_NotImplementedError, "access to %S is not implemented yet, sorry", py::cast(type).ptr()); + throw py::error_already_set{}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + Containers::Triple accessorsForVertexFormat(const VertexFormat format) { switch(format) { #define _c(format) \ @@ -1190,6 +1243,1062 @@ void trade(py::module_& m) { return pyObjectHolderFor(self).owner; }, "Memory owner"); + py::enum_{m, "MaterialLayer", "Material layer name"} + .value("CLEAR_COAT", Trade::MaterialLayer::ClearCoat) + .def_property_readonly("string", [](Trade::MaterialLayer value) { + /** @todo drop std::string in favor of our own string caster */ + return std::string{Trade::materialLayerName(value)}; + }); + + py::enum_{m, "MaterialAttribute", "Material attribute name"} + .value("LAYER_NAME", Trade::MaterialAttribute::LayerName) + .value("ALPHA_MASK", Trade::MaterialAttribute::AlphaMask) + .value("ALPHA_BLEND", Trade::MaterialAttribute::AlphaBlend) + .value("DOUBLE_SIDED", Trade::MaterialAttribute::DoubleSided) + .value("AMBIENT_COLOR", Trade::MaterialAttribute::AmbientColor) + .value("AMBIENT_TEXTURE", Trade::MaterialAttribute::AmbientTexture) + .value("AMBIENT_TEXTURE_MATRIX", Trade::MaterialAttribute::AmbientTextureMatrix) + .value("AMBIENT_TEXTURE_COORDINATES", Trade::MaterialAttribute::AmbientTextureCoordinates) + .value("AMBIENT_TEXTURE_LAYER", Trade::MaterialAttribute::AmbientTextureLayer) + .value("DIFFUSE_COLOR", Trade::MaterialAttribute::DiffuseColor) + .value("DIFFUSE_TEXTURE", Trade::MaterialAttribute::DiffuseTexture) + .value("DIFFUSE_TEXTURE_MATRIX", Trade::MaterialAttribute::DiffuseTextureMatrix) + .value("DIFFUSE_TEXTURE_COORDINATES", Trade::MaterialAttribute::DiffuseTextureCoordinates) + .value("DIFFUSE_TEXTURE_LAYER", Trade::MaterialAttribute::DiffuseTextureLayer) + .value("SPECULAR_COLOR", Trade::MaterialAttribute::SpecularColor) + .value("SPECULAR_TEXTURE", Trade::MaterialAttribute::SpecularTexture) + .value("SPECULAR_TEXTURE_SWIZZLE", Trade::MaterialAttribute::SpecularTextureSwizzle) + .value("SPECULAR_TEXTURE_MATRIX", Trade::MaterialAttribute::SpecularTextureMatrix) + .value("SPECULAR_TEXTURE_COORDINATES", Trade::MaterialAttribute::SpecularTextureCoordinates) + .value("SPECULAR_TEXTURE_LAYER", Trade::MaterialAttribute::SpecularTextureLayer) + .value("SHININESS", Trade::MaterialAttribute::Shininess) + .value("BASE_COLOR", Trade::MaterialAttribute::BaseColor) + .value("BASE_COLOR_TEXTURE", Trade::MaterialAttribute::BaseColorTexture) + .value("BASE_COLOR_TEXTURE_MATRIX", Trade::MaterialAttribute::BaseColorTextureMatrix) + .value("BASE_COLOR_TEXTURE_COORDINATES", Trade::MaterialAttribute::BaseColorTextureCoordinates) + .value("BASE_COLOR_TEXTURE_LAYER", Trade::MaterialAttribute::BaseColorTextureLayer) + .value("METALNESS", Trade::MaterialAttribute::Metalness) + .value("METALNESS_TEXTURE", Trade::MaterialAttribute::MetalnessTexture) + .value("METALNESS_TEXTURE_SWIZZLE", Trade::MaterialAttribute::MetalnessTextureSwizzle) + .value("METALNESS_TEXTURE_MATRIX", Trade::MaterialAttribute::MetalnessTextureMatrix) + .value("METALNESS_TEXTURE_COORDINATES", Trade::MaterialAttribute::MetalnessTextureCoordinates) + .value("METALNESS_TEXTURE_LAYER", Trade::MaterialAttribute::MetalnessTextureLayer) + .value("ROUGHNESS", Trade::MaterialAttribute::Roughness) + .value("ROUGHNESS_TEXTURE", Trade::MaterialAttribute::RoughnessTexture) + .value("ROUGHNESS_TEXTURE_SWIZZLE", Trade::MaterialAttribute::RoughnessTextureSwizzle) + .value("ROUGHNESS_TEXTURE_MATRIX", Trade::MaterialAttribute::RoughnessTextureMatrix) + .value("ROUGHNESS_TEXTURE_COORDINATES", Trade::MaterialAttribute::RoughnessTextureCoordinates) + .value("ROUGHNESS_TEXTURE_LAYER", Trade::MaterialAttribute::RoughnessTextureLayer) + .value("NONE_ROUGHNESS_METALLIC_TEXTURE", Trade::MaterialAttribute::NoneRoughnessMetallicTexture) + .value("GLOSSINESS", Trade::MaterialAttribute::Glossiness) + .value("GLOSSINESS_TEXTURE", Trade::MaterialAttribute::GlossinessTexture) + .value("GLOSSINESS_TEXTURE_SWIZZLE", Trade::MaterialAttribute::GlossinessTextureSwizzle) + .value("GLOSSINESS_TEXTURE_MATRIX", Trade::MaterialAttribute::GlossinessTextureMatrix) + .value("GLOSSINESS_TEXTURE_COORDINATES", Trade::MaterialAttribute::GlossinessTextureCoordinates) + .value("GLOSSINESS_TEXTURE_LAYER", Trade::MaterialAttribute::GlossinessTextureLayer) + .value("SPECULAR_GLOSSINESS_TEXTURE", Trade::MaterialAttribute::SpecularGlossinessTexture) + .value("NORMAL_TEXTURE", Trade::MaterialAttribute::NormalTexture) + .value("NORMAL_TEXTURE_SCALE", Trade::MaterialAttribute::NormalTextureScale) + .value("NORMAL_TEXTURE_SWIZZLE", Trade::MaterialAttribute::NormalTextureSwizzle) + .value("NORMAL_TEXTURE_MATRIX", Trade::MaterialAttribute::NormalTextureMatrix) + .value("NORMAL_TEXTURE_COORDINATES", Trade::MaterialAttribute::NormalTextureCoordinates) + .value("NORMAL_TEXTURE_LAYER", Trade::MaterialAttribute::NormalTextureLayer) + .value("OCCLUSION_TEXTURE", Trade::MaterialAttribute::OcclusionTexture) + .value("OCCLUSION_TEXTURE_STRENGTH", Trade::MaterialAttribute::OcclusionTextureStrength) + .value("OCCLUSION_TEXTURE_SWIZZLE", Trade::MaterialAttribute::OcclusionTextureSwizzle) + .value("OCCLUSION_TEXTURE_MATRIX", Trade::MaterialAttribute::OcclusionTextureMatrix) + .value("OCCLUSION_TEXTURE_COORDINATES", Trade::MaterialAttribute::OcclusionTextureCoordinates) + .value("OCCLUSION_TEXTURE_LAYER", Trade::MaterialAttribute::OcclusionTextureLayer) + .value("EMISSIVE_COLOR", Trade::MaterialAttribute::EmissiveColor) + .value("EMISSIVE_TEXTURE", Trade::MaterialAttribute::EmissiveTexture) + .value("EMISSIVE_TEXTURE_MATRIX", Trade::MaterialAttribute::EmissiveTextureMatrix) + .value("EMISSIVE_TEXTURE_COORDINATES", Trade::MaterialAttribute::EmissiveTextureCoordinates) + .value("EMISSIVE_TEXTURE_LAYER", Trade::MaterialAttribute::EmissiveTextureLayer) + .value("LAYER_FACTOR", Trade::MaterialAttribute::LayerFactor) + .value("LAYER_FACTOR_TEXTURE", Trade::MaterialAttribute::LayerFactorTexture) + .value("LAYER_FACTOR_TEXTURE_SWIZZLE", Trade::MaterialAttribute::LayerFactorTextureSwizzle) + .value("LAYER_FACTOR_TEXTURE_MATRIX", Trade::MaterialAttribute::LayerFactorTextureMatrix) + .value("LAYER_FACTOR_TEXTURE_COORDINATES", Trade::MaterialAttribute::LayerFactorTextureCoordinates) + .value("LAYER_FACTOR_TEXTURE_LAYER", Trade::MaterialAttribute::LayerFactorTextureLayer) + .value("TEXTURE_MATRIX", Trade::MaterialAttribute::TextureMatrix) + .value("TEXTURE_COORDINATES", Trade::MaterialAttribute::TextureCoordinates) + .value("TEXTURE_LAYER", Trade::MaterialAttribute::TextureLayer) + .def_property_readonly("string", [](Trade::MaterialAttribute value) { + /** @todo drop std::string in favor of our own string caster */ + return std::string{Trade::materialAttributeName(value)}; + }); + + py::enum_{m, "MaterialTextureSwizzle", "Material texture swizzle"} + .value("R", Trade::MaterialTextureSwizzle::R) + .value("G", Trade::MaterialTextureSwizzle::G) + .value("B", Trade::MaterialTextureSwizzle::B) + .value("A", Trade::MaterialTextureSwizzle::A) + .value("RG", Trade::MaterialTextureSwizzle::RG) + .value("GB", Trade::MaterialTextureSwizzle::GB) + .value("GA", Trade::MaterialTextureSwizzle::GA) + .value("BA", Trade::MaterialTextureSwizzle::BA) + .value("RGB", Trade::MaterialTextureSwizzle::RGB) + .value("GBA", Trade::MaterialTextureSwizzle::GBA) + .value("RGBA", Trade::MaterialTextureSwizzle::RGBA) + .def_property_readonly("component_count", [](Trade::MaterialTextureSwizzle value) { + return Trade::materialTextureSwizzleComponentCount(value); + }); + + py::enum_{m, "MaterialAttributeType", "Material attribute type"} + .value("BOOL", Trade::MaterialAttributeType::Bool) + .value("FLOAT", Trade::MaterialAttributeType::Float) + .value("DEG", Trade::MaterialAttributeType::Deg) + .value("RAD", Trade::MaterialAttributeType::Rad) + .value("UNSIGNED_INT", Trade::MaterialAttributeType::UnsignedInt) + .value("INT", Trade::MaterialAttributeType::Int) + .value("UNSIGNED_LONG", Trade::MaterialAttributeType::UnsignedLong) + .value("LONG", Trade::MaterialAttributeType::Long) + .value("VECTOR2", Trade::MaterialAttributeType::Vector2) + .value("VECTOR2UI", Trade::MaterialAttributeType::Vector2ui) + .value("VECTOR2I", Trade::MaterialAttributeType::Vector2i) + .value("VECTOR3", Trade::MaterialAttributeType::Vector3) + .value("VECTOR3UI", Trade::MaterialAttributeType::Vector3ui) + .value("VECTOR3I", Trade::MaterialAttributeType::Vector3i) + .value("VECTOR4", Trade::MaterialAttributeType::Vector4) + .value("VECTOR4UI", Trade::MaterialAttributeType::Vector4ui) + .value("VECTOR4I", Trade::MaterialAttributeType::Vector4i) + .value("MATRIX2X2", Trade::MaterialAttributeType::Matrix2x2) + .value("MATRIX2X3", Trade::MaterialAttributeType::Matrix2x3) + .value("MATRIX2X4", Trade::MaterialAttributeType::Matrix2x4) + .value("MATRIX3X2", Trade::MaterialAttributeType::Matrix3x2) + .value("MATRIX3X3", Trade::MaterialAttributeType::Matrix3x3) + .value("MATRIX3X4", Trade::MaterialAttributeType::Matrix3x4) + .value("MATRIX4X2", Trade::MaterialAttributeType::Matrix4x2) + .value("MATRIX4X3", Trade::MaterialAttributeType::Matrix4x3) + .value("POINTER", Trade::MaterialAttributeType::Pointer) + .value("MUTABLE_POINTER", Trade::MaterialAttributeType::MutablePointer) + .value("STRING", Trade::MaterialAttributeType::String) + .value("BUFFER", Trade::MaterialAttributeType::Buffer) + .value("TEXTURE_SWIZZLE", Trade::MaterialAttributeType::TextureSwizzle); + + py::enum_ materialType{m, "MaterialTypes", "Material types"}; + materialType + .value("FLAT", Trade::MaterialType::Flat) + .value("PHONG", Trade::MaterialType::Phong) + .value("PBR_METALLIC_ROUGHNESS", Trade::MaterialType::PbrMetallicRoughness) + .value("PBR_SPECULAR_GLOSSINESS", Trade::MaterialType::PbrSpecularGlossiness) + .value("PBR_CLEAR_COAT", Trade::MaterialType::PbrClearCoat); + corrade::enumOperators(materialType); + + py::enum_{m, "MaterialAlphaMode", "Material alpha mode"} + .value("OPAQUE", Trade::MaterialAlphaMode::Opaque) + .value("MASK", Trade::MaterialAlphaMode::Mask) + .value("BLEND", Trade::MaterialAlphaMode::Blend); + + py::class_>{m, "MaterialData", "Material data"} + .def_property_readonly("attribute_data_flags", [](const Trade::MaterialData& self) { + return Trade::DataFlag(Containers::enumCastUnderlyingType(self.attributeDataFlags())); + }, "Attribute data flags") + .def_property_readonly("layer_data_flags", [](const Trade::MaterialData& self) { + return Trade::DataFlag(Containers::enumCastUnderlyingType(self.layerDataFlags())); + }, "Layer data flags") + .def_property_readonly("types", [](const Trade::MaterialData& self) { + return Trade::MaterialType(Containers::enumCastUnderlyingType(self.types())); + }, "Material types") + /** @todo as(), how to even implement that? */ + /** @todo direct access to MaterialAttributeData and layer data, once + making custom MaterialData is desirable */ + .def_property_readonly("layer_count", &Trade::MaterialData::layerCount, "Layer count") + .def("attribute_data_offset", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer > self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + return self.attributeDataOffset(layer); + }, "Offset of a layer inside attribute data", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("has_layer", [](const Trade::MaterialData& self, const std::string& layer) { + return self.hasLayer(layer); + }, "Whether a material has given named layer", py::arg("layer")) + .def("has_layer", static_cast(&Trade::MaterialData::hasLayer), "Whether a material has given named layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("layer_id", [](const Trade::MaterialData& self, const std::string& layer) { + if(const Containers::Optional found = self.findLayerId(layer)) + return *found; + + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + }, "ID of a named layer", py::arg("layer")) + .def("layer_id", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + if(const Containers::Optional found = self.findLayerId(layer)) + return *found; + + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + }, "ID of a named layer", py::arg("layer")) + .def("layer_name", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + /** @todo drop std::string in favor of our own string caster */ + return std::string{self.layerName(layer)}; + }, "Layer name", py::arg("layer")) + + /* IMPORTANT: due to pybind11 behavioral differences on (already EOL'd) + Python 3.7 the following overloads need to have the MaterialLayer + and MaterialAttribute overloads *before* the UnsignedInt overloads, + otherwise the integer overload gets picked even if an enum is passed + from Python, causing massive suffering */ + /** @todo drop std::string in favor of our own string caster */ + .def("layer_factor", [](const Trade::MaterialData& self, const std::string& layer) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.layerFactor(*found); + + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + }, "Factor of a named layer", py::arg("layer")) + .def("layer_factor", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.layerFactor(*found); + + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + }, "Factor of a named layer", py::arg("layer")) + .def("layer_factor", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + return self.layerFactor(layer); + }, "Factor of given layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("layer_factor_texture", [](const Trade::MaterialData& self, const std::string& layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return self.layerFactorTexture(*found); + }, "Factor texture ID for a named layer", py::arg("layer")) + .def("layer_factor_texture", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.layerFactorTexture(*found); + }, "Factor texture ID for a named layer", py::arg("layer")) + .def("layer_factor_texture", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(layer, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer); + throw py::error_already_set{}; + } + + return self.layerFactorTexture(layer); + }, "Factor texture ID for given layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("layer_factor_texture_swizzle", [](const Trade::MaterialData& self, const std::string& layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureSwizzle(*found); + }, "Factor texture swizzle for a named layer", py::arg("layer")) + .def("layer_factor_texture_swizzle", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + PyErr_Format(PyExc_KeyError, "name %S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureSwizzle(*found); + }, "Factor texture swizzle for a named layer", py::arg("layer")) + .def("layer_factor_texture_swizzle", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(layer, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer); + throw py::error_already_set{}; + } + + return self.layerFactorTextureSwizzle(layer); + }, "Factor texture swizzle for given layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("layer_factor_texture_matrix", [](const Trade::MaterialData& self, const std::string& layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureMatrix(*found); + }, "Factor texture coordinate transformation matrix for a named layer", py::arg("layer")) + .def("layer_factor_texture_matrix", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureMatrix(*found); + }, "Factor texture coordinate transformation matrix for a named layer", py::arg("layer")) + .def("layer_factor_texture_matrix", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(layer, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer); + throw py::error_already_set{}; + } + + return self.layerFactorTextureMatrix(layer); + }, "Factor texture coordinate transformation matrix for given layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("layer_factor_texture_coordinates", [](const Trade::MaterialData& self, const std::string& layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureCoordinates(*found); + }, "Factor texture coordinate set for a named layer", py::arg("layer")) + .def("layer_factor_texture_coordinates", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureCoordinates(*found); + }, "Factor texture coordinate set for a named layer", py::arg("layer")) + .def("layer_factor_texture_coordinates", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(layer, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer); + throw py::error_already_set{}; + } + + return self.layerFactorTextureCoordinates(layer); + }, "Factor texture coordinate set for given layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("layer_factor_texture_layer", [](const Trade::MaterialData& self, const std::string& layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureLayer(*found); + }, "Factor array texture layer for a named layer", py::arg("layer")) + .def("layer_factor_texture_layer", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + const Containers::Optional found = self.findLayerId(layer); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(*found, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.layerFactorTextureLayer(*found); + }, "Factor array texture layer for a named layer", py::arg("layer")) + .def("layer_factor_texture_layer", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(!self.hasAttribute(layer, Trade::MaterialAttribute::LayerFactorTexture)) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(Trade::MaterialAttribute::LayerFactorTexture).ptr(), layer); + throw py::error_already_set{}; + } + + return self.layerFactorTextureLayer(layer); + }, "Factor array texture layer for given layer", py::arg("layer")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_count", [](const Trade::MaterialData& self, const std::string& layer) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.attributeCount(*found); + + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + }, "Attribute count in a named layer", py::arg("layer")) + .def("attribute_count", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.attributeCount(*found); + + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + }, "Attribute count in a named layer", py::arg("layer")) + .def("attribute_count", [](const Trade::MaterialData& self, const UnsignedInt layer) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + return self.attributeCount(layer); + }, "Attribute count in given layer", py::arg("layer")) + .def("attribute_count", static_cast(&Trade::MaterialData::attributeCount), + "Attribute count in the base material") + /** @todo drop std::string in favor of our own string caster */ + .def("has_attribute", [](const Trade::MaterialData& self, const std::string& layer, const std::string& name) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.hasAttribute(*found, name); + + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + }, "Whether a named material layer has given attribute", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("has_attribute", [](const Trade::MaterialData& self, const std::string& layer, const Trade::MaterialAttribute name) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.hasAttribute(*found, name); + + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + }, "Whether a named material layer has given attribute", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("has_attribute", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const std::string& name) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.hasAttribute(*found, name); + + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + }, "Whether a named material layer has given attribute", py::arg("layer"), py::arg("name")) + .def("has_attribute", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const Trade::MaterialAttribute name) { + if(const Containers::Optional found = self.findLayerId(layer)) + return self.hasAttribute(*found, name); + + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + }, "Whether a named material layer has given attribute", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("has_attribute", [](const Trade::MaterialData& self, const UnsignedInt layer, const std::string& name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + return self.hasAttribute(layer, name); + }, "Whether a material layer has given attribute", py::arg("layer"), py::arg("name")) + .def("has_attribute", [](const Trade::MaterialData& self, const UnsignedInt layer, const Trade::MaterialAttribute name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + return self.hasAttribute(layer, name); + }, "Whether a material layer has given attribute", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("has_attribute", [](const Trade::MaterialData& self, const std::string& name) { + return self.hasAttribute(name); + }, "Whether the base material has given attribute", py::arg("name")) + .def("has_attribute", static_cast(&Trade::MaterialData::hasAttribute), + "Whether the base material has given attribute", py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_id", [](const Trade::MaterialData& self, const std::string& layer, const std::string& name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(*foundLayer, name); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "attribute %s not found in layer %s", name.data(), layer.data()); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_id", [](const Trade::MaterialData& self, const std::string& layer, const Trade::MaterialAttribute name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(*foundLayer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(name).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_id", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const std::string& name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(*foundLayer, name); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "attribute %s not found in %S", name.data(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + .def("attribute_id", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const Trade::MaterialAttribute name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(*foundLayer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(name).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_id", [](const Trade::MaterialData& self, const UnsignedInt layer, const std::string& name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "attribute %s not found in layer %d", name.data(), layer); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in given material layer", py::arg("layer"), py::arg("name")) + .def("attribute_id", [](const Trade::MaterialData& self, const UnsignedInt layer, const Trade::MaterialAttribute name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %d", py::cast(name).ptr(), layer); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in given material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_id", [](const Trade::MaterialData& self, const std::string& name) { + const Containers::Optional found = self.findAttributeId(name); + if(!found) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "attribute %s not found in the base material", name.data()); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in the base material", py::arg("name")) + .def("attribute_id", [](const Trade::MaterialData& self, const Trade::MaterialAttribute name) { + const Containers::Optional found = self.findAttributeId(name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in the base material", py::cast(name).ptr()); + throw py::error_already_set{}; + } + + return *found; + }, "ID of a named attribute in the base material", py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_name", [](const Trade::MaterialData& self, const std::string& layer, const UnsignedInt id) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(*foundLayer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in layer %s", id, self.attributeCount(*foundLayer), layer.data()); + throw py::error_already_set{}; + } + + /** @todo drop std::string in favor of our own string caster */ + return std::string{self.attributeName(*foundLayer, id)}; + }, "Name of an attribute in a named material layer", py::arg("layer"), py::arg("id")) + .def("attribute_name", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const UnsignedInt id) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(*foundLayer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in %S", id, self.attributeCount(*foundLayer), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + /** @todo drop std::string in favor of our own string caster */ + return std::string{self.attributeName(*foundLayer, id)}; + }, "Name of an attribute in a named material layer", py::arg("layer"), py::arg("id")) + .def("attribute_name", [](const Trade::MaterialData& self, const UnsignedInt layer, const UnsignedInt id) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(layer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in layer %u", id, self.attributeCount(layer), layer); + throw py::error_already_set{}; + } + + return std::string{self.attributeName(layer, id)}; + }, "Name of an attribute in given material layer", py::arg("layer"), py::arg("id")) + .def("attribute_name", [](const Trade::MaterialData& self, const UnsignedInt id) { + if(id >= self.attributeCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in the base material", id, self.attributeCount()); + throw py::error_already_set{}; + } + + /** @todo drop std::string in favor of our own string caster */ + return std::string{self.attributeName(id)}; + }, "Name of an attribute in the base material", py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_type", [](const Trade::MaterialData& self, const std::string& layer, const std::string& name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in layer %s", name.data(), layer.data()); + throw py::error_already_set{}; + } + + return self.attributeType(*foundLayer, *found); + }, "Type of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_type", [](const Trade::MaterialData& self, const std::string& layer, const Trade::MaterialAttribute name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(name).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return self.attributeType(*foundLayer, *found); + }, "Type of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_type", [](const Trade::MaterialData& self, const std::string& layer, const UnsignedInt id) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(*foundLayer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in layer %s", id, self.attributeCount(*foundLayer), layer.data()); + throw py::error_already_set{}; + } + + return self.attributeType(*foundLayer, id); + }, "Type of an attribute in a named material layer", py::arg("layer"), py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_type", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const std::string& name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in %S", name.data(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.attributeType(*foundLayer, *found); + }, "Type of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + .def("attribute_type", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const Trade::MaterialAttribute name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(name).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.attributeType(*foundLayer, *found); + }, "Type of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + .def("attribute_type", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const UnsignedInt id) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(*foundLayer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in %S", id, self.attributeCount(*foundLayer), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return self.attributeType(*foundLayer, id); + }, "Type of an attribute in a named material layer", py::arg("layer"), py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_type", [](const Trade::MaterialData& self, const UnsignedInt layer, const std::string& name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in layer %u", name.data(), layer); + throw py::error_already_set{}; + } + + return self.attributeType(layer, *found); + }, "Type of a named attribute in given material layer", py::arg("layer"), py::arg("name")) + .def("attribute_type", [](const Trade::MaterialData& self, const UnsignedInt layer, const Trade::MaterialAttribute name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(name).ptr(), layer); + throw py::error_already_set{}; + } + + return self.attributeType(layer, *found); + }, "Type of a named attribute in given material layer", py::arg("layer"), py::arg("name")) + .def("attribute_type", [](const Trade::MaterialData& self, const UnsignedInt layer, const UnsignedInt id) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(layer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in layer %u", id, self.attributeCount(layer), layer); + throw py::error_already_set{}; + } + + return self.attributeType(layer, id); + }, "Type of an attribute in given material layer", py::arg("layer"), py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute_type", [](const Trade::MaterialData& self, const std::string& name) { + const Containers::Optional found = self.findAttributeId(name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in the base material", name.data()); + throw py::error_already_set{}; + } + + return self.attributeType(*found); + }, "Type of a named attribute in the base material", py::arg("name")) + .def("attribute_type", [](const Trade::MaterialData& self, const Trade::MaterialAttribute name) { + const Containers::Optional found = self.findAttributeId(name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in the base material", py::cast(name).ptr()); + throw py::error_already_set{}; + } + + return self.attributeType(*found); + }, "Type of a named attribute in the base material", py::arg("name")) + .def("attribute_type", [](const Trade::MaterialData& self, const UnsignedInt id) { + if(id >= self.attributeCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in the base material", id, self.attributeCount()); + throw py::error_already_set{}; + } + + return self.attributeType(id); + }, "Type of an attribute in the base material", py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute", [](const Trade::MaterialData& self, const std::string& layer, const std::string& name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in layer %s", name.data(), layer.data()); + throw py::error_already_set{}; + } + + return materialAttribute(self, *foundLayer, *found); + }, "Value of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute", [](const Trade::MaterialData& self, const std::string& layer, const Trade::MaterialAttribute name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %s", py::cast(name).ptr(), layer.data()); + throw py::error_already_set{}; + } + + return materialAttribute(self, *foundLayer, *found); + }, "Value of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute", [](const Trade::MaterialData& self, const std::string& layer, const UnsignedInt id) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u layers", layer.data(), self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(*foundLayer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in layer %s", id, self.attributeCount(*foundLayer), layer.data()); + throw py::error_already_set{}; + } + + return materialAttribute(self, *foundLayer, id); + }, "Value of an attribute in a named material layer", py::arg("layer"), py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const std::string& name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in %S", name.data(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return materialAttribute(self, *foundLayer, *found); + }, "Value of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + .def("attribute", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const Trade::MaterialAttribute name) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in %S", py::cast(name).ptr(), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return materialAttribute(self, *foundLayer, *found); + }, "Value of a named attribute in a named material layer", py::arg("layer"), py::arg("name")) + .def("attribute", [](const Trade::MaterialData& self, const Trade::MaterialLayer layer, const UnsignedInt id) { + const Containers::Optional foundLayer = self.findLayerId(layer); + if(!foundLayer) { + PyErr_Format(PyExc_KeyError, "%S not found among %u layers", py::cast(layer).ptr(), self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(*foundLayer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in %S", id, self.attributeCount(*foundLayer), py::cast(layer).ptr()); + throw py::error_already_set{}; + } + + return materialAttribute(self, *foundLayer, id); + }, "Value of an attribute in a named material layer", py::arg("layer"), py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute", [](const Trade::MaterialData& self, const UnsignedInt layer, const std::string& name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in layer %u", name.data(), layer); + throw py::error_already_set{}; + } + + return materialAttribute(self, layer, *found); + }, "Value of a named attribute in given material layer", py::arg("layer"), py::arg("name")) + .def("attribute", [](const Trade::MaterialData& self, const UnsignedInt layer, const Trade::MaterialAttribute name) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + const Containers::Optional found = self.findAttributeId(layer, name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in layer %u", py::cast(name).ptr(), layer); + throw py::error_already_set{}; + } + + return materialAttribute(self, layer, *found); + }, "Value of a named attribute in given material layer", py::arg("layer"), py::arg("name")) + .def("attribute", [](const Trade::MaterialData& self, const UnsignedInt layer, const UnsignedInt id) { + if(layer >= self.layerCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u layers", layer, self.layerCount()); + throw py::error_already_set{}; + } + + if(id >= self.attributeCount(layer)) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in layer %u", id, self.attributeCount(layer), layer); + throw py::error_already_set{}; + } + + return materialAttribute(self, layer, id); + }, "Value of an attribute in given material layer", py::arg("layer"), py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("attribute", [](const Trade::MaterialData& self, const std::string& name) { + const Containers::Optional found = self.findAttributeId(name); + if(!found) { + PyErr_Format(PyExc_KeyError, "attribute %s not found in the base material", name.data()); + throw py::error_already_set{}; + } + + return materialAttribute(self, 0, *found); + }, "Value of a named attribute in the base material", py::arg("name")) + .def("attribute", [](const Trade::MaterialData& self, const Trade::MaterialAttribute name) { + const Containers::Optional found = self.findAttributeId(name); + if(!found) { + PyErr_Format(PyExc_KeyError, "%S not found in the base material", py::cast(name).ptr()); + throw py::error_already_set{}; + } + + return materialAttribute(self, 0, *found); + }, "Value of a named attribute in the base material", py::arg("name")) + .def("attribute", [](const Trade::MaterialData& self, const UnsignedInt id) { + if(id >= self.attributeCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes in the base material", id, self.attributeCount()); + throw py::error_already_set{}; + } + + return materialAttribute(self, 0, id); + }, "Value of an attribute in the base material", py::arg("id")) + + .def_property_readonly("is_double_sided", &Trade::MaterialData::isDoubleSided, + "Whether a material is double-sided") + .def_property_readonly("alpha_mode", &Trade::MaterialData::alphaMode, + "Alpha mode") + .def_property_readonly("alpha_mask", &Trade::MaterialData::alphaMask, + "Alpha mask"); + 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"}; @@ -1729,6 +2838,58 @@ void trade(py::module_& m) { return {}; }, "String name for given custom mesh attribute", py::arg("name")) + .def_property_readonly("material_count", checkOpened, "Material count") + .def("material_for_name", checkOpenedString, "Material ID for given name", py::arg("name")) + .def("material_name", checkOpenedBoundsReturnsString, "Material name", py::arg("id")) + .def("material", [](Trade::AbstractImporter& self, const UnsignedInt id) { + /** @todo drop in favor of the generic helper once the + OptionalButAlsoPointer backwards compatibility helper is gone */ + if(!self.isOpened()) { + PyErr_SetString(PyExc_AssertionError, "no file opened"); + throw py::error_already_set{}; + } + + if(id >= self.materialCount()) { + PyErr_Format(PyExc_IndexError, "index %u out of range for %u entries", id, self.materialCount()); + throw py::error_already_set{}; + } + + Containers::Optional out = self.material(id); + if(!out) { + PyErr_SetString(PyExc_RuntimeError, "import failed"); + throw py::error_already_set{}; + } + + return *std::move(out); + }, "Material", py::arg("id")) + /** @todo drop std::string in favor of our own string caster */ + .def("material", [](Trade::AbstractImporter& self, const std::string& name) { + /** @todo drop in favor of the generic helper once the + OptionalButAlsoPointer backwards compatibility helper is gone */ + if(!self.isOpened()) { + PyErr_SetString(PyExc_AssertionError, "no file opened"); + throw py::error_already_set{}; + } + + const Int id = self.materialForName(name); + if(id == -1) { + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u entries", name.data(), self.materialCount()); + 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.material(id); + if(!out) { + PyErr_SetString(PyExc_RuntimeError, "import failed"); + throw py::error_already_set{}; + } + + return *std::move(out); + }, "Material for given name", py::arg("name")) + .def_property_readonly("texture_count", checkOpened, "Texture count") .def("texture_for_name", checkOpenedString, "Texture ID for given name", py::arg("name")) .def("texture_name", checkOpenedBoundsReturnsString, "Texture name", py::arg("id")) @@ -2129,6 +3290,29 @@ void trade(py::module_& m) { throw py::error_already_set{}; }, "Add a 2D image", py::arg("image"), py::arg("name") = std::string{}) /** @todo 3D images, once we have data & plugins to test with */ + .def_property_readonly("material_count", [](Trade::AbstractSceneConverter& self) { + if(!self.isConverting()) { + PyErr_SetString(PyExc_AssertionError, "no conversion in progress"); + throw py::error_already_set{}; + } + return self.materialCount(); + }, "Count of added materials") + /** @todo drop std::string in favor of our own string caster */ + .def("add", [](Trade::AbstractSceneConverter& self, const Trade::MaterialData& material, const std::string& name) { + if(!(self.features() >= Trade::SceneConverterFeature::AddMaterials)) { + PyErr_SetString(PyExc_AssertionError, "material conversion not supported"); + throw py::error_already_set{}; + } + if(!self.isConverting()) { + PyErr_SetString(PyExc_AssertionError, "no conversion in progress"); + throw py::error_already_set{}; + } + if(const Containers::Optional out = self.add(material, name)) + return *out; + + PyErr_SetString(PyExc_RuntimeError, "adding the material failed"); + throw py::error_already_set{}; + }, "Add a material", py::arg("material"), py::arg("name") = std::string{}) .def("add_importer_contents", [](Trade::AbstractSceneConverter& self, Trade::AbstractImporter& importer, Trade::SceneContent contents) { if(!self.isConverting()) { PyErr_SetString(PyExc_AssertionError, "no conversion in progress");