Skip to content

Commit

Permalink
TensorToMesh: Node to convert a vertices and faces tensor into a mesh.
Browse files Browse the repository at this point in the history
  • Loading branch information
lucienfostier committed Jan 21, 2025
1 parent feb37d0 commit 14b309b
Show file tree
Hide file tree
Showing 11 changed files with 485 additions and 2 deletions.
4 changes: 2 additions & 2 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -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" ],
},
Expand Down
87 changes: 87 additions & 0 deletions include/GafferML/TensorToMesh.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//////////////////////////////////////////////////////////////////////////
//
// 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 Gaffer
{

IE_CORE_FORWARDDECLARE( StringPlug )

} // namespace Gaffer

namespace GafferML
{

class GAFFERML_API TensorToMesh : public GafferScene::ObjectSource
{

public :

explicit TensorToMesh( const std::string &name=defaultName<TensorToMesh>() );
~TensorToMesh() override;

GAFFER_NODE_DECLARE_TYPE( GafferML::TensorToMesh, TensorToMeshTypeId, GafferScene::SceneNode );

TensorPlug *verticesTensorPlug();
const TensorPlug *verticesTensorPlug() const;

TensorPlug *facesTensorPlug();
const TensorPlug *facesTensorPlug() 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
1 change: 1 addition & 0 deletions include/GafferML/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ enum TypeId
InferenceTypeId = 110455,
TensorReaderTypeId = 110456,
DataToTensorTypeId = 110457,
TensorToMeshTypeId = 110458,

LastTypeId = 110500
};
Expand Down
1 change: 1 addition & 0 deletions python/GafferML/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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() )
Expand Down
153 changes: 153 additions & 0 deletions python/GafferMLTest/TensorToMeshTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
##########################################################################
#
# 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 Vertices 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 ]
vertices = IECore.FloatVectorData( points )
verticesTensor = GafferML.Tensor( vertices, [ 1, 3, 3 ] )
faces = IECore.Int64VectorData( [ 0, 1, 2 ] )
facesTensor = GafferML.Tensor( faces, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["vertices"].setValue( verticesTensor )
tensorToMesh["faces"].setValue( facesTensor )

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 ]
vertices = IECore.FloatVectorData( points )
verticesTensor = GafferML.Tensor( vertices, [ 3, 3 ] )
faces = IECore.Int64VectorData( [ 0, 1, 2 ] )
facesTensor = GafferML.Tensor( faces, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["vertices"].setValue( verticesTensor )
tensorToMesh["faces"].setValue( facesTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid 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 ]
vertices = IECore.FloatVectorData( points )
verticesTensor = GafferML.Tensor( vertices, [ 1, 3, 2 ] )
faces = IECore.Int64VectorData( [ 0, 1, 2 ] )
facesTensor = GafferML.Tensor( faces, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["vertices"].setValue( verticesTensor )
tensorToMesh["faces"].setValue( facesTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid vertices dimensions" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

def testEmptyVertices( self ):
tensorToMesh = GafferML.TensorToMesh()

with self.assertRaisesRegex( Gaffer.ProcessException, "Empty Vertices tensor" ):
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 ]
vertices = IECore.FloatVectorData( points )
verticesTensor = GafferML.Tensor( vertices, [ 1, 3, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["vertices"].setValue( verticesTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Empty Faces tensor" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

def testWrongDataTypeVertices( self ):
points = [ 0, 0, 0, 1, 1, 1, 1, 0, 2 ]
vertices = IECore.IntVectorData( points )
verticesTensor = GafferML.Tensor( vertices, [ 1, 3, 3 ] )
faces = IECore.Int64VectorData( [ 0, 1, 2 ] )
facesTensor = GafferML.Tensor( faces, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["vertices"].setValue( verticesTensor )
tensorToMesh["faces"].setValue( facesTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for vertices 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 ]
vertices = IECore.FloatVectorData( points )
verticesTensor = GafferML.Tensor( vertices, [ 1, 3, 3 ] )
faces = IECore.IntVectorData( [ 0, 1, 2 ] )
facesTensor = GafferML.Tensor( faces, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["vertices"].setValue( verticesTensor )
tensorToMesh["faces"].setValue( facesTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for faces tensor" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/GafferMLTest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions python/GafferMLUI/TensorToMeshUI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
##########################################################################
#
# 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

Gaffer.Metadata.registerNode(

GafferML.TensorToMesh,

plugs = {

"vertices" : [

"nodule:type", "GafferUI::StandardNodule",

],
"faces" : [

"nodule:type", "GafferUI::StandardNodule",

],

}
)
2 changes: 2 additions & 0 deletions python/GafferMLUI/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@
from . import InferenceUI
from . import ImageToTensorUI
from . import TensorToImageUI
from . import InferenceUI
from . import TensorToMeshUI

__import__( "IECore" ).loadConfig( "GAFFER_STARTUP_PATHS", subdirectory = "GafferMLUI" )
Loading

0 comments on commit 14b309b

Please sign in to comment.