Coordinate Systems
Certain movement operations provided by this library allow the specification of a coordinate system in which the motion is executed. Coordinate systems may be predefined, such as the robot, tool, or camera coordinate system, or they may be defined by the user. User-defined coordinate systems offer flexibility and can represent arbitrary frames of reference. These may be static or dynamic; in the latter case, they can be updated at runtime to reflect changing conditions.
New coordinate systems are defined by a pose that is expressed relative to the robot coordinate system.
The joint coordinate system is a special case as it is defined in joint space rather than Cartesian space. Manipulating the joint coordinate system is rarely necessary and is therefore not supported.
The coordinate systems are the variants of the Cs enum: Robot, Tool,
Camera, and the user coordinate systems User1 to User16. Only the user coordinate systems can be read and
written, which the UserCs enum encodes: it has only the User1 to User16 variants and is what the methods
below accept. A motion target takes a Cs; a UserCs converts into one with .into(), as in
with_cs(UserCs::User1.into()).
Reading a Coordinate System
To get the origin of a user coordinate system as a CartesianPose in robot coordinates, use cs_origin():
let usercs1_origin = robot.cs_origin(UserCs::User1)?;
Manipulating a Coordinate System
The origin of a user coordinate system is set with set_cs_origin(). It takes the coordinate system to
manipulate and the new origin, given as any motion target: a JointPose, which places the origin where the TCP of the
selected tool, or the flange without a tool, is at that joint pose, a CartesianPose, or a CartesianTarget. Of
a CartesianTarget only the pose and the coordinate system are used; arriving_cs and configuration_vector are
ignored. A CartesianTarget must not be expressed in the coordinate system it defines; such a call fails with
RobotArmError::InvalidArgument:
robot.set_cs_origin(UserCs::User1, home)?;
The robot control stores every origin in robot coordinates. A JointPose is therefore first converted into the TCP pose
in robot coordinates with the robot’s kinematics. Like a CartesianPose or a
CartesianTarget in Cs::Robot, the result is written directly and confirmed by reading it back. For a
CartesianTarget in another coordinate system, the call waits for two confirmations. Each wait is bounded by the
command timeout, so such a call can take up to twice the command timeout. If such a call fails, it writes the previous
origin back, without confirming that write, before returning the error. Do not redefine a coordinate system that a
running motion uses.
Moving in a Coordinate System
A motion is executed in a coordinate system by giving the target as a CartesianTarget with the coordinate
system attached; every motion method takes it. The following moves the tool to the origin of the first user
coordinate system, which after the call above is the home pose:
robot
.move_ptp(CartesianTarget::new(CartesianPose::default()).with_cs(UserCs::User1.into()))
.start()?
.join()?;
Full Example
The complete program sets a user coordinate system, moves to its origin, and verifies the result. It is
rust/crates/voraus-robot-arm/examples/coordinate_systems.rs in the repository.
Coordinate system example
//! Coordinate system examples.
//!
//! Demonstrates reading and manipulating user-defined coordinate systems,
//! and moving in those coordinate systems.
mod common;
use common::poses;
use thiserror::Error;
use tracing::info;
use voraus_robot_arm::prelude::*;
/// The errors of this example.
#[derive(Debug, Error)]
enum ExampleError {
/// A call to the robot failed.
#[error(transparent)]
Robot(#[from] RobotArmError),
/// The origin of USERCS1 is not the pose it was set to.
#[error("USERCS1 origin {origin} differs from {expected}")]
OriginMismatch {
/// The pose the origin was set to.
expected: CartesianPose,
/// The origin the robot control reports.
origin: CartesianPose,
},
/// The robot did not reach the origin of USERCS1.
#[error("TCP pose {tcp_pose} differs from {expected}")]
TcpPoseMismatch {
/// The origin of USERCS1, in robot coordinates.
expected: CartesianPose,
/// The TCP pose the robot reached.
tcp_pose: CartesianPose,
},
}
/// A near-flat pose used as an intermediate waypoint.
fn flat() -> JointPose {
JointPose::new(
Degrees(1.0),
Degrees(-1.0),
Degrees(1.0),
Degrees(-1.0),
Degrees(-1.0),
Degrees(-1.0),
)
}
/// Example application demonstrating coordinate system manipulation.
fn run_coordinate_system_example(
robot: &(impl SetCsOrigin + MovePtp + GetTcpPose + PoseConversion),
) -> Result<(), ExampleError> {
info!("--- Coordinate Systems ---");
let home = poses::home();
let flat_pose = flat();
// Convert the home pose into the TCP pose in robot coordinates.
let (home_cartesian, _) = robot.joint_pose_to_tcp(home, Cs::Robot)?;
robot.move_ptp(flat_pose).start()?.join()?;
// Set USERCS1 origin at the home pose. Only user coordinate systems can
// be written, which `UserCs` encodes; motion targets take the full
// `Cs`, which it converts into with `.into()`.
robot.set_cs_origin(UserCs::User1, home)?;
// Move to the origin of USERCS1.
robot
.move_ptp(CartesianTarget::new(CartesianPose::default()).with_cs(UserCs::User1.into()))
.start()?
.join()?;
// Verify that the origin of USERCS1 matches the home Cartesian pose.
let usercs1_origin = robot.cs_origin(UserCs::User1)?;
info!("USERCS1 origin: {usercs1_origin}");
if !home_cartesian.approx_eq(usercs1_origin, common::POSE_TOLERANCE) {
return Err(ExampleError::OriginMismatch {
expected: home_cartesian,
origin: usercs1_origin,
});
}
// Verify that we reached the home pose.
let current_tcp = robot.tcp_pose()?;
if !home_cartesian.approx_eq(current_tcp, common::POSE_TOLERANCE) {
return Err(ExampleError::TcpPoseMismatch {
expected: home_cartesian,
tcp_pose: current_tcp,
});
}
info!("Coordinate system verification passed.");
Ok(())
}
fn main() -> Result<(), ExampleError> {
common::init_logging();
let robot = common::create_connected_robot()?;
run_coordinate_system_example(&robot)?;
info!("Done.");
Ok(())
}
Trait Definitions
Reading an origin is the GetCsOrigin trait, which a read-only view implements as well; setting one is the
SetCsOrigin trait, which extends it. Application code calls cs_origin() and set_cs_origin(), which accepts any
motion target. A robot type implements cs_origin() and the hidden apply_cs_origin(). The traits are included here
from the crate’s source.
GetCsOrigin
pub trait GetCsOrigin {
/// Get the origin of a user coordinate system in robot coordinates.
///
/// # Arguments
///
/// * `cs` - The user coordinate system whose origin is read.
///
/// # Returns
///
/// The origin as a [`CartesianPose`].
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the origin
/// cannot be read.
fn cs_origin(&self, cs: UserCs) -> Result<CartesianPose>;
}
SetCsOrigin
pub trait SetCsOrigin: GetCsOrigin {
/// Implementation hook behind
/// [`SetCsOrigin::set_cs_origin`], which documents
/// the semantics, arguments and errors.
///
/// # Arguments
///
/// * `request` - The coordinate system and its new origin.
///
/// # Returns
///
/// `Ok(())` on success.
///
/// # Errors
///
/// See [`SetCsOrigin::set_cs_origin`].
#[doc(hidden)]
fn apply_cs_origin(&self, request: CsOriginRequest) -> Result<()>;
/// Define the origin of a user coordinate system.
///
/// The origin may be given as a joint pose, a Cartesian pose in the robot
/// coordinate system, or a [`CartesianTarget`](crate::types::CartesianTarget)
/// expressed in another coordinate system. Of a `CartesianTarget`
/// only `pose` and `cs` are used; `arriving_cs` and
/// `configuration_vector` are ignored.
///
/// A joint pose places the origin where the TCP of the selected tool is
/// at that joint pose, or the flange without a tool.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl SetCsOrigin, home: CartesianPose) -> Result<()> {
/// robot.set_cs_origin(UserCs::User1, home)?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `cs` - The user coordinate system whose origin is defined.
/// * `origin` - The new origin: a [`JointPose`](crate::types::JointPose),
/// a [`CartesianPose`] or a
/// [`CartesianTarget`](crate::types::CartesianTarget).
///
/// # Returns
///
/// `Ok(())` on success.
///
/// # Errors
///
/// Returns [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// for a non-finite origin component,
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// for an origin given relative to `cs` itself,
/// [`RobotArmError::PoseConversionFailed`](crate::error::RobotArmError::PoseConversionFailed)
/// ([`PoseConversionError::Rejected`](crate::error::PoseConversionError::Rejected) or
/// [`PoseConversionError::EmptyResult`](crate::error::PoseConversionError::EmptyResult))
/// if a joint pose cannot be converted,
/// [`RobotArmError::RobotInErrorState`](crate::error::RobotArmError::RobotInErrorState)
/// with the control's error if the control refuses the write,
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm a write within the command timeout,
/// also when the control reported an error before the write (the
/// message then names that error),
/// or the error the write failed with. An origin in another coordinate
/// system waits for two confirmations, so the call can take up to
/// twice the command timeout. If such a call fails, the previous origin
/// is written back, but that write is not confirmed.
fn set_cs_origin(&self, cs: UserCs, origin: impl Into<PoseTarget>) -> Result<()>
where
Self: Sized,
{
self.apply_cs_origin(CsOriginRequest {
cs,
origin: origin.into(),
})
}
}