Skip to content

Commit 57a0dcc

Browse files
authored
Merge pull request #2413 from lvjonok/feature/parse-mjcf-equality
Feature: Support for Parsing MJCF `equality/connect` Tag for Closed Chains
2 parents ca07368 + 4775bfa commit 57a0dcc

File tree

8 files changed

+476
-13
lines changed

8 files changed

+476
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

1111
- Default visualizer can be changed with `PINOCCHIO_VIEWER` environment variable ([#2419](https://github.com/stack-of-tasks/pinocchio/pull/2419))
1212
- Add more Python and C++ examples related to inverse kinematics with 3d tasks ([#2428](https://github.com/stack-of-tasks/pinocchio/pull/2428))
13+
- Add parsing of equality/connect tag for closed-loop chains for MJCF format ([#2413](https://github.com/stack-of-tasks/pinocchio/pull/2413))
1314

1415
### Fixed
1516
- Fix linkage of Boost.Serialization on Windows ([#2400](https://github.com/stack-of-tasks/pinocchio/pull/2400))

include/pinocchio/parsers/mjcf.hpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
#include "pinocchio/multibody/model.hpp"
99
#include "pinocchio/parsers/urdf.hpp"
10+
#include "pinocchio/multibody/model.hpp"
11+
#include "pinocchio/multibody/geometry.hpp"
12+
#include "pinocchio/algorithm/contact-info.hpp"
1013

1114
namespace pinocchio
1215
{
@@ -75,6 +78,38 @@ namespace pinocchio
7578
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
7679
const bool verbose = false);
7780

81+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
82+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModelFromXML(
83+
const std::string & xmlStream,
84+
const typename ModelTpl<Scalar, Options, JointCollectionTpl>::JointModel & rootJoint,
85+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
86+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
87+
const bool verbose = false);
88+
89+
// TODO: update description, buildModel with contact model
90+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
91+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModel(
92+
const std::string & filename,
93+
const typename ModelTpl<Scalar, Options, JointCollectionTpl>::JointModel & rootJoint,
94+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
95+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
96+
const bool verbose = false);
97+
98+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
99+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModelFromXML(
100+
const std::string & xmlStream,
101+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
102+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
103+
const bool verbose = false);
104+
105+
// TODO: update description, buildModel with contact model
106+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
107+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModel(
108+
const std::string & filename,
109+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
110+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
111+
const bool verbose = false);
112+
78113
/**
79114
* @brief Build The GeometryModel from a Mjcf file
80115
*

include/pinocchio/parsers/mjcf/mjcf-graph.hpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "pinocchio/parsers/urdf.hpp"
99
#include "pinocchio/multibody/model.hpp"
1010
#include "pinocchio/multibody/joint/joints.hpp"
11-
11+
#include "pinocchio/algorithm/contact-info.hpp"
1212
#include <boost/property_tree/xml_parser.hpp>
1313
#include <boost/property_tree/ptree.hpp>
1414
#include <boost/foreach.hpp>
@@ -21,6 +21,7 @@
2121
#include <limits>
2222
#include <iostream>
2323
#include <unordered_map>
24+
#include <map>
2425

2526
namespace pinocchio
2627
{
@@ -325,6 +326,55 @@ namespace pinocchio
325326
void goThroughElement(const ptree & el, const MjcfGraph & currentGraph);
326327
};
327328

329+
/*
330+
typedef struct mjsEquality_ { // equality specification
331+
mjsElement* element; // element type
332+
mjString* name; // name
333+
mjtEq type; // constraint type
334+
double data[mjNEQDATA]; // type-dependent data
335+
mjtByte active; // is equality initially active
336+
mjString* name1; // name of object 1
337+
mjString* name2; // name of object 2
338+
mjtNum solref[mjNREF]; // solver reference
339+
mjtNum solimp[mjNIMP]; // solver impedance
340+
mjString* info; // message appended to errors
341+
} mjsEquality;
342+
*/
343+
struct PINOCCHIO_PARSERS_DLLAPI MjcfEquality
344+
{
345+
typedef boost::property_tree::ptree ptree;
346+
347+
// Optional name of the equality constraint
348+
std::string name;
349+
350+
// Type of the constraint: (connect for now)
351+
std::string type;
352+
353+
// // Optional class for setting unspecified attributes
354+
// std::string class;
355+
356+
// active: 'true' or 'false' (default: 'true')
357+
// solref and solimp
358+
359+
// Name of the first body participating in the constraint
360+
std::string body1;
361+
// Name of the second body participating in the constraint (optional, default: world)
362+
std::string body2;
363+
364+
// Coordinates of the 3D anchor point where the two bodies are connected.
365+
// Specified relative to the local coordinate frame of the first body.
366+
Eigen::Vector3d anchor = Eigen::Vector3d::Zero();
367+
368+
// TODO: implement when weld is introduced
369+
// This attribute specifies the relative pose (3D position followed by 4D quaternion
370+
// orientation) of body2 relative to body1. If the quaternion part (i.e., last 4 components
371+
// of the vector) are all zeros, as in the default setting, this attribute is ignored and
372+
// the relative pose is the one corresponding to the model reference pose in qpos0. The
373+
// unusual default is because all equality constraint types share the same default for their
374+
// numeric parameters.
375+
// Eigen::VectorXd relativePose = Eigen::VectorXd::Zero(7);
376+
};
377+
328378
/// @brief The graph which contains all information taken from the mjcf file
329379
struct PINOCCHIO_PARSERS_DLLAPI MjcfGraph
330380
{
@@ -337,6 +387,7 @@ namespace pinocchio
337387
typedef std::unordered_map<std::string, MjcfMesh> MeshMap_t;
338388
typedef std::unordered_map<std::string, MjcfTexture> TextureMap_t;
339389
typedef std::unordered_map<std::string, Eigen::VectorXd> ConfigMap_t;
390+
typedef std::map<std::string, MjcfEquality> EqualityMap_t;
340391

341392
// Compiler Info needed to properly parse the rest of file
342393
MjcfCompiler compilerInfo;
@@ -352,6 +403,8 @@ namespace pinocchio
352403
TextureMap_t mapOfTextures;
353404
// Map of model configurations
354405
ConfigMap_t mapOfConfigs;
406+
// Map of equality constraints
407+
EqualityMap_t mapOfEqualities;
355408

356409
// reference configuration
357410
Eigen::VectorXd referenceConfig;
@@ -428,6 +481,10 @@ namespace pinocchio
428481
/// @param el ptree keyframe node
429482
void parseKeyFrame(const ptree & el);
430483

484+
/// @brief Parse all the info from the equality tag
485+
/// @param el ptree equality node
486+
void parseEquality(const ptree & el);
487+
431488
/// @brief parse the mjcf file into a graph
432489
void parseGraph();
433490

@@ -469,6 +526,13 @@ namespace pinocchio
469526
/// @param keyName Name of the keyframe
470527
void addKeyFrame(const Eigen::VectorXd & keyframe, const std::string & keyName);
471528

529+
/// @brief Parse the equality constraints and add them to the model
530+
/// @param model Model to add the constraints to
531+
/// @param contact_models Vector of contact models to add the constraints to
532+
void parseContactInformation(
533+
const Model & model,
534+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models);
535+
472536
/// @brief Fill geometry model with all the info taken from the mjcf model file
473537
/// @param type Type of geometry to parse (COLLISION or VISUAL)
474538
/// @param geomModel geometry model to fill

include/pinocchio/parsers/mjcf/model.hxx

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "pinocchio/parsers/mjcf.hpp"
99
#include "pinocchio/parsers/mjcf/mjcf-graph.hpp"
10+
#include "pinocchio/multibody/model.hpp"
11+
#include "pinocchio/algorithm/contact-info.hpp"
1012

1113
namespace pinocchio
1214
{
@@ -37,12 +39,46 @@ namespace pinocchio
3739

3840
graph.parseGraphFromXML(xmlStr);
3941

40-
// // Use the Mjcf graph to create the model
42+
// Use the Mjcf graph to create the model
4143
graph.parseRootTree();
4244

4345
return model;
4446
}
4547

48+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
49+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModel(
50+
const std::string & filename,
51+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
52+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
53+
const bool verbose)
54+
{
55+
return buildModelFromXML(filename, model, contact_models, verbose);
56+
}
57+
58+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
59+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModelFromXML(
60+
const std::string & xmlStr,
61+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
62+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
63+
const bool verbose)
64+
{
65+
::pinocchio::urdf::details::UrdfVisitor<Scalar, Options, JointCollectionTpl> visitor(model);
66+
67+
typedef ::pinocchio::mjcf::details::MjcfGraph MjcfGraph;
68+
69+
MjcfGraph graph(visitor, xmlStr);
70+
if (verbose)
71+
visitor.log = &std::cout;
72+
73+
graph.parseGraphFromXML(xmlStr);
74+
75+
// Use the Mjcf graph to create the model
76+
graph.parseRootTree();
77+
graph.parseContactInformation(model, contact_models);
78+
79+
return model;
80+
}
81+
4682
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
4783
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModel(
4884
const std::string & filename,
@@ -71,8 +107,45 @@ namespace pinocchio
71107

72108
graph.parseGraphFromXML(xmlStr);
73109

74-
// // Use the Mjcf graph to create the model
110+
// Use the Mjcf graph to create the model
111+
graph.parseRootTree();
112+
113+
return model;
114+
}
115+
116+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
117+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModel(
118+
const std::string & filename,
119+
const typename ModelTpl<Scalar, Options, JointCollectionTpl>::JointModel & rootJoint,
120+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
121+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
122+
const bool verbose)
123+
{
124+
return buildModelFromXML(filename, rootJoint, model, contact_models, verbose);
125+
}
126+
127+
template<typename Scalar, int Options, template<typename, int> class JointCollectionTpl>
128+
ModelTpl<Scalar, Options, JointCollectionTpl> & buildModelFromXML(
129+
const std::string & xmlStr,
130+
const typename ModelTpl<Scalar, Options, JointCollectionTpl>::JointModel & rootJoint,
131+
ModelTpl<Scalar, Options, JointCollectionTpl> & model,
132+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models,
133+
const bool verbose)
134+
{
135+
::pinocchio::urdf::details::UrdfVisitorWithRootJoint<Scalar, Options, JointCollectionTpl>
136+
visitor(model, rootJoint);
137+
138+
typedef ::pinocchio::mjcf::details::MjcfGraph MjcfGraph;
139+
140+
MjcfGraph graph(visitor, xmlStr);
141+
if (verbose)
142+
visitor.log = &std::cout;
143+
144+
graph.parseGraphFromXML(xmlStr);
145+
146+
// Use the Mjcf graph to create the model
75147
graph.parseRootTree();
148+
graph.parseContactInformation(model, contact_models);
76149

77150
return model;
78151
}

src/parsers/mjcf/mjcf-graph.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//
44

55
#include "pinocchio/parsers/mjcf/mjcf-graph.hpp"
6+
#include "pinocchio/multibody/model.hpp"
7+
#include "pinocchio/algorithm/contact-info.hpp"
68

79
namespace pinocchio
810
{
@@ -735,6 +737,53 @@ namespace pinocchio
735737
}
736738
}
737739

740+
void MjcfGraph::parseEquality(const ptree & el)
741+
{
742+
for (const ptree::value_type & v : el)
743+
{
744+
std::string type = v.first;
745+
// List of supported constraints from mjcf description
746+
// equality -> connect
747+
748+
// The constraints below are not supported and will be ignored with the following
749+
// warning: joint, flex, distance, weld
750+
if (type != "connect")
751+
{
752+
// TODO(jcarpent): support extra constraint types such as joint, flex, distance, weld.
753+
continue;
754+
}
755+
756+
MjcfEquality eq;
757+
eq.type = type;
758+
759+
// get the name of first body
760+
auto body1 = v.second.get_optional<std::string>("<xmlattr>.body1");
761+
if (body1)
762+
eq.body1 = *body1;
763+
else
764+
throw std::invalid_argument("Equality constraint needs a first body");
765+
766+
// get the name of second body
767+
auto body2 = v.second.get_optional<std::string>("<xmlattr>.body2");
768+
if (body2)
769+
eq.body2 = *body2;
770+
771+
// get the name of the constraint (if it exists)
772+
auto name = v.second.get_optional<std::string>("<xmlattr>.name");
773+
if (name)
774+
eq.name = *name;
775+
else
776+
eq.name = eq.body1 + "_" + eq.body2 + "_constraint";
777+
778+
// get the anchor position
779+
auto anchor = v.second.get_optional<std::string>("<xmlattr>.anchor");
780+
if (anchor)
781+
eq.anchor = internal::getVectorFromStream<3>(*anchor);
782+
783+
mapOfEqualities.insert(std::make_pair(eq.name, eq));
784+
}
785+
}
786+
738787
void MjcfGraph::parseGraph()
739788
{
740789
boost::property_tree::ptree el;
@@ -772,6 +821,11 @@ namespace pinocchio
772821
boost::optional<std::string> childClass;
773822
parseJointAndBody(el.get_child("worldbody").get_child("body"), childClass);
774823
}
824+
825+
if (v.first == "equality")
826+
{
827+
parseEquality(el.get_child("equality"));
828+
}
775829
}
776830
}
777831

@@ -1040,6 +1094,37 @@ namespace pinocchio
10401094
throw std::invalid_argument("Keyframe size does not match model size");
10411095
}
10421096

1097+
void MjcfGraph::parseContactInformation(
1098+
const Model & model,
1099+
PINOCCHIO_STD_VECTOR_WITH_EIGEN_ALLOCATOR(RigidConstraintModel) & contact_models)
1100+
{
1101+
for (const auto & entry : mapOfEqualities)
1102+
{
1103+
const MjcfEquality & eq = entry.second;
1104+
1105+
SE3 jointPlacement;
1106+
jointPlacement.setIdentity();
1107+
jointPlacement.translation() = eq.anchor;
1108+
1109+
// Get Joint Indices from the model
1110+
const JointIndex body1 = urdfVisitor.getParentId(eq.body1);
1111+
1112+
// when body2 is not specified, we link to the world
1113+
if (eq.body2 == "")
1114+
{
1115+
RigidConstraintModel rcm(CONTACT_3D, model, body1, jointPlacement, LOCAL);
1116+
contact_models.push_back(rcm);
1117+
}
1118+
else
1119+
{
1120+
const JointIndex body2 = urdfVisitor.getParentId(eq.body2);
1121+
RigidConstraintModel rcm(
1122+
CONTACT_3D, model, body1, jointPlacement, body2, jointPlacement.inverse(), LOCAL);
1123+
contact_models.push_back(rcm);
1124+
}
1125+
}
1126+
}
1127+
10431128
void MjcfGraph::parseRootTree()
10441129
{
10451130
urdfVisitor.setName(modelName);

0 commit comments

Comments
 (0)