diff --git a/Sofa/framework/Core/CMakeLists.txt b/Sofa/framework/Core/CMakeLists.txt
index 54e7838a0c1..e328e392c67 100644
--- a/Sofa/framework/Core/CMakeLists.txt
+++ b/Sofa/framework/Core/CMakeLists.txt
@@ -145,6 +145,7 @@ set(HEADER_FILES
${SRC_ROOT}/collision/IntersectorFactory.h
${SRC_ROOT}/collision/NarrowPhaseDetection.h
${SRC_ROOT}/collision/Pipeline.h
+ ${SRC_ROOT}/data/Data[Eigen].h
${SRC_ROOT}/loader/BaseLoader.h
${SRC_ROOT}/loader/ImageLoader.h
${SRC_ROOT}/loader/MeshLoader.h
@@ -343,6 +344,7 @@ set(SOURCE_FILES
${SRC_ROOT}/objectmodel/ScriptEvent.cpp
${SRC_ROOT}/objectmodel/Tag.cpp
${SRC_ROOT}/objectmodel/TagSet.cpp
+ ${SRC_ROOT}/data/Data[Eigen].cpp
${SRC_ROOT}/topology/BaseMeshTopology.cpp
${SRC_ROOT}/topology/BaseTopology.cpp
${SRC_ROOT}/topology/BaseTopologyObject.cpp
diff --git a/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].cpp b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].cpp
new file mode 100644
index 00000000000..e07e5253e0c
--- /dev/null
+++ b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].cpp
@@ -0,0 +1,243 @@
+/******************************************************************************
+ * SOFA, Simulation Open-Framework Architecture *
+ * (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+ * *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms of the GNU Lesser General Public License as published by *
+ * the Free Software Foundation; either version 2.1 of the License, or (at *
+ * your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, but WITHOUT *
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+ * for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public License *
+ * along with this program. If not, see . *
+ *******************************************************************************
+ * Authors: The SOFA Team and external contributors (see Authors.txt) *
+ * *
+ * Contact information: contact@sofa-framework.org *
+ ******************************************************************************/
+#include
+
+#include
+
+namespace sofa::core::objectmodel
+{
+
+/// Shared IO implementation for Eigen matrices, partially specialized on the
+/// whole Eigen::Matrix family (dynamic or fixed size,
+/// any scalar type) so that a single implementation serves every matrix type.
+template struct EigenMatrixDataIO; // only the Eigen::Matrix family is specialized
+
+template
+struct EigenMatrixDataIO>
+{
+ using Matrix = Eigen::Matrix;
+
+ /// Reads the dimensions, resizes dynamic matrices accordingly, then reads
+ /// the coefficients in row-major order. Sets failbit and returns false on
+ /// invalid dimensions (negative, or conflicting with a compile-time size)
+ /// or on parsing failure.
+ static bool read(std::istream& in, Matrix& matrix)
+ {
+ Eigen::Index rows = 0;
+ Eigen::Index cols = 0;
+ in >> rows >> cols;
+
+ const bool dimsValid = !in.fail()
+ && rows >= 0 && cols >= 0
+ && (Rows == Eigen::Dynamic || rows == Rows)
+ && (Cols == Eigen::Dynamic || cols == Cols);
+ if (!dimsValid)
+ {
+ in.setstate(std::ios::failbit);
+ return false;
+ }
+
+ if constexpr (Rows == Eigen::Dynamic || Cols == Eigen::Dynamic)
+ {
+ matrix.resize(rows, cols);
+ }
+
+ for (Eigen::Index row = 0; row < rows; ++row)
+ {
+ for (Eigen::Index col = 0; col < cols; ++col)
+ {
+ in >> matrix(row, col);
+ }
+ }
+ return !in.fail();
+ }
+
+ /// Prints the dimensions, then the coefficients in row-major order.
+ static void print(std::ostream& out, const Matrix& matrix)
+ {
+ out << matrix.rows() << ' ' << matrix.cols();
+ for (Eigen::Index row = 0; row < matrix.rows(); ++row)
+ {
+ for (Eigen::Index col = 0; col < matrix.cols(); ++col)
+ {
+ out << ' ' << matrix(row, col);
+ }
+ }
+ }
+};
+
+/// The read() and getValueString() specializations are thin forwarders to the
+/// shared EigenMatrixDataIO helper (see Data[Eigen].h).
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+template<>
+bool SOFA_CORE_API Data::read( const std::string& str )
+{
+ std::istringstream in( str );
+ const bool ok = EigenMatrixDataIO::read( in, *beginEdit() );
+ endEdit();
+ return ok;
+}
+
+template<>
+std::string SOFA_CORE_API Data::getValueString() const
+{
+ std::ostringstream out;
+ EigenMatrixDataIO::print( out, getValue() );
+ return out.str();
+}
+
+} // namespace sofa::core::objectmodel
diff --git a/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].h b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].h
new file mode 100644
index 00000000000..8adf7c6dc48
--- /dev/null
+++ b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].h
@@ -0,0 +1,70 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#pragma once
+
+#include
+#include
+
+/// Support of Eigen matrices in sofa::core::objectmodel::Data.
+///
+/// The string format is "rows cols c00 c01 ... c(rows-1)(cols-1)": the
+/// dimensions, then the coefficients in row-major order, all separated by
+/// whitespaces. Reading resizes dynamic matrices to the given dimensions, so
+/// no pre-sized default value is needed; for fixed-size matrices the
+/// dimensions must match the compile-time sizes, otherwise the read fails.
+///
+/// Both directions are explicit specializations of Data::read() and
+/// Data::getValueString(), declared below and defined in Data[Eigen].cpp.
+/// No operator<< or operator>> is added to namespace
+///
+/// Note: a matrix type that is not listed below cannot be stored in a Data:
+/// the generic Data::read() would be instantiated and fail to compile, as
+/// Eigen matrices have no stream operators.
+namespace sofa::core::objectmodel
+{
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+
+template<> bool SOFA_CORE_API Data::read( const std::string& str );
+template<> std::string SOFA_CORE_API Data::getValueString() const;
+} // namespace sofa::core::objectmodel
diff --git a/Sofa/framework/Core/test/CMakeLists.txt b/Sofa/framework/Core/test/CMakeLists.txt
index 45bc7d7f088..46c068d93e6 100644
--- a/Sofa/framework/Core/test/CMakeLists.txt
+++ b/Sofa/framework/Core/test/CMakeLists.txt
@@ -13,6 +13,7 @@ set(SOURCE_FILES
objectmodel/BaseLink_test.cpp
objectmodel/BaseObjectDescription_test.cpp
objectmodel/Data_test.cpp
+ objectmodel/DataEigen_test.cpp
objectmodel/DataLink_test.cpp
objectmodel/DataFileName_test.cpp
objectmodel/DataFileNameVector_test.cpp
diff --git a/Sofa/framework/Core/test/objectmodel/DataEigen_test.cpp b/Sofa/framework/Core/test/objectmodel/DataEigen_test.cpp
new file mode 100644
index 00000000000..3ba432a4164
--- /dev/null
+++ b/Sofa/framework/Core/test/objectmodel/DataEigen_test.cpp
@@ -0,0 +1,153 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#include
+
+#include
+#include
+#include
+#include
+using sofa::testing::BaseTest ;
+
+namespace sofa {
+
+using namespace core::objectmodel;
+
+class DataEigen_test : public BaseTest
+{
+public:
+ // No default value needed: the dimensions are read from the string and
+ // dynamic matrices are resized accordingly.
+ Data dataMatrixXf;
+ Data dataMatrixXd;
+ Data dataMatrixXi;
+ Data dataMatrix3d;
+};
+
+TEST_F(DataEigen_test, read_dynamicFloatMatrix)
+{
+ EXPECT_TRUE(dataMatrixXf.read("2 2 1 2 3 4"));
+ const Eigen::MatrixXf& m = dataMatrixXf.getValue();
+ ASSERT_EQ(m.rows(), 2);
+ ASSERT_EQ(m.cols(), 2);
+ EXPECT_FLOAT_EQ(m(0, 0), 1.f);
+ EXPECT_FLOAT_EQ(m(0, 1), 2.f);
+ EXPECT_FLOAT_EQ(m(1, 0), 3.f);
+ EXPECT_FLOAT_EQ(m(1, 1), 4.f);
+}
+
+TEST_F(DataEigen_test, read_dynamicDoubleMatrix)
+{
+ EXPECT_TRUE(dataMatrixXd.read("2 3 1.5 2.5 3.5 4.5 5.5 6.5"));
+ const Eigen::MatrixXd& m = dataMatrixXd.getValue();
+ ASSERT_EQ(m.rows(), 2);
+ ASSERT_EQ(m.cols(), 3);
+ EXPECT_DOUBLE_EQ(m(0, 0), 1.5);
+ EXPECT_DOUBLE_EQ(m(0, 2), 3.5);
+ EXPECT_DOUBLE_EQ(m(1, 0), 4.5);
+ EXPECT_DOUBLE_EQ(m(1, 2), 6.5);
+}
+
+TEST_F(DataEigen_test, read_integerMatrix)
+{
+ EXPECT_TRUE(dataMatrixXi.read("2 2 1 2 3 4"));
+ const Eigen::MatrixXi& m = dataMatrixXi.getValue();
+ ASSERT_EQ(m.rows(), 2);
+ ASSERT_EQ(m.cols(), 2);
+ EXPECT_EQ(m(0, 0), 1);
+ EXPECT_EQ(m(0, 1), 2);
+ EXPECT_EQ(m(1, 0), 3);
+ EXPECT_EQ(m(1, 1), 4);
+}
+
+TEST_F(DataEigen_test, read_fixedSizeMatrix)
+{
+ EXPECT_TRUE(dataMatrix3d.read("3 3 1 2 3 4 5 6 7 8 9"));
+ const Eigen::Matrix3d& m = dataMatrix3d.getValue();
+ EXPECT_DOUBLE_EQ(m(0, 0), 1.0);
+ EXPECT_DOUBLE_EQ(m(1, 1), 5.0);
+ EXPECT_DOUBLE_EQ(m(2, 2), 9.0);
+}
+
+TEST_F(DataEigen_test, read_resizesPreviousValue)
+{
+ Data data{Eigen::MatrixXf::Zero(3, 3)};
+ EXPECT_TRUE(data.read("2 2 1 2 3 4"));
+ const Eigen::MatrixXf& m = data.getValue();
+ ASSERT_EQ(m.rows(), 2);
+ ASSERT_EQ(m.cols(), 2);
+ EXPECT_FLOAT_EQ(m(1, 1), 4.f);
+}
+
+TEST_F(DataEigen_test, read_fixedSizeDimensionMismatch_returnsFalse)
+{
+ EXPECT_FALSE(dataMatrix3d.read("2 2 1 2 3 4"));
+}
+
+TEST_F(DataEigen_test, read_invalidString_returnsFalse)
+{
+ EXPECT_FALSE(dataMatrixXf.read("2 2 1 2 abc 4"));
+}
+
+TEST_F(DataEigen_test, read_negativeDimensions_returnsFalse)
+{
+ EXPECT_FALSE(dataMatrixXf.read("-1 2 1 2"));
+}
+
+TEST_F(DataEigen_test, getValueString_format)
+{
+ ASSERT_TRUE(dataMatrixXd.read("2 3 1.5 2.5 3.5 4.5 5.5 6.5"));
+ EXPECT_EQ(dataMatrixXd.getValueString(), "2 3 1.5 2.5 3.5 4.5 5.5 6.5");
+}
+
+TEST_F(DataEigen_test, getValueString_roundTrip)
+{
+ ASSERT_TRUE(dataMatrixXd.read("2 3 1.5 2.5 3.5 4.5 5.5 6.5"));
+ const std::string str = dataMatrixXd.getValueString();
+ EXPECT_FALSE(str.empty());
+
+ Data copy;
+ ASSERT_TRUE(copy.read(str));
+ ASSERT_EQ(copy.getValue().rows(), 2);
+ ASSERT_EQ(copy.getValue().cols(), 3);
+ EXPECT_TRUE(copy.getValue().isApprox(dataMatrixXd.getValue()));
+}
+
+TEST_F(DataEigen_test, getValueString_fixedSizeMatrix)
+{
+ ASSERT_TRUE(dataMatrix3d.read("3 3 1 2 3 4 5 6 7 8 9"));
+
+ Data copy;
+ ASSERT_TRUE(copy.read(dataMatrix3d.getValueString()));
+ EXPECT_TRUE(copy.getValue().isApprox(dataMatrix3d.getValue()));
+}
+
+TEST_F(DataEigen_test, eigenStreamFormat_isUnchanged)
+{
+ // Data[Eigen].h must not add any operator<< to namespace Eigen:
+ // streaming a matrix directly keeps Eigen's own format.
+ ASSERT_TRUE(dataMatrix3d.read("3 3 1 2 3 4 5 6 7 8 9"));
+ std::ostringstream out;
+ out << dataMatrix3d.getValue();
+ EXPECT_EQ(out.str(), "1 2 3\n4 5 6\n7 8 9");
+}
+
+}