aboutsummaryrefslogtreecommitdiff
path: root/crates/rapier3d-urdf
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rapier3d-urdf')
-rw-r--r--crates/rapier3d-urdf/CHANGELOG.md16
-rw-r--r--crates/rapier3d-urdf/Cargo.toml26
-rw-r--r--crates/rapier3d-urdf/LICENSE201
-rw-r--r--crates/rapier3d-urdf/README.md37
-rw-r--r--crates/rapier3d-urdf/src/lib.rs627
5 files changed, 907 insertions, 0 deletions
diff --git a/crates/rapier3d-urdf/CHANGELOG.md b/crates/rapier3d-urdf/CHANGELOG.md
new file mode 100644
index 0000000..852e45a
--- /dev/null
+++ b/crates/rapier3d-urdf/CHANGELOG.md
@@ -0,0 +1,16 @@
+## Unreleased
+
+This is the initial release of the `rapier3d-urdf` crate.
+
+### Added
+
+- Add `UrdfRobot` which is a collection of colliders, rigid-bodies and joints representing a robot loaded from an URDF
+ file.
+- Add `UrdfRobot::from_file` to load an `UrdfRobot` from an URDF file.
+- Add `UrdfRobot::from_str` to load an `UrdfRobot` from a string in URDF format.
+- Add `UrdfRobot::from_robot` to load an `UrdfRobot` from an already loaded URDF
+ robot (pre-parsed with the `xurdf` crate).
+- Add `UrdfRobot::insert_using_impulse_joints` to insert the robot to the rapier sets. Joints are represented as
+ **impulse** joints.
+- Add `UrdfRobot::insert_using_impulse_joints` to insert the robot to the rapier sets. Joints are represented as
+ **multibody** joints.
diff --git a/crates/rapier3d-urdf/Cargo.toml b/crates/rapier3d-urdf/Cargo.toml
new file mode 100644
index 0000000..1394f2a
--- /dev/null
+++ b/crates/rapier3d-urdf/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "rapier3d-urdf"
+version = "0.1.0"
+authors = ["Sébastien Crozet <sebcrozet@dimforge.com>"]
+description = "URDF file loader for the 3D rapier physics engine."
+documentation = "https://docs.rs/rapier3d-urdf"
+homepage = "https://rapier.rs"
+repository = "https://github.com/dimforge/rapier"
+readme = "README.md"
+categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
+keywords = ["physics", "joints", "multibody", "robotics", "urdf"]
+license = "Apache-2.0"
+edition = "2021"
+
+[features]
+stl = ["rapier3d-stl"]
+
+[dependencies]
+log = "0.4"
+anyhow = "1"
+bitflags = "2"
+# NOTE: we are not using the (more recent) urdf-rs crate because of https://github.com/openrr/urdf-rs/issues/94
+xurdf = "0.2"
+
+rapier3d = { version = "0.19", path = "../rapier3d" }
+rapier3d-stl = { version = "0.1.0", path = "../rapier3d-stl", optional = true }
diff --git a/crates/rapier3d-urdf/LICENSE b/crates/rapier3d-urdf/LICENSE
new file mode 100644
index 0000000..97f4383
--- /dev/null
+++ b/crates/rapier3d-urdf/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 Sébastien Crozet
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/crates/rapier3d-urdf/README.md b/crates/rapier3d-urdf/README.md
new file mode 100644
index 0000000..ae2e21d
--- /dev/null
+++ b/crates/rapier3d-urdf/README.md
@@ -0,0 +1,37 @@
+## STL loader for the Rapier physics engine
+
+Rapier is a set of 2D and 3D physics engines for games, animation, and robotics. The `rapier3d-urdf`
+crate lets you convert an URDF file into a set of rigid-bodies, colliders, and joints, for usage with the
+`rapier3d` physics engine.
+
+## Optional cargo features
+
+- `stl`: enables loading STL meshes referenced by the URDF file.
+
+## Limitations
+
+Are listed below some known limitations you might want to be aware of before picking this library. Contributions to
+improve
+these elements are very welcome!
+
+- Mesh file types other than `stl` are not supported yet. Contributions are welcome. You my check the `rapier3d-stl`
+ repository for an example of mesh loader.
+- When inserting joints as multibody joints, they will be reset to their neutral position (all coordinates = 0).
+- The following fields are currently ignored:
+ - `Joint::dynamics`
+ - `Joint::limit.effort` / `limit.velocity`
+ - `Joint::mimic`
+ - `Joint::safety_controller`
+
+## Resources and discussions
+
+- [Dimforge](https://dimforge.com): See all the open-source projects we are working on! Follow our announcements
+ on our [blog](https://www.dimforge.com/blog).
+- [User guide](https://www.rapier.rs/docs/): Learn to use Rapier in your project by reading the official User Guides.
+- [Discord](https://discord.gg/vt9DJSW): Come chat with us, get help, suggest features, on Discord!
+- [NPM packages](https://www.npmjs.com/search?q=%40dimforge): Check out our NPM packages for Rapier, if you need to
+ use it with JavaScript/Typescript.
+
+Please make sure to familiarize yourself with our [Code of Conduct](CODE_OF_CONDUCT.md)
+and our [Contribution Guidelines](CONTRIBUTING.md) before contributing or participating in
+discussions with the community.
diff --git a/crates/rapier3d-urdf/src/lib.rs b/crates/rapier3d-urdf/src/lib.rs
new file mode 100644
index 0000000..0a9a2fc
--- /dev/null
+++ b/crates/rapier3d-urdf/src/lib.rs
@@ -0,0 +1,627 @@
+//! ## STL loader for the Rapier physics engine
+//!
+//! Rapier is a set of 2D and 3D physics engines for games, animation, and robotics. The `rapier3d-urdf`
+//! crate lets you convert an URDF file into a set of rigid-bodies, colliders, and joints, for usage with the
+//! `rapier3d` physics engine.
+//!
+//! ## Optional cargo features
+//!
+//! - `stl`: enables loading STL meshes referenced by the URDF file.
+//!
+//! ## Limitations
+//!
+//! Are listed below some known limitations you might want to be aware of before picking this library. Contributions to
+//! improve
+//! these elements are very welcome!
+//!
+//! - Mesh file types other than `stl` are not supported yet. Contributions are welcome. You my check the `rapier3d-stl`
+//! repository for an example of mesh loader.
+//! - When inserting joints as multibody joints, they will be reset to their neutral position (all coordinates = 0).
+//! - The following fields are currently ignored:
+//! - `Joint::dynamics`
+//! - `Joint::limit.effort` / `limit.velocity`
+//! - `Joint::mimic`
+//! - `Joint::safety_controller`
+
+#![warn(missing_docs)]
+
+use na::RealField;
+use rapier3d::{
+ dynamics::{
+ GenericJoint, GenericJointBuilder, ImpulseJointHandle, ImpulseJointSet, JointAxesMask,
+ JointAxis, MassProperties, MultibodyJointHandle, MultibodyJointSet, RigidBody,
+ RigidBodyBuilder, RigidBodyHandle, RigidBodySet, RigidBodyType,
+ },
+ geometry::{
+ Collider, ColliderBuilder, ColliderHandle, ColliderSet, MeshConverter, SharedShape,
+ TriMeshFlags,
+ },
+ math::{Isometry, Point, Real, Vector},
+ na,
+};
+use std::collections::HashMap;
+use std::path::Path;
+use xurdf::{Geometry, Inertial, Joint, Pose, Robot};
+
+bitflags::bitflags! {
+ /// Options applied to multibody joints created from the URDF joints.
+ #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
+ pub struct UrdfMultibodyOptions: u8 {
+ /// If this flag is set, the created multibody joint will be marked as kinematic.
+ ///
+ /// A kinematic joint is entirely controlled by the user (it is not affected by any force).
+ /// This particularly useful if you intend to control the robot through inverse-kinematics.
+ const JOINTS_ARE_KINEMATIC = 0b0001;
+ /// If enabled, any contact between two links belonging to the same generated multibody robot will
+ /// be ignored.
+ ///
+ /// This is useful if the generated colliders are known to be overlapping (e.g. if creating colliders
+ /// from visual meshes was enabled) or that collision detection is not needed a computationally
+ /// expensive (e.g. if any of these colliders is a high-quality triangle mesh).
+ const DISABLE_SELF_CONTACTS = 0b0010;
+ }
+}
+
+/// The index of an urdf link.
+pub type LinkId = usize;
+
+/// A set of configurable options for loading URDF files.
+#[derive(Clone, Debug)]
+pub struct UrdfLoaderOptions {
+ /// If `true` one collider will be created for each **collision** shape from the urdf file (default: `true`).
+ pub create_colliders_from_collision_shapes: bool,
+ /// If `true` one collider will be created for each **visual** shape from the urdf file (default: `false`).
+ ///
+ /// Note that visual shapes are usually significantly higher-resolution than collision shapes.
+ /// Most of the time they might also overlap, or generate a lot of contacts due to them being
+ /// thin triangle meshes.
+ ///
+ /// So if this option is set to `true`, it is recommended to also keep
+ /// [`UrdfLoaderOptions::enable_joint_collisions`] set to `false`. If the model is then added
+ /// to the physics sets using multibody joints, it is recommended to call
+ /// [`UrdfRobot::insert_with_multibody_joints`] with the [`UrdfMultibodyOptions::DISABLE_SELF_CONTACTS`]
+ /// flag enabled.
+ pub create_colliders_from_visual_shapes: bool,
+ /// If `true`, the mass properties (center-of-mass, mass, and angular inertia) read from the urdf
+ /// file will be added to the corresponding rigid-body (default: `true`).
+ ///
+ /// Note that by default, all colliders created will be given a density of 0.0, meaning that,
+ /// by default, the imported mass properties are the only ones added to the created rigid-bodies.
+ /// To give colliders a non-zero density, see [`UrdfLoaderOptions::collider_blueprint`].
+ pub apply_imported_mass_props: bool,
+ /// If `true`, collisions between two links sharing a joint will be disabled (default: `false`).
+ ///
+ /// It is strongly recommended to leave this to `false` unless you are certain adjacent links
+ /// colliders don’t overlap.
+ pub enable_joint_collisions: bool,
+ /// If `true`, the rigid-body at the root of the kinematic chains will be initialized as [`RigidBodyType::Fixed`]
+ /// (default: `false`).
+ pub make_roots_fixed: bool,
+ /// This is the set of flags set on all the loaded triangle meshes (default: [`TriMeshFlags::all`]).
+ ///
+ /// Note that the default enables all the flags. This is operating under the assumption that the provided
+ /// mesh are generally well-formed and properly oriented (2-manifolds with outward normals).
+ pub trimesh_flags: TriMeshFlags,
+ /// The transform appended to every created rigid-bodies (default: [`Isometry::identity`]).
+ pub shift: Isometry<Real>,
+ /// A description of the collider properties that need to be applied to every collider created
+ /// by the loader (default: `ColliderBuilder::default().density(0.0)`).
+ ///
+ /// This collider builder will be used for initializing every collider created by the loader.
+ /// The shape specified by this builder isn’t important and will be replaced by the shape read
+ /// from the urdf file.
+ ///
+ /// Note that by default, the collider is given a density of 0.0 so that it doesn’t contribute
+ /// to its parent rigid-body’s mass properties (since they should be already provided by the
+ /// urdf file assuming the [`UrdfLoaderOptions::apply_imported_mass_props`] wasn’t set `false`).
+ pub collider_blueprint: ColliderBuilder,
+ /// A description of the rigid-body properties that need to be applied to every rigid-body
+ /// created by the loader (default: `RigidBodyBuilder::dynamic()`).
+ ///
+ /// This rigid-body builder will be used for initializing every rigid-body created by the loader.
+ /// The rigid-body type is not important as it will always be set to [`RigidBodyType::Dynamic`]
+ /// for non-root links. Root links will be set to [`RigidBodyType::Fixed`] instead of
+ /// [`RigidBodyType::Dynamic`] if the [`UrdfLoaderOptions::make_roots_fixed`] is set to `true`.
+ pub rigid_body_blueprint: RigidBodyBuilder,
+}
+
+impl Default for UrdfLoaderOptions {
+ fn default() -> Self {
+ Self {
+ create_colliders_from_collision_shapes: true,
+ create_colliders_from_visual_shapes: false,
+ apply_imported_mass_props: true,
+ enable_joint_collisions: false,
+ make_roots_fixed: false,
+ trimesh_flags: TriMeshFlags::all(),
+ shift: Isometry::identity(),
+ collider_blueprint: ColliderBuilder::default().density(0.0),
+ rigid_body_blueprint: RigidBodyBuilder::dynamic(),
+ }
+ }
+}
+
+/// An urdf link loaded as a rapier [`RigidBody`] and its [`Collider`]s.
+#[derive(Clone, Debug)]
+pub struct UrdfLink {
+ /// The rigid-body created for this link.
+ pub body: RigidBody,
+ /// All the colliders build from the URDF visual and/or collision shapes (if the corresponding
+ /// [`UrdfLoaderOptions`] option is enabled).
+ pub colliders: Vec<Collider>,
+}
+
+/// An urdf joint loaded as a rapier [`GenericJoint`].
+#[derive(Clone, Debug)]
+pub struct UrdfJoint {
+ /// The rapier version for the corresponding urdf joint.
+ pub joint: GenericJoint,
+ /// Index of the rigid-body (from the [`UrdfRobot`] array) at the first
+ /// endpoint of this joint.
+ pub link1: LinkId,
+ /// Index of the rigid-body (from the [`UrdfRobot`] array) at the second
+ /// endpoint of this joint.
+ pub link2: LinkId,
+}
+
+/// A robot represented as a set of rapier rigid-bodies, colliders, and joints.
+#[derive(Clone, Debug)]
+pub struct UrdfRobot {
+ /// The bodies and colliders loaded from the urdf file.
+ ///
+ /// This vector matches the order of [`Robot::links`].
+ pub links: Vec<UrdfLink>,
+ /// The joints loaded from the urdf file.
+ ///
+ /// This vector matches the order of [`Robot::joints`].
+ pub joints: Vec<UrdfJoint>,
+}
+
+/// Handle of a joint read from the URDF file and inserted into rapier’s `ImpulseJointSet`
+/// or a `MultibodyJointSet`.
+pub struct UrdfJointHandle<JointHandle> {
+ /// The inserted joint handle.
+ pub joint: JointHandle,
+ /// The handle of the first rigid-body attached by this joint.
+ pub link1: RigidBodyHandle,
+ /// The handle of the second rigid-body attached by this joint.
+ pub link2: RigidBodyHandle,
+}
+
+/// The handles related to a link read from the URDF file and inserted into Rapier’s
+/// `RigidBodySet` and `ColliderSet`.
+pub struct UrdfLinkHandle {
+ /// Handle of the inserted link’s rigid-body.
+ pub body: RigidBodyHandle,
+ /// Handle of the colliders attached to [`Self::body`].
+ pub colliders: Vec<ColliderHandle>,
+}
+
+/// Handles to all the Rapier objects created when inserting this robot into Rapier’s
+/// `RigidBodySet`, `ColliderSet`, `ImpulseJointSet`, `MultibodyJointSet`.
+pub struct UrdfRobotHandles<JointHandle> {
+ /// The handles related to each URDF robot link.
+ pub links: Vec<UrdfLinkHandle>,
+ /// The handles related to each URDF robot joint.
+ pub joints: Vec<UrdfJointHandle<JointHandle>>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
+enum JointType {
+ #[default]
+ Fixed,
+ Revolute,
+ Continuous,
+ Floating,
+ Planar,
+ Prismatic,
+ Spherical,
+}
+
+impl JointType {
+ fn from_str(str: &str) -> Option<Self> {
+ match str.as_ref() {
+ "fixed" | "Fixed" => Some(Self::Fixed),
+ "continuous" | "Continuous" => Some(Self::Continuous),
+ "revolute" | "Revolute" => Some(Self::Revolute),
+ "floating" | "Floating" => Some(Self::Floating),
+ "planar" | "Planar" => Some(Self::Planar),
+ "prismatic" | "Prismatic" => Some(Self::Prismatic),
+ "spherical" | "Spherical" => Some(Self::Spherical),
+ _ => None,
+ }
+ }
+}
+
+impl UrdfRobot {
+ /// Parses a URDF file and returns both the rapier objects (`UrdfRobot`) and the original urdf
+ /// structures (`Robot`). Both structures are arranged the same way, with matching indices for each part.
+ ///
+ /// If the URDF file references external meshes, they will be loaded automatically if the format
+ /// is supported. The format is detected from the file’s extension. All the mesh formats are
+ /// disabled by default and can be enabled through cargo features (e.g. the `stl` feature of
+ /// this crate enabled loading referenced meshes in stl format).
+ ///
+ /// # Parameters
+ /// - `path`: the path of the URDF file.
+ /// - `options`: customize the creation of rapier objects from the URDF description.
+ /// - `mesh_dir`: the base directory containing the meshes referenced by the URDF file. When
+ /// a mesh reference is found in the URDF file, this `mesh_dir` is appended
+ /// to the file path. If `mesh_dir` is `None` then the mesh directory is assumed
+ /// to be the same directory as the one containing the URDF file.
+ pub fn from_file(
+ path: impl AsRef<Path>,
+ options: UrdfLoaderOptions,
+ mesh_dir: Option<&Path>,
+ ) -> anyhow::Result<(Self, Robot)> {
+ let path = path.as_ref().canonicalize()?;
+ let mesh_dir = mesh_dir
+ .or_else(|| path.parent())
+ .unwrap_or_else(|| Path::new("./"));
+ let robot = xurdf::parse_urdf_from_file(&path)?;
+ Ok((Self::from_robot(&robot, options, mesh_dir), robot))
+ }
+
+ /// Parses a string in URDF format and returns both the rapier objects (`UrdfRobot`) and the original urdf
+ /// structures (`Robot`). Both structures are arranged the same way, with matching indices for each part.
+ ///
+ /// If the URDF file references external meshes, they will be loaded automatically if the format
+ /// is supported. The format is detected from the file’s extension. All the mesh formats are
+ /// disabled by default and can be enabled through cargo features (e.g. the `stl` feature of
+ /// this crate enabled loading referenced meshes in stl format).
+ ///
+ /// # Parameters
+ /// - `str`: the string content of an URDF file.
+ /// - `options`: customize the creation of rapier objects from the URDF description.
+ /// - `mesh_dir`: the base directory containing the meshes referenced by the URDF file. When
+ /// a mesh reference is found in the URDF file, this `mesh_dir` is appended
+ /// to the file path.
+ pub fn from_str(
+ str: &str,
+ options: UrdfLoaderOptions,
+ mesh_dir: &Path,
+ ) -> anyhow::Result<(Self, Robot)> {
+ let robot = xurdf::parse_urdf_from_string(str)?;
+ Ok((Self::from_robot(&robot, options, mesh_dir), robot))
+ }
+
+ /// From an already loaded urdf file as a `Robot`, this creates the matching rapier objects
+ /// (`UrdfRobot`). Both structures are arranged the same way, with matching indices for each part.
+ ///
+ /// If the URDF file references external meshes, they will be loaded automatically if the format
+ /// is supported. The format is detected from the file’s extension. All the mesh formats are
+ /// disabled by default and can be enabled through cargo features (e.g. the `stl` feature of
+ /// this crate enabled loading referenced meshes in stl format).
+ ///
+ /// # Parameters
+ /// - `robot`: the robot loaded from an URDF file.
+ /// - `options`: customize the creation of rapier objects from the URDF description.
+ /// - `mesh_dir`: the base directory containing the meshes referenced by the URDF file. When
+ /// a mesh reference is found in the URDF file, this `mesh_dir` is appended
+ /// to the file path.
+ pub fn from_robot(robot: &Robot, options: UrdfLoaderOptions, mesh_dir: &Path) -> Self {
+ let mut name_to_link_id = HashMap::new();
+ let mut link_is_root = vec![true; robot.links.len()];
+ let mut links: Vec<_> = robot
+ .links
+ .iter()
+ .enumerate()
+ .map(|(id, link)| {
+ name_to_link_id.insert(&link.name, id);
+ let mut colliders = vec![];
+ if options.create_colliders_from_collision_shapes {
+ colliders.extend(link.collisions.iter().filter_map(|co| {
+ urdf_to_collider(&options, mesh_dir, &co.geometry, &co.origin)
+ }))
+ }
+ if options.create_colliders_from_visual_shapes {
+ colliders.extend(link.visuals.iter().filter_map(|vis| {
+ urdf_to_collider(&options, mesh_dir, &vis.geometry, &vis.origin)
+ }))
+ }
+ let mut body = urdf_to_rigid_body(&options, &link.inertial);
+ body.set_position(options.shift * body.position(), false);
+ UrdfLink { body, colliders }
+ })
+ .collect();
+ let joints: Vec<_> = robot
+ .joints
+ .iter()
+ .map(|joint| {
+ let link1 = name_to_link_id[&joint.parent];
+ let link2 = name_to_link_id[&joint.child];
+ let pose1 = *links[link1].body.position();
+ let rb2 = &mut links[link2].body;
+ let joint = urdf_to_joint(&options, joint, &pose1, rb2);
+ link_is_root[link2] = false;
+
+ UrdfJoint {
+ joint,
+ link1,
+ link2,
+ }
+ })
+ .collect();
+
+ if options.make_roots_fixed {
+ for (link, is_root) in links.iter_mut().zip(link_is_root.into_iter()) {
+ if is_root {
+ link.body.set_body_type(RigidBodyType::Fixed, false)
+ }
+ }
+ }
+
+ Self { links, joints }
+ }
+
+ /// Inserts all the robots elements to the rapier rigid-body, collider, and impulse joint, sets.
+ ///
+ /// Joints are represented as impulse joints. This implies that joint constraints are simulated
+ /// in full coordinates using impulses. For a reduced-coordinates approach, see
+ /// [`UrdfRobot::insert_using_multibody_joints`].
+ pub fn insert_using_impulse_joints(
+ self,
+ rigid_body_set: &mut RigidBodySet,
+ collider_set: &mut ColliderSet,
+ joint_set: &mut ImpulseJointSet,
+ ) -> UrdfRobotHandles<ImpulseJointHandle> {
+ let links: Vec<_> = self
+ .links
+ .into_iter()
+ .map(|link| {
+ let body = rigid_body_set.insert(link.body);
+ let colliders = link
+ .colliders
+ .into_iter()
+ .map(|co| collider_set.insert_with_parent(co, body, rigid_body_set))
+ .collect();
+ UrdfLinkHandle { body, colliders }
+ })
+ .collect();
+ let joints: Vec<_> = self
+ .joints
+ .into_iter()
+ .map(|joint| {
+ let link1 = links[joint.link1].body;
+ let link2 = links[joint.link2].body;
+ let joint = joint_set.insert(link1, link2, joint.joint, false);
+ UrdfJointHandle {
+ joint,
+ link1,
+ link2,
+ }
+ })
+ .collect();
+
+ UrdfRobotHandles { links, joints }
+ }
+
+ /// Inserts all the robots elements to the rapier rigid-body, collider, and multibody joint, sets.
+ ///
+ /// Joints are represented as multibody joints. This implies that the robot as a whole can be
+ /// accessed as a single [`Multibody`] from the [`MultibodyJointSet`]. That multibody uses reduced
+ /// coordinates for modeling joints, meaning that it will be very close to the way they are usually
+ /// represented for robotics applications. Multibodies also support inverse kinematics.
+ pub fn insert_using_multibody_joints(
+ self,
+ rigid_body_set: &mut RigidBodySet,
+ collider_set: &mut ColliderSet,
+ joint_set: &mut MultibodyJointSet,
+ multibody_options: UrdfMultibodyOptions,
+ ) -> UrdfRobotHandles<Option<MultibodyJointHandle>> {
+ let links: Vec<_> = self
+ .links
+ .into_iter()
+ .map(|link| {
+ let body = rigid_body_set.insert(link.body);
+ let colliders = link
+ .colliders
+ .into_iter()
+ .map(|co| collider_set.insert_with_parent(co, body, rigid_body_set))
+ .collect();
+ UrdfLinkHandle { body, colliders }
+ })
+ .collect();
+ let joints: Vec<_> = self
+ .joints
+ .into_iter()
+ .map(|joint| {
+ let link1 = links[joint.link1].body;
+ let link2 = links[joint.link2].body;
+ let joint =
+ if multibody_options.contains(UrdfMultibodyOptions::JOINTS_ARE_KINEMATIC) {
+ joint_set.insert_kinematic(link1, link2, joint.joint, false)
+ } else {
+ joint_set.insert(link1, link2, joint.joint, false)
+ };
+
+ if let Some(joint) = joint {
+ let (multibody, _) = joint_set.get_mut(joint).unwrap_or_else(|| unreachable!());
+ multibody.set_self_contacts_enabled(
+ !multibody_options.contains(UrdfMultibodyOptions::DISABLE_SELF_CONTACTS),
+ );
+ }
+
+ UrdfJointHandle {
+ joint,
+ link1,
+ link2,
+ }
+ })
+ .collect();
+
+ UrdfRobotHandles { links, joints }
+ }
+
+ /// Appends a transform to all the rigid-bodie of this robot.
+ pub fn append_transform(&mut self, transform: &Isometry<Real>) {
+ for link in &mut self.links {
+ link.body
+ .set_position(transform * link.body.position(), true);
+ }
+ }
+}
+
+fn urdf_to_rigid_body(options: &UrdfLoaderOptions, inertial: &Inertial) -> RigidBody {
+ let origin = urdf_to_isometry(&inertial.origin);
+ let mut builder = options.rigid_body_blueprint.clone();
+ builder.body_type = RigidBodyType::Dynamic;
+
+ if options.apply_imported_mass_props {
+ builder = builder.additional_mass_properties(MassProperties::with_inertia_matrix(
+ origin.translation.vector.into(),
+ inertial.mass as Real,
+ na::Matrix3::new(
+ inertial.inertia.m11 as Real,
+ inertial.inertia.m12 as Real,
+ inertial.inertia.m13 as Real,
+ inertial.inertia.m21 as Real,
+ inertial.inertia.m22 as Real,
+ inertial.inertia.m23 as Real,
+ inertial.inertia.m31 as Real,
+ inertial.inertia.m32 as Real,
+ inertial.inertia.m33 as Real,
+ ),
+ ))
+ }
+
+ builder.build()
+}
+
+fn urdf_to_collider(
+ options: &UrdfLoaderOptions,
+ mesh_dir: &Path,
+ geometry: &Geometry,
+ origin: &Pose,
+) -> Option<Collider> {
+ let mut builder = options.collider_blueprint.clone();
+ let mut shape_transform = Isometry::identity();
+ let shape = match &geometry {
+ Geometry::Box { size } => SharedShape::cuboid(
+ size[0] as Real / 2.0,
+ size[1] as Real / 2.0,
+ size[2] as Real / 2.0,
+ ),
+ Geometry::Cylinder { radius, length } => {
+ // This rotation will make the cylinder Z-up as per the URDF spec,
+ // instead of rapier’s default Y-up.
+ shape_transform = Isometry::rotation(Vector::x() * Real::frac_pi_2());
+ SharedShape::cylinder(*length as Real / 2.0, *radius as Real)
+ }
+ Geometry::Sphere { radius } => SharedShape::ball(*radius as Real),
+ Geometry::Mesh { filename, scale } => {
+ let path: &Path = filename.as_ref();
+ let scale = scale
+ .map(|s| Vector::new(s.x as Real, s.y as Real, s.z as Real))
+ .unwrap_or_else(|| Vector::<Real>::repeat(1.0));
+ match path.extension().and_then(|ext| ext.to_str()) {
+