-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for exporting additional frames in ModelExporter #557
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -703,6 +703,29 @@ LinkIndex Model::getFrameLink(const FrameIndex frameIndex) const | |||
return LINK_INVALID_INDEX; | ||||
} | ||||
|
||||
bool Model::getLinkAdditionalFrames(const LinkIndex lnkIndex, std::vector<FrameIndex>& frameIndices) const | ||||
{ | ||||
if (!isValidLinkIndex(lnkIndex)) { | ||||
std::stringstream ss; | ||||
ss << "LinkIndex " << lnkIndex << " is not valid, should be between 0 and " << this->getNrOfLinks()-1; | ||||
reportError("Model", "getLinkAdditionalFrames", ss.str().c_str()); | ||||
return false; | ||||
} | ||||
|
||||
frameIndices.resize(0); | ||||
// FrameIndex from 0 to this->getNrOfLinks()-1 are reserved for implicit frame of Links | ||||
// with the corresponding LinkIndex, while the frameIndex from getNrOfLinks() to getNrOfFrames()-1 | ||||
// are the one of actual additional frames. See iDynTree::Model docs for more details | ||||
for (FrameIndex frameIndex=this->getNrOfLinks(); frameIndex < this->getNrOfFrames(); frameIndex++) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For quicker understanding and readabilityof code, it would be great to document the relationship between the link serialization and the additional frames serialization, also here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see it is documented here,
In that case, we can give a pointer to this documentation here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 81e7f7a . |
||||
if (this->getFrameLink(frameIndex) == lnkIndex) { | ||||
frameIndices.push_back(frameIndex); | ||||
} | ||||
} | ||||
|
||||
return true; | ||||
} | ||||
|
||||
|
||||
|
||||
unsigned int Model::getNrOfNeighbors(const LinkIndex link) const | ||||
{ | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,11 +63,30 @@ class ModelExporterOptions | |
* | ||
* Furthermore, currently the model exporter only exports a subset of the features | ||
* supported in iDynTree::Model. In particular, the following features are not exported: | ||
* * Additional frames | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the way how |
||
* * Visual and collision geometries | ||
* * Sensors | ||
* * Joint limits, damping and static friction. | ||
* | ||
* # Format documentation | ||
* | ||
* The following format are supported by the exporter. | ||
* | ||
* | Format | Extendend Name | Website | String for filetype argument | | ||
* |:-----------------------------:|:-------:|:-------:|:--------:| | ||
* | URDF | Unified Robot Description Format | http://wiki.ros.org/urdf | `urdf` | | ||
* | ||
* ## URDF | ||
* | ||
* As the URDF format does not distinguish between frames and links (see https://discourse.ros.org/t/urdf-ng-link-and-frame-concepts/56 | ||
* for an extensive discussion on this) the URDF model exporter converts iDynTree's *additional frames* to mass-less and shape-less | ||
* *fake* URDF links that are connected as child links via `fixed` joints to the corresponding **real** URDF links. | ||
* | ||
* Furthermore, it is widespread use in URDF models to never use a real link (with mass) as the root link of a model, mainly | ||
* due to workaround a bug in official %KDL parser used in ROS (see https://github.com/ros/kdl_parser/issues/27 for more info). For this reason, | ||
* if the selected base_link has at least one additional frame, the first additional frame of the base link is added as a **parent** fake URDF link, | ||
* instead as a **child** fake URDF link as done with the rest of %iDynTree's additional frames. If no additional frame is available for the base link, | ||
* the base link of the URDF will have a mass, and will generate a warning then used with the ROS's [`kdl_parser`](https://github.com/ros/kdl_parser) . | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, is this the reason why Travis is failing in this pull request robotology/icub-models-generator#113 related to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think so, I think that is an error is always there even if non-failing PRs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I thought was, due to the parent-child direction inverting workaround, the root_tlink-base_link gets inverted parent reference and the base link might have a mass throwing an error? Because, the segfaults was due to the fact that root_link has a described and the exact words from the failure read...
Would that be possible? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Are you sure that the same error were not present in the successful builds? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Uhmm.. of this, I am not sure. |
||
* | ||
*/ | ||
class ModelExporter | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,15 @@ | |
namespace iDynTree | ||
{ | ||
|
||
/* | ||
* This file uses extensivly the libxml2's tree API, see | ||
* http://www.xmlsoft.org/examples/index.html#Tree for more info. | ||
* Usage of the C-based libxml2 API in C++ means that is necessary to | ||
* use the BAD_CAST macro for handling const-correctness. | ||
* See https://stackoverflow.com/questions/45058878/why-libxml2-uses-bad-cast-everywhere-in-a-c-c-code | ||
* and http://www.xmlsoft.org/html/libxml-xmlstring.html#BAD_CAST for details on this. | ||
*/ | ||
|
||
/* | ||
* Add a <origin> URDF element to the specified parent element with the specified transform. | ||
* | ||
|
@@ -133,6 +142,7 @@ bool exportLink(const Link &link, const std::string linkName, xmlNodePtr parent_ | |
return ok; | ||
} | ||
|
||
|
||
/** | ||
* Add a <joint> URDF element to the specified parent element with the specified joint info. | ||
* | ||
|
@@ -241,6 +251,68 @@ bool exportJoint(IJointConstPtr joint, LinkConstPtr parentLink, LinkConstPtr chi | |
return ok; | ||
} | ||
|
||
/* | ||
* Add a <link> and <joint> URDF elements to the specified parent element for the specified additional frame. | ||
* | ||
* URDF does not have the concept of frame (see https://discourse.ros.org/t/urdf-ng-link-and-frame-concepts/56), | ||
* so for each additional frame we need to add a URDF "fake" mass-less link. | ||
* | ||
* If parent_element contains a pointer to a tag <parent_element></parent_element>, this function modifies it to be something like: | ||
* | ||
* <parent_element> | ||
* <link name="<frame_name>"/> | ||
* <joint name="<frame_name>_fixed_joint" type="fixed"> | ||
* <origin xyz="0.1 0.2 0.3" rpy="0 0 3.1416"/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The numbers here are misleading. Can we change them to placeholders? UPDATE: Ok, given that this is just an example of it might look on the generated URDF, please ignore my comment. |
||
* <parent link="<link_name>"/> | ||
* <child link="<frame_name>"/> | ||
* </joint> | ||
* <parent_element/> | ||
* | ||
* where the actual values of the added element and attributes depend on the input frame_name, link_H_frame, parent_link_name and direction_options arguments. | ||
*/ | ||
enum exportAdditionalFrameDirectionOption { | ||
FAKE_LINK_IS_CHILD, | ||
FAKE_LINK_IS_PARENT | ||
}; | ||
bool exportAdditionalFrame(const std::string frame_name, Transform link_H_frame, const std::string link_name, exportAdditionalFrameDirectionOption direction_option, xmlNodePtr parent_element) | ||
{ | ||
bool ok=true; | ||
|
||
// Export fake link | ||
xmlNodePtr link_xml = xmlNewChild(parent_element, NULL, BAD_CAST "link", NULL); | ||
xmlNewProp(link_xml, BAD_CAST "name", BAD_CAST frame_name.c_str()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If any future developer asks why there is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in ee923f4 . |
||
|
||
// Export fake joint | ||
xmlNodePtr joint_xml = xmlNewChild(parent_element, NULL, BAD_CAST "joint", NULL); | ||
std::string fake_joint_name = frame_name + "_fixed_joint"; | ||
xmlNewProp(joint_xml, BAD_CAST "name", BAD_CAST fake_joint_name.c_str()); | ||
xmlNewProp(joint_xml, BAD_CAST "type", BAD_CAST "fixed"); | ||
|
||
// origin | ||
exportTransform(link_H_frame, joint_xml); | ||
|
||
std::string parent_of_fake_joint, child_of_fake_joint; | ||
|
||
if (direction_option == FAKE_LINK_IS_CHILD) { | ||
parent_of_fake_joint = link_name; | ||
child_of_fake_joint = frame_name; | ||
} else { | ||
// direction_option == FAKE_LINK_IS_PARENT | ||
parent_of_fake_joint = frame_name; | ||
child_of_fake_joint = link_name; | ||
} | ||
|
||
// parent | ||
xmlNodePtr parent_xml = xmlNewChild(joint_xml, NULL, BAD_CAST "parent", NULL); | ||
xmlNewProp(parent_xml, BAD_CAST "link", BAD_CAST parent_of_fake_joint.c_str()); | ||
|
||
// child | ||
xmlNodePtr child_xml = xmlNewChild(joint_xml, NULL, BAD_CAST "child", NULL); | ||
xmlNewProp(child_xml, BAD_CAST "link", BAD_CAST child_of_fake_joint.c_str()); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see a common trend of using the method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this also depends on how many properties we add in sequence. So the complexity of having a single function to handle all properties might be big. But can still be handled by a vector of key-value pairs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this could make sense, but at least initially I preferred to avoid start writing a C++ wrapper on the top of the libxml2 API, without having a clear idea about our need from the libxml2's API. Furthermore, I am a bit afraid that adding a layer between the libxml2's API and the parser could impact readability. However, a C++ wrapper could make sense, but I would prefer to complete the URDF exporter implementation to understand our needs, and eventually do an libxml2 abstraction as a refactoring. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. |
||
return ok; | ||
} | ||
|
||
bool URDFStringFromModel(const iDynTree::Model & model, | ||
std::string & urdf_string, | ||
const ModelExporterOptions options) | ||
|
@@ -272,6 +344,21 @@ bool URDFStringFromModel(const iDynTree::Model & model, | |
return false; | ||
} | ||
|
||
// If the base link has at least an additional frame, add it as parent URDF link | ||
// as a workaround for https://github.com/ros/kdl_parser/issues/27 | ||
FrameIndex baseFakeLinkFrameIndex = FRAME_INVALID_INDEX; | ||
std::vector<FrameIndex> frameIndices; | ||
ok = model.getLinkAdditionalFrames(baseLinkIndex, frameIndices); | ||
if (ok && frameIndices.size() >= 1) { | ||
baseFakeLinkFrameIndex = frameIndices[0]; | ||
ok = ok && exportAdditionalFrame(model.getFrameName(baseFakeLinkFrameIndex), | ||
model.getFrameTransform(baseFakeLinkFrameIndex), | ||
model.getLinkName(model.getFrameLink(baseFakeLinkFrameIndex)), | ||
FAKE_LINK_IS_PARENT, | ||
robot); | ||
} | ||
|
||
|
||
// Create a Traversal | ||
Traversal exportTraversal; | ||
if (!model.computeFullTreeTraversal(exportTraversal, baseLinkIndex)) | ||
|
@@ -300,7 +387,18 @@ bool URDFStringFromModel(const iDynTree::Model & model, | |
ok = ok && exportLink(*visitedLink, visitedLinkName, robot); | ||
} | ||
|
||
// TODO(traversaro) : export additional frames | ||
// Export all the additional frames that are not parent of the base link | ||
for (FrameIndex frameIndex=model.getNrOfLinks(); frameIndex < static_cast<FrameIndex>(model.getNrOfFrames()); frameIndex++) | ||
{ | ||
if (frameIndex != baseFakeLinkFrameIndex) { | ||
ok = ok && exportAdditionalFrame(model.getFrameName(frameIndex), | ||
model.getFrameTransform(frameIndex), | ||
model.getLinkName(model.getFrameLink(frameIndex)), | ||
FAKE_LINK_IS_CHILD, | ||
robot); | ||
} | ||
} | ||
|
||
|
||
xmlChar *xmlbuff=0; | ||
int buffersize=0; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,9 +57,7 @@ void checkImportExportURDF(std::string fileName) | |
ASSERT_EQUAL_DOUBLE(model.getNrOfLinks(), modelReloaded.getNrOfLinks()); | ||
ASSERT_EQUAL_DOUBLE(model.getNrOfJoints(), modelReloaded.getNrOfJoints()); | ||
ASSERT_EQUAL_DOUBLE(model.getNrOfDOFs(), modelReloaded.getNrOfDOFs()); | ||
|
||
// TODO(traversaro) : uncomment the following line when frames are correctly handled by the exporter | ||
// ASSERT_EQUAL_DOUBLE(model.getNrOfFrames(), modelReloaded.getNrOfLinks()); | ||
ASSERT_EQUAL_DOUBLE(model.getNrOfFrames(), modelReloaded.getNrOfFrames()); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for remembering to remove this. Sorry I missed this in the review! |
||
// Verify that the link correspond (note that the serialization could have changed) | ||
for(int lnkIndex=0; lnkIndex < model.getNrOfLinks(); lnkIndex++) { | ||
|
@@ -72,6 +70,16 @@ void checkImportExportURDF(std::string fileName) | |
ASSERT_EQUAL_MATRIX(inertia.asMatrix(), inertiaReloaded.asMatrix()); | ||
} | ||
|
||
// Verify that the frame correspond (note that the serialization could have changed) | ||
for(FrameIndex frameIndex=model.getNrOfLinks(); frameIndex < model.getNrOfFrames(); frameIndex++) { | ||
FrameIndex frameIndexInReloaded = modelReloaded.getFrameIndex(model.getFrameName(frameIndex)); | ||
ASSERT_IS_TRUE(frameIndexInReloaded != FRAME_INVALID_INDEX); | ||
ASSERT_IS_TRUE(model.getFrameName(frameIndex) == modelReloaded.getFrameName(frameIndexInReloaded)); | ||
Transform link_H_frame = model.getFrameTransform(frameIndex); | ||
Transform link_H_frame_reloaded = modelReloaded.getFrameTransform(frameIndexInReloaded); | ||
ASSERT_EQUAL_MATRIX(link_H_frame.asHomogeneousTransform(), link_H_frame_reloaded.asHomogeneousTransform()); | ||
} | ||
|
||
} | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it also make sense to have an overload with
linkName
as input argument?Not specifically for this scenario. but in general nice-to-have feature in the Model API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar overload for
frameNames
as output?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could make sense, but I would leave this to a separate PR. Probably we can have a guideline to have for all methods in
iDynTree::Model
both the index and the names one, and then have a PR for add names-variant for all methods missing them. Can you open a new issue on this?