Skip to content

Commit

Permalink
Merge pull request RobotLocomotion#9635 from sammy-tri/multibody_plan…
Browse files Browse the repository at this point in the history
…t_urdf_geometry

Parse URDF geometry
  • Loading branch information
sammy-tri authored Oct 17, 2018
2 parents 2b8b439 + 20bc8cc commit 53b825c
Show file tree
Hide file tree
Showing 16 changed files with 857 additions and 12 deletions.
4 changes: 2 additions & 2 deletions attic/multibody/parsers/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ drake_cc_googletest(

drake_cc_googletest(
name = "urdf_parser_test",
srcs = ["test/urdf_parser_test/urdf_parser_test.cc"],
srcs = ["test/urdf_parser_test.cc"],
data = [
":test_models",
"//examples/atlas:models",
"//multibody/multibody_tree/parsing:test_models",
],
deps = [
":parsers",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ GTEST_TEST(URDFParserTest, ParseJointProperties) {
}

GTEST_TEST(URDFParserTest, TestParseMaterial) {
const string resource_dir{"drake/multibody/parsers/test/urdf_parser_test/"};
const string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const string file_no_conflict_1 = FindResourceOrThrow(
resource_dir + "non_conflicting_materials_1.urdf");
const string file_no_conflict_2 = FindResourceOrThrow(
Expand Down Expand Up @@ -123,7 +124,8 @@ GTEST_TEST(URDFParserTest, TestParseMaterial) {


GTEST_TEST(URDFParserTest, TestDuplicateMaterials) {
const string resource_dir{"drake/multibody/parsers/test/urdf_parser_test/"};
const string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const string file_duplicate = FindResourceOrThrow(
resource_dir + "duplicate_materials.urdf");

Expand All @@ -133,7 +135,8 @@ GTEST_TEST(URDFParserTest, TestDuplicateMaterials) {
}

GTEST_TEST(URDFParserTest, TestConflictingMaterials) {
const string resource_dir{"drake/multibody/parsers/test/urdf_parser_test/"};
const string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const string file_conflict = FindResourceOrThrow(
resource_dir + "conflicting_materials.urdf");

Expand All @@ -158,7 +161,8 @@ string ReadTextFile(const string& file) {
// Tests that a URDF string can be loaded using a quaternion floating joint.
// This was added as a result of #4248.
GTEST_TEST(URDFParserTest, TestAddWithQuaternionFloatingDof) {
const string resource_dir{"drake/multibody/parsers/test/urdf_parser_test/"};
const string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const string model_file = FindResourceOrThrow(
resource_dir + "zero_dof_robot.urdf");
const string model_string = ReadTextFile(model_file);
Expand Down Expand Up @@ -206,7 +210,8 @@ GTEST_TEST(URDFParserTest,
// Tests that AddModelInstanceFromUrdfString()'s weld_to_frame parameter works.
// This prevents a regression of #5928.
GTEST_TEST(URDFParserTest, TestAddModelInstanceFromUrdfStringWeldToFrame) {
const string resource_dir{"drake/multibody/parsers/test/urdf_parser_test/"};
const string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const string model_file = FindResourceOrThrow(
resource_dir + "non_conflicting_materials_1.urdf");
const string model_string = ReadTextFile(model_file);
Expand Down
33 changes: 33 additions & 0 deletions multibody/multibody_tree/parsing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ drake_cc_package_library(
name = "parsing",
deps = [
":multibody_plant_sdf_parser",
":multibody_plant_urdf_parser",
":package_map",
":parser_common",
":parser_path_utils",
Expand Down Expand Up @@ -157,6 +158,26 @@ drake_cc_library(
],
)

drake_cc_library(
# TODO(sam.creasey) Add the rest of the parsing code to this
# library once the PR for urdf_geometry is merged.
name = "multibody_plant_urdf_parser",
srcs = [
"urdf_geometry.cc",
],
hdrs = [
"urdf_geometry.h",
],
deps = [
":package_map",
":parser_common",
":parser_path_utils",
":tinyxml_util",
"//multibody/multibody_tree/multibody_plant",
"@tinyxml2",
],
)

drake_cc_library(
name = "test_loaders",
testonly = 1,
Expand Down Expand Up @@ -238,6 +259,18 @@ drake_cc_googletest(
],
)

drake_cc_googletest(
name = "urdf_geometry_test",
data = [
":test_models",
],
deps = [
":multibody_plant_urdf_parser",
"//common:find_resource",
"//common/test_utilities",
],
)

drake_cc_googletest(
name = "package_map_test",
data = [
Expand Down
5 changes: 5 additions & 0 deletions multibody/multibody_tree/parsing/test/tinyxml_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ GTEST_TEST(TinyxmlUtilTest, ParseAttributeTest) {
XMLElement* element = xml_doc.FirstChildElement("element");
ASSERT_TRUE(element != nullptr);

std::string str;
EXPECT_TRUE(ParseStringAttribute(element, "bad", &str));
EXPECT_EQ(str, "one");
EXPECT_FALSE(ParseStringAttribute(element, "missing", &str));

double scalar{};
EXPECT_TRUE(ParseScalarAttribute(element, "scalar", &scalar));
EXPECT_EQ(scalar, 1.);
Expand Down
257 changes: 257 additions & 0 deletions multibody/multibody_tree/parsing/test/urdf_geometry_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
#include "drake/multibody/multibody_tree/parsing/urdf_geometry.h"

#include <vector>

#include <gtest/gtest.h>

#include "drake/common/find_resource.h"
#include "drake/common/test_utilities/eigen_matrix_compare.h"
#include "drake/common/test_utilities/expect_throws_message.h"
#include "drake/math/rigid_transform.h"
#include "drake/multibody/multibody_tree/parsing/package_map.h"
#include "drake/multibody/multibody_tree/parsing/parser_path_utils.h"

namespace drake {
namespace multibody {
namespace parsing {
namespace detail {
namespace {

using tinyxml2::XMLDocument;
using tinyxml2::XMLElement;

using geometry::GeometryInstance;
using geometry::VisualMaterial;
using multibody_plant::CoulombFriction;


class UrdfGeometryTests : public testing::Test {
public:
// Loads a URDF file and parses the minimal amount of it which
// urdf_geometry.cc handles.
void ParseUrdfGeometry(const std::string& file_name) {
const std::string full_path = parsing::GetFullPath(file_name);

xml_doc_.LoadFile(full_path.c_str());
ASSERT_FALSE(xml_doc_.ErrorID()) << xml_doc_.ErrorName();

size_t found = full_path.find_last_of("/\\");
if (found != std::string::npos) {
root_dir_ = full_path.substr(0, found);
}

// TODO(sam.creasey) Add support for using an existing package map.
package_map_.PopulateUpstreamToDrake(full_path);

const XMLElement* node = xml_doc_.FirstChildElement("robot");
ASSERT_TRUE(node);

for (const XMLElement* material_node = node->FirstChildElement("material");
material_node;
material_node = material_node->NextSiblingElement("material")) {
ParseMaterial(material_node, &materials_);
}

// Parses geometry out of the model's link elements.
for (const XMLElement* link_node = node->FirstChildElement("link");
link_node;
link_node = link_node->NextSiblingElement("link")) {
const char* attr = node->Attribute("name");
ASSERT_TRUE(attr);
const std::string body_name = attr;

for (const XMLElement* visual_node =
link_node->FirstChildElement("visual");
visual_node;
visual_node = visual_node->NextSiblingElement("visual")) {
geometry::GeometryInstance geometry_instance =
detail::ParseVisual(body_name, package_map_, root_dir_,
visual_node, &materials_);
visual_instances_.push_back(geometry_instance);
}

for (const XMLElement* collision_node =
link_node->FirstChildElement("collision");
collision_node;
collision_node = collision_node->NextSiblingElement("collision")) {
CoulombFriction<double> friction;
geometry::GeometryInstance geometry_instance =
detail::ParseCollision(body_name, package_map_, root_dir_,
collision_node, &friction);
collision_instances_.push_back(geometry_instance);
}
}
}

protected:
XMLDocument xml_doc_;
std::string root_dir_{"."};
PackageMap package_map_;
MaterialMap materials_;

std::vector<GeometryInstance> visual_instances_;
std::vector<GeometryInstance> collision_instances_;
};

// This test dives into more detail for some things than other tests. We
// assume if parsing certain things works here that it will keep working.
TEST_F(UrdfGeometryTests, TestParseMaterial1) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const std::string file_no_conflict_1 = FindResourceOrThrow(
resource_dir + "non_conflicting_materials_1.urdf");

EXPECT_NO_THROW(ParseUrdfGeometry(file_no_conflict_1));

ASSERT_EQ(materials_.size(), 3);

Eigen::Vector4d brown(0.93333333333, 0.79607843137, 0.67843137254, 1);
EXPECT_TRUE(CompareMatrices(materials_.at("brown"), brown, 1e-10));

Eigen::Vector4d red(0.93333333333, 0.2, 0.2, 1);
EXPECT_TRUE(CompareMatrices(materials_.at("red"), red, 1e-10));

Eigen::Vector4d green(0, 1, 0, 1);
EXPECT_TRUE(CompareMatrices(materials_.at("green"), green, 1e-10));

ASSERT_EQ(visual_instances_.size(), 1);
const auto& visual = visual_instances_.front();
const std::string name_base = "non_conflicting_materials_1";
EXPECT_EQ(visual.name().substr(0, name_base.size()), name_base);

EXPECT_TRUE(CompareMatrices(
visual.pose().matrix(), Eigen::Isometry3d::Identity().matrix()));

const geometry::Box* box =
dynamic_cast<const geometry::Box*>(&visual.shape());
ASSERT_TRUE(box);
EXPECT_TRUE(CompareMatrices(box->size(), Eigen::Vector3d(0.2, 0.2, 0.2)));

EXPECT_TRUE(CompareMatrices(
visual.visual_material().diffuse(), materials_.at("green")));
}

TEST_F(UrdfGeometryTests, TestParseMaterial2) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const std::string file_no_conflict_2 = FindResourceOrThrow(
resource_dir + "non_conflicting_materials_2.urdf");

EXPECT_NO_THROW(ParseUrdfGeometry(file_no_conflict_2));
EXPECT_EQ(materials_.size(), 1);

ASSERT_EQ(visual_instances_.size(), 2);
const auto& visual = visual_instances_.front();

Eigen::Isometry3d expected_pose = Eigen::Isometry3d::Identity();
expected_pose.translation() = Eigen::Vector3d(0, 0, 0.3);
EXPECT_TRUE(CompareMatrices(
visual.pose().matrix(), expected_pose.matrix()));

const geometry::Cylinder* cylinder =
dynamic_cast<const geometry::Cylinder*>(&visual.shape());
ASSERT_TRUE(cylinder);
EXPECT_EQ(cylinder->get_radius(), 0.1);
EXPECT_EQ(cylinder->get_length(), 0.6);

const auto& mesh_visual = visual_instances_.back();
const geometry::Mesh* mesh =
dynamic_cast<const geometry::Mesh*>(&mesh_visual.shape());
ASSERT_TRUE(mesh);

const std::string& mesh_filename = mesh->filename();
std::string obj_name = "tri_cube.obj";
EXPECT_EQ(mesh_filename.rfind(obj_name),
mesh_filename.size() - obj_name.size());

ASSERT_EQ(collision_instances_.size(), 1);
const auto& collision = collision_instances_.front();

const geometry::Sphere* sphere =
dynamic_cast<const geometry::Sphere*>(&collision.shape());
ASSERT_TRUE(sphere);
EXPECT_EQ(sphere->get_radius(), 0.2);
}

TEST_F(UrdfGeometryTests, TestParseMaterial3) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const std::string file_no_conflict_3 = FindResourceOrThrow(
resource_dir + "non_conflicting_materials_3.urdf");

EXPECT_NO_THROW(ParseUrdfGeometry(file_no_conflict_3));
}

TEST_F(UrdfGeometryTests, TestParseMaterialDuplicateButSame) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
// This URDF defines the same color multiple times in different links.
const std::string file_same_color_diff_links = FindResourceOrThrow(
resource_dir + "duplicate_but_same_materials.urdf");
EXPECT_NO_THROW(ParseUrdfGeometry(file_same_color_diff_links));

ASSERT_GE(visual_instances_.size(), 1);

math::RollPitchYaw<double> expected_rpy(0, 1.57, 0);
Eigen::Vector3d expected_xyz(0.01429, 0, 0);
math::RigidTransform<double> expected_pose(
math::RotationMatrix<double>(expected_rpy), expected_xyz);

const auto& visual = visual_instances_.front();
math::RigidTransform<double> actual_pose(visual.pose());
EXPECT_TRUE(actual_pose.IsNearlyEqualTo(expected_pose, 1e-10));
}

TEST_F(UrdfGeometryTests, TestDuplicateMaterials) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const std::string file_duplicate = FindResourceOrThrow(
resource_dir + "duplicate_materials.urdf");

EXPECT_THROW(ParseUrdfGeometry(file_duplicate), std::runtime_error);
}

TEST_F(UrdfGeometryTests, TestConflictingMaterials) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const std::string file_conflict = FindResourceOrThrow(
resource_dir + "conflicting_materials.urdf");

EXPECT_THROW(ParseUrdfGeometry(file_conflict), std::runtime_error);
}

TEST_F(UrdfGeometryTests, TestWrongElementType) {
const std::string resource_dir{
"drake/multibody/multibody_tree/parsing/test/urdf_parser_test/"};
const std::string file_no_conflict_1 = FindResourceOrThrow(
resource_dir + "non_conflicting_materials_1.urdf");

EXPECT_NO_THROW(ParseUrdfGeometry(file_no_conflict_1));

const XMLElement* node = xml_doc_.FirstChildElement("robot");
ASSERT_TRUE(node);

DRAKE_EXPECT_THROWS_MESSAGE(
detail::ParseMaterial(node, &materials_), std::runtime_error,
"Expected material element, got robot");

const XMLElement* material_node = node->FirstChildElement("material");
ASSERT_TRUE(material_node);

DRAKE_EXPECT_THROWS_MESSAGE(
detail::ParseVisual("fake_name", package_map_, root_dir_, material_node,
&materials_), std::runtime_error,
"In link fake_name expected visual element, got material");

multibody_plant::CoulombFriction<double> friction;
DRAKE_EXPECT_THROWS_MESSAGE(
detail::ParseCollision("fake_name", package_map_, root_dir_,
material_node, &friction), std::runtime_error,
"In link fake_name expected collision element, got material");
}

} // namespace
} // namespace detail
} // namespace parsing
} // namespace multibody
} // namespace drake
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,3 @@ should NOT be a problem.
<limit effort="100" lower="1.092" upper="1.940" velocity="100"/>
</joint>
</robot>

Loading

0 comments on commit 53b825c

Please sign in to comment.