From 1794a3ab460d3d653f7ee6b3f973f08563da40d1 Mon Sep 17 00:00:00 2001 From: lucien Date: Tue, 22 Oct 2024 20:06:57 -0700 Subject: [PATCH] TensorToMesh: Node to convert a position and vertexIds tensors into a mesh. --- SConstruct | 4 +- include/GafferML/TensorToMesh.h | 80 ++++++++++ include/GafferML/TypeIds.h | 1 + python/GafferML/__init__.py | 1 + python/GafferMLTest/TensorToMeshTest.py | 147 +++++++++++++++++ python/GafferMLTest/__init__.py | 1 + python/GafferMLUI/TensorToMeshUI.py | 90 +++++++++++ python/GafferMLUI/__init__.py | 1 + python/GafferMLUITest/DocumentationTest.py | 3 +- src/GafferML/TensorToMesh.cpp | 177 +++++++++++++++++++++ src/GafferMLModule/GafferMLModule.cpp | 2 + startup/gui/menus.py | 1 + 12 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 include/GafferML/TensorToMesh.h create mode 100644 python/GafferMLTest/TensorToMeshTest.py create mode 100644 python/GafferMLUI/TensorToMeshUI.py create mode 100644 src/GafferML/TensorToMesh.cpp diff --git a/SConstruct b/SConstruct index 5357f837464..94fd919caf8 100644 --- a/SConstruct +++ b/SConstruct @@ -1119,12 +1119,12 @@ libraries = { "envAppends" : { "CPPPATH" : [ "$ONNX_ROOT/include" ], "LIBPATH" : [ "$ONNX_ROOT/lib" ], - "LIBS" : [ "Gaffer", "GafferImage", "onnxruntime" ], + "LIBS" : [ "Gaffer", "GafferImage", "onnxruntime", "GafferScene", "IECoreScene$CORTEX_LIB_SUFFIX" ], }, "pythonEnvAppends" : { "CPPPATH" : [ "$ONNX_ROOT/include" ], "LIBPATH" : [ "$ONNX_ROOT/lib" ], - "LIBS" : [ "GafferBindings", "GafferImage", "GafferML", "onnxruntime" ], + "LIBS" : [ "GafferBindings", "GafferImage", "GafferML", "onnxruntime", "GafferScene", "IECoreScene$CORTEX_LIB_SUFFIX" ], }, "requiredOptions" : [ "ONNX_ROOT" ], }, diff --git a/include/GafferML/TensorToMesh.h b/include/GafferML/TensorToMesh.h new file mode 100644 index 00000000000..950f2733088 --- /dev/null +++ b/include/GafferML/TensorToMesh.h @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Lucien Fostier. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "GafferML/Export.h" +#include "GafferML/TensorPlug.h" + +#include "GafferScene/ObjectSource.h" +#include "Gaffer/TransformPlug.h" + +namespace GafferML +{ + +class GAFFERML_API TensorToMesh : public GafferScene::ObjectSource +{ + + public : + + explicit TensorToMesh( const std::string &name=defaultName() ); + ~TensorToMesh() override; + + GAFFER_NODE_DECLARE_TYPE( GafferML::TensorToMesh, TensorToMeshTypeId, GafferScene::ObjectSource ); + + TensorPlug *positionTensorPlug(); + const TensorPlug *positionTensorPlug() const; + + TensorPlug *vertexIdsTensorPlug(); + const TensorPlug *vertexIdsTensorPlug() const; + + void affects( const Gaffer::Plug *input, Gaffer::DependencyNode::AffectedPlugsContainer &outputs ) const override; + + protected : + + void hashSource( const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeSource( const Gaffer::Context *context ) const override; + + private : + + + static size_t g_firstPlugIndex; + +}; + +IE_CORE_DECLAREPTR( TensorToMesh ) + +} // namespace GafferML diff --git a/include/GafferML/TypeIds.h b/include/GafferML/TypeIds.h index 95125b0d2a0..607ea775a98 100644 --- a/include/GafferML/TypeIds.h +++ b/include/GafferML/TypeIds.h @@ -48,6 +48,7 @@ enum TypeId InferenceTypeId = 110455, TensorReaderTypeId = 110456, DataToTensorTypeId = 110457, + TensorToMeshTypeId = 110458, LastTypeId = 110500 }; diff --git a/python/GafferML/__init__.py b/python/GafferML/__init__.py index 707fcbc0d8f..658c0e6ffd2 100644 --- a/python/GafferML/__init__.py +++ b/python/GafferML/__init__.py @@ -39,6 +39,7 @@ __import__( "Gaffer" ) __import__( "GafferImage" ) +__import__( "GafferScene" ) if hasattr( os, "add_dll_directory" ) : os.add_dll_directory( ( pathlib.Path( os.environ["ONNX_ROOT"] ) / "lib" ).resolve() ) diff --git a/python/GafferMLTest/TensorToMeshTest.py b/python/GafferMLTest/TensorToMeshTest.py new file mode 100644 index 00000000000..3d18fe06287 --- /dev/null +++ b/python/GafferMLTest/TensorToMeshTest.py @@ -0,0 +1,147 @@ +########################################################################## +# +# Copyright (c) 2024, Lucien Fostier. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import unittest + +import imath + +import IECore + +import Gaffer +import GafferTest +import GafferScene +import GafferML + +class TensorToMeshTest( GafferTest.TestCase ) : + def testNoInput( self ) : + + node = GafferML.TensorToMesh() + with self.assertRaisesRegex( Gaffer.ProcessException, "Empty Position tensor" ) : + node["out"].object("/tensorMesh"), + + def test( self ) : + + points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + self.assertEqual( tensorToMesh["out"].object( "/" ), IECore.NullObject() ) + self.assertEqual( tensorToMesh["out"].transform( "/" ), imath.M44f() ) + self.assertEqual( tensorToMesh["out"].bound( "/" ), imath.Box3f( imath.V3f( 0 ), imath.V3f( 1, 1, 2 ) ) ) + self.assertEqual( tensorToMesh["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "tensorMesh" ] ) ) + + self.assertEqual( tensorToMesh["out"].transform( "/tensorMesh" ), imath.M44f() ) + self.assertEqual( tensorToMesh["out"].bound( "/tensorMesh" ), imath.Box3f( imath.V3f( 0 ), imath.V3f( 1, 1, 2 ) ) ) + self.assertEqual( tensorToMesh["out"].childNames( "/tensorMesh" ), IECore.InternedStringVectorData() ) + + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + expectedValue = [component for vec in mesh["P"].data for component in (vec.x, vec.y, vec.z)] + + self.assertEqual( expectedValue, points ) + self.assertEqual( mesh.numFaces(), 1 ) + self.assertEqual( mesh.verticesPerFace, IECore.IntVectorData( [ 3 ] ) ) + + def testWrongVertexTensorDimension( self ) : + + points = [ 0.0, 0.0, 0.0 , 1.0, 1.0, 0.0, 1.0, 1.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 3, 3 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid position tensor number of dimensions" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testWrongVertexDimension( self ) : + + points = [ 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 2 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid position dimensions" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testEmptyFaces( self ): + points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Empty VertexIds tensor" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testWrongDataTypeVertices( self ): + points = [ 0, 0, 0, 1, 1, 1, 1, 0, 2 ] + position = IECore.IntVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for position tensor" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testWrongDataTypeFaces( self ): + points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + vertexIds = IECore.IntVectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for vertexIds tensor" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferMLTest/__init__.py b/python/GafferMLTest/__init__.py index b926385c6b5..ce1bfdb9b7e 100644 --- a/python/GafferMLTest/__init__.py +++ b/python/GafferMLTest/__init__.py @@ -40,6 +40,7 @@ from .InferenceTest import InferenceTest from .ImageToTensorTest import ImageToTensorTest from .TensorToImageTest import TensorToImageTest +from .TensorToMeshTest import TensorToMeshTest if __name__ == "__main__": import unittest diff --git a/python/GafferMLUI/TensorToMeshUI.py b/python/GafferMLUI/TensorToMeshUI.py new file mode 100644 index 00000000000..ce0d9a52da9 --- /dev/null +++ b/python/GafferMLUI/TensorToMeshUI.py @@ -0,0 +1,90 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferML + +import GafferSceneUI + +Gaffer.Metadata.registerNode( + + GafferML.TensorToMesh, + + "description", + """ + Turns input tensors into a mesh. + """, + + + plugs = { + + "position" : [ + + "description", + """ + The input tensor containing the point positions for the mesh to create. Typically this would be connected + to the output of an Inference node that is doing mesh generation/processing. + """, + + "plugValueWidget:type", "", + "nodule:type", "GafferUI::StandardNodule", + + ], + "vertexIds" : [ + + "description", + """ + The input tensor containing the vertex ids for the mesh to create. Typically this would be connected + to the output of an Inference node that is doing mesh generation/processing. + """, + + "plugValueWidget:type", "", + "nodule:type", "GafferUI::StandardNodule", + + ], + + "out" : [ + + "description", + """ + The output mesh. + """, + + ], + + + } +) diff --git a/python/GafferMLUI/__init__.py b/python/GafferMLUI/__init__.py index 446a2d81fac..6b045220cd6 100644 --- a/python/GafferMLUI/__init__.py +++ b/python/GafferMLUI/__init__.py @@ -38,5 +38,6 @@ from . import InferenceUI from . import ImageToTensorUI from . import TensorToImageUI +from . import TensorToMeshUI __import__( "IECore" ).loadConfig( "GAFFER_STARTUP_PATHS", subdirectory = "GafferMLUI" ) diff --git a/python/GafferMLUITest/DocumentationTest.py b/python/GafferMLUITest/DocumentationTest.py index 45b5fdff387..cb89a2f33df 100644 --- a/python/GafferMLUITest/DocumentationTest.py +++ b/python/GafferMLUITest/DocumentationTest.py @@ -37,6 +37,7 @@ import GafferUITest import GafferImage +import GafferScene import GafferML import GafferMLUI @@ -47,7 +48,7 @@ def test( self ) : self.maxDiff = None self.assertNodesAreDocumented( GafferML, - additionalTerminalPlugTypes = ( GafferImage.ImagePlug, ) + additionalTerminalPlugTypes = ( GafferImage.ImagePlug, GafferScene.ScenePlug ) ) if __name__ == "__main__": diff --git a/src/GafferML/TensorToMesh.cpp b/src/GafferML/TensorToMesh.cpp new file mode 100644 index 00000000000..fa259191bec --- /dev/null +++ b/src/GafferML/TensorToMesh.cpp @@ -0,0 +1,177 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2014, Lucien Fostier. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "GafferML/TensorToMesh.h" + +#include "IECoreScene/MeshPrimitive.h" + + +using namespace std; +using namespace Imath; +using namespace IECore; +using namespace IECoreScene; +using namespace Gaffer; +using namespace GafferML; + +GAFFER_NODE_DEFINE_TYPE( TensorToMesh ); + +size_t TensorToMesh::g_firstPlugIndex = 0; + +TensorToMesh::TensorToMesh( const std::string &name ) + : ObjectSource( name, "tensorMesh" ) +{ + + storeIndexOfNextChild( g_firstPlugIndex ); + + addChild( new TensorPlug( "position" ) ); + addChild( new TensorPlug( "vertexIds" ) ); + +} + +TensorToMesh::~TensorToMesh() +{ +} + +TensorPlug *TensorToMesh::positionTensorPlug() +{ + return getChild( g_firstPlugIndex ); +} + +const TensorPlug *TensorToMesh::positionTensorPlug() const +{ + return getChild( g_firstPlugIndex ); +} + +TensorPlug *TensorToMesh::vertexIdsTensorPlug() +{ + return getChild( g_firstPlugIndex + 1 ); +} + +const TensorPlug *TensorToMesh::vertexIdsTensorPlug() const +{ + return getChild( g_firstPlugIndex + 1 ); +} + +void TensorToMesh::affects( const Plug *input, AffectedPlugsContainer &outputs ) const +{ + ObjectSource::affects( input, outputs ); + if ( input == positionTensorPlug() || input == vertexIdsTensorPlug() ) + { + outputs.push_back(sourcePlug()); + } + +} + +void TensorToMesh::hashSource( const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + positionTensorPlug()->hash( h ); + vertexIdsTensorPlug()->hash( h ); +} + +IECore::ConstObjectPtr TensorToMesh::computeSource( const Context *context ) const +{ + ConstTensorPtr positionTensorData = positionTensorPlug()->getValue(); + + if( !positionTensorData->value() ) + { + throw IECore::Exception( "Empty Position tensor" ); + } + + auto positionShape = positionTensorData->shape(); + if ( positionShape.size() != 3 ) + { + throw IECore::Exception( "Invalid position tensor number of dimensions, should have 3 dimensions" ); + } + + if ( positionShape[2] % 3 != 0 ) + { + throw IECore::Exception( "Invalid position dimensions, only 3d coordinates are supported" ); + } + + if ( positionTensorData->value().GetTensorTypeAndShapeInfo().GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT ) + { + throw IECore::Exception( "Invalid data type input for position tensor, only float is currently supported" ); + } + + const size_t count = positionTensorData->value().GetTensorTypeAndShapeInfo().GetElementCount(); + + const float *sourceData = positionTensorData->value().GetTensorData(); + + ConstTensorPtr vertexIdsTensorData = vertexIdsTensorPlug()->getValue(); + if( !vertexIdsTensorData->value() ) + { + throw IECore::Exception( "Empty VertexIds tensor" ); + } + + if ( vertexIdsTensorData->value().GetTensorTypeAndShapeInfo().GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 ) + { + throw IECore::Exception( "Invalid data type input for vertexIds tensor, only int64 is currently supported" ); + } + + const int64_t *sourceVertexIdsData = vertexIdsTensorData->value().GetTensorData(); + + // Copy out topology + IntVectorDataPtr verticesPerFaceData = new IntVectorData; + vector &verticesPerFace = verticesPerFaceData->writable(); + + IntVectorDataPtr vertexIdsData = new IntVectorData; + vector &vertexIds = vertexIdsData->writable(); + + V3fVectorDataPtr pointsData = new V3fVectorData; + vector &points = pointsData->writable(); + + for( size_t i = 0; i < count; i += 3 ) + { + Imath::V3f v; + for( size_t j = 0; j < 3; j++ ) + { + v[j] = *( sourceData + ( i + j ) ); + } + points.push_back( v ); + } + + int vertexPerFace = vertexIdsTensorData->value().GetTensorTypeAndShapeInfo().GetShape()[1]; + for( int i = 0; i < vertexIdsTensorData->value().GetTensorTypeAndShapeInfo().GetShape()[0]; i++ ) + { + verticesPerFace.push_back(vertexPerFace); + for ( int j = 0; j < vertexPerFace; j++ ) + { + vertexIds.push_back( *( sourceVertexIdsData + i * vertexPerFace + j ) ); + } + } + + return new MeshPrimitive( verticesPerFaceData, vertexIdsData, "linear", pointsData ); +} diff --git a/src/GafferMLModule/GafferMLModule.cpp b/src/GafferMLModule/GafferMLModule.cpp index dc0e32b16cf..031650535d2 100644 --- a/src/GafferMLModule/GafferMLModule.cpp +++ b/src/GafferMLModule/GafferMLModule.cpp @@ -46,6 +46,7 @@ #include "GafferML/Tensor.h" #include "GafferML/TensorPlug.h" #include "GafferML/TensorToImage.h" +#include "GafferML/TensorToMesh.h" #include "IECorePython/RunTimeTypedBinding.h" @@ -237,6 +238,7 @@ BOOST_PYTHON_MODULE( _GafferML ) GafferBindings::DependencyNodeClass() .def( "loadModel", &loadModelWrapper ) ; + GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); diff --git a/startup/gui/menus.py b/startup/gui/menus.py index 260e976bb2c..e4386896ff5 100644 --- a/startup/gui/menus.py +++ b/startup/gui/menus.py @@ -566,6 +566,7 @@ def __usdLightCreator( lightType ) : nodeMenu.append( "/ML/Image To Tensor", GafferML.ImageToTensor, searchText = "ImageToTensor" ) nodeMenu.append( "/ML/Tensor To Image", GafferML.TensorToImage, searchText = "TensorToImage" ) nodeMenu.append( "/ML/Inference", GafferML.Inference, searchText = "Inference" ) + nodeMenu.append( "/ML/Tensor To Mesh", GafferML.TensorToMesh, searchText = "TensorToMesh" ) # Utility nodes