diff --git a/Changes b/Changes index 212f23d2bd..c902a0ea96 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,11 @@ 10.5.x.x (relative to 10.5.13.1) ======== +Features +-------- + +- ObjectMatrix : Added new class providing an object that holds a matrix of child objects. + Fixes ----- diff --git a/include/IECore/ObjectMatrix.h b/include/IECore/ObjectMatrix.h new file mode 100644 index 0000000000..363f22cd3b --- /dev/null +++ b/include/IECore/ObjectMatrix.h @@ -0,0 +1,85 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORE_OBJECTMATRIX_H +#define IECORE_OBJECTMATRIX_H + +#include "IECore/Export.h" +#include "IECore/Object.h" + +namespace IECore +{ + +/// An Object which holds a matrix of child Objects. +class IECORE_API ObjectMatrix : public Object +{ + + public : + + ObjectMatrix( size_t rows = 0, size_t columns = 0 ); + ~ObjectMatrix() override; + + IE_CORE_DECLAREOBJECT( ObjectMatrix, Object ); + + size_t numRows() const; + size_t numColumns() const; + + /// Resizes the matrix, preserving the original positions of its values. + void resize( size_t rows, size_t columns ); + + ObjectPtr *operator[]( size_t row ) + { + return &m_members[ row * m_columns ]; + } + + const ObjectPtr *operator[]( size_t row ) const + { + return &m_members[ row * m_columns ]; + } + + private : + + using MemberContainer = std::vector; + + MemberContainer m_members; + size_t m_rows; + size_t m_columns; + +}; + +IE_CORE_DECLAREPTR( ObjectMatrix ); + +} // namespace IECore + +#endif // IECORE_OBJECTMATRIX_H diff --git a/include/IECore/TypeIds.h b/include/IECore/TypeIds.h index c54b7bf043..28883e12b2 100644 --- a/include/IECore/TypeIds.h +++ b/include/IECore/TypeIds.h @@ -254,6 +254,7 @@ enum TypeId V3iVectorDataBaseTypeId = 386, LensModelTypeId = 387, StandardRadialLensModelTypeId = 388, + ObjectMatrixTypeId = 389, // Remember to update TypeIdBinding.cpp !!! diff --git a/include/IECorePython/ObjectMatrixBinding.h b/include/IECorePython/ObjectMatrixBinding.h new file mode 100644 index 0000000000..0fad79ab1d --- /dev/null +++ b/include/IECorePython/ObjectMatrixBinding.h @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECOREPYTHON_OBJECTMATRIXBINDING_H +#define IECOREPYTHON_OBJECTMATRIXBINDING_H + +#include "IECorePython/Export.h" + +namespace IECorePython +{ +IECOREPYTHON_API void bindObjectMatrix(); +} + +#endif // IECOREPYTHON_OBJECTMATRIXBINDING_H diff --git a/src/IECore/ObjectMatrix.cpp b/src/IECore/ObjectMatrix.cpp new file mode 100644 index 0000000000..99e73bb106 --- /dev/null +++ b/src/IECore/ObjectMatrix.cpp @@ -0,0 +1,173 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, 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 Image Engine Design 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 "IECore/ObjectMatrix.h" + +#include "IECore/MurmurHash.h" + +using namespace IECore; + +IE_CORE_DEFINEOBJECTTYPEDESCRIPTION( ObjectMatrix ); + +ObjectMatrix::ObjectMatrix( size_t rows, size_t columns ) + : m_rows( rows ), m_columns( columns ) +{ + m_members.resize( rows * columns, nullptr ); +} + +ObjectMatrix::~ObjectMatrix() +{ +} + +size_t ObjectMatrix::numRows() const +{ + return m_rows; +} + +size_t ObjectMatrix::numColumns() const +{ + return m_columns; +} + +void ObjectMatrix::resize( size_t rows, size_t columns ) +{ + MemberContainer originalMembers( m_members ); + m_members.clear(); + m_members.resize( rows * columns, nullptr ); + for( size_t i = 0; i < rows && i < m_rows; ++i ) + { + for( size_t j = 0; j < columns && j < m_columns; ++j ) + { + m_members[ i * columns + j ] = originalMembers[ i * m_columns + j ]; + } + } + m_rows = rows; + m_columns = columns; +} + +void ObjectMatrix::copyFrom( const Object *other, CopyContext *context ) +{ + Object::copyFrom( other, context ); + const ObjectMatrix *tOther = static_cast( other ); + m_members.resize( tOther->m_members.size() ); + for( MemberContainer::size_type i = 0; i < m_members.size(); ++i ) + { + if( !tOther->m_members[i] ) + { + m_members[i] = nullptr; + } + else + { + m_members[i] = context->copy( tOther->m_members[i].get() ); + } + } + m_rows = tOther->m_rows; + m_columns = tOther->m_columns; +} + +void ObjectMatrix::save( Object::SaveContext *context ) const +{ + Object::save( context ); + throw IECore::NotImplementedException( "ObjectMatrix::save" ); +} + +void ObjectMatrix::load( Object::LoadContextPtr context ) +{ + Object::load( context ); + throw IECore::NotImplementedException( "ObjectMatrix::load" ); +} + +bool ObjectMatrix::isEqualTo( const Object *other ) const +{ + if( !Object::isEqualTo( other ) ) + { + return false; + } + const ObjectMatrix *tOther = static_cast( other ); + if( m_rows != tOther->m_rows || m_columns != tOther->m_columns ) + { + return false; + } + for( MemberContainer::size_type i = 0; i < m_members.size(); ++i ) + { + if( m_members[i] ) + { + if( !tOther->m_members[i] || !m_members[i]->isEqualTo( tOther->m_members[i].get() ) ) + { + return false; + } + } + else + { + if( tOther->m_members[i] ) + { + return false; + } + } + } + + return true; +} + +void ObjectMatrix::memoryUsage( Object::MemoryAccumulator &a ) const +{ + Object::memoryUsage( a ); + for( const auto &m : m_members ) + { + if( m ) + { + a.accumulate( m.get() ); + } + } + a.accumulate( sizeof( m_rows ) ); + a.accumulate( sizeof( m_columns ) ); +} + +void ObjectMatrix::hash( MurmurHash &h ) const +{ + Object::hash( h ); + for( const auto &m : m_members ) + { + if( m ) + { + m->hash( h ); + } + else + { + h.append( 0 ); + } + } + h.append( m_rows ); + h.append( m_columns ); +} diff --git a/src/IECorePython/ObjectMatrixBinding.cpp b/src/IECorePython/ObjectMatrixBinding.cpp new file mode 100644 index 0000000000..afd1d2cdff --- /dev/null +++ b/src/IECorePython/ObjectMatrixBinding.cpp @@ -0,0 +1,172 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, 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 Image Engine Design 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 "boost/python.hpp" + +#include "IECorePython/RunTimeTypedBinding.h" + +#include "IECorePython/ObjectMatrixBinding.h" + +#include "IECore/ObjectMatrix.h" + +using namespace boost::python; +using namespace IECorePython; +using namespace IECore; + +namespace +{ + +ObjectMatrixPtr constructFromSequence( object o ) +{ + const size_t rows = boost::python::len( o ); + size_t columns = 0; + for( size_t i = 0; i < rows; ++i ) + { + object row = o[i]; + if( !PyList_Check( row.ptr() ) ) + { + PyErr_SetString( PyExc_ValueError, "Each element must be a list" ); + throw_error_already_set(); + } + size_t rowLength = boost::python::len( row ); + if( rowLength > columns ) + { + columns = rowLength; + } + } + + ObjectMatrixPtr result = new ObjectMatrix( rows, columns ); + ObjectMatrix *m = result.get(); + for( size_t i = 0; i < rows; ++i ) + { + for( size_t j = 0; j < columns; ++j ) + { + if( j < (size_t)boost::python::len( o[i] ) ) + { + (*m)[i][j] = extract( o[i][j] ); + } + } + } + + return result; +} + +std::string repr( const ObjectMatrix &m ) +{ + std::stringstream s; + + s << "IECore.ObjectMatrix("; + + if( m.numRows() > 0 ) + { + s << " ["; + + for( size_t x = 0; x < m.numRows(); x++ ) + { + s << " ["; + + for( size_t y = 0; y < m.numColumns(); y++ ) + { + object item( m[x][y] ); + std::string v = call_method< std::string >( item.ptr(), "__repr__" ); + s << " " << v << ( y == m.numColumns() -1 ? " " : "," ); + } + + s << "]" << ( x == m.numRows() - 1 ? " " : "," ); + } + + s << "] "; + } + + s << ")"; + + return s.str(); +} + +size_t convertRowIndex( const ObjectMatrix &m, tuple index ) +{ + int64_t row = extract( index[0] ); + if( row < 0 ) + { + row += m.numRows(); + } + if( row >= (int64_t)m.numRows() || row < 0 ) + { + PyErr_SetString( PyExc_IndexError, "Index out of range" ); + throw_error_already_set(); + } + return row; +} + +size_t convertColumnIndex( const ObjectMatrix &m, tuple index ) +{ + int64_t column = extract( index[1] ); + if( column < 0 ) + { + column += m.numColumns(); + } + if( column >= (int64_t)m.numColumns() || column < 0 ) + { + PyErr_SetString( PyExc_IndexError, "Index out of range" ); + throw_error_already_set(); + } + + return column; +} + +IECore::ObjectPtr getItem( const ObjectMatrix &m, tuple index ) +{ + return m[ convertRowIndex( m, index ) ][ convertColumnIndex( m, index ) ]; +} + +void setItem( ObjectMatrix &m, tuple index, IECore::ObjectPtr value ) +{ + m[ convertRowIndex( m, index ) ][ convertColumnIndex( m, index ) ] = value; +} + +} // namespace + +void IECorePython::bindObjectMatrix() +{ + RunTimeTypedClass() + .def( init< size_t, size_t >( ( arg( "rows" ) = 0, arg( "columns" ) = 0 ) ) ) + .def( "__init__", make_constructor( &constructFromSequence ) ) + .def( "__repr__", &repr ) + .def( "__getitem__", &getItem ) + .def( "__setitem__", &setItem ) + .def( "numRows", &ObjectMatrix::numRows ) + .def( "numColumns", &ObjectMatrix::numColumns ) + .def( "resize", &ObjectMatrix::resize ) + ; +} diff --git a/src/IECorePython/TypeIdBinding.cpp b/src/IECorePython/TypeIdBinding.cpp index bcd6628b64..f9120eaecf 100644 --- a/src/IECorePython/TypeIdBinding.cpp +++ b/src/IECorePython/TypeIdBinding.cpp @@ -292,6 +292,7 @@ void bindTypeId() .value( "V3iVectorDataBase", V3iVectorDataBaseTypeId ) .value( "LensModel", LensModelTypeId ) .value( "StandardRadialLensModel", StandardRadialLensModelTypeId ) + .value( "ObjectMatrix", ObjectMatrixTypeId ) ; converter::registry::push_back( diff --git a/src/IECorePythonModule/IECore.cpp b/src/IECorePythonModule/IECore.cpp index 6c69deee6f..97b5ff0375 100644 --- a/src/IECorePythonModule/IECore.cpp +++ b/src/IECorePythonModule/IECore.cpp @@ -157,6 +157,7 @@ #include "IECorePython/PathMatcherBinding.h" #include "IECorePython/CancellerBinding.h" #include "IECorePython/IndexedIOAlgoBinding.h" +#include "IECorePython/ObjectMatrixBinding.h" #include "IECore/IECore.h" #include "IECore/Version.h" @@ -305,6 +306,7 @@ BOOST_PYTHON_MODULE(_IECore) bindCanceller(); bindIndexedIOAlgo(); bindTBB(); + bindObjectMatrix(); def( "milestoneVersion", &IECore::milestoneVersion ); def( "majorVersion", &IECore::majorVersion ); diff --git a/test/IECore/All.py b/test/IECore/All.py index 5ec910237d..f0b2175445 100644 --- a/test/IECore/All.py +++ b/test/IECore/All.py @@ -151,6 +151,7 @@ from PathMatcherTest import PathMatcherTest from PathMatcherDataTest import PathMatcherDataTest from CancellerTest import CancellerTest +from ObjectMatrixTest import ObjectMatrixTest unittest.TestProgram( testRunner = unittest.TextTestRunner( diff --git a/test/IECore/ObjectMatrixTest.py b/test/IECore/ObjectMatrixTest.py new file mode 100644 index 0000000000..f0493e0055 --- /dev/null +++ b/test/IECore/ObjectMatrixTest.py @@ -0,0 +1,219 @@ +########################################################################## +# +# Copyright (c) 2025, 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 Image Engine Design 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 IECore + +class ObjectMatrixTest( unittest.TestCase ) : + + def test( self ) : + + m = IECore.ObjectMatrix() + self.assertEqual( m.numRows(), 0 ) + self.assertEqual( m.numColumns(), 0 ) + + m = IECore.ObjectMatrix( 4, 3 ) + self.assertEqual( m.numRows(), 4 ) + self.assertEqual( m.numColumns(), 3 ) + + self.assertIsNone( m[0, 0] ) + self.assertIsNone( m[0, 1] ) + m[0, 1] = IECore.IntData( 123 ) + + self.assertIsNone( m[0, 0] ) + self.assertEqual( m[0, 1], IECore.IntData( 123 ) ) + + m[0, 1] = None + self.assertIsNone( m[0, 1] ) + + m[0, 1] = "foo" + self.assertEqual( m[0, 1], IECore.StringData( "foo" ) ) + + m[3, 2] = IECore.StringData( "test" ) + self.assertEqual( m[3, 2], IECore.StringData( "test" ) ) + self.assertEqual( m[-1, -1], IECore.StringData( "test" ) ) + + with self.assertRaises( IndexError ) : + m[7, 0] + m[0, 7] + m[7, 7] + m[-7, -7] + + def testConstructFromSequence( self ) : + + m = IECore.ObjectMatrix( [ [ IECore.IntData( 123 ) ] ] ) + self.assertEqual( m.numRows(), 1 ) + self.assertEqual( m.numColumns(), 1 ) + self.assertEqual( m[0, 0], IECore.IntData( 123 ) ) + + m = IECore.ObjectMatrix( [ [ "foo" ], [ 1 ] ] ) + self.assertEqual( m.numRows(), 2 ) + self.assertEqual( m.numColumns(), 1 ) + self.assertEqual( m[0, 0], IECore.StringData( "foo" ) ) + self.assertEqual( m[1, 0], IECore.IntData( 1 ) ) + + m = IECore.ObjectMatrix( [ + [ IECore.IntData( row * 4 + column ) for column in range( 4 ) ] + for row in range( 3 ) + ] ) + self.assertEqual( m.numRows(), 3 ) + self.assertEqual( m.numColumns(), 4 ) + + for row in range( m.numRows() ) : + for column in range( m.numColumns() ) : + self.assertEqual( m[row, column], IECore.IntData( row * m.numColumns() + column ) ) + + m = IECore.ObjectMatrix( [ + [ IECore.IntData( 1 ), IECore.StringData( "B" ), IECore.FloatData( 1.0 ) ], + [ None, IECore.StringData( "C" ) ] + ] ) + self.assertEqual( m.numRows(), 2 ) + self.assertEqual( m.numColumns(), 3 ) + self.assertEqual( m[0, 0], IECore.IntData( 1 ) ) + self.assertEqual( m[0, 1], IECore.StringData( "B" ) ) + self.assertEqual( m[0, 2], IECore.FloatData( 1.0 ) ) + self.assertEqual( m[1, 0], None ) + self.assertEqual( m[1, 1], IECore.StringData( "C" ) ) + self.assertEqual( m[1, 2], None ) + + with self.assertRaises( ValueError ) : + IECore.ObjectMatrix( [ "notAList" ] ) + IECore.ObjectMatrix( [ [ IECore.IntData( 123 ), IECore.FloatData( 456.0 ) ], "alsoNotAList" ] ) + + def testCompare( self ) : + + m1 = IECore.ObjectMatrix( 4, 4 ) + for row in range( 4 ) : + for column in range( 4 ) : + m1[row, column] = IECore.IntData( row * 4 + column ) + + for value in [ IECore.FloatData( 1.0 ), None ] : + + m2 = m1.copy() + self.assertEqual( m1, m2 ) + self.assertFalse( m2 != m1 ) + + m2[0, 0] = value + self.assertNotEqual( m1, m2 ) + self.assertNotEqual( m2, m1 ) + + m3 = m2.copy() + self.assertEqual( m2, m3 ) + self.assertEqual( m3[0, 0], value ) + + m3[0, 0] = IECore.CompoundData() + self.assertNotEqual( m2, m3 ) + self.assertEqual( m2[0, 0], value ) + self.assertEqual( m3[0, 0], IECore.CompoundData() ) + + m3[0, 0] = m2[0, 0] + self.assertEqual( m2, m3 ) + self.assertEqual( m3, m2 ) + + def testHash( self ) : + + m = IECore.ObjectMatrix( 1, 1 ) + h = m.hash() + + m[0, 0] = IECore.IntData( 10 ) + self.assertNotEqual( m.hash(), h ) + + m1 = IECore.ObjectMatrix( 2, 1 ) + m1[0, 0] = IECore.StringData( "A" ) + m1[1, 0] = IECore.FloatData( 2.0 ) + + m2 = IECore.ObjectMatrix( 1, 2 ) + m2[0, 0] = IECore.StringData( "A" ) + m2[0, 1] = IECore.FloatData( 2.0 ) + self.assertNotEqual( m1.hash(), m2.hash() ) + + def testRepr( self ) : + + m = IECore.ObjectMatrix( 0, 0 ) + self.assertEqual( repr( m ), "IECore.ObjectMatrix()" ) + + m = IECore.ObjectMatrix( 1, 1 ) + self.assertEqual( repr( m ), "IECore.ObjectMatrix( [ [ None ] ] )" ) + + m = IECore.ObjectMatrix( 1, 2 ) + self.assertEqual( repr( m ), "IECore.ObjectMatrix( [ [ None, None ] ] )" ) + + m = IECore.ObjectMatrix( 2, 1 ) + self.assertEqual( repr( m ), "IECore.ObjectMatrix( [ [ None ], [ None ] ] )" ) + + m = IECore.ObjectMatrix( 2, 2 ) + self.assertEqual( repr( m ), "IECore.ObjectMatrix( [ [ None, None ], [ None, None ] ] )" ) + + m = IECore.ObjectMatrix( 3, 2 ) + m[0, 0] = IECore.IntData( 42 ) + m[0, 1] = IECore.StringData( "123" ) + m[1, 0] = IECore.FloatData( 0.1 ) + m[1, 1] = IECore.CompoundData() + m[2, 1] = IECore.StringVectorData( [ "a" ] ) + self.assertEqual( repr( m ), "IECore.ObjectMatrix( [ [ IECore.IntData( 42 ), IECore.StringData( '123' ) ], [ IECore.FloatData( 0.1 ), IECore.CompoundData() ], [ None, IECore.StringVectorData( [ 'a' ] ) ] ] )" ) + + def testResize( self ) : + + m1 = IECore.ObjectMatrix( 3, 3 ) + for row in range( 3 ) : + for column in range( 3 ) : + m1[row, column] = IECore.IntData( row * 3 + column ) + + m2 = m1.copy() + self.assertEqual( m1, m2 ) + + # Enlarge m2. Its values should match their equivalent coordinate in m1. + m2.resize( 5, 4 ) + self.assertNotEqual( m1, m2 ) + + for row in range( m2.numRows() ) : + for column in range( m2.numColumns() ) : + if row < m1.numRows() and column < m1.numColumns() : + self.assertEqual( m1[row, column], m2[row, column] ) + else : + self.assertIsNone( m2[row, column] ) + + # Shrink m2 back to the same size as m1. Both should match. + m2.resize( 3, 3 ) + self.assertEqual( m1, m2 ) + + # Shrink m2 smaller than m1, remaining values should still match their equivalent coordinate in m1. + m2.resize( 2, 2 ) + for row in range( m2.numRows() ) : + for column in range( m2.numRows() ) : + self.assertEqual( m1[row, column], m2[row, column] ) + +if __name__ == "__main__": + unittest.main()