Project Item State Saving¶
Overview¶
As explained in Projects and Items, Choreonoid can save project item configurations, states, various data, and the states of various interfaces such as related toolbars and views all together in a project file. By loading the saved project file, you can restore the entire project state and continue your work.
This section explains how to enable saving project item states to project files.
State Store/Restore Functions¶
The Item class defines the following virtual functions for saving and restoring item states to/from project files:
virtual bool store(Archive& archive)
Saves the item state.
virtual bool restore(const Archive& archive)
Restores the item state.
By overriding and implementing these functions in each item class, you can support state saving and restoration for items. Each should return true on successful processing and false on failure. When false is returned, saving to or restoration from the project file is skipped.
These functions exchange information about item states through the archive argument of Archive type. Details about this will be described later.
Similar to Outputting Properties with PutPropertyFunction, when an item type implementing these functions inherits from another item type (not the Item class), you usually need to call the same functions of the parent class. For example, for BarItem inheriting from FooItem, the store and restore functions should be implemented in the following form:
bool BarItem::store(Archive& archive)
{
if(FooItem::store(archive)){
// BarItem's store processing
...
return true;
}
return false;
}
bool BarItem::restore(const Archive& archive)
{
if(FooItem::restore(archive)){
// BarItem's restore processing
...
return true;
}
return false;
}
YAML-Type Structured Data Classes¶
The Archive class, which is the argument for state store/restore functions, inherits from the Mapping class and adds functions related to project saving/restoration. The Mapping class is part of a group of classes called “structured data classes,” through which various data can be stored in a structured manner.
YAML, which prefixes the “YAML-type structured data classes,” is a general-purpose data description language for describing structured data and objects in text. It is primarily defined for serializing data and objects, and is simple, highly readable, supported by various programming languages, and widely used today. Text written in YAML is often saved to files for use. These files are called YAML files.
When using the “YAML-type structured data classes” including Archive, it’s desirable to first have basic knowledge about YAML. If you don’t have this knowledge, please refer to resources like the following to understand the basic specifications and usage:
The “YAML-type structured data classes” express YAML structures in C++ classes and are defined and implemented in the Util library of the Choreonoid SDK. Parsers that read YAML text to generate objects of these classes and writers that output objects of these classes as YAML text are also available, making it easy to read and write YAML files.
This class group consists of the following classes:
-
Corresponds to YAML Mapping (associative array, hash)
Can store multiple key-value pairs
Each value is an object of either Mapping, Listing, or ScalarNode
MappingPtr is defined as the corresponding smart pointer type
-
Corresponds to YAML Sequence (array)
Can store multiple values as array elements
Each value is an object of either Mapping, Listing, or ScalarNode
ListingPtr is defined as the corresponding smart pointer type
-
Corresponds to YAML scalar values
Stores one of bool, int, double, or string as a scalar value
ScalarNodePtr is defined as the corresponding smart pointer type
All inherit from the ValueNode class as the base class. Since ValueNode inherits from Referenced, all the above classes are Referenced Type Objects. This class hierarchy is illustrated as follows:
+ Referenced
+ ValueNode
+ Mapping
+ Listing
+ ScalarNode
These classes are defined and implemented in ValueTree.h and ValueTree.cpp in the Util library, and can be used in the Choreonoid SDK by including the ValueTree header.
Let’s introduce an example of data construction using these classes. For example, suppose there is data described in YAML as follows:
color: red
height: 1.8
translation: [ 0.0, 1.0, 2.0 ]
The corresponding data can be constructed using structured data class objects as follows:
Mapping
Key: color
Value: ScalarNode(“red”)
Key: height
Value: ScalarNode(1.8)
Key: translation
Value: Listing
Value: ScalarNode(0.0)
Value: ScalarNode(1.0)
Value: ScalarNode(2.0)
The parts in bold are objects of the above classes. (The key parts are not objects by themselves but are part of the Mapping object.) These objects are called “nodes” in the data structure. Multiple nodes form a tree structure with hierarchical parent-child relationships. Strictly speaking, it’s a graph structure since a node can be shared by multiple parent nodes.
In this example, the top-level Mapping node corresponds to the entire data. From there, each value is held hierarchically as nodes.
The C++ code to generate this data can be written as follows:
#include <cnoid/ValueTree>
...
// Generate the top-level Mapping object
MappingPtr data = new Mapping;
// Add key-value pairs (ScalarNode) to the node
data->write("color", "red");
data->write("height", 1.8);
// Add a Listing node as a value
auto translation = data->createListing("translation");
// Add elements (ScalarNode) to the Listing node
translation->append(0.0);
translation->append(1.0);
translation->append(2.0);
For constructing the translation node, if the value is stored in a three-dimensional vector type Vector3, you can write as follows using functions from the EigenArchive header:
#include <cnoid/EigenArchive>
...
Vector3 translation;
...
write(data, translation);
This data can be output as a YAML file using the YAMLWriter class. This is done as follows:
#include <cnoid/YAMLWriter>
...
YAMLWriter writer("data.yaml")
writer.putNode(data);
Conversely, you can also read a YAML file to construct structured data. This is done using the YAMLReader class as follows:
#include <cnoid/YAMLReader>
...
YAMLReader reader;
MappingPtr data;
try {
data = reader.loadDocument("data.yaml")->toMapping()
}
catch(const ValueNode::Exception& ex){
...
}
In this case, if reading succeeds, a Mapping type object is assigned to the variable node. If there’s a problem with the YAML file, a ValueNode::Exception type exception is thrown.
Code to read data expecting the above structure to be stored in a Mapping object can be written as follows:
std::string color;
double height;
Vector3 translation;
data->read("color", color);
data->read("height", height);
// Reading the 3 elements of translation
auto translationNode = data->findListing("translation");
if(translationNode->isValid()){
if(translationNode->size() == 3){
for(int i=0; i < 3; ++i){
translation[i] = translationNode->at(i)->toDouble();
}
}
}
In this example, if the data has the expected structure, the read values are assigned to the variables color, height, and translation.
By using the get function instead of the read function, you can read with default values specified. For example:
std::string color = data->get("color", "red");
double height = data->get("height", 1.8);
If the top-level node doesn’t contain the keys color or height, “red” and 1.8 are returned as default values, respectively.
Reading translation can also be written in one line using functions from the EigenArchive header:
#include <cnoid/EigenArchive>
...
read(data, "translation", translation);
Thus, by using the YAML-type structured data classes and related classes, you can read and write structured data with the same structure as YAML. Each class in the structured data classes has various member functions for reading and writing, allowing flexible coding for reading and writing. Functions like those in the EigenArchive header are also provided to concisely write reading and writing for specific types. For details about these, please refer to the API reference manual. Also, parts of Choreonoid’s source code that implement store and restore functions can serve as references for usage.
Archive Class¶
As seen in previous examples, YAML-type structured data often uses Mapping as the top-level node, and Mapping plays a central role in reading and writing data. Therefore, it’s conceivable to read and write states through Mapping class objects in item state store/restore functions.
However, since the Mapping class is a general-purpose class for storing structured data, it may not have all the functions necessary for project saving and restoration. To consolidate these into a single argument for a simpler API, the Archive class is defined. This adds functions related to project saving and restoration to the Mapping class.
Please refer to the Archive class reference for the added functions. Below, we introduce the main functions that can be used in implementing project item state saving/restoration by category.
Project File Structure¶
Data output to archive by the item class’s store function is ultimately saved as a YAML project file. This is usually saved with the extension cnoid.
This project file records the following information:
Item states
View states
Toolbar states
Other object states
View layout
Toolbar layout
※ View and toolbar layouts are only effective when “Layout” is checked in the main menu’s “File” - “Project File Options” - “Layout”, as introduced in Saving Layout.
Among the above, “item states” are the target of the item class’s store and restore. Other information is saved and restored by similar functions of the corresponding objects. These will be explained separately.
Since YAML used for describing project files is a highly readable format, if you open a project file in a text editor, you can generally understand its contents. Project files are solely generated by Choreonoid’s project saving and are not intended to be directly generated or edited, but we hope you’ll make flexible use of YAML’s advantages as needed.