Initial URDF Tools Package
This commit is contained in:
commit
a331a40d17
|
@ -0,0 +1,17 @@
|
|||
cmake_minimum_required(VERSION 2.4.6)
|
||||
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||
|
||||
# Append to CPACK_SOURCE_IGNORE_FILES a semicolon-separated list of
|
||||
# directories (or patterns, but directories should suffice) that should
|
||||
# be excluded from the distro. This is not the place to put things that
|
||||
# should be ignored everywhere, like "build" directories; that happens in
|
||||
# rosbuild/rosbuild.cmake. Here should be listed packages that aren't
|
||||
# ready for inclusion in a distro.
|
||||
#
|
||||
# This list is combined with the list in rosbuild/rosbuild.cmake. Note
|
||||
# that CMake 2.6 may be required to ensure that the two lists are combined
|
||||
# properly. CMake 2.4 seems to have unpredictable scoping rules for such
|
||||
# variables.
|
||||
#list(APPEND CPACK_SOURCE_IGNORE_FILES /core/experimental)
|
||||
|
||||
rosbuild_make_distribution(0.1.0)
|
|
@ -0,0 +1,19 @@
|
|||
cmake_minimum_required(VERSION 2.4.6)
|
||||
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||
|
||||
# Set the build type. Options are:
|
||||
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
|
||||
# Debug : w/ debug symbols, w/o optimization
|
||||
# Release : w/o debug symbols, w/ optimization
|
||||
# RelWithDebInfo : w/ debug symbols, w/ optimization
|
||||
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
|
||||
#set(ROS_BUILD_TYPE RelWithDebInfo)
|
||||
|
||||
rosbuild_init()
|
||||
|
||||
#set the default path for built executables to the "bin" directory
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
||||
#set the default path for built libraries to the "lib" directory
|
||||
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
|
||||
|
||||
rosbuild_add_executable(arm_kinematics arm_kinematics.cpp)
|
|
@ -0,0 +1 @@
|
|||
include $(shell rospack find mk)/cmake.mk
|
|
@ -0,0 +1,353 @@
|
|||
// bsd license blah blah
|
||||
#include <cstring>
|
||||
#include <ros/ros.h>
|
||||
#include <tf/transform_listener.h>
|
||||
#include <tf_conversions/tf_kdl.h>
|
||||
#include <tf/transform_datatypes.h>
|
||||
#include <kdl_parser/kdl_parser.hpp>
|
||||
#include <kdl/jntarray.hpp>
|
||||
#include <kdl/chainiksolverpos_nr_jl.hpp>
|
||||
#include <kdl/chainiksolvervel_pinv.hpp>
|
||||
#include <kdl/chainfksolverpos_recursive.hpp>
|
||||
#include <kinematics_msgs/GetPositionFK.h>
|
||||
#include <kinematics_msgs/GetPositionIK.h>
|
||||
#include <kinematics_msgs/GetKinematicSolverInfo.h>
|
||||
#include <kinematics_msgs/KinematicSolverInfo.h>
|
||||
using std::string;
|
||||
|
||||
static const std::string IK_SERVICE = "get_ik";
|
||||
static const std::string FK_SERVICE = "get_fk";
|
||||
static const std::string IK_INFO_SERVICE = "get_ik_solver_info";
|
||||
static const std::string FK_INFO_SERVICE = "get_fk_solver_info";
|
||||
|
||||
class Kinematics {
|
||||
public:
|
||||
Kinematics();
|
||||
bool init();
|
||||
|
||||
private:
|
||||
ros::NodeHandle nh, nh_private;
|
||||
std::string root_name, tip_name;
|
||||
KDL::JntArray joint_min, joint_max;
|
||||
KDL::Chain chain;
|
||||
unsigned int num_joints;
|
||||
|
||||
KDL::ChainFkSolverPos_recursive* fk_solver;
|
||||
KDL::ChainIkSolverPos_NR_JL *ik_solver_pos;
|
||||
KDL::ChainIkSolverVel_pinv* ik_solver_vel;
|
||||
|
||||
ros::ServiceServer ik_service,ik_solver_info_service;
|
||||
ros::ServiceServer fk_service,fk_solver_info_service;
|
||||
|
||||
tf::TransformListener tf_listener;
|
||||
|
||||
kinematics_msgs::KinematicSolverInfo info;
|
||||
|
||||
bool loadModel(const std::string xml);
|
||||
bool readJoints(urdf::Model &robot_model);
|
||||
int getJointIndex(const std::string &name);
|
||||
int getKDLSegmentIndex(const std::string &name);
|
||||
|
||||
/**
|
||||
* @brief This is the basic IK service method that will compute and return an IK solution.
|
||||
* @param A request message. See service definition for GetPositionIK for more information on this message.
|
||||
* @param The response message. See service definition for GetPositionIK for more information on this message.
|
||||
*/
|
||||
bool getPositionIK(kinematics_msgs::GetPositionIK::Request &request,
|
||||
kinematics_msgs::GetPositionIK::Response &response);
|
||||
|
||||
/**
|
||||
* @brief This is the basic kinematics info service that will return information about the kinematics node.
|
||||
* @param A request message. See service definition for GetKinematicSolverInfo for more information on this message.
|
||||
* @param The response message. See service definition for GetKinematicSolverInfo for more information on this message.
|
||||
*/
|
||||
bool getIKSolverInfo(kinematics_msgs::GetKinematicSolverInfo::Request &request,
|
||||
kinematics_msgs::GetKinematicSolverInfo::Response &response);
|
||||
|
||||
/**
|
||||
* @brief This is the basic kinematics info service that will return information about the kinematics node.
|
||||
* @param A request message. See service definition for GetKinematicSolverInfo for more information on this message.
|
||||
* @param The response message. See service definition for GetKinematicSolverInfo for more information on this message.
|
||||
*/
|
||||
bool getFKSolverInfo(kinematics_msgs::GetKinematicSolverInfo::Request &request,
|
||||
kinematics_msgs::GetKinematicSolverInfo::Response &response);
|
||||
|
||||
/**
|
||||
* @brief This is the basic forward kinematics service that will return information about the kinematics node.
|
||||
* @param A request message. See service definition for GetPositionFK for more information on this message.
|
||||
* @param The response message. See service definition for GetPositionFK for more information on this message.
|
||||
*/
|
||||
bool getPositionFK(kinematics_msgs::GetPositionFK::Request &request,
|
||||
kinematics_msgs::GetPositionFK::Response &response);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Kinematics::Kinematics(): nh_private ("~") {
|
||||
}
|
||||
|
||||
bool Kinematics::init() {
|
||||
// Get URDF XML
|
||||
std::string urdf_xml, full_urdf_xml;
|
||||
nh.param("urdf_xml",urdf_xml,std::string("robot_description"));
|
||||
nh.searchParam(urdf_xml,full_urdf_xml);
|
||||
ROS_DEBUG("Reading xml file from parameter server");
|
||||
std::string result;
|
||||
if (!nh.getParam(full_urdf_xml, result)) {
|
||||
ROS_FATAL("Could not load the xml from parameter server: %s", urdf_xml.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get Root and Tip From Parameter Service
|
||||
if (!nh_private.getParam("root_name", root_name)) {
|
||||
ROS_FATAL("GenericIK: No root name found on parameter server");
|
||||
return false;
|
||||
}
|
||||
if (!nh_private.getParam("tip_name", tip_name)) {
|
||||
ROS_FATAL("GenericIK: No tip name found on parameter server");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load and Read Models
|
||||
if (!loadModel(result)) {
|
||||
ROS_FATAL("Could not load models!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get Solver Parameters
|
||||
int maxIterations;
|
||||
double epsilon;
|
||||
|
||||
nh_private.param("maxIterations", maxIterations, 1000);
|
||||
nh_private.param("epsilon", epsilon, 1e-2);
|
||||
|
||||
// Build Solvers
|
||||
fk_solver = new KDL::ChainFkSolverPos_recursive(chain);
|
||||
ik_solver_vel = new KDL::ChainIkSolverVel_pinv(chain);
|
||||
ik_solver_pos = new KDL::ChainIkSolverPos_NR_JL(chain, joint_min, joint_max,
|
||||
*fk_solver, *ik_solver_vel, maxIterations, epsilon);
|
||||
|
||||
ROS_INFO("Advertising services");
|
||||
fk_service = nh_private.advertiseService(FK_SERVICE,&Kinematics::getPositionFK,this);
|
||||
ik_service = nh_private.advertiseService(IK_SERVICE,&Kinematics::getPositionIK,this);
|
||||
ik_solver_info_service = nh_private.advertiseService(IK_INFO_SERVICE,&Kinematics::getIKSolverInfo,this);
|
||||
fk_solver_info_service = nh_private.advertiseService(FK_INFO_SERVICE,&Kinematics::getFKSolverInfo,this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kinematics::loadModel(const std::string xml) {
|
||||
urdf::Model robot_model;
|
||||
KDL::Tree tree;
|
||||
|
||||
if (!robot_model.initString(xml)) {
|
||||
ROS_FATAL("Could not initialize robot model");
|
||||
return -1;
|
||||
}
|
||||
if (!kdl_parser::treeFromString(xml, tree)) {
|
||||
ROS_ERROR("Could not initialize tree object");
|
||||
return false;
|
||||
}
|
||||
if (!tree.getChain(root_name, tip_name, chain)) {
|
||||
ROS_ERROR("Could not initialize chain object");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readJoints(robot_model)) {
|
||||
ROS_FATAL("Could not read information about the joints");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kinematics::readJoints(urdf::Model &robot_model) {
|
||||
num_joints = 0;
|
||||
// get joint maxs and mins
|
||||
boost::shared_ptr<const urdf::Link> link = robot_model.getLink(tip_name);
|
||||
boost::shared_ptr<const urdf::Joint> joint;
|
||||
|
||||
while (link && link->name != root_name) {
|
||||
joint = robot_model.getJoint(link->parent_joint->name);
|
||||
if (!joint) {
|
||||
ROS_ERROR("Could not find joint: %s",link->parent_joint->name.c_str());
|
||||
return false;
|
||||
}
|
||||
if (joint->type != urdf::Joint::UNKNOWN && joint->type != urdf::Joint::FIXED) {
|
||||
ROS_INFO( "adding joint: [%s]", joint->name.c_str() );
|
||||
num_joints++;
|
||||
}
|
||||
link = robot_model.getLink(link->getParent()->name);
|
||||
}
|
||||
|
||||
joint_min.resize(num_joints);
|
||||
joint_max.resize(num_joints);
|
||||
info.joint_names.resize(num_joints);
|
||||
info.limits.resize(num_joints);
|
||||
|
||||
link = robot_model.getLink(tip_name);
|
||||
unsigned int i = 0;
|
||||
while (link && i < num_joints) {
|
||||
joint = robot_model.getJoint(link->parent_joint->name);
|
||||
if (joint->type != urdf::Joint::UNKNOWN && joint->type != urdf::Joint::FIXED) {
|
||||
ROS_INFO( "getting bounds for joint: [%s]", joint->name.c_str() );
|
||||
|
||||
float lower, upper;
|
||||
int hasLimits;
|
||||
if ( joint->type != urdf::Joint::CONTINUOUS ) {
|
||||
lower = joint->limits->lower;
|
||||
upper = joint->limits->upper;
|
||||
hasLimits = 1;
|
||||
} else {
|
||||
lower = -M_PI;
|
||||
upper = M_PI;
|
||||
hasLimits = 0;
|
||||
}
|
||||
int index = num_joints - i -1;
|
||||
|
||||
joint_min.data[index] = lower;
|
||||
joint_max.data[index] = upper;
|
||||
info.joint_names[index] = joint->name;
|
||||
info.limits[index].joint_name = joint->name;
|
||||
info.limits[index].has_position_limits = hasLimits;
|
||||
info.limits[index].min_position = lower;
|
||||
info.limits[index].max_position = upper;
|
||||
i++;
|
||||
}
|
||||
link = robot_model.getLink(link->getParent()->name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int Kinematics::getJointIndex(const std::string &name) {
|
||||
for (unsigned int i=0; i < info.joint_names.size(); i++) {
|
||||
if (info.joint_names[i] == name)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Kinematics::getKDLSegmentIndex(const std::string &name) {
|
||||
int i=0;
|
||||
while (i < (int)chain.getNrOfSegments()) {
|
||||
if (chain.getSegment(i).getName() == name) {
|
||||
return i+1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Kinematics::getPositionIK(kinematics_msgs::GetPositionIK::Request &request,
|
||||
kinematics_msgs::GetPositionIK::Response &response) {
|
||||
|
||||
geometry_msgs::PoseStamped pose_msg_in = request.ik_request.pose_stamped;
|
||||
tf::Stamped<tf::Pose> transform;
|
||||
tf::poseStampedMsgToTF( pose_msg_in, transform );
|
||||
|
||||
//Do the IK
|
||||
KDL::JntArray jnt_pos_in;
|
||||
KDL::JntArray jnt_pos_out;
|
||||
jnt_pos_in.resize(num_joints);
|
||||
for (unsigned int i=0; i < num_joints; i++) {
|
||||
int tmp_index = getJointIndex(request.ik_request.ik_seed_state.joint_state.name[i]);
|
||||
if (tmp_index >=0) {
|
||||
jnt_pos_in(tmp_index) = request.ik_request.ik_seed_state.joint_state.position[i];
|
||||
} else {
|
||||
ROS_ERROR("i: %d, No joint index for %s",i,request.ik_request.ik_seed_state.joint_state.name[i].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// populate F_dest from tf::Transform parameter
|
||||
KDL::Frame F_dest;
|
||||
tf::TransformTFToKDL(transform, F_dest);
|
||||
|
||||
int ik_valid = ik_solver_pos->CartToJnt(jnt_pos_in, F_dest, jnt_pos_out);
|
||||
|
||||
if (ik_valid >= 0) {
|
||||
response.solution.joint_state.name = info.joint_names;
|
||||
response.solution.joint_state.position.resize(num_joints);
|
||||
for (unsigned int i=0; i < num_joints; i++) {
|
||||
response.solution.joint_state.position[i] = jnt_pos_out(i);
|
||||
ROS_DEBUG("IK Solution: %s %d: %f",response.solution.joint_state.name[i].c_str(),i,jnt_pos_out(i));
|
||||
}
|
||||
response.error_code.val = response.error_code.SUCCESS;
|
||||
return true;
|
||||
} else {
|
||||
ROS_DEBUG("An IK solution could not be found");
|
||||
response.error_code.val = response.error_code.NO_IK_SOLUTION;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Kinematics::getIKSolverInfo(kinematics_msgs::GetKinematicSolverInfo::Request &request,
|
||||
kinematics_msgs::GetKinematicSolverInfo::Response &response) {
|
||||
response.kinematic_solver_info = info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kinematics::getFKSolverInfo(kinematics_msgs::GetKinematicSolverInfo::Request &request,
|
||||
kinematics_msgs::GetKinematicSolverInfo::Response &response) {
|
||||
response.kinematic_solver_info = info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kinematics::getPositionFK(kinematics_msgs::GetPositionFK::Request &request,
|
||||
kinematics_msgs::GetPositionFK::Response &response) {
|
||||
KDL::Frame p_out;
|
||||
KDL::JntArray jnt_pos_in;
|
||||
geometry_msgs::PoseStamped pose;
|
||||
tf::Stamped<tf::Pose> tf_pose;
|
||||
|
||||
jnt_pos_in.resize(num_joints);
|
||||
for (unsigned int i=0; i < num_joints; i++) {
|
||||
int tmp_index = getJointIndex(request.robot_state.joint_state.name[i]);
|
||||
if (tmp_index >=0)
|
||||
jnt_pos_in(tmp_index) = request.robot_state.joint_state.position[i];
|
||||
}
|
||||
|
||||
response.pose_stamped.resize(request.fk_link_names.size());
|
||||
response.fk_link_names.resize(request.fk_link_names.size());
|
||||
|
||||
bool valid = true;
|
||||
for (unsigned int i=0; i < request.fk_link_names.size(); i++) {
|
||||
int segmentIndex = getKDLSegmentIndex(request.fk_link_names[i]);
|
||||
ROS_DEBUG("End effector index: %d",segmentIndex);
|
||||
ROS_DEBUG("Chain indices: %d",chain.getNrOfSegments());
|
||||
if (fk_solver->JntToCart(jnt_pos_in,p_out,segmentIndex) >=0) {
|
||||
tf_pose.frame_id_ = root_name;
|
||||
tf_pose.stamp_ = ros::Time();
|
||||
tf::PoseKDLToTF(p_out,tf_pose);
|
||||
try {
|
||||
tf_listener.transformPose(request.header.frame_id,tf_pose,tf_pose);
|
||||
} catch (...) {
|
||||
ROS_ERROR("Could not transform FK pose to frame: %s",request.header.frame_id.c_str());
|
||||
response.error_code.val = response.error_code.FRAME_TRANSFORM_FAILURE;
|
||||
return false;
|
||||
}
|
||||
tf::poseStampedTFToMsg(tf_pose,pose);
|
||||
response.pose_stamped[i] = pose;
|
||||
response.fk_link_names[i] = request.fk_link_names[i];
|
||||
response.error_code.val = response.error_code.SUCCESS;
|
||||
} else {
|
||||
ROS_ERROR("Could not compute FK for %s",request.fk_link_names[i].c_str());
|
||||
response.error_code.val = response.error_code.NO_FK_SOLUTION;
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
ros::init(argc, argv, "arm_kinematics");
|
||||
Kinematics k;
|
||||
if (k.init()<0) {
|
||||
ROS_ERROR("Could not initialize kinematics node");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ros::spin();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
\mainpage
|
||||
\htmlinclude manifest.html
|
||||
|
||||
\b arm_kinematics is ...
|
||||
|
||||
<!--
|
||||
Provide an overview of your package.
|
||||
-->
|
||||
|
||||
|
||||
\section codeapi Code API
|
||||
|
||||
<!--
|
||||
Provide links to specific auto-generated API documentation within your
|
||||
package that is of particular interest to a reader. Doxygen will
|
||||
document pretty much every part of your code, so do your best here to
|
||||
point the reader to the actual API.
|
||||
|
||||
If your codebase is fairly large or has different sets of APIs, you
|
||||
should use the doxygen 'group' tag to keep these APIs together. For
|
||||
example, the roscpp documentation has 'libros' group.
|
||||
-->
|
||||
|
||||
|
||||
*/
|
|
@ -0,0 +1,20 @@
|
|||
<package>
|
||||
<description brief="arm_kinematics">
|
||||
|
||||
arm_kinematics
|
||||
|
||||
</description>
|
||||
<author>David Lu!!</author>
|
||||
<license>BSD</license>
|
||||
<review status="unreviewed" notes=""/>
|
||||
<url>http://ros.org/wiki/arm_kinematics</url>
|
||||
<depend package="roscpp"/>
|
||||
<depend package="tf"/>
|
||||
<depend package="sensor_msgs"/>
|
||||
<depend package="tf_conversions"/>
|
||||
<depend package="kdl_parser"/>
|
||||
<depend package="kinematics_msgs"/>
|
||||
|
||||
</package>
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
cmake_minimum_required(VERSION 2.4.6)
|
||||
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||
|
||||
# Set the build type. Options are:
|
||||
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
|
||||
# Debug : w/ debug symbols, w/o optimization
|
||||
# Release : w/o debug symbols, w/ optimization
|
||||
# RelWithDebInfo : w/ debug symbols, w/ optimization
|
||||
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
|
||||
#set(ROS_BUILD_TYPE RelWithDebInfo)
|
||||
|
||||
rosbuild_init()
|
||||
|
||||
#set the default path for built executables to the "bin" directory
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
||||
#set the default path for built libraries to the "lib" directory
|
||||
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
|
||||
|
||||
#uncomment if you have defined messages
|
||||
#rosbuild_genmsg()
|
||||
#uncomment if you have defined services
|
||||
#rosbuild_gensrv()
|
||||
|
||||
rosbuild_add_executable(easy_state_publisher src/easy_state_publisher.cpp)
|
||||
rosbuild_add_executable(easy_tf_publisher src/easy_tf_publisher.cpp)
|
||||
|
|
@ -0,0 +1 @@
|
|||
include $(shell rospack find mk)/cmake.mk
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
\mainpage
|
||||
\htmlinclude manifest.html
|
||||
|
||||
\b easy_state_publisher is ...
|
||||
|
||||
<!--
|
||||
Provide an overview of your package.
|
||||
-->
|
||||
|
||||
|
||||
\section codeapi Code API
|
||||
|
||||
<!--
|
||||
Provide links to specific auto-generated API documentation within your
|
||||
package that is of particular interest to a reader. Doxygen will
|
||||
document pretty much every part of your code, so do your best here to
|
||||
point the reader to the actual API.
|
||||
|
||||
If your codebase is fairly large or has different sets of APIs, you
|
||||
should use the doxygen 'group' tag to keep these APIs together. For
|
||||
example, the roscpp documentation has 'libros' group.
|
||||
-->
|
||||
|
||||
|
||||
*/
|
|
@ -0,0 +1,16 @@
|
|||
<package>
|
||||
<description brief="Easy State Publisher publishes JointState and/or TF messages for a given robot model">
|
||||
Easy State Publisher publishes JointState and/or TF messages for a given robot model
|
||||
</description>
|
||||
<author>David Lu!!</author>
|
||||
<license>BSD</license>
|
||||
<review status="unreviewed" notes=""/>
|
||||
<url>http://ros.org/wiki/easy_state_publisher</url>
|
||||
<depend package="roscpp"/>
|
||||
<depend package="robot_state_publisher"/>
|
||||
<depend package="urdf" />
|
||||
<depend package="kdl_parser"/>
|
||||
<depend package="sensor_msgs"/>
|
||||
</package>
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#include <string>
|
||||
#include <ros/ros.h>
|
||||
#include <sensor_msgs/JointState.h>
|
||||
#include <urdf/model.h>
|
||||
#include <urdf/joint.h>
|
||||
#include <map>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ros::init(argc, argv, "easy_state_publisher");
|
||||
ros::NodeHandle n;
|
||||
ros::Publisher m_joint_pub = n.advertise
|
||||
<sensor_msgs::JointState>("joint_states", 1);
|
||||
ros::Rate loop_rate(2);
|
||||
|
||||
urdf::Model my_model;
|
||||
if (!my_model.initParam("/robot_description")) {
|
||||
ROS_ERROR("Failed to parse urdf robot model");
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef std::map<std::string, boost::shared_ptr<urdf::Joint> > joint_map;
|
||||
typedef std::map<std::string, double> position_map;
|
||||
joint_map joints = my_model.joints_;
|
||||
position_map positions;
|
||||
|
||||
int count = 0;
|
||||
for (joint_map::const_iterator it = joints.begin(); it != joints.end(); ++it) {
|
||||
std::string name = it->first;
|
||||
urdf::Joint* joint = it->second.get();
|
||||
if (joint->type!=urdf::Joint::FIXED) {
|
||||
positions[name] = 0;
|
||||
count++;
|
||||
ROS_INFO("%s", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
sensor_msgs::JointState joint_state;
|
||||
joint_state.name.resize(count);
|
||||
joint_state.position.resize(count);
|
||||
joint_state.velocity.resize(count);
|
||||
int i = 0;
|
||||
for (position_map::const_iterator it = positions.begin(); it != positions.end(); ++it) {
|
||||
joint_state.name[i++] = it->first.c_str();
|
||||
}
|
||||
|
||||
while (ros::ok()) {
|
||||
|
||||
joint_state.header.stamp = ros::Time::now();
|
||||
m_joint_pub.publish(joint_state);
|
||||
|
||||
|
||||
// This will adjust as needed per iteration
|
||||
loop_rate.sleep();
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#include <string>
|
||||
#include <ros/ros.h>
|
||||
#include <robot_state_publisher/robot_state_publisher.h>
|
||||
#include <urdf/model.h>
|
||||
#include <urdf/joint.h>
|
||||
#include <kdl_parser/kdl_parser.hpp>
|
||||
#include <map>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ros::init(argc, argv, "easy_state_publisher");
|
||||
ros::NodeHandle n;
|
||||
ros::Rate loop_rate(30);
|
||||
|
||||
urdf::Model my_model;
|
||||
if (!my_model.initParam("/robot_description")) {
|
||||
ROS_ERROR("Failed to parse urdf robot model");
|
||||
return false;
|
||||
}
|
||||
KDL::Tree my_tree;
|
||||
if (!kdl_parser::treeFromUrdfModel(my_model, my_tree)) {
|
||||
ROS_ERROR("Failed to construct kdl tree");
|
||||
return false;
|
||||
}
|
||||
robot_state_publisher::RobotStatePublisher* publisher = new robot_state_publisher::RobotStatePublisher(my_tree);
|
||||
|
||||
typedef std::map<std::string, boost::shared_ptr<urdf::Joint> > joint_map;
|
||||
|
||||
std::map<std::string, double> positions;
|
||||
joint_map joints = my_model.joints_;
|
||||
|
||||
for (joint_map::const_iterator it = joints.begin(); it != joints.end(); ++it) {
|
||||
std::string name = it->first;
|
||||
urdf::Joint* joint = it->second.get();
|
||||
if (joint->type==urdf::Joint::REVOLUTE || joint->type==urdf::Joint::CONTINUOUS ||
|
||||
joint->type==urdf::Joint::PRISMATIC)
|
||||
positions[name] = 0;
|
||||
}
|
||||
while (ros::ok()) {
|
||||
publisher->publishTransforms(positions, ros::Time::now());
|
||||
|
||||
// Process a round of subscription messages
|
||||
ros::spinOnce();
|
||||
|
||||
// This will adjust as needed per iteration
|
||||
loop_rate.sleep();
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
cmake_minimum_required(VERSION 2.4.6)
|
||||
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||
|
||||
rosbuild_init()
|
||||
|
|
@ -0,0 +1 @@
|
|||
include $(shell rospack find mk)/cmake.mk
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import roslib; roslib.load_manifest('joint_controller')
|
||||
import rospy
|
||||
import wx
|
||||
import xml.dom.minidom
|
||||
from sensor_msgs.msg import JointState
|
||||
|
||||
RANGE = 1000
|
||||
|
||||
class JointController(wx.Frame):
|
||||
def __init__(self, parent, id, title):
|
||||
self.dependent_joints = rospy.get_param("dependent_joints", {})
|
||||
(self.free_joints, self.fixed_joints, ordered_joints) = self.getFreeJoints()
|
||||
|
||||
wx.Frame.__init__(self, parent, id, title, (-1, -1));
|
||||
panel = wx.Panel(self, wx.ID_ANY);
|
||||
box = wx.BoxSizer(wx.VERTICAL)
|
||||
font = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD)
|
||||
|
||||
### Sliders ###
|
||||
for name in ordered_joints:
|
||||
joint = self.free_joints[name]
|
||||
row = wx.GridSizer(1,2)
|
||||
label = wx.StaticText(panel, -1, name)
|
||||
label.SetFont(font)
|
||||
row.Add(label, 1, wx.ALIGN_CENTER_VERTICAL)
|
||||
|
||||
display = wx.TextCtrl (panel, value=str(0), style=wx.TE_READONLY | wx.ALIGN_RIGHT)
|
||||
|
||||
row.Add(display, flag= wx.ALIGN_RIGHT| wx.ALIGN_CENTER_VERTICAL)
|
||||
box.Add(row, 1, wx.EXPAND)
|
||||
slide = wx.Slider(panel, -1, RANGE/2, 0, RANGE, style= wx.SL_AUTOTICKS | wx.SL_HORIZONTAL)
|
||||
slide.SetFont(font)
|
||||
box.Add(slide, 1, wx.EXPAND)
|
||||
|
||||
joint['display'] = display
|
||||
joint['slider'] = slide
|
||||
|
||||
### Buttons ###
|
||||
self.ctrbutton = wx.Button(panel, 1, 'Center')
|
||||
self.Bind(wx.EVT_SLIDER, self.sliderUpdate)
|
||||
|
||||
wx.EVT_BUTTON(self, 1, self.center)
|
||||
|
||||
box.Add(self.ctrbutton, 0, wx.EXPAND)
|
||||
|
||||
panel.SetSizer(box)
|
||||
self.Center()
|
||||
|
||||
self.pub = rospy.Publisher('joint_states', JointState)
|
||||
|
||||
box.Fit(self)
|
||||
self.publish()
|
||||
|
||||
def getFreeJoints(self):
|
||||
description = rospy.get_param("robot_description")
|
||||
robot = xml.dom.minidom.parseString(description).getElementsByTagName('robot')[0]
|
||||
joints = {}
|
||||
fixed_joints = {}
|
||||
ordered_joints = []
|
||||
|
||||
for child in robot.childNodes:
|
||||
if child.nodeType is child.TEXT_NODE:
|
||||
continue
|
||||
if child.localName == 'joint':
|
||||
jtype = child.getAttribute('type')
|
||||
if jtype == 'fixed':
|
||||
continue
|
||||
name = child.getAttribute('name')
|
||||
if jtype == 'continuous':
|
||||
minval = -3.1415
|
||||
maxval = 3.1415
|
||||
else:
|
||||
limit = child.getElementsByTagName('limit')[0]
|
||||
minval = float(limit.getAttribute('lower'))
|
||||
maxval = float(limit.getAttribute('upper'))
|
||||
if minval == maxval:
|
||||
fixed_joints[name] = minval
|
||||
continue
|
||||
if name in self.dependent_joints:
|
||||
continue
|
||||
zeroval = (int)(-minval * RANGE / (maxval - minval))
|
||||
joint = {'min':minval, 'max':maxval, 'zero':zeroval, 'slidervalue':zeroval, 'value':0 }
|
||||
joints[name] = joint
|
||||
ordered_joints.append(name)
|
||||
return (joints, fixed_joints, ordered_joints)
|
||||
|
||||
def center(self, event):
|
||||
rospy.loginfo("Centering")
|
||||
for (name,joint) in self.free_joints.items():
|
||||
joint['slidervalue'] = joint['zero']
|
||||
self.publish()
|
||||
|
||||
def sliderUpdate(self, event):
|
||||
for (name,joint) in self.free_joints.items():
|
||||
joint['slidervalue'] = joint['slider'].GetValue()
|
||||
self.publish()
|
||||
|
||||
def publish(self):
|
||||
msg = JointState()
|
||||
msg.header.stamp = rospy.Time.now()
|
||||
|
||||
for (name,joint) in self.free_joints.items():
|
||||
msg.name.append(str(name))
|
||||
purevalue = joint['slidervalue']
|
||||
pctvalue = purevalue / float( RANGE)
|
||||
value = joint['min'] + (joint['max']-joint['min']) * pctvalue
|
||||
joint['value'] = value
|
||||
msg.position.append(value)
|
||||
joint['slider'].SetValue(purevalue)
|
||||
joint['display'].SetValue("%.2f"%value)
|
||||
for (name,value) in self.fixed_joints.items():
|
||||
msg.name.append(str(name))
|
||||
msg.position.append(value)
|
||||
for (name,param) in self.dependent_joints.items():
|
||||
msg.name.append(str(name))
|
||||
parent = param['parent']
|
||||
baseval = self.free_joints[parent]['value']
|
||||
value = baseval * param.get('factor', 1)
|
||||
msg.position.append(value)
|
||||
self.pub.publish(msg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
rospy.init_node('joint_controller')
|
||||
app = wx.App()
|
||||
gui = JointController(None, -1, "Joint Controller")
|
||||
gui.Show()
|
||||
app.MainLoop()
|
||||
except rospy.ROSInterruptException: pass
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<package>
|
||||
<description brief="A node for inputting joint angles directly">
|
||||
A node for inputting joint angles directly
|
||||
</description>
|
||||
<author>David Lu!!</author>
|
||||
<license>BSD</license>
|
||||
<review status="unreviewed" notes=""/>
|
||||
<url>http://ros.org/wiki/joint_controller</url>
|
||||
<depend package="rospy" />
|
||||
<depend package="sensor_msgs" />
|
||||
<rosdep name="wxpython" />
|
||||
</package>
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
cmake_minimum_required(VERSION 2.4.6)
|
||||
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||
|
||||
# Set the build type. Options are:
|
||||
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
|
||||
# Debug : w/ debug symbols, w/o optimization
|
||||
# Release : w/o debug symbols, w/ optimization
|
||||
# RelWithDebInfo : w/ debug symbols, w/ optimization
|
||||
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
|
||||
#set(ROS_BUILD_TYPE RelWithDebInfo)
|
||||
|
||||
rosbuild_init()
|
||||
|
||||
#set the default path for built executables to the "bin" directory
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
||||
#set the default path for built libraries to the "lib" directory
|
||||
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
|
||||
|
||||
#uncomment if you have defined messages
|
||||
#rosbuild_genmsg()
|
||||
#uncomment if you have defined services
|
||||
#rosbuild_gensrv()
|
||||
|
||||
#common commands for building c++ executables and libraries
|
||||
#rosbuild_add_library(${PROJECT_NAME} src/example.cpp)
|
||||
#target_link_libraries(${PROJECT_NAME} another_library)
|
||||
#rosbuild_add_boost_directories()
|
||||
#rosbuild_link_boost(${PROJECT_NAME} thread)
|
||||
#rosbuild_add_executable(example examples/example.cpp)
|
||||
#target_link_libraries(example ${PROJECT_NAME})
|
|
@ -0,0 +1 @@
|
|||
include $(shell rospack find mk)/cmake.mk
|
|
@ -0,0 +1,732 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import roslib; roslib.load_manifest('simmechanics_to_urdf')
|
||||
import rospy
|
||||
import sys
|
||||
import tf
|
||||
from tf.transformations import euler_from_quaternion, quaternion_from_matrix
|
||||
import xml.dom.minidom
|
||||
from xml.dom.minidom import Document
|
||||
import threading
|
||||
import math
|
||||
import numpy
|
||||
import yaml
|
||||
|
||||
# Conversion Factors
|
||||
INCH2METER = 0.0254
|
||||
SLUG2KG = 14.5939029
|
||||
SLUGININ2KGMM = .009415402
|
||||
MM2M = .001
|
||||
|
||||
# Special Reference Frame(s)
|
||||
WORLD = "WORLD"
|
||||
|
||||
# Arbitrary List of colors to give pieces different looks
|
||||
COLORS =[("green", "0 1 0 1"), ("black", "0 0 0 1"), ("red", "1 0 0 1"),
|
||||
("blue", "0 0 1 1"), ("yellow", "1 1 0 1"), ("pink", "1 0 1 1"),
|
||||
("cyan", "0 1 1 1"), ("green", "0 1 0 1"), ("white", "1 1 1 1"),
|
||||
("dblue", "0 0 .8 1"), ("dgreen", ".1 .8 .1 1"), ("gray", ".5 .5 .5 1")]
|
||||
|
||||
class Converter:
|
||||
def __init__(self):
|
||||
# initialize member variables
|
||||
self.links = {}
|
||||
self.frames = {}
|
||||
self.joints = {}
|
||||
self.names = {}
|
||||
self.colormap = {}
|
||||
self.colorindex = 0
|
||||
self.usedcolors = {}
|
||||
|
||||
# Start the Transform Manager
|
||||
self.tfman = TransformManager()
|
||||
self.tfman.start()
|
||||
|
||||
# Extra Transforms for Debugging
|
||||
self.tfman.add([0,0,0], [0.70682518,0,0,0.70682518], "ROOT", WORLD) # rotate so Z axis is up
|
||||
|
||||
def convert(self, filename, configfile, mode):
|
||||
self.mode = mode
|
||||
|
||||
# Parse the configuration file
|
||||
self.parseConfig(configfile)
|
||||
|
||||
# Parse the input file
|
||||
self.parse(xml.dom.minidom.parse(filename))
|
||||
self.buildTree(self.root)
|
||||
|
||||
# Create the output
|
||||
self.result = Document()
|
||||
self.output(self.root)
|
||||
|
||||
# output the output
|
||||
if mode == "xml":
|
||||
print self.result.toprettyxml()
|
||||
if mode == "graph":
|
||||
print self.graph()
|
||||
if mode == "groups":
|
||||
print self.groups(root)
|
||||
|
||||
def parseConfig(self, configFile):
|
||||
"""Parse the Configuration File, if it exists.
|
||||
Set the fields the default if the config does
|
||||
not set them """
|
||||
if configFile == None:
|
||||
configuration = {}
|
||||
else:
|
||||
configuration = yaml.load(file(configFile, 'r'))
|
||||
if configuration == None:
|
||||
configuration = {}
|
||||
|
||||
self.freezeList = []
|
||||
self.redefinedjoints = {}
|
||||
|
||||
self.root = configuration.get('root', None)
|
||||
self.extrajoints = configuration.get('extrajoints', {})
|
||||
self.filenameformat = configuration.get('filenameformat', "%s")
|
||||
self.forcelowercase = configuration.get('forcelowercase', False)
|
||||
self.scale = configuration.get('scale', None)
|
||||
self.freezeAll = configuration.get('freezeAll', False)
|
||||
self.baseframe = configuration.get('baseframe', WORLD)
|
||||
|
||||
# Get lists converted to strings
|
||||
self.removeList = [ str(e) for e in configuration.get('remove', []) ]
|
||||
self.freezeList = [ str(e) for e in configuration.get('freeze', []) ]
|
||||
|
||||
# Get map with key converted to strings
|
||||
jointmap = configuration.get('redefinedjoints', {})
|
||||
for x in jointmap.keys():
|
||||
self.redefinedjoints[str(x)] = jointmap[x]
|
||||
|
||||
# Add Extra Frames
|
||||
for frame in configuration.get('moreframes', []):
|
||||
self.tfman.add(frame['offset'], frame['orientation'], frame['parent'], frame['child'])
|
||||
|
||||
|
||||
def parse(self, element):
|
||||
"""Recursively goes through all XML elements
|
||||
and branches off for important elements"""
|
||||
name = element.localName
|
||||
# Grab name from root element AND recursively parse
|
||||
if name == "PhysicalModelingXMLFile":
|
||||
dict = getDictionary(element)
|
||||
self.name = dict['name']
|
||||
|
||||
if name == "Body":
|
||||
self.parseLink(element)
|
||||
elif name == "SimpleJoint":
|
||||
self.parseJoint(element)
|
||||
elif name == "Ground":
|
||||
dict = getDictionary(element)
|
||||
self.parseFrames(dict['frame'], "GROUND")
|
||||
else:
|
||||
for child in element.childNodes:
|
||||
self.parse(child)
|
||||
|
||||
def parseLink(self, link):
|
||||
"""Parse the important bits of a link element"""
|
||||
linkdict = getDictionary(link)
|
||||
uid = self.getName(linkdict['name'])
|
||||
linkdict['neighbors'] = []
|
||||
linkdict['children'] = []
|
||||
linkdict['jointmap'] = {}
|
||||
|
||||
# Save the frames for separate parsing
|
||||
frames = linkdict['frames']
|
||||
linkdict['frames'] = None
|
||||
|
||||
# Save the color if it exists
|
||||
if 'MaterialProp' in linkdict:
|
||||
colorelement = linkdict['MaterialProp'][1]
|
||||
color = colorelement.childNodes[0].data
|
||||
linkdict['MaterialProp'] = None
|
||||
linkdict['color'] = color.replace(",", " ") + " 1"
|
||||
|
||||
|
||||
self.links[uid] = linkdict
|
||||
self.parseFrames(frames, uid)
|
||||
|
||||
# Save First Actual Element as Root, if not defined already
|
||||
if self.root == None and "geometryFileName" in linkdict:
|
||||
self.root = uid
|
||||
|
||||
def parseFrames(self, frames, parent):
|
||||
"""Parse the frames from xml"""
|
||||
for frame in frames:
|
||||
if frame.nodeType is frame.TEXT_NODE:
|
||||
continue
|
||||
fdict = getDictionary(frame)
|
||||
fid = str(frame.getAttribute("ref"))
|
||||
fdict['parent'] = parent
|
||||
|
||||
offset = getlist(fdict['position'])
|
||||
units = fdict['positionUnits']
|
||||
for i in range(0, len(offset)):
|
||||
offset[i] = convert(offset[i], units)
|
||||
|
||||
orientation = getlist(fdict['orientation'])
|
||||
quat = matrixToQuaternion(orientation)
|
||||
|
||||
# If the frame does not have a reference number,
|
||||
# use the name plus a suffix (for CG or CS1...
|
||||
# otherwise ignore the frame
|
||||
if fid == "":
|
||||
name = fdict['name']
|
||||
if name == "CG":
|
||||
fid = parent + "CG"
|
||||
elif name == "CS1":
|
||||
fid = parent + "CS1"
|
||||
else:
|
||||
continue
|
||||
|
||||
self.tfman.add(offset, quat, WORLD, fid)
|
||||
self.frames[fid] = fdict
|
||||
|
||||
def parseJoint(self, element):
|
||||
"""Parse the joint from xml"""
|
||||
dict = getDictionary(element)
|
||||
joint = {}
|
||||
joint['name'] = dict['name']
|
||||
uid = self.getName(joint['name'])
|
||||
|
||||
frames = element.getElementsByTagName("Frame")
|
||||
joint['parent'] = str(frames[0].getAttribute("ref"))
|
||||
joint['child'] = str(frames[1].getAttribute("ref"))
|
||||
type = element.getElementsByTagName("Primitive")
|
||||
|
||||
# If there multiple elements, assume a fixed joint
|
||||
if len(type)==1:
|
||||
pdict = getDictionary(type[0])
|
||||
joint['type'] = pdict['name']
|
||||
joint['axis'] = pdict['axis']
|
||||
if joint['type'] == 'weld':
|
||||
joint['type'] = 'fixed'
|
||||
else:
|
||||
joint['type'] = 'fixed'
|
||||
|
||||
# Ignore joints on the remove list
|
||||
if joint['parent'] in self.removeList:
|
||||
return
|
||||
|
||||
# Force joints to be fixed on the freezeList
|
||||
if joint['parent'] in self.freezeList or self.freezeAll:
|
||||
joint['type'] = 'fixed'
|
||||
|
||||
# Redefine specified joints on redefined list
|
||||
if joint['parent'] in self.redefinedjoints.keys():
|
||||
jdict = self.redefinedjoints[joint['parent']]
|
||||
if 'name' in jdict:
|
||||
uid = jdict['name']
|
||||
|
||||
# Default to continuous joints
|
||||
joint['type'] = jdict.get('type', 'continuous')
|
||||
|
||||
if 'axis' in jdict:
|
||||
joint['axis'] = jdict['axis']
|
||||
if 'limits' in jdict:
|
||||
joint['limits'] = jdict['limits']
|
||||
|
||||
self.joints[uid] = joint
|
||||
|
||||
def buildTree(self, root):
|
||||
"""Reduce the graph structure of links and joints to a tree
|
||||
by breadth first search. Then construct new coordinate frames
|
||||
from new tree structure"""
|
||||
|
||||
# Create a list of all neighboring links at each link
|
||||
for jid in self.joints:
|
||||
jointdict = self.joints[jid]
|
||||
if "Root" in jointdict['name']:
|
||||
continue
|
||||
pid = self.getLinkNameByFrame(jointdict['parent'])
|
||||
cid = self.getLinkNameByFrame(jointdict['child'])
|
||||
parent = self.links[pid]
|
||||
child = self.links[cid]
|
||||
|
||||
parent['neighbors'].append(cid)
|
||||
parent['jointmap'][cid] = jid
|
||||
child['neighbors'].append(pid)
|
||||
child['jointmap'][pid] = jid
|
||||
|
||||
# Add necessary information for any user-defined joints
|
||||
for (name, extrajoint) in self.extrajoints.items():
|
||||
pid = extrajoint['pid']
|
||||
cid = extrajoint['cid']
|
||||
jorigin = extrajoint['jorigin']
|
||||
newframe = name + "_frame"
|
||||
|
||||
self.links[pid]['neighbors'].append(cid)
|
||||
self.links[pid]['jointmap'][cid] = name
|
||||
self.links[cid]['neighbors'].append(pid)
|
||||
self.links[cid]['jointmap'][pid] = name
|
||||
self.joints[name] = {'name': name, 'parent': jorigin, 'child': newframe}
|
||||
for (k,v) in extrajoint['attributes'].items():
|
||||
self.joints[name][k] = v
|
||||
self.frames[jorigin] = {'parent': pid}
|
||||
self.frames[newframe] = {'parent': cid}
|
||||
|
||||
# Starting with designated root node, perform BFS to
|
||||
# create the tree
|
||||
queue = [ root ]
|
||||
self.links[root]['parent'] = "GROUND"
|
||||
while len(queue) > 0:
|
||||
id = queue.pop(0)
|
||||
link = self.links[id]
|
||||
for n in link['neighbors']:
|
||||
nbor = self.links[n]
|
||||
# if a neighbor has not been visited yet,
|
||||
# add it as a child node
|
||||
if not 'parent' in nbor:
|
||||
nbor['parent'] = id
|
||||
queue.append(n)
|
||||
link['children'].append(n)
|
||||
|
||||
# build new coordinate frames
|
||||
for id in self.links:
|
||||
link = self.links[id]
|
||||
if not 'parent' in link:
|
||||
continue
|
||||
parentid = link['parent']
|
||||
if parentid == "GROUND":
|
||||
ref = self.baseframe
|
||||
else:
|
||||
joint = self.joints[link['jointmap'][parentid]]
|
||||
ref = joint['parent']
|
||||
# The root of each link is the offset to the joint
|
||||
# and the rotation of the CS1 frame
|
||||
(off1, rot1) = self.tfman.get(WORLD, ref)
|
||||
(off2, rot2) = self.tfman.get(WORLD, id + "CS1")
|
||||
self.tfman.add(off1, rot2, WORLD, "X" + id)
|
||||
|
||||
|
||||
def output(self, rootid):
|
||||
"""Creates the URDF from the parsed document.
|
||||
Makes the document and starts the recursive build process"""
|
||||
doc = self.result;
|
||||
self.root = doc.createElement("robot")
|
||||
doc.appendChild(self.root)
|
||||
self.root.setAttribute("name", self.name)
|
||||
self.outputLink(rootid)
|
||||
self.processLink(rootid)
|
||||
|
||||
def processLink(self, id):
|
||||
""" Creates the output for the specified node's
|
||||
child links, the connecting joints, then
|
||||
recursively processes each child """
|
||||
link = self.links[id]
|
||||
for cid in link['children']:
|
||||
jid = link['jointmap'][cid]
|
||||
|
||||
self.outputLink(cid)
|
||||
self.outputJoint(jid, id)
|
||||
self.processLink(cid)
|
||||
|
||||
def outputLink(self, id):
|
||||
""" Creates the URDF output for a single link """
|
||||
doc = self.result
|
||||
linkdict = self.links[id]
|
||||
if linkdict['name'] == "RootPart":
|
||||
return
|
||||
|
||||
link = doc.createElement("link")
|
||||
link.setAttribute("name", id)
|
||||
|
||||
visual = doc.createElement("visual")
|
||||
inertial = doc.createElement("inertial")
|
||||
collision = doc.createElement("collision")
|
||||
link.appendChild(visual)
|
||||
link.appendChild(inertial)
|
||||
link.appendChild(collision)
|
||||
|
||||
# Define Geometry
|
||||
geometry = doc.createElement("geometry")
|
||||
mesh = doc.createElement("mesh")
|
||||
filename = linkdict['geometryFileName']
|
||||
if self.forcelowercase:
|
||||
filename = filename.lower()
|
||||
|
||||
mesh.setAttribute("filename", self.filenameformat % filename)
|
||||
if self.scale != None:
|
||||
mesh.setAttribute("scale", self.scale)
|
||||
geometry.appendChild(mesh)
|
||||
visual.appendChild(geometry)
|
||||
collision.appendChild(geometry.cloneNode(1))
|
||||
|
||||
# Define Inertial Frame
|
||||
mass = doc.createElement("mass")
|
||||
units = linkdict['massUnits']
|
||||
massval = convert(float(linkdict['mass']), units)
|
||||
mass.setAttribute("value", str(massval))
|
||||
inertial.appendChild(mass)
|
||||
|
||||
inertia = doc.createElement("inertia")
|
||||
matrix = getlist(linkdict["inertia"])
|
||||
units = linkdict['inertiaUnits']
|
||||
|
||||
for i in range(0,len(matrix)):
|
||||
matrix[i] = convert(matrix[i], units)
|
||||
|
||||
inertia.setAttribute("ixx", str(matrix[0]))
|
||||
inertia.setAttribute("ixy", str(matrix[1]))
|
||||
inertia.setAttribute("ixz", str(matrix[2]))
|
||||
inertia.setAttribute("iyy", str(matrix[4]))
|
||||
inertia.setAttribute("iyz", str(matrix[5]))
|
||||
inertia.setAttribute("izz", str(matrix[8]))
|
||||
inertial.appendChild(inertia)
|
||||
|
||||
# Inertial origin is the center of gravity
|
||||
iorigin = doc.createElement("origin")
|
||||
(off, rot) = self.tfman.get("X" + id, id+"CG")
|
||||
rpy = list(euler_from_quaternion(rot))
|
||||
iorigin.setAttribute("xyz", " ".join(map(str,zero(off))))
|
||||
iorigin.setAttribute("rpy", " ".join(map(str,zero(rpy))))
|
||||
inertial.appendChild(iorigin)
|
||||
|
||||
# Create link's origin
|
||||
origin = doc.createElement("origin")
|
||||
|
||||
# Visual offset is difference between origin and CS1
|
||||
(off, rot) = self.tfman.get("X" + id, id+"CS1")
|
||||
rpy = list(euler_from_quaternion(rot))
|
||||
origin.setAttribute("xyz", " ".join(map(str,zero(off))))
|
||||
origin.setAttribute("rpy", " ".join(map(str,zero(rpy))))
|
||||
visual.appendChild(origin)
|
||||
collision.appendChild(origin.cloneNode(1))
|
||||
|
||||
# Define Material
|
||||
material = doc.createElement("material")
|
||||
|
||||
# Use specified color, if exists. Otherwise, get random color
|
||||
if 'color' in linkdict:
|
||||
cname = "%s_color"%id
|
||||
rgba = linkdict['color']
|
||||
else:
|
||||
(cname, rgba) = self.getColor(linkdict['name'])
|
||||
material.setAttribute("name", cname)
|
||||
visual.appendChild(material)
|
||||
|
||||
# If color has already been output, only output name
|
||||
if not cname in self.usedcolors:
|
||||
color = doc.createElement("color")
|
||||
color.setAttribute("rgba", rgba)
|
||||
material.appendChild(color)
|
||||
self.usedcolors[cname] = True
|
||||
|
||||
self.root.appendChild(link)
|
||||
|
||||
def getColor(self, s):
|
||||
""" Gets a two element list containing a color name,
|
||||
and it's rgba. The color selected is based on the mesh name.
|
||||
If already seen, returns the saved color
|
||||
Otherwise, returns the next available color"""
|
||||
if s in self.colormap:
|
||||
return self.colormap[s]
|
||||
color = COLORS[self.colorindex]
|
||||
self.colormap[s] = color
|
||||
self.colorindex = (self.colorindex + 1) % len(COLORS)
|
||||
return color
|
||||
|
||||
def outputJoint(self, id, parentname):
|
||||
""" Outputs URDF for a single joint """
|
||||
doc = self.result
|
||||
jointdict = self.joints[id]
|
||||
|
||||
if "Root" in jointdict['name']:
|
||||
return
|
||||
|
||||
joint = doc.createElement("joint")
|
||||
joint.setAttribute('name', id)
|
||||
|
||||
# Define the parent and child
|
||||
pid = self.getLinkNameByFrame(jointdict['parent'])
|
||||
cid = self.getLinkNameByFrame(jointdict['child'])
|
||||
|
||||
# If the original joint was reversed while building the tree,
|
||||
# swap the two ids
|
||||
if parentname != pid:
|
||||
cid = pid
|
||||
pid = parentname
|
||||
|
||||
parent = doc.createElement("parent")
|
||||
child = doc.createElement("child")
|
||||
parent.setAttribute('link', pid)
|
||||
child.setAttribute('link', cid)
|
||||
joint.appendChild(parent)
|
||||
joint.appendChild(child)
|
||||
|
||||
# Define joint type
|
||||
jtype = jointdict['type']
|
||||
joint.setAttribute('type', jtype)
|
||||
if 'limits' in jointdict:
|
||||
limit = doc.createElement("limit")
|
||||
for (k,v) in jointdict['limits'].items():
|
||||
limit.setAttribute(str(k), str(v))
|
||||
joint.appendChild(limit)
|
||||
if 'axis' in jointdict and jtype != 'fixed':
|
||||
axis = doc.createElement("axis")
|
||||
xyz = jointdict['axis'].replace(',', ' ')
|
||||
axis.setAttribute('xyz', xyz)
|
||||
joint.appendChild(axis)
|
||||
|
||||
# Define the origin
|
||||
origin = doc.createElement("origin")
|
||||
(off, rot) = self.tfman.get("X" + pid, "X" + cid)
|
||||
rpy = list(euler_from_quaternion(rot))
|
||||
off = zero(off)
|
||||
rpy = zero(rpy)
|
||||
origin.setAttribute("xyz", " ".join(map(str,off)))
|
||||
origin.setAttribute("rpy", " ".join(map(str,rpy)))
|
||||
joint.appendChild(origin)
|
||||
|
||||
self.root.appendChild(joint)
|
||||
|
||||
def getName(self, basename):
|
||||
"""Return a unique name of the format
|
||||
basenameD where D is the lowest number
|
||||
to make the name unique"""
|
||||
index = 1
|
||||
name = basename + str(index)
|
||||
while name in self.names:
|
||||
index = index + 1
|
||||
name = basename + str(index)
|
||||
self.names[name] = 1
|
||||
return name
|
||||
|
||||
def getLinkNameByFrame(self, key):
|
||||
"""Gets the link name from the frame object"""
|
||||
return self.frames[key]['parent']
|
||||
|
||||
def graph(self):
|
||||
"""For debugging purposes, output a graphviz
|
||||
representation of the tree structure, noting
|
||||
which joints have been reversed and which have
|
||||
been removed"""
|
||||
graph = "digraph proe {\n"
|
||||
for jkey in self.joints:
|
||||
joint = self.joints[jkey]
|
||||
pref = joint['parent']
|
||||
cref = joint['child']
|
||||
label = pref + ":" + cref
|
||||
pkey = self.getLinkNameByFrame(pref)
|
||||
ckey = self.getLinkNameByFrame(cref)
|
||||
case = 'std'
|
||||
if pkey != "GROUND":
|
||||
parent = self.links[pkey]
|
||||
if not ckey in parent['children']:
|
||||
child = self.links[ckey]
|
||||
if pkey in child['children']:
|
||||
case = 'rev'
|
||||
else:
|
||||
case = 'not'
|
||||
pkey = pkey.replace("-", "_")
|
||||
ckey = ckey.replace("-", "_")
|
||||
|
||||
if (case == 'std' or case == 'rev') and (joint['type'] != "fixed"):
|
||||
style = " penwidth=\"5\""
|
||||
else:
|
||||
style = "";
|
||||
|
||||
if case == 'std':
|
||||
s = pkey + " -> " + ckey + " [ label = \""+label+"\"";
|
||||
elif case == 'not':
|
||||
s = pkey + " -> " + ckey + " [ label = \""+label+"\" color=\"yellow\""
|
||||
elif case == 'rev':
|
||||
s = ckey + " -> " + pkey + " [ label = \""+label+"\" color=\"blue\""
|
||||
s = s + style + "];"
|
||||
|
||||
if not "Root" in s and "-> SCR_" not in s:
|
||||
graph = graph + s + "\n"
|
||||
return graph + "}\n"
|
||||
|
||||
def groups(self, root):
|
||||
""" For planning purposes, print out lists of
|
||||
all the links between the different joints"""
|
||||
self.groups = {}
|
||||
self.makeGroup(root, "BASE")
|
||||
s = ""
|
||||
for key in self.groups.keys():
|
||||
s = s + key + ":\n\t"
|
||||
ids = self.groups[key]
|
||||
for id in ids:
|
||||
s = s+id + " "
|
||||
s = s + "\n\n"
|
||||
return s
|
||||
|
||||
def makeGroup(self, id, gid):
|
||||
""" Helper function for recursively gathering
|
||||
groups of joints. """
|
||||
if gid in self.groups:
|
||||
idlist = self.groups[gid]
|
||||
idlist.append(id)
|
||||
else:
|
||||
idlist = [id]
|
||||
self.groups[gid] = idlist
|
||||
link = self.links[id]
|
||||
for child in link['children']:
|
||||
jid = link['jointmap'][child]
|
||||
joint = self.joints[jid]
|
||||
if joint['type'] == 'weld':
|
||||
ngid = gid
|
||||
else:
|
||||
ngid = jid
|
||||
|
||||
self.makeGroup(child, ngid)
|
||||
|
||||
def getDictionary(tag):
|
||||
"""Builds a dictionary from the specified xml tag
|
||||
where each child of the tag is entered into the dictionary
|
||||
with the name of the child tag as the key, and the contents
|
||||
as the value. Also removes quotes from quoted values"""
|
||||
x = {}
|
||||
for child in tag.childNodes:
|
||||
if child.nodeType is child.TEXT_NODE:
|
||||
continue
|
||||
key = str(child.localName)
|
||||
if len(child.childNodes) == 1:
|
||||
data = str(child.childNodes[0].data)
|
||||
if data[0] == '"' and data[-1] == '"':
|
||||
if len(data) != 2:
|
||||
x[key] = data[1:-1]
|
||||
else:
|
||||
x[key] = data
|
||||
else:
|
||||
data = child.childNodes
|
||||
x[key] = data
|
||||
return x
|
||||
|
||||
def getlist(string):
|
||||
"""Splits a string of comma delimited floats to
|
||||
a list of floats"""
|
||||
slist = string.split(",")
|
||||
flist = []
|
||||
for s in slist:
|
||||
flist.append(float(s))
|
||||
return flist
|
||||
|
||||
|
||||
def convert(value, units):
|
||||
"""Convert value from the specified units to mks units"""
|
||||
if units == 'kg' or units == 'm' or units == 'kg*m^2':
|
||||
return value
|
||||
elif units == 'slug*in^2':
|
||||
return value * SLUGININ2KGMM
|
||||
elif units == 'slug':
|
||||
return value * SLUG2KG
|
||||
elif units == 'in':
|
||||
return value * INCH2METER
|
||||
elif units == 'mm':
|
||||
return value * MM2M
|
||||
else:
|
||||
raise Exception("unsupported mass unit: %s" % units)
|
||||
|
||||
def matrixToQuaternion(matrix):
|
||||
"""Concert 3x3 rotation matrix into a quaternion"""
|
||||
(R11, R12, R13, R21, R22, R23, R31, R32, R33) = matrix
|
||||
# Build 4x4 matrix
|
||||
M = [[R11, R21, R31, 0],
|
||||
[R12, R22, R32, 0],
|
||||
[R13, R23, R33, 0],
|
||||
[0, 0, 0, 1]]
|
||||
A = numpy.array(M)
|
||||
[w,x,y,z] = quaternion_from_matrix(A)
|
||||
return [w,x,y,z]
|
||||
|
||||
def quaternion_to_rpy(quat):
|
||||
"""Convert quaternion into roll pitch yaw list (in degrees)"""
|
||||
rpy = list(euler_from_quaternion(quat))
|
||||
for i in range(0, len(rpy)):
|
||||
rpy[i] = rpy[i]*180/math.pi
|
||||
return rpy
|
||||
|
||||
def zero(arr):
|
||||
"""Converts any numbers less than 1e-7 to 0 in the array"""
|
||||
for i in range(0,len(arr)):
|
||||
if math.fabs(arr[i]) < 1e-7:
|
||||
arr[i] = 0
|
||||
return arr
|
||||
|
||||
|
||||
class TransformManager(threading.Thread):
|
||||
"""Bridge to the ROS tf package. Constantly broadcasts
|
||||
all tfs, and retrieves them on demand."""
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
self.running = True
|
||||
self.tfs = []
|
||||
self.listener = tf.TransformListener()
|
||||
|
||||
def run(self):
|
||||
"""Broadcasts tfs until shutdown"""
|
||||
rate = rospy.Rate(10.0)
|
||||
br = tf.TransformBroadcaster()
|
||||
while self.running:
|
||||
for transform in self.tfs:
|
||||
br.sendTransform(transform['trans'],
|
||||
transform['rot'],
|
||||
rospy.Time.now(),
|
||||
transform['child'],
|
||||
transform['parent'])
|
||||
rate.sleep()
|
||||
|
||||
def add(self, trans, rot, parent, child):
|
||||
"""Adds a new transform to the set"""
|
||||
tf = {'trans':trans, 'rot':rot, 'child':child, 'parent':parent}
|
||||
self.tfs.append(tf)
|
||||
|
||||
def get(self, parent, child):
|
||||
"""Attempts to retrieve a transform"""
|
||||
attempts = 0
|
||||
rate = rospy.Rate(10.0)
|
||||
while attempts < 50:
|
||||
try:
|
||||
(off,rpy) = self.listener.lookupTransform(parent, child, rospy.Time(0))
|
||||
return [list(off), list(rpy)]
|
||||
except (tf.LookupException, tf.ConnectivityException):
|
||||
attempts+=1
|
||||
rate.sleep()
|
||||
continue
|
||||
|
||||
raise Exception("Attempt to transform %s exceeded attempt limit" % (parent + "/" + child))
|
||||
|
||||
def printTransform(self, parent, child):
|
||||
"""Attempts to print the specified transform"""
|
||||
(off, rot) = self.get(parent, child)
|
||||
rpy = quaternion_to_rpy(rot)
|
||||
|
||||
s = parent + "-->" + child
|
||||
l = (30 - len(s))*" "
|
||||
print "%s%s[%+.5f %+.5f %+.5f] \t [%+3.3f %+.3f %+.3f] \t [%+.6f %+.6f %+.6f %+.6f] " \
|
||||
% (s, l, off[0], off[1], off[2], rpy[0], rpy[1], rpy[2], rot[0], rot[1], rot[2], rot[3])
|
||||
|
||||
def kill(self):
|
||||
"""Stops thread from running"""
|
||||
self.running = False
|
||||
|
||||
if __name__ == '__main__':
|
||||
argc = len(sys.argv)
|
||||
if argc == 3:
|
||||
filename = sys.argv[1]
|
||||
config = None
|
||||
mode = sys.argv[2]
|
||||
elif argc == 4:
|
||||
filename = sys.argv[1]
|
||||
config = sys.argv[2]
|
||||
mode = sys.argv[3]
|
||||
else:
|
||||
print "Usage: " + sys.argv[0] + " {XML filename} [configfile] {tf|xml|graph|groups|none}"
|
||||
sys.exit(-1)
|
||||
try:
|
||||
rospy.init_node('convert')
|
||||
con = Converter()
|
||||
try:
|
||||
con.convert(filename, config, mode)
|
||||
while mode == "tf" and not rospy.is_shutdown():
|
||||
None
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
con.tfman.kill()
|
||||
|
||||
except rospy.ROSInterruptException: pass
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/python
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 2:
|
||||
print "Usage: " + sys.argv[0] + " [directory]"
|
||||
sys.exit(-1)
|
||||
path= sys.argv[1]
|
||||
dirList=os.listdir(path)
|
||||
for fname in dirList:
|
||||
path1 = path + fname
|
||||
path2 = path + fname + "b"
|
||||
cmd = "rosrun ivcon ivcon " + path1 + " " + path2
|
||||
proc = subprocess.Popen([cmd], stdout=subprocess.PIPE, shell=True)
|
||||
(out, err) = proc.communicate()
|
||||
print err
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
\mainpage
|
||||
\htmlinclude manifest.html
|
||||
|
||||
\b simmechanics_to_urdf is ...
|
||||
|
||||
<!--
|
||||
Provide an overview of your package.
|
||||
-->
|
||||
|
||||
|
||||
\section codeapi Code API
|
||||
|
||||
<!--
|
||||
Provide links to specific auto-generated API documentation within your
|
||||
package that is of particular interest to a reader. Doxygen will
|
||||
document pretty much every part of your code, so do your best here to
|
||||
point the reader to the actual API.
|
||||
|
||||
If your codebase is fairly large or has different sets of APIs, you
|
||||
should use the doxygen 'group' tag to keep these APIs together. For
|
||||
example, the roscpp documentation has 'libros' group.
|
||||
-->
|
||||
|
||||
|
||||
*/
|
|
@ -0,0 +1,15 @@
|
|||
<package>
|
||||
<description brief="Converts SimMechanics XML to URDF">
|
||||
Converts SimMechanics XML to URDF
|
||||
</description>
|
||||
<author>David Lu!!</author>
|
||||
<license>BSD</license>
|
||||
<review status="unreviewed" notes=""/>
|
||||
<url>http://ros.org/wiki/simmechanics_to_urdf</url>
|
||||
<depend package="rospy"/>
|
||||
<depend package="sensor_msgs"/>
|
||||
<depend package="tf"/>
|
||||
<rosdep name="python-yaml"/>
|
||||
</package>
|
||||
|
||||
|
Loading…
Reference in New Issue