mc_rtc::Configuration
general purpose configurationmc_rtc::Schema
JSON Schema is a specification that allows to document the expected data format to go from a JSON object to a given data structure.
It is used in mc_rtc to document the expected data for loading objects – e.g. tasks or constraints – into the framework.
As you write code within mc_rtc you will likely encounter a scenarion where you load a number of parameters from an mc_rtc::Configuration
general purpose configuration object.
In addition to loading this configuration object into your data structure, you will also likely be interested in:
mc_rtc::Configuration
file – and then to disk;Writing all this code by hand is possible but tedious and error-prone, especially when adding or removing fields from the structure.
This is where mc_rtc::Schema
comes in. Its objective is to allow you to write a simple structure that behaves like a simple structure but comes packed with extra features.
The following example is a simple schema structure that illustrates the basic usage of the feature:
#include <mc_rtc/Schema.h>
struct SimpleSchema
{
MC_RTC_NEW_SCHEMA(SimpleSchema)
#define MEMBER(...) MC_RTC_SCHEMA_REQUIRED_DEFAULT_MEMBER(SimpleSchema, __VA_ARGS__)
MEMBER(bool, useFeature, "Use magic feature")
MEMBER(double, weight, "Task weight")
MEMBER(std::vector<std::string>, names, "Some names")
using MapType = std::map<std::string, double>
MEMBER(MapType, jointValues, "Map of joint names and values")
MEMBER(sva::ForceVecd, wrench, "Target wrench")
MEMBER(sva::PTransformd, pt, "Some transform")
#undef MEMBER
};
If you intend to use the MEMBER
macro, beware of the following pre-processor limitations.
Due to limitations of the pre-processor, you cannot directly pass types depending on multiple template parameters to the macro. For example, the following will give a compilation error.
MEMBER(std::map<std::string, double>, jointValues, "Map of joint names and values")
This occurs because the pre-processor splits the arguments as follows:
std::map<std::string
double>
jointValues
"Map of joint names and values"
To avoid this you can explicitly alias the type
using MapType = std::map<std::string, double>;
MEMBER(MapType, jointValues, "Map of joint names and values")
If this is not an option, you may use BOOST_IDENTIY_TYPE
instead.
If you intend the above code to be used with the Microsoft Visual C++ Compiler (MSVC), the MEMBER
definition should be changed to:
#define MEMBER(...) MC_RTC_PP_ID(MC_RTC_SCHEMA_REQUIRED_DEFAULT_MEMBER(SimpleSchema, __VA_ARGS__))
This works around a pre-processor issue in MSVC.
The structure that we defined this way is used a regular C++ structure:
void do_foo(const sva::ForceVecd &);
// The structure can be default constructed (more on the defaults later)
SimpleSchema simple;
// Access a member as a simple structure
do_foo(simple.wrench);
// The members are of the type you specified so this is also possible
do_foo(simple.pt * simple.wrench);
However, this structure also has a number of extra functionalities built-in:
// Load from an mc_rtc::Configuration value
simple.load(config);
// Save to an mc_rtc::Configuration object
simple.save(config);
// We can also take advantage of mc_rtc::Configuration user defined conversions
config.add("simple", simple);
SimpleSchema simple2 = config("simple");
// Print to the console
mc_rtc::log::info("simple:\n{}", simple.dump(true, true));
// Add a form to the GUI that will edit simple in place
simple.addToGUI(*controller.gui(), {"Form category"}, "Form name",
[this]() {
// The simple object has been updated with new values at this point
on_simple_update();
});
You can also use aggregate initialization or designated initialization (from C++20) to initialize the structure:
SimpleSchema simple{true, 42.42, {"a", "b"}, wrench, pt};
Finally the structure we created here has the same size as the simpler structure we could have created by hand.
MC_RTC_SCHEMA_MEMBER
macroIn the previous example, we have used the MC_RTC_SCHEMA_REQUIRED_DEFAULT_MEMBER
macro. It is actually a wrapper around the more general MC_RTC_SCHEMA_MEMBER
macro which has the following siganture:
MC_RTC_SCHEMA_MEMBER(T, TYPE, NAME, DESCRIPTION, FLAGS, DEFAULT, ...)
The parameters have the following usage:
T
is the type of the Schema object where this member appears;TYPE
is the type of the member variable;NAME
is the name of the member variable;DESCRIPTION
is a documentation string for the member variable, it is also used in the generated Form;FLAGS
is used to add additional information about the member, most notably whether the member is required or not;DEFAULT
is the default value used for this member;...
/NAMES
these extra parameters are used for two purposes:
TYPE
is std::string
this can be used to provide valid values;TYPE
is std::variant<T...>
this is used to assign meaningful names to the variant altneratives;TYPE
The intent is for any TYPE
to be usable – at least for the load/save scenario, not all types may be supported for the form-based edition.
If a given TYPE
is not supported you will get a compilation error, please report the issue to mc_rtc developpers.
As of now, most types that can be loaded/saved through an mc_rtc::Configuration
object are supported as well as schema-based types and std::vector
and std::map
of such types.
The following is an example using such types:
struct ComposeSchema
{
MC_RTC_NEW_SCHEMA(ComposeSchema)
#define MEMBER(...) MC_RTC_PP_ID(MC_RTC_SCHEMA_REQUIRED_DEFAULT_MEMBER(ComposeSchema, __VA_ARGS__))
MEMBER(int, integer, "Integer value")
MEMBER(SimpleSchema, simple, "Simple schema")
MEMBER(std::vector<SimpleSchema>, many, "Multiple simple schema")
#undef MEMBER
};
FLAGS
There is current two flags:
mc_rtc::schema::Required
mc_rtc::schema::Interactive
mc_rtc::schema::Required
When a member is required:
Otherwise the member is only read if it is present in the configuration.
Note: members are always saved into a Configuration object, whether they are required or not
mc_rtc::schema::Interactive
The schema can treat the following types as interactive:
Eigen::Vector3d
sva::PTransformd
If members of these types are added to a form then interactive form elements will be used.
However, it does not always make sense. For example, a 3D gain could be represented with an Eigen::Vector3d
and there is no point to use a 3D marker to edit this value.
You can control this behavior by setting the interactive flag accordingly. In all macros, except the basic MC_RTC_SCHEMA_MEMBER
, the flag is set by default.
The following example illustrates the use of these flags in the MC_RTC_SCHEMA_MEMBER
macro call:
struct InteractiveSchema
{
MC_RTC_NEW_SCHEMA(InteractiveSchema)
#define MEMBER(...) MC_RTC_PP_ID(MC_RTC_SCHEMA_MEMBER(InteractiveSchema, __VA_ARGS__))
MEMBER(Eigen::Vector3d,
point,
"3D point",
mc_rtc::schema::Required | mc_rtc::schema::Interactive,
Eigen::Vector3d::Zero())
MEMBER(Eigen::Vector3d, gain, "3D gain", mc_rtc::schema::Required, Eigen::Vector3d::Ones())
MEMBER(Eigen::Vector3d, gainOpt, "3D optional gain", mc_rtc::schema::None, Eigen::Vector3d::Zero())
#undef MEMBER
};
DEFAULT
This lets you specify the default value of the member. Anything that would be valid on the right-hand side of the assignment operator is ok here.
If you are using a _DEFAULT_
macro, this is mc_rtc::Default<T>
which is:
0
for arithmetic types (thus false
for bool
)sva::PTransformd
sva::ForceVecd
, sva::MotionVecd
, sva::ImpedanceVecd
, sva::AdmittanceVecd
std::string
std::vector<T>
std::map<K, V>
mc_rtc::Default<T>
for std::variant<T, Others...>
NAMES
These extra parameter has two possible use-case:
TYPE
is std::string
TYPE
is std::variant<T...>
In this example we have a gain represented as variant. It can be a double
scalar or an Eigen::Vector3d
. The mc_rtc::schema::Choices
is passed to the MEMBER
macro and those names will be used when displaying the choice in the GUI.
struct SimpleVariant
{
MC_RTC_NEW_SCHEMA(SimpleVariant)
using gain_t = std::variant<double, Eigen::Vector3d>;
#define MEMBER(...) MC_RTC_PP_ID(MC_RTC_SCHEMA_REQUIRED_DEFAULT_MEMBER(SimpleVariant, __VA_ARGS__))
MEMBER(gain_t, stiffness, "Task stiffness", mc_rtc::schema::Choices({"scalar", "dimensional"}))
#undef MEMBER
};
MC_RTC_NEW_SCHEMA
and MC_RTC_SCHEMA
macrosIn all examples so far we have seen usage of MC_RTC_NEW_SCHEMA
. As the name suggests, this macro is used to introduce a new schema declaration.
It takes a single argument which is the name of the structure we are creating.
However, it is sometimes useful to extend a schema with additional members. In such cases one should use the MC_RTC_SCHEMA
macro instead:
struct ExtendedSchema : public SimpleSchema
{
MC_RTC_SCHEMA(ExtendedSchema, SimpleSchema)
#define MEMBER(...) MC_RTC_PP_ID(MC_RTC_SCHEMA_REQUIRED_DEFAULT_MEMBER(ExtendedSchema, __VA_ARGS__))
MEMBER(double, extendedGain, "Gain for the extended algorithm")
#undef MEMBER
};
This macro simply takes the schema name as well as the base class the schema is using.