From cfd516f4f8625b61d4f0e6ab54b00241287ced6a Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Wed, 10 Apr 2024 15:14:41 -0700 Subject: [PATCH] MeshPrimitive : Add subdivision options --- include/IECoreScene/MeshPrimitive.h | 50 ++++++- src/IECoreScene/MeshPrimitive.cpp | 129 +++++++++++++++++- .../bindings/MeshPrimitiveBinding.cpp | 39 ++++++ test/IECoreScene/MeshPrimitive.py | 66 +++++++++ 4 files changed, 282 insertions(+), 2 deletions(-) diff --git a/include/IECoreScene/MeshPrimitive.h b/include/IECoreScene/MeshPrimitive.h index 7a0aee1803..649b724e91 100644 --- a/include/IECoreScene/MeshPrimitive.h +++ b/include/IECoreScene/MeshPrimitive.h @@ -38,6 +38,7 @@ #include "IECoreScene/Export.h" #include "IECoreScene/Primitive.h" +#include "IECore/InternedString.h" #include "IECore/VectorTypedData.h" namespace IECoreScene @@ -61,12 +62,30 @@ class IECORESCENE_API MeshPrimitive : public Primitive IE_CORE_DECLAREEXTENSIONOBJECT( MeshPrimitive, MeshPrimitiveTypeId, Primitive ); + //! @name Define supported interpolations + /// \todo : In the future, we hope to use InternedStrings whenever we get/set + /// interpolations. + /// \todo : The meaning of "linear" has ended up being somewhat misaligned to + /// what we actually want. The ideal would probably be if "linear" was instead + /// named "none" - indicating that no subdivision is requested, and there was + /// a new value "bilinear", which indicated that the limit surface is simple + /// polygons, but subdivision is still being requested. + ///////////////////////////////////////////////////////////////////////////// + //@{ + static const IECore::InternedString interpolationLinear; + static const IECore::InternedString interpolationCatmullClark; + static const IECore::InternedString interpolationLoop; + //@} + /// Construct a MeshPrimitive with no faces. MeshPrimitive(); /// Construct a MeshPrimitive. The number of faces specified by verticesPerFace->readable()->size(). /// Copies of the IntVectorData objects are taken rather than references to the initial data. MeshPrimitive( IECore::ConstIntVectorDataPtr verticesPerFace, IECore::ConstIntVectorDataPtr vertexIds, - const std::string &interpolation = "linear", IECore::V3fVectorDataPtr p = nullptr ); + const std::string &interpolation = interpolationLinear.string(), IECore::V3fVectorDataPtr p = nullptr ); + + /// Destructor + ~MeshPrimitive() override; //! @name Topology access /// These functions allow access to get and set topology after construction. @@ -102,6 +121,35 @@ class IECORESCENE_API MeshPrimitive : public Primitive void removeCreases(); //@} + //! @name Subdivision options + /// These parameters control various details that affect the shape of the limit surface + ///////////////////////////////////////////////////////////////////////////// + //@{ + + const IECore::InternedString &getInterpolateBoundary() const; + void setInterpolateBoundary( const IECore::InternedString &interpolateBoundary ); + + static const IECore::InternedString interpolateBoundaryNone; + static const IECore::InternedString interpolateBoundaryEdgeOnly; + static const IECore::InternedString interpolateBoundaryEdgeAndCorner; + + const IECore::InternedString &getFaceVaryingLinearInterpolation() const; + void setFaceVaryingLinearInterpolation( const IECore::InternedString &faceVaryingLinearInterpolation ); + + static const IECore::InternedString faceVaryingLinearInterpolationNone; + static const IECore::InternedString faceVaryingLinearInterpolationCornersOnly; + static const IECore::InternedString faceVaryingLinearInterpolationCornersPlus1; + static const IECore::InternedString faceVaryingLinearInterpolationCornersPlus2; + static const IECore::InternedString faceVaryingLinearInterpolationBoundaries; + static const IECore::InternedString faceVaryingLinearInterpolationAll; + + const IECore::InternedString &getTriangleSubdivisionRule() const; + void setTriangleSubdivisionRule( const IECore::InternedString &triangleSubdivisionRule ); + + static const IECore::InternedString triangleSubdivisionRuleCatmullClark; + static const IECore::InternedString triangleSubdivisionRuleSmooth; + //@} + size_t variableSize( PrimitiveVariable::Interpolation interpolation ) const override; /// Render the mesh diff --git a/src/IECoreScene/MeshPrimitive.cpp b/src/IECoreScene/MeshPrimitive.cpp index e51b731c0e..7825b617a9 100644 --- a/src/IECoreScene/MeshPrimitive.cpp +++ b/src/IECoreScene/MeshPrimitive.cpp @@ -37,10 +37,13 @@ #include "IECoreScene/PolygonIterator.h" #include "IECoreScene/Renderer.h" +#include "IECore/ClassData.h" #include "IECore/MurmurHash.h" #include "boost/format.hpp" +#include "tbb/spin_rw_mutex.h" + #include #include @@ -62,6 +65,9 @@ IndexedIO::EntryID g_cornerSharpnessesEntry("cornerSharpnesses"); IndexedIO::EntryID g_creaseLengthsEntry("creaseLengths"); IndexedIO::EntryID g_creaseIdsEntry("creaseIds"); IndexedIO::EntryID g_creaseSharpnessesEntry("creaseSharpnesses"); +IndexedIO::EntryID g_interpolateBoundaryEntry("interpolateBoundary"); +IndexedIO::EntryID g_faceVaryingLinearInterpolationEntry("faceVaryingLinearInterpolation"); +IndexedIO::EntryID g_triangleSubdivisionRuleEntry("triangleSubdivisionRule"); const IntVectorData *emptyIntVectorData() { @@ -75,14 +81,50 @@ const FloatVectorData *emptyFloatVectorData() return g_d.get(); } +// \todo : Replace these with actual class members once we can break binary compatibility +struct MeshClassData +{ + IECore::InternedString interpolateBoundary; + IECore::InternedString faceVaryingLinearInterpolation; + IECore::InternedString triangleSubdivisionRule; +}; + +// We intentionally don't clean up this static memory at shutdown, because we can't destruct it until +// every mesh has been destructed, and other static globals ( like the ObjectPool ) might be holding onto meshes. +// There's no real problem with leaking memory when the process is shutting down anyway. +static IECore::ClassData< MeshPrimitive, MeshClassData > *g_classData = new IECore::ClassData< MeshPrimitive, MeshClassData >(); + +// This lock must be held in read mode to read or write properties of individual class data entries, +// and held in write mode to add or remove class data entries +static tbb::spin_rw_mutex *g_classDataMutex = new tbb::spin_rw_mutex(); + } // namespace +const IECore::InternedString MeshPrimitive::interpolationLinear( "linear" ); +const IECore::InternedString MeshPrimitive::interpolationCatmullClark( "catmullClark" ); +const IECore::InternedString MeshPrimitive::interpolationLoop( "loop" ); +const IECore::InternedString MeshPrimitive::interpolateBoundaryNone( "none" ); +const IECore::InternedString MeshPrimitive::interpolateBoundaryEdgeOnly( "edgeOnly" ); +const IECore::InternedString MeshPrimitive::interpolateBoundaryEdgeAndCorner( "edgeAndCorner" ); +const IECore::InternedString MeshPrimitive::faceVaryingLinearInterpolationNone( "none" ); +const IECore::InternedString MeshPrimitive::faceVaryingLinearInterpolationCornersOnly( "cornersOnly" ); +const IECore::InternedString MeshPrimitive::faceVaryingLinearInterpolationCornersPlus1( "cornersPlus1" ); +const IECore::InternedString MeshPrimitive::faceVaryingLinearInterpolationCornersPlus2( "cornersPlus2" ); +const IECore::InternedString MeshPrimitive::faceVaryingLinearInterpolationBoundaries( "boundaries" ); +const IECore::InternedString MeshPrimitive::faceVaryingLinearInterpolationAll( "all" ); +const IECore::InternedString MeshPrimitive::triangleSubdivisionRuleCatmullClark( "catmullClark" ); +const IECore::InternedString MeshPrimitive::triangleSubdivisionRuleSmooth( "smooth" ); + const unsigned int MeshPrimitive::m_ioVersion = 0; IE_CORE_DEFINEOBJECTTYPEDESCRIPTION(MeshPrimitive); MeshPrimitive::MeshPrimitive() : m_verticesPerFace( new IntVectorData ), m_vertexIds( new IntVectorData ), m_numVertices( 0 ), m_interpolation( "linear" ), m_minVerticesPerFace( 0 ), m_maxVerticesPerFace( 0 ) { + { + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, true ); + g_classData->create( this, { interpolateBoundaryEdgeAndCorner, faceVaryingLinearInterpolationCornersPlus1, triangleSubdivisionRuleCatmullClark } ); + } removeCorners(); removeCreases(); } @@ -90,6 +132,10 @@ MeshPrimitive::MeshPrimitive() MeshPrimitive::MeshPrimitive( ConstIntVectorDataPtr verticesPerFace, ConstIntVectorDataPtr vertexIds, const std::string &interpolation, V3fVectorDataPtr p ) { + { + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, true ); + g_classData->create( this, { interpolateBoundaryEdgeAndCorner, faceVaryingLinearInterpolationCornersPlus1, triangleSubdivisionRuleCatmullClark } ); + } setTopology( verticesPerFace, vertexIds, interpolation ); if( p ) { @@ -99,6 +145,12 @@ MeshPrimitive::MeshPrimitive( ConstIntVectorDataPtr verticesPerFace, ConstIntVec } } +MeshPrimitive::~MeshPrimitive() +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, true ); + g_classData->erase( this ); +} + size_t MeshPrimitive::numFaces() const { return m_verticesPerFace->readable().size(); @@ -343,6 +395,42 @@ void MeshPrimitive::removeCreases() m_creaseSharpnesses = emptyFloatVectorData(); } +const IECore::InternedString &MeshPrimitive::getInterpolateBoundary() const +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, false ); + return (*g_classData)[ this ].interpolateBoundary; +} + +void MeshPrimitive::setInterpolateBoundary( const IECore::InternedString &interpolateBoundary ) +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, false ); + (*g_classData)[ this ].interpolateBoundary = interpolateBoundary; +} + +const IECore::InternedString &MeshPrimitive::getFaceVaryingLinearInterpolation() const +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, false ); + return (*g_classData)[ this ].faceVaryingLinearInterpolation; +} + +void MeshPrimitive::setFaceVaryingLinearInterpolation( const IECore::InternedString &faceVaryingLinearInterpolation ) +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, false ); + (*g_classData)[ this ].faceVaryingLinearInterpolation = faceVaryingLinearInterpolation; +} + +const IECore::InternedString &MeshPrimitive::getTriangleSubdivisionRule() const +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, false ); + return (*g_classData)[ this ].triangleSubdivisionRule; +} + +void MeshPrimitive::setTriangleSubdivisionRule( const IECore::InternedString &triangleSubdivisionRule ) +{ + tbb::spin_rw_mutex::scoped_lock lock( *g_classDataMutex, false ); + (*g_classData)[ this ].triangleSubdivisionRule = triangleSubdivisionRule; +} + size_t MeshPrimitive::variableSize( PrimitiveVariable::Interpolation interpolation ) const { switch(interpolation) @@ -388,6 +476,9 @@ void MeshPrimitive::copyFrom( const Object *other, IECore::Object::CopyContext * m_creaseLengths = tOther->m_creaseLengths; m_creaseIds = tOther->m_creaseIds; m_creaseSharpnesses = tOther->m_creaseSharpnesses; + setInterpolateBoundary( tOther->getInterpolateBoundary() ); + setFaceVaryingLinearInterpolation( tOther->getFaceVaryingLinearInterpolation() ); + setTriangleSubdivisionRule( tOther->getTriangleSubdivisionRule() ); } void MeshPrimitive::save( IECore::Object::SaveContext *context ) const @@ -416,6 +507,10 @@ void MeshPrimitive::save( IECore::Object::SaveContext *context ) const context->save( m_creaseIds.get(), container.get(), g_creaseIdsEntry ); context->save( m_creaseSharpnesses.get(), container.get(), g_creaseSharpnessesEntry ); } + + container->write( g_interpolateBoundaryEntry, getInterpolateBoundary().string() ); + container->write( g_faceVaryingLinearInterpolationEntry, getFaceVaryingLinearInterpolation().string() ); + container->write( g_triangleSubdivisionRuleEntry, getTriangleSubdivisionRule().string() ); } void MeshPrimitive::load( IECore::Object::LoadContextPtr context ) @@ -454,6 +549,23 @@ void MeshPrimitive::load( IECore::Object::LoadContextPtr context ) { removeCreases(); } + + std::string interpolateBoundary, faceVaryingLinearInterpolation, triangleSubdivisionRule; + if( container->hasEntry( g_interpolateBoundaryEntry ) ) + { + container->read( g_interpolateBoundaryEntry, interpolateBoundary ); + setInterpolateBoundary( interpolateBoundary ); + } + if( container->hasEntry( g_faceVaryingLinearInterpolationEntry ) ) + { + container->read( g_faceVaryingLinearInterpolationEntry, faceVaryingLinearInterpolation ); + setFaceVaryingLinearInterpolation( faceVaryingLinearInterpolation ); + } + if( container->hasEntry( g_triangleSubdivisionRuleEntry ) ) + { + container->read( g_triangleSubdivisionRuleEntry, triangleSubdivisionRule ); + setTriangleSubdivisionRule( triangleSubdivisionRule ); + } } bool MeshPrimitive::isEqualTo( const Object *other ) const @@ -501,6 +613,18 @@ bool MeshPrimitive::isEqualTo( const Object *other ) const { return false; } + if( getInterpolateBoundary() != tOther->getInterpolateBoundary() ) + { + return false; + } + if( getFaceVaryingLinearInterpolation() != tOther->getFaceVaryingLinearInterpolation() ) + { + return false; + } + if( getTriangleSubdivisionRule() != tOther->getTriangleSubdivisionRule() ) + { + return false; + } return true; } @@ -526,6 +650,9 @@ void MeshPrimitive::hash( MurmurHash &h ) const m_creaseIds->hash( h ); m_creaseSharpnesses->hash( h ); h.append( m_interpolation ); + h.append( getInterpolateBoundary() ); + h.append( getFaceVaryingLinearInterpolation() ); + h.append( getTriangleSubdivisionRule() ); } void MeshPrimitive::topologyHash( MurmurHash &h ) const @@ -677,7 +804,7 @@ MeshPrimitivePtr MeshPrimitive::createPlane( const Box2f &b, const Imath::V2i &d nData->setInterpretation( GeometricData::Normal ); Canceller::check( canceller ); nData->writable().resize( p.size(), V3f( 0, 0, 1 ) ); - + result->variables["N"] = PrimitiveVariable( PrimitiveVariable::Vertex, nData ); return result; diff --git a/src/IECoreScene/bindings/MeshPrimitiveBinding.cpp b/src/IECoreScene/bindings/MeshPrimitiveBinding.cpp index b863a87429..0903dccae2 100644 --- a/src/IECoreScene/bindings/MeshPrimitiveBinding.cpp +++ b/src/IECoreScene/bindings/MeshPrimitiveBinding.cpp @@ -95,6 +95,21 @@ MeshPrimitivePtr createSphereWrapper( float radius, float zMin, float zMax, floa return MeshPrimitive::createSphere( radius, zMin, zMax, thetaMax, divisions, canceller ); } +InternedString getInterpolateBoundaryWrapper( const MeshPrimitive &p ) +{ + return p.getInterpolateBoundary(); +} + +InternedString getFaceVaryingLinearInterpolationWrapper( const MeshPrimitive &p ) +{ + return p.getFaceVaryingLinearInterpolation(); +} + +InternedString getTriangleSubdivisionRuleWrapper( const MeshPrimitive &p ) +{ + return p.getTriangleSubdivisionRule(); +} + } // namespace void IECoreSceneModule::bindMeshPrimitive() @@ -122,6 +137,30 @@ void IECoreSceneModule::bindMeshPrimitive() .def( "creaseIds", &creaseIds ) .def( "creaseSharpnesses", &creaseSharpnesses ) .def( "removeCreases", &MeshPrimitive::removeCreases ) + + // Note these are bound as functions, not properties - this is inconsistent with how interpolation is + // bound, but we hope to switch interpolation at some point, see todo above. + .def( "getInterpolateBoundary", &getInterpolateBoundaryWrapper ) + .def( "setInterpolateBoundary", &MeshPrimitive::setInterpolateBoundary ) + .def( "getFaceVaryingLinearInterpolation", &getFaceVaryingLinearInterpolationWrapper ) + .def( "setFaceVaryingLinearInterpolation", &MeshPrimitive::setFaceVaryingLinearInterpolation ) + .def( "getTriangleSubdivisionRule", &getTriangleSubdivisionRuleWrapper ) + .def( "setTriangleSubdivisionRule", &MeshPrimitive::setTriangleSubdivisionRule ) + .def_readonly( "interpolationLinear", MeshPrimitive::interpolationLinear ) + .def_readonly( "interpolationCatmullClark", MeshPrimitive::interpolationCatmullClark ) + .def_readonly( "interpolationLoop", MeshPrimitive::interpolationLoop ) + .def_readonly( "interpolateBoundaryNone", MeshPrimitive::interpolateBoundaryNone ) + .def_readonly( "interpolateBoundaryEdgeOnly", MeshPrimitive::interpolateBoundaryEdgeOnly ) + .def_readonly( "interpolateBoundaryEdgeAndCorner", MeshPrimitive::interpolateBoundaryEdgeAndCorner ) + .def_readonly( "faceVaryingLinearInterpolationNone", MeshPrimitive::faceVaryingLinearInterpolationNone ) + .def_readonly( "faceVaryingLinearInterpolationCornersOnly", MeshPrimitive::faceVaryingLinearInterpolationCornersOnly ) + .def_readonly( "faceVaryingLinearInterpolationCornersPlus1", MeshPrimitive::faceVaryingLinearInterpolationCornersPlus1 ) + .def_readonly( "faceVaryingLinearInterpolationCornersPlus2", MeshPrimitive::faceVaryingLinearInterpolationCornersPlus2 ) + .def_readonly( "faceVaryingLinearInterpolationBoundaries", MeshPrimitive::faceVaryingLinearInterpolationBoundaries ) + .def_readonly( "faceVaryingLinearInterpolationAll", MeshPrimitive::faceVaryingLinearInterpolationAll ) + .def_readonly( "triangleSubdivisionRuleCatmullClark", MeshPrimitive::triangleSubdivisionRuleCatmullClark ) + .def_readonly( "triangleSubdivisionRuleSmooth", MeshPrimitive::triangleSubdivisionRuleSmooth ) + .def( "createBox", &MeshPrimitive::createBox, ( arg_( "bounds" ) ) ).staticmethod( "createBox" ) .def( "createPlane", &createPlaneWrapper, ( arg_( "bounds" ), arg_( "divisions" ) = Imath::V2i( 1 ), arg( "canceller" ) = object() ) ).staticmethod( "createPlane" ) .def( "createSphere", &createSphereWrapper, ( arg_( "radius" ), arg_( "zMin" ) = -1.0f, arg_( "zMax" ) = 1.0f, arg_( "thetaMax" ) = 360.0f, arg_( "divisions" ) = Imath::V2i( 20, 40 ), arg( "canceller" ) = object() ) ).staticmethod( "createSphere" ) diff --git a/test/IECoreScene/MeshPrimitive.py b/test/IECoreScene/MeshPrimitive.py index c024cf4085..7ffd78aca1 100644 --- a/test/IECoreScene/MeshPrimitive.py +++ b/test/IECoreScene/MeshPrimitive.py @@ -53,6 +53,9 @@ def test( self ) : self.assertEqual( m.verticesPerFace, IECore.IntVectorData() ) self.assertEqual( m.vertexIds, IECore.IntVectorData() ) self.assertEqual( m.interpolation, "linear" ) + self.assertEqual( m.getInterpolateBoundary(), IECoreScene.MeshPrimitive.interpolateBoundaryEdgeAndCorner ) + self.assertEqual( m.getFaceVaryingLinearInterpolation(), IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus1 ) + self.assertEqual( m.getTriangleSubdivisionRule(), IECoreScene.MeshPrimitive.triangleSubdivisionRuleCatmullClark ) self.assertEqual( m, m.copy() ) self.assertEqual( m.maxVerticesPerFace(), 0 ) @@ -176,8 +179,12 @@ def testSetInterpolation( self ) : m = IECoreScene.MeshPrimitive() self.assertEqual( m.interpolation, "linear" ) + + hashBefore = m.hash() + m.interpolation = "catmullClark" self.assertEqual( m.interpolation, "catmullClark" ) + self.assertNotEqual( m.hash(), hashBefore ) def testEmptyMeshConstructor( self ) : @@ -529,6 +536,65 @@ def testSaveAndLoadCorners( self ) : m2 = IECore.Object.load( io, "test" ) self.assertEqual( m, m2 ) + def testSubdivOptions( self ) : + self.assertEqual( IECoreScene.MeshPrimitive.interpolateBoundaryNone, "none" ) + self.assertEqual( IECoreScene.MeshPrimitive.interpolateBoundaryEdgeOnly, "edgeOnly" ) + self.assertEqual( IECoreScene.MeshPrimitive.interpolateBoundaryEdgeAndCorner, "edgeAndCorner" ) + + self.assertEqual( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationNone, "none" ) + self.assertEqual( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersOnly, "cornersOnly" ) + self.assertEqual( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus1, "cornersPlus1" ) + self.assertEqual( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus2, "cornersPlus2" ) + self.assertEqual( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationBoundaries, "boundaries" ) + self.assertEqual( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationAll, "all" ) + + self.assertEqual( IECoreScene.MeshPrimitive.triangleSubdivisionRuleCatmullClark, "catmullClark" ) + self.assertEqual( IECoreScene.MeshPrimitive.triangleSubdivisionRuleSmooth, "smooth" ) + + default = IECoreScene.MeshPrimitive.createPlane( imath.Box2f( imath.V2f( 0 ), imath.V2f( 1 ) ) ) + m = IECoreScene.MeshPrimitive.createPlane( imath.Box2f( imath.V2f( 0 ), imath.V2f( 1 ) ) ) + self.assertEqual( m.getInterpolateBoundary(), IECoreScene.MeshPrimitive.interpolateBoundaryEdgeAndCorner ) + self.assertEqual( m.getFaceVaryingLinearInterpolation(), IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus1 ) + self.assertEqual( m.getTriangleSubdivisionRule(), IECoreScene.MeshPrimitive.triangleSubdivisionRuleCatmullClark ) + self.assertEqual( m, default ) + self.assertEqual( m.hash(), default.hash() ) + + m.setInterpolateBoundary( IECoreScene.MeshPrimitive.interpolateBoundaryEdgeOnly ) + self.assertNotEqual( m, default ) + self.assertNotEqual( m.hash(), default.hash() ) + self.assertEqual( m.getInterpolateBoundary(), IECoreScene.MeshPrimitive.interpolateBoundaryEdgeOnly ) + m.setInterpolateBoundary( IECoreScene.MeshPrimitive.interpolateBoundaryEdgeAndCorner ) + self.assertEqual( m, default ) + self.assertEqual( m.hash(), default.hash() ) + m.setFaceVaryingLinearInterpolation( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus2 ) + self.assertEqual( m.getFaceVaryingLinearInterpolation(), IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus2 ) + self.assertNotEqual( m, default ) + self.assertNotEqual( m.hash(), default.hash() ) + m.setFaceVaryingLinearInterpolation( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus1 ) + self.assertEqual( m, default ) + self.assertEqual( m.hash(), default.hash() ) + m.setTriangleSubdivisionRule( IECoreScene.MeshPrimitive.triangleSubdivisionRuleSmooth ) + self.assertEqual( m.getTriangleSubdivisionRule(), IECoreScene.MeshPrimitive.triangleSubdivisionRuleSmooth ) + self.assertNotEqual( m, default ) + self.assertNotEqual( m.hash(), default.hash() ) + + m.setInterpolateBoundary( IECoreScene.MeshPrimitive.interpolateBoundaryEdgeOnly ) + m.setFaceVaryingLinearInterpolation( IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus2 ) + + mCopy = m.copy() + self.assertEqual( mCopy.getInterpolateBoundary(), IECoreScene.MeshPrimitive.interpolateBoundaryEdgeOnly ) + self.assertEqual( mCopy.getFaceVaryingLinearInterpolation(), IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus2 ) + self.assertEqual( m, mCopy ) + + io = IECore.MemoryIndexedIO( IECore.CharVectorData(), [], IECore.IndexedIO.OpenMode.Append ) + + m.save( io, "test" ) + m2 = IECore.Object.load( io, "test" ) + self.assertEqual( m2.getInterpolateBoundary(), IECoreScene.MeshPrimitive.interpolateBoundaryEdgeOnly ) + self.assertEqual( m2.getFaceVaryingLinearInterpolation(), IECoreScene.MeshPrimitive.faceVaryingLinearInterpolationCornersPlus2 ) + self.assertEqual( m.getTriangleSubdivisionRule(), IECoreScene.MeshPrimitive.triangleSubdivisionRuleSmooth ) + self.assertEqual( m, m2 ) + def tearDown( self ) : for f in (