Overview
This example demonstrates the fundamental concepts of Tesseract's PropertyTree system, which provides a hierarchical structure for managing YAML-based configurations with built-in metadata and validation support.
PropertyTree is useful for:
- Defining schema for configuration files
- Merging user configs into schemas with automatic default application
- Validating configuration data against schemas
- Collecting all validation errors at once (not just the first one)
- Attaching metadata (docs, GUI hints, constraints) to configuration properties
Key Concepts
- PropertyTree Node: A single node representing a property. Can have a value (YAML::Node), metadata attributes (doc, type, required, etc.), and child nodes.
- Schema: A PropertyTree that defines the structure and constraints of a config. Typically created with PropertyTreeBuilder for clean, fluent syntax.
- PropertyTreeBuilder: A fluent API for constructing PropertyTree schemas with minimal boilerplate. Supports type methods (string, integer, boolean, etc.) and attribute setters (doc, required, defaultVal, minimum, maximum, etc.).
- Config Merge: Populates schema tree with values from a user-supplied YAML config, applying defaults for missing properties. Tracks extra properties for validation.
- Validation: Recursively checks all constraints (required, enum, range, type, custom validators) and collects ALL error messages at once for comprehensive feedback.
- Attributes: Metadata attached to nodes. Built-in attributes include:
- Type information (TYPE)
- Constraints (REQUIRED, ENUM, MINIMUM, MAXIMUM)
- Defaults (DEFAULT)
- GUI metadata (LABEL, PLACEHOLDER, GROUP, READ_ONLY, HIDDEN, DOC)
Typical Workflow
The example demonstrates:
- Build a Schema using PropertyTreeBuilder with types and constraints
- Load Configuration from a YAML node
- Merge Config into Schema to populate values and apply defaults
- Validate to check all constraints, collecting all errors
- Access Results from the merged, validated schema tree
Example Code
auto schema = PropertyTreeBuilder()
.attribute(TYPE, CONTAINER)
.string("name").required().doc("Robot name").label("Robot Name").done()
.integer("dof").required().doc("Degrees of freedom").minimum(1).maximum(20).done()
.doubleNum("speed").doc("Max speed (units/sec)").defaultVal(1.0)
.minimum(0.0).maximum(10.0).done()
.container("workspace").doc("Work envelope")
.doubleNum("x_min").required().done()
.doubleNum("x_max").required().done()
.doubleNum("y_min").required().done()
.doubleNum("y_max").required().done()
.done()
.build();
The PropertyTreeBuilder fluent API makes it easy to define schemas without nested boilerplate. Type methods create children, attribute setters configure the current node, and done() pops back to the parent.
YAML::Node valid_config;
valid_config["name"] = "UR10";
valid_config["dof"] = 6;
valid_config["workspace"]["x_min"] = -2.0;
valid_config["workspace"]["x_max"] = 2.0;
valid_config["workspace"]["y_min"] = -3.0;
valid_config["workspace"]["y_max"] = 3.0;
auto schema_copy = schema;
schema_copy.mergeConfig(valid_config);
auto errors = schema_copy.validate();
After defining a schema, merge a user config into it. The mergeConfig() call populates values and applies defaults. Then validate() checks all constraints and returns a vector of error strings (empty on success).
Running the Example
Execute the example:
./tesseract_common_basic_property_tree_example
The example creates valid and invalid configurations, demonstrating both successful merges and comprehensive error collection.