moveit2
The MoveIt Motion Planning Framework for ROS 2.
test_cartesian_interpolator.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2019, PickNik Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the PickNik nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 /* Author: Michael Lautman */
36 
42 
43 #include <rclcpp/node.hpp>
44 
45 using namespace moveit::core;
46 
47 class SimpleRobot : public testing::Test
48 {
49 protected:
50  void SetUp() override
51  {
52  RobotModelBuilder builder("simple", "a");
53  builder.addChain("a->b", "continuous");
54  builder.addChain("b->c", "prismatic");
55  builder.addGroupChain("a", "c", "group");
56  robot_model_ = builder.build();
57  }
58 
59  void TearDown() override
60  {
61  }
62 
63 protected:
64  RobotModelConstPtr robot_model_;
65 
66  static std::size_t generateTestTraj(std::vector<std::shared_ptr<RobotState>>& traj,
67  const RobotModelConstPtr& robot_model_);
68 };
69 
70 std::size_t SimpleRobot::generateTestTraj(std::vector<std::shared_ptr<RobotState>>& traj,
71  const RobotModelConstPtr& robot_model_)
72 {
73  traj.clear();
74 
75  auto robot_state = std::make_shared<RobotState>(robot_model_);
76  robot_state->setToDefaultValues();
77  double ja, jc;
78 
79  // 3 waypoints with default joints
80  for (std::size_t traj_ix = 0; traj_ix < 3; ++traj_ix)
81  {
82  traj.push_back(std::make_shared<RobotState>(*robot_state));
83  }
84 
85  ja = robot_state->getVariablePosition("a-b-joint"); // revolute joint
86  jc = robot_state->getVariablePosition("b-c-joint"); // prismatic joint
87 
88  // 4th waypoint with a small jump of 0.01 in revolute joint and prismatic joint. This should not be considered a jump
89  ja = ja - 0.01;
90  robot_state->setVariablePosition("a-b-joint", ja);
91  jc = jc - 0.01;
92  robot_state->setVariablePosition("b-c-joint", jc);
93  traj.push_back(std::make_shared<RobotState>(*robot_state));
94 
95  // 5th waypoint with a large jump of 1.01 in first revolute joint
96  ja = ja + 1.01;
97  robot_state->setVariablePosition("a-b-joint", ja);
98  traj.push_back(std::make_shared<RobotState>(*robot_state));
99 
100  // 6th waypoint with a large jump of 1.01 in first prismatic joint
101  jc = jc + 1.01;
102  robot_state->setVariablePosition("b-c-joint", jc);
103  traj.push_back(std::make_shared<RobotState>(*robot_state));
104 
105  // 7th waypoint with no jump
106  traj.push_back(std::make_shared<RobotState>(*robot_state));
107 
108  return traj.size();
109 }
110 
111 TEST_F(SimpleRobot, testGenerateTrajectory)
112 {
113  std::vector<std::shared_ptr<RobotState>> traj;
114 
115  // The full trajectory should be of length 7
116  const std::size_t expected_full_traj_len = 7;
117 
118  // Generate a test trajectory
119  std::size_t full_traj_len = generateTestTraj(traj, robot_model_);
120 
121  // Test the generateTestTraj still generates a trajectory of length 7
122  EXPECT_EQ(full_traj_len, expected_full_traj_len); // full traj should be 7 waypoints long
123 }
124 
125 TEST_F(SimpleRobot, checkAbsoluteJointSpaceJump)
126 {
127  const JointModelGroup* joint_model_group = robot_model_->getJointModelGroup("group");
128  std::vector<std::shared_ptr<RobotState>> traj;
129 
130  // A revolute joint jumps 1.01 at the 5th waypoint and a prismatic joint jumps 1.01 at the 6th waypoint
131  const std::size_t expected_revolute_jump_traj_len = 4;
132  const std::size_t expected_prismatic_jump_traj_len = 5;
133 
134  // Pre-compute expected results for tests
135  std::size_t full_traj_len = generateTestTraj(traj, robot_model_);
136  const double expected_revolute_jump_fraction =
137  static_cast<double>(expected_revolute_jump_traj_len) / static_cast<double>(full_traj_len);
138  const double expected_prismatic_jump_fraction =
139  static_cast<double>(expected_prismatic_jump_traj_len) / static_cast<double>(full_traj_len);
140 
141  // Container for results
142  double fraction;
143 
144  // Revolute and prismatic joints.
145  generateTestTraj(traj, robot_model_);
146  fraction = CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::absolute(1.0, 1.0));
147  EXPECT_EQ(expected_revolute_jump_traj_len, traj.size()); // traj should be cut before the revolute jump
148  EXPECT_NEAR(expected_revolute_jump_fraction, fraction, 0.01);
149 
150  // Test revolute joints
151  generateTestTraj(traj, robot_model_);
152  fraction = CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::absolute(0.01, 10.0));
153  EXPECT_EQ(expected_revolute_jump_traj_len, traj.size()); // traj should be cut before the revolute jump
154  EXPECT_NEAR(expected_revolute_jump_fraction, fraction, 0.01);
155 
156  // Test prismatic joints
157  generateTestTraj(traj, robot_model_);
158  fraction = CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::absolute(10.0, 0.01));
159  EXPECT_EQ(expected_prismatic_jump_traj_len, traj.size()); // traj should be cut before the prismatic jump
160  EXPECT_NEAR(expected_prismatic_jump_fraction, fraction, 0.01);
161 
162  // Pre-condition checks.
163  EXPECT_ANY_THROW(
164  CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::absolute(0.0, 0.01)));
165  EXPECT_ANY_THROW(
166  CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::absolute(0.01, 0.0)));
167 }
168 
169 TEST_F(SimpleRobot, checkRelativeJointSpaceJump)
170 {
171  const JointModelGroup* joint_model_group = robot_model_->getJointModelGroup("group");
172  std::vector<std::shared_ptr<RobotState>> traj;
173 
174  // The first large jump of 1.01 occurs at the 5th waypoint so the test should trim the trajectory to length 4
175  const std::size_t expected_relative_jump_traj_len = 4;
176 
177  // Pre-compute expected results for tests
178  std::size_t full_traj_len = generateTestTraj(traj, robot_model_);
179  const double expected_relative_jump_fraction =
180  static_cast<double>(expected_relative_jump_traj_len) / static_cast<double>(full_traj_len);
181 
182  // Container for results
183  double fraction = 0.0;
184 
185  // Call of relative version: 1.01 > 2.97 * (0.01 * 2 + 1.01 * 2)/6.
186  generateTestTraj(traj, robot_model_);
187  fraction = CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::relative(2.97));
188  EXPECT_EQ(expected_relative_jump_traj_len, traj.size()); // traj should be cut before the first jump of 1.01
189  EXPECT_NEAR(expected_relative_jump_fraction, fraction, 0.01);
190 
191  // Trajectory should not be cut: 1.01 < 2.98 * (0.01 * 2 + 1.01 * 2)/6.
192  generateTestTraj(traj, robot_model_);
193  fraction = CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::relative(2.98));
194  EXPECT_EQ(full_traj_len, traj.size()); // traj should not be cut
195  EXPECT_NEAR(1.0, fraction, 0.01);
196 
197  // Pre-condition checks.
198  EXPECT_ANY_THROW(CartesianInterpolator::checkJointSpaceJump(joint_model_group, traj, JumpThreshold::relative(0.0)));
199 }
200 
201 // TODO - The tests below fail since no kinematic plugins are found. Move the tests to IK plugin package.
202 // class PandaRobot : public testing::Test
203 // {
204 // protected:
205 // static void SetUpTestSuite() // setup resources shared between tests
206 // {
207 // robot_model_ = loadTestingRobotModel("panda");
208 // jmg = robot_model_->getJointModelGroup("panda_arm");
209 // link = robot_model_->getLinkModel("panda_link8");
210 // ASSERT_TRUE(link);
211 // node = rclcpp::Node::make_shared("test_cartesian_interpolator");
212 // loadIKPluginForGroup(node, jmg, "panda_link0", link->getName());
213 // }
214 //
215 // static void TearDownTestSuite()
216 // {
217 // robot_model_.reset();
218 // }
219 //
220 // void SetUp() override
221 // {
222 // start_state = std::make_shared<RobotState>(robot_model_);
223 // ASSERT_TRUE(start_state->setToDefaultValues(jmg, "ready"));
224 // start_pose = start_state->getGlobalLinkTransform(link);
225 // }
226 //
227 // double computeCartesianPath(std::vector<std::shared_ptr<RobotState>>& result, const Eigen::Vector3d& translation,
228 // bool global)
229 // {
230 // return CartesianInterpolator::computeCartesianPath(start_state.get(), jmg, result, link, translation, global,
231 // MaxEEFStep(0.1), JumpThreshold::Disabled(),
232 // GroupStateValidityCallbackFn(),
233 // kinematics::KinematicsQueryOptions());
234 // }
235 //
236 // double computeCartesianPath(std::vector<std::shared_ptr<RobotState>>& result, const Eigen::Isometry3d& target,
237 // bool global, const Eigen::Isometry3d& offset = Eigen::Isometry3d::Identity())
238 // {
239 // return CartesianInterpolator::computeCartesianPath(start_state.get(), jmg, result, link, target, global,
240 // MaxEEFStep(0.1), JumpThreshold::Disabled(),
241 // GroupStateValidityCallbackFn(),
242 // kinematics::KinematicsQueryOptions(),
243 // kinematics::KinematicsBase::IKCostFn(), offset);
244 // }
245 //
246 // protected:
247 // static RobotModelPtr robot_model_;
248 // static JointModelGroup* jmg_;
249 // static const LinkModel* link_;
250 // static rclcpp::Node::SharedPtr node;
251 //
252 // const double prec_ = 1e-8;
253 // RobotStatePtr start_state_;
254 // Eigen::Isometry3d start_pose_;
255 // std::vector<std::shared_ptr<RobotState>> result_;
256 // };
257 //
258 // TEST_F(PandaRobot, testVectorGlobal)
259 // {
260 // Eigen::Vector3d translation(0.2, 0, 0); // move by 0.2 along world's x axis
261 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, translation, true), 0.2); // moved full distance of 0.2
262 // // first pose of trajectory should be identical to start_pose
263 // EXPECT_EIGEN_EQ(result.front()->getGlobalLinkTransform(link), start_pose);
264 // // last pose of trajectory should have same orientation, and offset of 0.2 along world's x-axis
265 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).linear(), start_pose.linear(), prec);
266 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).translation(),
267 // start_pose.translation() + translation, prec);
268 // }
269 // TEST_F(PandaRobot, testVectorLocal)
270 // {
271 // Eigen::Vector3d translation(0.2, 0, 0); // move by 0.2 along link's x axis
272 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, translation, false), 0.2); // moved full distance of 0.2
273 // // first pose of trajectory should be identical to start_pose
274 // EXPECT_EIGEN_EQ(result.front()->getGlobalLinkTransform(link), start_pose);
275 // // last pose of trajectory should have same orientation, and offset of 0.2 along link's x-axis
276 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).linear(), start_pose.linear(), prec);
277 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).translation(), start_pose * translation, prec);
278 // }
279 
280 // TEST_F(PandaRobot, testTranslationGlobal)
281 // {
282 // Eigen::Isometry3d goal = start_pose;
283 // goal.translation().x() += 0.2; // move by 0.2 along world's x-axis
284 
285 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, goal, true), 1.0); // 100% of distance generated
286 // // first pose of trajectory should be identical to start_pose
287 // EXPECT_EIGEN_EQ(result.front()->getGlobalLinkTransform(link), start_pose);
288 // // last pose of trajectory should have same orientation, but offset of 0.2 along world's x-axis
289 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).linear(), start_pose.linear(), prec);
290 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).translation(), goal.translation(), prec);
291 // }
292 // TEST_F(PandaRobot, testTranslationLocal)
293 // {
294 // Eigen::Isometry3d offset(Eigen::Translation3d(0.2, 0, 0)); // move along link's x-axis
295 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, offset, false), 1.0); // 100% of distance generated
296 // // first pose of trajectory should be identical to start_pose
297 // EXPECT_EIGEN_EQ(result.front()->getGlobalLinkTransform(link), start_pose);
298 // // last pose of trajectory should have same orientation, but offset of 0.2 along link's x-axis
299 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).linear(), start_pose.linear(), prec);
300 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).translation(), start_pose * offset.translation(),
301 // prec);
302 // }
303 
304 // TEST_F(PandaRobot, testRotationLocal)
305 // {
306 // // 45° rotation about links's x-axis
307 // Eigen::Isometry3d rot(Eigen::AngleAxisd(M_PI / 4, Eigen::Vector3d::UnitX()));
308 // Eigen::Isometry3d goal = start_pose * rot;
309 
310 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, rot, false), 1.0); // 100% of distance generated
311 // // first pose of trajectory should be identical to start_pose
312 // EXPECT_EIGEN_EQ(result.front()->getGlobalLinkTransform(link), start_pose);
313 // // last pose of trajectory should have same position,
314 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).translation(), start_pose.translation(), prec);
315 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link), goal, prec);
316 // }
317 // TEST_F(PandaRobot, testRotationGlobal)
318 // {
319 // // 45° rotation about links's x-axis
320 // Eigen::Isometry3d rot(Eigen::AngleAxisd(M_PI / 4, Eigen::Vector3d::UnitX()));
321 // Eigen::Isometry3d goal = start_pose * rot;
322 
323 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, goal, true), 1.0); // 100% of distance generated
324 // // first pose of trajectory should be identical to start_pose
325 // EXPECT_EIGEN_NEAR(result.front()->getGlobalLinkTransform(link), start_pose, prec);
326 // // last pose of trajectory should have same position,
327 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link).translation(), start_pose.translation(), prec);
328 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link), goal, prec);
329 // }
330 // TEST_F(PandaRobot, testRotationOffset)
331 // {
332 // // define offset to virtual center frame
333 // Eigen::Isometry3d offset = Eigen::Translation3d(0, 0, 0.2) * Eigen::AngleAxisd(-M_PI / 4,
334 // Eigen::Vector3d::UnitZ());
335 // // 45° rotation about center's x-axis
336 // Eigen::Isometry3d rot(Eigen::AngleAxisd(M_PI / 4, Eigen::Vector3d::UnitX()));
337 // Eigen::Isometry3d goal = start_pose * offset * rot;
338 
339 // ASSERT_DOUBLE_EQ(computeCartesianPath(result, goal, true, offset), 1.0); // 100% of distance generated
340 // // first pose of trajectory should be identical to start_pose
341 // EXPECT_EIGEN_NEAR(result.front()->getGlobalLinkTransform(link), start_pose, prec);
342 
343 // // All waypoints of trajectory should have same position in virtual frame
344 // for (const auto& waypoint : result)
345 // EXPECT_EIGEN_NEAR((waypoint->getGlobalLinkTransform(link) * offset).translation(),
346 // (start_pose * offset).translation(), prec);
347 // // goal should be reached by virtual frame
348 // EXPECT_EIGEN_NEAR(result.back()->getGlobalLinkTransform(link) * offset, goal, prec);
349 // }
350 
351 int main(int argc, char** argv)
352 {
353  testing::InitGoogleTest(&argc, argv);
354  rclcpp::init(argc, argv);
355  return RUN_ALL_TESTS();
356 }
static std::size_t generateTestTraj(std::vector< std::shared_ptr< RobotState >> &traj, const RobotModelConstPtr &robot_model_)
RobotModelConstPtr robot_model_
static Percentage checkJointSpaceJump(const JointModelGroup *group, std::vector< std::shared_ptr< RobotState >> &path, const JumpThreshold &jump_threshold)
Checks if a path has a joint-space jump, and truncates the path at the jump.
Easily build different robot models for testing. Essentially a programmer-friendly light wrapper arou...
void addChain(const std::string &section, const std::string &type, const std::vector< geometry_msgs::msg::Pose > &joint_origins={}, urdf::Vector3 joint_axis=urdf::Vector3(1.0, 0.0, 0.0))
Adds a chain of links and joints to the builder. The joint names are generated automatically as "<par...
moveit::core::RobotModelPtr build()
Builds and returns the robot model added to the builder.
void addGroupChain(const std::string &base_link, const std::string &tip_link, const std::string &name="")
Adds a new group using a chain of links. The group is the parent joint of each link in the chain.
Core components of MoveIt.
static JumpThreshold relative(double relative_factor)
Detect joint-space jumps relative to the average joint-space displacement along the path.
static JumpThreshold absolute(double revolute, double prismatic)
Detect joint-space jumps greater than the given absolute thresholds.
int main(int argc, char **argv)
TEST_F(SimpleRobot, testGenerateTrajectory)