diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c6e7f3dc9d4f104e30a5200a28d5b01cdb855d9..81651f0e3289f5364da7cebab39b1321f954d822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,9 @@ ADD_OPTIONAL_DEPENDENCY("metapod >= 1.0.7") ADD_OPTIONAL_DEPENDENCY("urdfdom >= 0.2.10") ADD_OPTIONAL_DEPENDENCY("lua5.1") ADD_OPTIONAL_DEPENDENCY("hpp-fcl") +IF(HPP_FCL_FOUND AND URDFDOM_FOUND) + ADD_REQUIRED_DEPENDENCY("assimp") +ENDIF(HPP_FCL_FOUND AND URDFDOM_FOUND) SET(BOOST_COMPONENTS filesystem unit_test_framework) SEARCH_FOR_BOOST() @@ -178,6 +181,15 @@ IF(URDFDOM_FOUND) multibody/parser/urdf.hpp multibody/parser/urdf.hxx ) + + IF(HPP_FCL_FOUND ) + SET(${PROJECT_NAME}_MULTIBODY_PARSER_HEADERS + ${${PROJECT_NAME}_MULTIBODY_PARSER_HEADERS} + multibody/parser/from-collada-to-fcl.hpp + multibody/parser/urdf-with-geometry.hpp + ) + ENDIF(HPP_FCL_FOUND) + LIST(APPEND HEADERS ${${PROJECT_NAME}_MULTIBODY_PARSER_HEADERS} ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1c10506648acb6d8e9f99c8c7bb7ee05a6bc788..3b12df4f8d666fe5812911da91ae49026c75d772 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,6 +67,13 @@ IF (UNIX) SET_TARGET_PROPERTIES( ${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) PKG_CONFIG_USE_DEPENDENCY(${PROJECT_NAME} eigen3) + IF(URDFDOM_FOUND) + PKG_CONFIG_USE_DEPENDENCY(${PROJECT_NAME} urdfdom) + IF(HPP_FCL_FOUND) + PKG_CONFIG_USE_DEPENDENCY(${PROJECT_NAME} assimp) + ENDIF(HPP_FCL_FOUND) + ENDIF(URDFDOM_FOUND) + IF(HPP_FCL_FOUND) PKG_CONFIG_USE_DEPENDENCY(${PROJECT_NAME} hpp-fcl) ADD_TARGET_CFLAGS (${PROJECT_NAME} "-DWITH_HPP_FCL") @@ -96,6 +103,9 @@ IF(EIGENPY_FOUND) PKG_CONFIG_USE_DEPENDENCY(${PYWRAP} eigenpy) IF(URDFDOM_FOUND) PKG_CONFIG_USE_DEPENDENCY(${PYWRAP} urdfdom) + IF(HPP_FCL_FOUND) + PKG_CONFIG_USE_DEPENDENCY(${PYWRAP} assimp) + ENDIF(HPP_FCL_FOUND) ENDIF(URDFDOM_FOUND) IF(HPP_FCL_FOUND) PKG_CONFIG_USE_DEPENDENCY(${PYWRAP} hpp-fcl) diff --git a/src/multibody/parser/from-collada-to-fcl.hpp b/src/multibody/parser/from-collada-to-fcl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d46ec85c335084ddbdf6ff6aa064b304cb735e12 --- /dev/null +++ b/src/multibody/parser/from-collada-to-fcl.hpp @@ -0,0 +1,165 @@ +// +// Copyright (c) 2015 CNRS +// +// This file is part of Pinocchio and is mainly inspired +// by software hpp-model-urdf +// Pinocchio is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version +// 3 of the License, or (at your option) any later version. +// +// Pinocchio is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Lesser Public License for more details. You should have +// received a copy of the GNU Lesser General Public License along with +// Pinocchio If not, see +// <http://www.gnu.org/licenses/>. +#include <limits> + +#include <boost/filesystem/fstream.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +#include <assimp/DefaultLogger.h> + +#include <assimp/assimp.hpp> +#include <assimp/aiScene.h> +#include <assimp/aiPostProcess.h> +#include <assimp/IOStream.h> +#include <assimp/IOSystem.h> + + +#include <hpp/fcl/BV/OBBRSS.h> +#include <hpp/fcl/BVH/BVH_model.h> + +typedef fcl::BVHModel< fcl::OBBRSS > PolyhedronType; +typedef boost::shared_ptr <PolyhedronType> PolyhedronPtrType; + +struct TriangleAndVertices +{ + void clear() + { + vertices_.clear (); + triangles_.clear (); + } + std::vector <fcl::Vec3f> vertices_; + std::vector <fcl::Triangle> triangles_; +}; + +void buildMesh (const ::urdf::Vector3& scale, const aiScene* scene, const aiNode* node, + std::vector<unsigned>& subMeshIndexes, const PolyhedronPtrType& mesh, + TriangleAndVertices& tv) +{ + if (!node) return; + + aiMatrix4x4 transform = node->mTransformation; + aiNode *pnode = node->mParent; + while (pnode) + { + // Don't convert to y-up orientation, which is what the root node in + // Assimp does + if (pnode->mParent != NULL) + { + transform = pnode->mTransformation * transform; + } + pnode = pnode->mParent; + } + + for (uint32_t i = 0; i < node->mNumMeshes; i++) + { + aiMesh* input_mesh = scene->mMeshes[node->mMeshes[i]]; + + unsigned oldNbPoints = mesh->num_vertices; + unsigned oldNbTriangles = mesh->num_tris; + + // Add the vertices + for (uint32_t j = 0; j < input_mesh->mNumVertices; j++) + { + aiVector3D p = input_mesh->mVertices[j]; + p *= transform; + tv.vertices_.push_back (fcl::Vec3f (p.x * scale.x, + p.y * scale.y, + p.z * scale.z)); + } + + // add the indices + for (uint32_t j = 0; j < input_mesh->mNumFaces; j++) + { + aiFace& face = input_mesh->mFaces[j]; + // FIXME: can add only triangular faces. + tv.triangles_.push_back (fcl::Triangle( oldNbPoints + face.mIndices[0], + oldNbPoints + face.mIndices[1], + oldNbPoints + face.mIndices[2])); + } + + // Save submesh triangles indexes interval. + if (subMeshIndexes.size () == 0) + { + subMeshIndexes.push_back (0); + } + + subMeshIndexes.push_back (oldNbTriangles + input_mesh->mNumFaces); + } + + for (uint32_t i=0; i < node->mNumChildren; ++i) + { + buildMesh(scale, scene, node->mChildren[i], subMeshIndexes, mesh, tv); + } +} + + +void meshFromAssimpScene (const std::string& name, const ::urdf::Vector3& scale, + const aiScene* scene,const PolyhedronPtrType& mesh) + { + TriangleAndVertices tv; + + if (!scene->HasMeshes()) + { + throw std::runtime_error (std::string ("No meshes found in file ")+ + name); + } + + std::vector<unsigned> subMeshIndexes; + int res = mesh->beginModel (); + + if (res != fcl::BVH_OK) + { + std::ostringstream error; + error << "fcl BVHReturnCode = " << res; + throw std::runtime_error (error.str ()); + } + + tv.clear(); + + buildMesh (scale, scene, scene->mRootNode, subMeshIndexes, mesh, tv); + mesh->addSubModel (tv.vertices_, tv.triangles_); + + mesh->endModel (); + } + +void loadPolyhedronFromResource ( const std::string& resource_path, const ::urdf::Vector3& scale, + const PolyhedronPtrType& polyhedron) +{ + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(resource_path.c_str(), aiProcess_SortByPType| aiProcess_GenNormals| + aiProcess_Triangulate|aiProcess_GenUVCoords| + aiProcess_FlipUVs); + if (!scene) + { + throw std::runtime_error (std::string ("Could not load resource ") + resource_path + std::string ("\n") + + importer.GetErrorString ()); + } + + meshFromAssimpScene (resource_path, scale, scene, polyhedron); +} + +std::string fromURDFMeshPathToAbsolutePath(std::string & urdf_mesh_path, std::string meshRootDir) +{ + + std::string absolutePath = std::string(meshRootDir + + urdf_mesh_path.substr(10, urdf_mesh_path.size())); + + return absolutePath; +} + diff --git a/src/multibody/parser/urdf-with-geometry.hpp b/src/multibody/parser/urdf-with-geometry.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f03a0ec1183604c47a2bfb248d2218357f22abba --- /dev/null +++ b/src/multibody/parser/urdf-with-geometry.hpp @@ -0,0 +1,345 @@ +// +// Copyright (c) 2015 CNRS +// +// This file is part of Pinocchio +// Pinocchio is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version +// 3 of the License, or (at your option) any later version. +// +// Pinocchio is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Lesser Public License for more details. You should have +// received a copy of the GNU Lesser General Public License along with +// Pinocchio If not, see +// <http://www.gnu.org/licenses/>. + +#ifndef __se3_urdf_geom_hpp__ +#define __se3_urdf_geom_hpp__ + +#include <urdf_model/model.h> +#include <urdf_parser/urdf_parser.h> + +#include <iostream> +#include <boost/foreach.hpp> +#include "pinocchio/multibody/model.hpp" + +#include <hpp/fcl/collision_object.h> +#include <hpp/fcl/collision.h> +#include <hpp/fcl/shape/geometric_shapes.h> +#include "pinocchio/multibody/parser/from-collada-to-fcl.hpp" + +#include <exception> + +namespace urdf +{ + typedef boost::shared_ptr<ModelInterface> ModelInterfacePtr; + typedef boost::shared_ptr<const Joint> JointConstPtr; + typedef boost::shared_ptr<const Link> LinkConstPtr; + typedef boost::shared_ptr<Link> LinkPtr; + typedef boost::shared_ptr<const Inertial> InertialConstPtr; +} + +namespace se3 +{ + namespace urdf + { + Inertia convertFromUrdf( const ::urdf::Inertial& Y ) + { + const ::urdf::Vector3 & p = Y.origin.position; + const ::urdf::Rotation & q = Y.origin.rotation; + + const Eigen::Vector3d com(p.x,p.y,p.z); + const Eigen::Matrix3d & R = Eigen::Quaterniond(q.w,q.x,q.y,q.z).matrix(); + + Eigen::Matrix3d I; I << Y.ixx,Y.ixy,Y.ixz + , Y.ixy,Y.iyy,Y.iyz + , Y.ixz,Y.iyz,Y.izz; + return Inertia(Y.mass,com,R*I*R.transpose()); + } + + SE3 convertFromUrdf( const ::urdf::Pose & M ) + { + const ::urdf::Vector3 & p = M.position; + const ::urdf::Rotation & q = M.rotation; + return SE3( Eigen::Quaterniond(q.w,q.x,q.y,q.z).matrix(), Eigen::Vector3d(p.x,p.y,p.z)); + } + + enum AxisCartesian { AXIS_X, AXIS_Y, AXIS_Z, AXIS_UNALIGNED }; + AxisCartesian extractCartesianAxis( const ::urdf::Vector3 & axis ) + { + if( (axis.x==1.0)&&(axis.y==0.0)&&(axis.z==0.0) ) + return AXIS_X; + else if( (axis.x==0.0)&&(axis.y==1.0)&&(axis.z==0.0) ) + return AXIS_Y; + else if( (axis.x==0.0)&&(axis.y==0.0)&&(axis.z==1.0) ) + return AXIS_Z; + else + return AXIS_UNALIGNED; + } + + +fcl::CollisionObject retrieveCollisionGeometry (const ::urdf::LinkConstPtr & link, std::string meshRootDir) + { + boost::shared_ptr < ::urdf::Collision> collision = link->collision; + boost::shared_ptr < fcl::CollisionGeometry > geometry; + + // Handle the case where collision geometry is a mesh + if (collision->geometry->type == ::urdf::Geometry::MESH) + { + boost::shared_ptr < ::urdf::Mesh> collisionGeometry = boost::dynamic_pointer_cast< ::urdf::Mesh> (collision->geometry); + std::string collisionFilename = collisionGeometry->filename; + + std::string full_path = fromURDFMeshPathToAbsolutePath(collisionFilename, meshRootDir); + + ::urdf::Vector3 scale = collisionGeometry->scale; + + // Create FCL mesh by parsing Collada file. + PolyhedronPtrType polyhedron (new PolyhedronType); + + loadPolyhedronFromResource (full_path, scale, polyhedron); + geometry = polyhedron; + } + + + // Compute body position in world frame. + // MatrixHomogeneousType position = computeBodyAbsolutePosition (link, collision->origin); + if (!geometry) + { + throw std::runtime_error(std::string("The polyhedron retrived is empty")); + } + fcl::CollisionObject collisionObject (geometry, fcl::Transform3f()); + + return collisionObject; // TO CHECK: what happens if geometry is empty ? + + } + + void parseTreeWithGeom( ::urdf::LinkConstPtr link, Model & model,GeometryModel & model_geom, std::string meshRootDir, const SE3 & placementOffset = SE3::Identity()) throw (std::invalid_argument) +{ + + + ::urdf::JointConstPtr joint = link->parent_joint; + SE3 nextPlacementOffset = SE3::Identity(); // OffSet of the next link. In case we encounter a fixed joint, we need to propagate the length of its attached body to next joint. + + // std::cout << " *** " << link->name << " < attached by joint "; + // if(joint) + // std::cout << "#" << link->parent_joint->name << std::endl; + // else std::cout << "###ROOT" << std::endl; + + // std::cout << "placementOffset: " << placementOffset << std::endl; + + if(joint!=NULL) + { + assert(link->getParent()!=NULL); + + if (!link->inertial && joint->type != ::urdf::Joint::FIXED) + { + const std::string exception_message (link->name + " - spatial inertia information missing."); + throw std::invalid_argument(exception_message); + } + + Model::Index parent = (link->getParent()->parent_joint==NULL) ? (model.existJointName("root_joint") ? model.getJointId("root_joint") : 0) : + model.getJointId( link->getParent()->parent_joint->name ); + //std::cout << joint->name << " === " << parent << std::endl; + + const SE3 & jointPlacement = placementOffset*convertFromUrdf(joint->parent_to_joint_origin_transform); + + const Inertia & Y = (link->inertial) ? convertFromUrdf(*link->inertial) : + Inertia::Identity(); + + bool visual = (link->visual) ? true : false; + + + //std::cout << "Parent = " << parent << std::endl; + //std::cout << "Placement = " << (Matrix4)jointPlacement << std::endl; + + switch(joint->type) + { + case ::urdf::Joint::REVOLUTE: + case ::urdf::Joint::CONTINUOUS: // Revolute with no joint limits + { + + Eigen::VectorXd maxEffort; + Eigen::VectorXd velocity; + Eigen::VectorXd lowerPosition; + Eigen::VectorXd upperPosition; + + if (joint->limits) + { + maxEffort.resize(1); maxEffort << joint->limits->effort; + velocity.resize(1); velocity << joint->limits->velocity; + lowerPosition.resize(1); lowerPosition << joint->limits->lower; + upperPosition.resize(1); upperPosition << joint->limits->upper; + } + + Eigen::Vector3d jointAxis(Eigen::Vector3d::Zero()); + AxisCartesian axis = extractCartesianAxis(joint->axis); + switch(axis) + { + case AXIS_X: + model.addBody( parent, JointModelRX(), jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + case AXIS_Y: + model.addBody( parent, JointModelRY(), jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + case AXIS_Z: + model.addBody( parent, JointModelRZ(), jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + case AXIS_UNALIGNED: + jointAxis= Eigen::Vector3d( joint->axis.x,joint->axis.y,joint->axis.z ); + jointAxis.normalize(); + model.addBody( parent, JointModelRevoluteUnaligned(jointAxis), + jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + default: + assert( false && "Fatal Error while extracting revolute joint axis"); + break; + } + break; + } + case ::urdf::Joint::PRISMATIC: + { + Eigen::VectorXd maxEffort = Eigen::VectorXd(0.); + Eigen::VectorXd velocity = Eigen::VectorXd(0.); + Eigen::VectorXd lowerPosition = Eigen::VectorXd(0.); + Eigen::VectorXd upperPosition = Eigen::VectorXd(0.); + + if (joint->limits) + { + maxEffort.resize(1); maxEffort << joint->limits->effort; + velocity.resize(1); velocity << joint->limits->velocity; + lowerPosition.resize(1); lowerPosition << joint->limits->lower; + upperPosition.resize(1); upperPosition << joint->limits->upper; + } + AxisCartesian axis = extractCartesianAxis(joint->axis); + switch(axis) + { + case AXIS_X: + model.addBody( parent, JointModelPX(), jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + case AXIS_Y: + model.addBody( parent, JointModelPY(), jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + case AXIS_Z: + model.addBody( parent, JointModelPZ(), jointPlacement, Y, + maxEffort, velocity, lowerPosition, upperPosition, + joint->name,link->name, visual ); + break; + case AXIS_UNALIGNED: + std::cerr << "Bad axis = (" << joint->axis.x <<"," << joint->axis.y << "," << joint->axis.z << ")" << std::endl; + assert(false && "Only X, Y or Z axis are accepted." ); + break; + default: + assert( false && "Fatal Error while extracting prismatic joint axis"); + break; + } + break; + } + case ::urdf::Joint::FIXED: + { + // In case of fixed joint, if link has inertial tag: + // -add the inertia of the link to his parent in the model + // Otherwise do nothing. + // In all cases: + // -let all the children become children of parent + // -inform the parser of the offset to apply + // -add fixed body in model to display it in gepetto-viewer + if (link->inertial) + { + model.mergeFixedBody(parent, jointPlacement, Y); //Modify the parent inertia in the model + } + + SE3 ptjot_se3 = convertFromUrdf(link->parent_joint->parent_to_joint_origin_transform); + + //transformation of the current placement offset + nextPlacementOffset = placementOffset*ptjot_se3; + + //add the fixed Body in the model for the viewer + model.addFixedBody(parent,nextPlacementOffset,link->name,visual); + + BOOST_FOREACH(::urdf::LinkPtr child_link,link->child_links) + { + child_link->setParent(link->getParent() ); //skip the fixed generation + } + break; + } + default: + { + std::cerr << "The joint type " << joint->type << " is not supported." << std::endl; + assert(false && "Only revolute, prismatic and fixed joints are accepted." ); + break; + } + } + if (link->collision) + { + fcl::CollisionObject collision_object = retrieveCollisionGeometry(link, meshRootDir); + SE3 geomPlacement = convertFromUrdf(link->collision->origin); + std::string collision_object_name = link->name ; + model_geom.addGeomObject(model.getJointId(joint->name), collision_object, geomPlacement, collision_object_name); + } + } + else if (link->getParent() != NULL) + { + const std::string exception_message (link->name + " - joint information missing."); + throw std::invalid_argument(exception_message); + } + + + BOOST_FOREACH(::urdf::LinkConstPtr child,link->child_links) + { + parseTree(child, model, nextPlacementOffset); + } +} + + + template <typename D> + void parseTreeWithGeom( ::urdf::LinkConstPtr link, Model & model, GeometryModel & model_geom, std::string meshRootDir, const SE3 & placementOffset , const JointModelBase<D> & root_joint ) + { + const Inertia & Y = (link->inertial) ? + convertFromUrdf(*link->inertial) + : Inertia::Identity(); + model.addBody( 0, root_joint, placementOffset, Y , "root_joint", link->name, true ); + BOOST_FOREACH(::urdf::LinkConstPtr child,link->child_links) + { + parseTreeWithGeom(child, model, model_geom, meshRootDir, SE3::Identity()); + } + } + + + template <typename D> + std::pair<Model, GeometryModel > buildModelAndGeom( const std::string & filename, const std::string & meshRootDir, const JointModelBase<D> & root_joint ) + { + Model model; GeometryModel model_geom; + + ::urdf::ModelInterfacePtr urdfTree = ::urdf::parseURDFFile (filename); + parseTreeWithGeom(urdfTree->getRoot(), model, model_geom, meshRootDir, SE3::Identity(), root_joint); + return std::make_pair(model, model_geom); + } + + std::pair<Model, GeometryModel > buildModelAndGeom( const std::string & filename, const std::string & meshRootDir) + { + Model model; GeometryModel model_geom; + + ::urdf::ModelInterfacePtr urdfTree = ::urdf::parseURDFFile (filename); + parseTreeWithGeom(urdfTree->getRoot(), model, model_geom, meshRootDir, SE3::Identity()); + return std::make_pair(model, model_geom); + } + + } // namespace urdf +} // namespace se3 + +#endif // ifndef __se3_urdf_geom_hpp__ +