diff --git a/Sofa/framework/Core/CMakeLists.txt b/Sofa/framework/Core/CMakeLists.txt
index 5d049ec19bf..439e7724da2 100644
--- a/Sofa/framework/Core/CMakeLists.txt
+++ b/Sofa/framework/Core/CMakeLists.txt
@@ -160,6 +160,7 @@ set(HEADER_FILES
${SRC_ROOT}/objectmodel/BaseLink.h
${SRC_ROOT}/objectmodel/BaseNode.h
${SRC_ROOT}/objectmodel/BaseObjectDescription.h
+ ${SRC_ROOT}/objectmodel/Snapshot.h
${SRC_ROOT}/objectmodel/ClassInfo.h
${SRC_ROOT}/objectmodel/ComponentState.h
${SRC_ROOT}/objectmodel/ConfigurationSetting.h
@@ -178,6 +179,7 @@ set(HEADER_FILES
${SRC_ROOT}/objectmodel/HapticDeviceEvent.h
${SRC_ROOT}/objectmodel/IdleEvent.h
${SRC_ROOT}/objectmodel/JoystickEvent.h
+ ${SRC_ROOT}/objectmodel/SnapshotJSONExporter.h
${SRC_ROOT}/objectmodel/KeypressedEvent.h
${SRC_ROOT}/objectmodel/KeyreleasedEvent.h
${SRC_ROOT}/objectmodel/lifecycle/DeprecatedData.h
@@ -310,6 +312,7 @@ set(SOURCE_FILES
${SRC_ROOT}/objectmodel/BaseLink.cpp
${SRC_ROOT}/objectmodel/BaseNode.cpp
${SRC_ROOT}/objectmodel/BaseObjectDescription.cpp
+ ${SRC_ROOT}/objectmodel/Snapshot.cpp
${SRC_ROOT}/objectmodel/ClassInfo.cpp
${SRC_ROOT}/objectmodel/ComponentState.cpp
${SRC_ROOT}/objectmodel/ConfigurationSetting.cpp
@@ -328,6 +331,7 @@ set(SOURCE_FILES
${SRC_ROOT}/objectmodel/HapticDeviceEvent.cpp
${SRC_ROOT}/objectmodel/IdleEvent.cpp
${SRC_ROOT}/objectmodel/JoystickEvent.cpp
+ ${SRC_ROOT}/objectmodel/SnapshotJSONExporter.cpp
${SRC_ROOT}/objectmodel/KeypressedEvent.cpp
${SRC_ROOT}/objectmodel/KeyreleasedEvent.cpp
${SRC_ROOT}/objectmodel/lifecycle/DeprecatedData.cpp
diff --git a/Sofa/framework/Core/simutest/CMakeLists.txt b/Sofa/framework/Core/simutest/CMakeLists.txt
index 5ec8342f6b6..6095de998c2 100644
--- a/Sofa/framework/Core/simutest/CMakeLists.txt
+++ b/Sofa/framework/Core/simutest/CMakeLists.txt
@@ -10,6 +10,7 @@ set(SOURCE_FILES
objectmodel/BaseContext_test.cpp
objectmodel/BaseLink_simutest.cpp
objectmodel/PathResolver_simutest.cpp
+ objectmodel/Snapshot_test.cpp
)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
diff --git a/Sofa/framework/Core/simutest/objectmodel/Snapshot_test.cpp b/Sofa/framework/Core/simutest/objectmodel/Snapshot_test.cpp
new file mode 100644
index 00000000000..27cbf510ba3
--- /dev/null
+++ b/Sofa/framework/Core/simutest/objectmodel/Snapshot_test.cpp
@@ -0,0 +1,419 @@
+/******************************************************************************
+* 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 "gtest/gtest.h"
+using sofa::core::objectmodel::Base ;
+using sofa::core::objectmodel::ComponentState;
+
+#include
+using sofa::testing::BaseSimulationTest ;
+using sofa::simulation::Node ;
+
+#include
+using sofa::core::objectmodel::BaseComponent;
+
+#include
+using sofa::core::objectmodel::Snapshot;
+
+#include
+using sofa::simulation::SaveSnapshotVisitor;
+
+#include
+
+#include
+using sofa::core::objectmodel::Data;
+using sofa::core::objectmodel::BaseLink;
+using sofa::core::objectmodel::SingleLink;
+using sofa::core::objectmodel::MultiLink;
+using sofa::core::objectmodel::Snapshot;
+using sofa::core::objectmodel::BaseNode;
+
+#include
+#include
+
+class TestComponent : public BaseComponent
+{
+public:
+ SOFA_CLASS(TestComponent, BaseComponent);
+
+ Data d_value;
+ sofa::MultiLink l_target;
+
+ TestComponent()
+ : d_value(initData(&d_value, 3.14f, "pi", "test value"))
+ , l_target(initLink("target","target test"))
+ {
+ this->setName("TestComponent");
+ }
+
+ void saveData(Snapshot::SnapshotObject& snapshot)
+ {
+ for (const auto& dataFields = this->getDataFields(); const auto& data : dataFields)
+ {
+ Snapshot::DataInfo dataInfo;
+ dataInfo.name = data->getName();
+ dataInfo.type = data->getValueTypeString();
+ dataInfo.value = data->getValueString();
+
+ snapshot.m_dataContainer.push_back(dataInfo);
+ }
+ }
+
+ void saveLinks(Snapshot::SnapshotObject& snapshot)
+ {
+ for (const auto& links = this->getLinks(); const auto& link : links)
+ {
+ Snapshot::LinkInfo linkInfo;
+ linkInfo.name = link->getName();
+ linkInfo.type = link->getValueTypeString();
+ linkInfo.value = link->getValueString();
+
+ std::string search = "//";
+ sofa::helper::replaceAll(linkInfo.value, search,"");
+ snapshot.m_linkContainer.push_back(linkInfo);
+ }
+ }
+
+ std::shared_ptr createSnapshotObjectTest(std::vector>& parents) const
+ {
+
+ return this->createSnapshotObject(parents);
+ }
+
+ std::shared_ptr findSnapshotObjectTest(const std::shared_ptr& parents, const std::string& objectname)
+ {
+ return this->findSnapshotObject(parents, objectname);
+ }
+
+};
+
+class Snapshot_test: public BaseSimulationTest
+{
+public:
+ Snapshot_test() {}
+ ~Snapshot_test() override {}
+};
+
+/**
+ * @brief Test of saveDataIn
+ *
+ * This test verifies that saveDataIn save data correctly in a SnapshotObject.
+ *
+ * Test steps:
+ * 1. Create a component (Component) and a snapshot
+ * 2. Save component's data in the snapshot
+ * 3. Check if the snapshot contains the component with expected data
+ *
+ */
+TEST_F(Snapshot_test, saveDataIn)
+{
+ TestComponent Component;
+ auto snapshot = std::make_shared();
+ Component.saveData(*snapshot);
+ for (auto& data : snapshot->m_dataContainer)
+ {
+ if (data.name == "name")
+ {
+ EXPECT_EQ(data.value, "TestComponent");
+ }
+ if(data.name == "pi")
+ {
+ EXPECT_EQ(data.value, "3.14");
+ }
+ }
+}
+
+/**
+ * @brief Test of saveLinksIn
+ *
+ * This test verifies that saveLinksIn save links correctly in a SnapshotObject.
+ *
+ * Test steps:
+ * 1. Create a component (Component) and a snapshot
+ * 2. Save component's links in the snapshot
+ * 3. Check if the snapshot contains the component with expected links
+ *
+ */
+TEST_F(Snapshot_test, saveLinkIn)
+{
+
+ TestComponent tComponent;
+ auto snapshot = std::make_shared();
+
+ tComponent.saveLinks(*snapshot);
+ for (auto& link : snapshot->m_linkContainer)
+ {
+ if (link.name == "name")
+ {
+ EXPECT_EQ(link.value, "@./");
+ }
+ if (link.name == "slaves")
+ {
+ EXPECT_EQ(link.value, "");
+ }
+ if (link.name == "master")
+ {
+ EXPECT_EQ(link.value, "");
+ }
+ }
+}
+
+/**
+ * @brief Test of createSnapshotObject
+ *
+ * This test verifies that createSnapshotObject can find the SnapshotObject in a Snapshot with the component's name.
+ *
+ * Test steps:
+ * 1. Create a component (Component)
+ * 2. Create a SnapshotObject with the function createSnapshotObject
+ * 3. Save Component's data in the SnapshotObject
+ * 4. Verify if every data is correctly saved in the SnapshotObject
+ *
+ */
+TEST_F(Snapshot_test, createSnapshotObject)
+{
+ TestComponent Component;
+
+ std::vector> snapshotParents;
+ auto snapshotObject = Component.createSnapshotObjectTest(snapshotParents);
+
+ snapshotObject->m_name = "snapshotObject";
+ Component.saveData(*snapshotObject);
+
+ EXPECT_NE(snapshotObject, nullptr);
+ EXPECT_EQ(snapshotObject->m_name, "snapshotObject");
+ for (auto& data : snapshotObject->m_dataContainer)
+ {
+ if(data.name == "pi")
+ {
+ EXPECT_EQ(data.value, "3.14");
+ }
+ }
+}
+
+/**
+ * @brief Test of findSnapshotObject
+ *
+ * This test verifies that findSnapshotObject can find the SnapshotObject in a Snapshot with the component's name.
+ *
+ * Test steps:
+ * 1. Create a component (Component) and a graph of with a SnapshotNode
+ * 2. Save Component in the SnapshotNode (as a SnapshotObject)
+ * 3. Use findSnapshotObject to find the SnapshotObject corresponding to Component
+ * 4. Verify if the SnapshotObject has been correctly found
+ *
+ */
+TEST_F(Snapshot_test, findSnapshotObject)
+{
+ TestComponent Component;
+ auto snapshotNode = std::make_shared("root");
+ std::vector> snapshotParents;
+ snapshotParents.push_back(snapshotNode);
+
+ auto snapshot = Component.saveSnapshot(snapshotParents);
+ snapshotNode->components.push_back(*snapshot);
+
+ auto expectedObject = Component.findSnapshotObjectTest(snapshotNode, "TestComponent");
+
+ EXPECT_NE(expectedObject, nullptr);
+ EXPECT_EQ(Component.getName(), expectedObject->m_name);
+}
+
+/**
+ * @brief Test of saveSnapshot
+ *
+ * This test verifies that saveSnapshot save the data to a previously saved snapshot.
+ *
+ * Test steps:
+ * 1. Create a component (Component) and a empty snapshot
+ * 2. Save Component1's data in the snapshot
+ * 3. Verify if the snapshot contains all the data from Component1
+ *
+ */
+TEST_F(Snapshot_test, saveSnapshot)
+{
+ TestComponent Component;
+ auto snapshot = std::make_shared();
+ std::vector> snapshotParents;
+
+ ASSERT_NE(snapshot, nullptr);
+ EXPECT_EQ(snapshot->m_name, "");
+ EXPECT_TRUE(snapshot->m_dataContainer.empty());
+ EXPECT_TRUE(snapshot->m_linkContainer.empty());
+
+ snapshot = Component.saveSnapshot(snapshotParents);
+
+ EXPECT_EQ(snapshot->m_name, "TestComponent");
+ EXPECT_EQ(snapshot->m_dataContainer.size(), 7);
+ EXPECT_EQ(snapshot->m_dataContainer[0].name, "name");
+ EXPECT_EQ(snapshot->m_dataContainer[0].value, "TestComponent");
+ EXPECT_EQ(snapshot->m_dataContainer.back().name, "pi");
+ EXPECT_EQ(snapshot->m_dataContainer.back().value, "3.14");
+ EXPECT_EQ(snapshot->m_linkContainer[0].name, "context");
+ EXPECT_EQ(snapshot->m_linkContainer[0].value, "@./");
+ EXPECT_EQ(snapshot->m_linkContainer.back().name, "target");
+ EXPECT_EQ(snapshot->m_linkContainer.back().value, "");
+
+}
+
+/**
+ * @brief Test of loadDataSnapshot
+ *
+ * This test verifies that loadLinkSnapshot restores the state of data to a previously saved snapshot
+ *
+ * Test steps:
+ * 1. Create a component (Component1) and a snapshot
+ * 2. Save Component1's data in the snapshot
+ * 3. Create another component (Component2) and change the data d_value
+ * 4. Load Component1's data into Component2 with loadDataSnapshot
+ * 5. Verify if Component2 has same value as Component1
+ *
+ */
+TEST_F(Snapshot_test, loadDataSnapshot)
+{
+ TestComponent Component1;
+ auto snapshot = std::make_shared();
+ std::vector> snapshotParents;
+
+ snapshot = Component1.saveSnapshot(snapshotParents);
+
+ TestComponent Component2;
+ Component2.d_value.setValue(0.0f);
+ Component2.loadSnapshot(snapshot);
+
+ EXPECT_EQ(Component2.d_value.getValue(), 3.14f);
+}
+
+/**
+ * @brief Test of loadLinkSnapshot
+ *
+ * This test verifies that loadLinkSnapshot restores the state of a link to a previously saved snapshot.
+ * First, it set up a graph "root" with 3 components: Component1, Component2 and Component3
+ * Each component holds a multi-link l_target pointing to other components
+ *
+ * Test steps:
+ * 1. Add Component2 to Component1's l_target link.
+ * 2. Save a snapshot of Component1 (l_target points to @Component2 only).
+ * 3. Add Component3 to Component1's l_target link.
+ * 4. Verify that l_target now points to both @Component2 and@Component3.
+ * 5. Restore Component1 from the snapshot.
+ * 6. Verify that l_target is back to pointing to @Component2 only,
+ * confirming that it @Component3 was correctly removed by loadLinkSnapshot.
+ *
+ */
+TEST_F(Snapshot_test, loadLinkSnapshot)
+{
+ const SceneInstance scene("root");
+ auto Component1 = sofa::core::objectmodel::New();
+ Component1->setName("Component1");
+ auto Component2 = sofa::core::objectmodel::New();
+ Component2->setName("Component2");
+ auto Component3 = sofa::core::objectmodel::New();
+ Component3->setName("Component3");
+ scene.root->addObject(Component1);
+ scene.root->addObject(Component2);
+ scene.root->addObject(Component3);
+
+ auto snapshotNode = std::make_shared("root");
+ std::vector> snapshotParents;
+ snapshotParents.push_back(snapshotNode);
+
+ TestComponent* ptr = Component2.get();
+ Component1->l_target.add(ptr);
+
+ EXPECT_EQ(Component1->l_target.getValueString(), "@Component2");
+
+ auto snapshotObject1 = std::make_shared();
+ snapshotObject1 = Component1->saveSnapshot(snapshotParents);
+
+ ptr = Component3.get();
+ Component1->l_target.add(ptr);
+
+ EXPECT_EQ(Component1->l_target.getValueString(), "@Component2 @Component3");
+
+ Component1->loadSnapshot(snapshotObject1);
+
+ EXPECT_EQ(Component1->l_target.getValueString(), "@Component2");
+}
+
+/**
+ * @brief Test of SnapshotJSONExporter
+ *
+ * This test verifies the behavior of the export and the import with SnapshotJSONExporter
+ *
+ * Test steps:
+ * 1. Init the scene
+ * 2. Create a snapshot (snapshot)
+ * 3. Run SaveSnapshotVisitor to save the state in m_snapshot
+ * 4. Export m_snapshot to a JSON file and check if the file is valid
+ * 5. Create another snapshot (snapshot_import)
+ * 6. Import the JSON file in snapshot_import
+ * 7. Compare m_snapshot and snapshot_import
+ *
+ */
+TEST_F(Snapshot_test, SnapshotJSONExporter)
+{
+ const std::string scene = R"(
+
+
+
+
+
+
+
+
+
+ )";
+
+ SceneInstance c("xml", scene) ;
+ c.initScene() ;
+
+ Node* root = c.root.get() ;
+
+ std::filesystem::path path = std::filesystem::temp_directory_path() / "test_file.json";
+ auto snapshot = std::make_shared();
+
+ auto visitor = SaveSnapshotVisitor(nullptr, *snapshot);
+ root->execute(visitor);
+
+ exportToJSON(*snapshot, path.string());
+
+ std::ifstream checkFile(path);
+ EXPECT_TRUE(checkFile.good());
+ checkFile.close();
+
+ auto snapshot_import = std::make_shared();
+ importFrom(*snapshot_import, path.string());
+
+ EXPECT_NE(snapshot_import->m_graphRoot,nullptr);
+
+ EXPECT_EQ(snapshot_import->m_graphRoot->m_name,"Root");
+ EXPECT_EQ(snapshot_import->m_graphRoot->components[1].m_name,"DefaultAnimationLoop1");
+ EXPECT_EQ(snapshot_import->m_graphRoot->components[2].m_name,"DefaultVisualManagerLoop1");
+ EXPECT_EQ(snapshot_import->m_graphRoot->children[0]->m_name,"child1");
+ EXPECT_EQ(snapshot_import->m_graphRoot->children[0]->components[0].m_name,"MechanicalObject1");
+
+ std::filesystem::remove(path);
+}
diff --git a/Sofa/framework/Core/src/sofa/core/objectmodel/Base.cpp b/Sofa/framework/Core/src/sofa/core/objectmodel/Base.cpp
index ed3a4d942de..ecf5c501e97 100644
--- a/Sofa/framework/Core/src/sofa/core/objectmodel/Base.cpp
+++ b/Sofa/framework/Core/src/sofa/core/objectmodel/Base.cpp
@@ -20,8 +20,8 @@
* Contact information: contact@sofa-framework.org *
******************************************************************************/
#define SOFA_CORE_OBJECTMODEL_BASE_CPP
-#include
+#include
#include
#include
#include
@@ -37,11 +37,15 @@ using sofa::helper::logging::Message ;
#include
using sofa::helper::getClosestMatch;
+#include
+
#include