Moving a Robot on a Circular Path
A circular motion is a movement where the robot travels to a target position along a circular path in Cartesian space. Unlike point-to-point (PTP) motion, which follows the shortest path in joint space, a circular motion ensures a predictable trajectory in Cartesian coordinates. However, depending on the robot’s kinematics, this type of movement may not always be the fastest.
This chapter specifically explains circular movements. For detailed information on how to use these methods synchronously or asynchronously, see the asynchronous instructions section and the instructions chapter.
Performing a Circular Movement
A circular movement is started with move_circular() from the MoveCircular trait. It requires a target pose, a
via pose, and the maximum translational speed of the tool center point along the arc, written as
MetersPerSecond(0.25). The via pose is an intermediate pose that defines the circular arc between the start pose
and the target pose. Both poses can be a JointPose, a CartesianPose or a CartesianTarget. Of a
CartesianTarget via only the coordinate system is used.
The via pose is wrapped in the Via newtype, so that target and via cannot be confused at the call site. The speed
argument takes the same forms as for linear motions and, like there, is required; see
Moving on a Linear Path for the forms and the reasoning.
Perform a Circular Movement Using JointPose Parameters
To perform a circular movement to a target defined in joint space, pass JointPose values. The robot control
resolves them to Cartesian poses:
let target = poses::home()
+ JointPose::new(
Radians(FRAC_PI_2),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
);
// Via: home + 45 degrees on joint 1.
let via = poses::home()
+ JointPose::new(
Radians(FRAC_PI_4),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
);
robot
.move_circular(target, Via(via), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Circular arc (JointPose) complete.");
Target and via do not have to be of the same type; give each as a JointPose or a CartesianPose, depending on the
circumstances.
Perform a Circular Movement Using CartesianPose Parameters
To perform a circular movement in Cartesian space, pass CartesianPose values. The x() and y() helpers build
poses that are zero except for one component, which makes it easy to derive the target and via from a known pose:
let cartesian_target = poses::home_c() + y(Meters(0.4));
let cartesian_via = poses::home_c() + x(Meters(0.2)) + y(Meters(0.2));
robot
.move_circular(cartesian_target, Via(cartesian_via), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Circular arc (CartesianPose) complete.");
Since a Cartesian pose is ambiguous for a six-axis robot, the resulting joint configuration at the target can differ depending on the starting pose of the robot.
Note
A CartesianPose is interpreted in the robot coordinate system; a CartesianTarget names another one, see
Coordinate Systems.
A CartesianPose itself can be ambiguous with regard to the exact joint configuration of the robot. It is at the
discretion of the underlying robot control to choose a convenient joint configuration to reach the given pose. This
means that the configuration reached depends on the starting position.
Perform a Relative Circular Movement
For moving on a circular path relative to the current end effector pose, use move_circular_relative(), which
accepts the same arguments as move_circular(). Both the target and the via pose are then offsets from the current
pose. In this example, the robot moves in the positive x- and y-direction from its current pose:
fn relative_circular(robot: &(impl MoveCircular + MovePtp)) -> Result<()> {
info!("--- Relative circular ---");
robot.move_ptp(poses::home()).start()?.join()?;
robot
.move_circular_relative(
y(Meters(0.4)),
Via(x(Meters(0.2)) + y(Meters(0.2))),
common::TRAVEL_SPEED,
)
.start()?
.join()?;
info!("Relative circular arc complete.");
Ok(())
}
Perform a Circular Movement with Blending
Circular movements can use blending by chaining .blending() with a Factor or Percent. The higher the blending
value, the more and earlier the blended path deviates from the original path that would reach the target pose. In
most cases this allows the robot to maintain a higher path velocity and complete the overall path faster.
Blending only works if the instruction that follows is known to the robot control in advance, so blended motions
form one block that is sent as a whole by start():
fn circular_with_blending(robot: &(impl MoveCircular + MovePtp)) -> Result<()> {
info!("--- Circular with blending ---");
robot.move_ptp(poses::home()).start()?.join()?;
let cartesian_target_1 = poses::home_c() + y(Meters(0.2));
let cartesian_target_2 = poses::home_c() + x(Meters(0.2)) + y(Meters(0.2));
let cartesian_via = poses::home_c() + x(Meters(0.1)) + y(Meters(0.1));
robot
.move_circular(cartesian_target_1, Via(cartesian_via), common::TRAVEL_SPEED)
.blending(Percent(50.0))
.move_circular(cartesian_target_2, Via(cartesian_via), common::TRAVEL_SPEED)
.blending(Factor(0.5))
.move_circular(
poses::home_c(),
Via(cartesian_target_1),
common::TRAVEL_SPEED,
)
.start()?
.join()?;
info!("Blending sequence complete.");
Ok(())
}
Note how the block ends on a step without blending; a block that ends on a blended step does not compile. A blended circular step needs a linear or circular successor: the robot control cannot blend a circular motion into a PTP motion, so a PTP step after it does not compile either.
Note
The exact behavior and interpretation of the blending parameter as well as implicit limitations are highly dependent on the specific robot used. For more details on blending, please refer to the instructions chapter.
Blending parameters can also be amended after an instruction was sent with amend_blending(); see
Amending Blending Parameters.
Blending Radius and Motion Profile
Like a linear step, a circular step blends by a radius in Meters and takes a .parameters() motion profile; see
Blending Radius and Motion Profile.
Full Example
Circular motion example
//! Circular motion examples.
//!
//! Demonstrates circular movements using joint poses, Cartesian poses,
//! relative movements, and blending.
mod common;
use common::poses;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
use tracing::info;
use voraus_robot_arm::prelude::*;
use voraus_robot_arm::types::coordinates::{x, y};
/// Circular movement using `JointPose` targets.
///
/// Defines a via point and target by offsetting the home pose,
/// then performs a circular arc through all three points. The via pose
/// is wrapped in `Via` so it cannot be confused with the target.
fn circular_with_joint_pose(robot: &(impl MoveCircular + MovePtp)) -> Result<()> {
info!("--- Circular with JointPose ---");
robot.move_ptp(poses::home()).start()?.join()?;
// Target: home + 90 degrees on joint 1.
let target = poses::home()
+ JointPose::new(
Radians(FRAC_PI_2),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
);
// Via: home + 45 degrees on joint 1.
let via = poses::home()
+ JointPose::new(
Radians(FRAC_PI_4),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
Radians(0.0),
);
robot
.move_circular(target, Via(via), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Circular arc (JointPose) complete.");
Ok(())
}
/// Circular movement using `CartesianPose` targets.
fn circular_with_cartesian_pose(robot: &(impl MoveCircular + MovePtp)) -> Result<()> {
info!("--- Circular with CartesianPose ---");
robot.move_ptp(poses::home()).start()?.join()?;
let cartesian_target = poses::home_c() + y(Meters(0.4));
let cartesian_via = poses::home_c() + x(Meters(0.2)) + y(Meters(0.2));
robot
.move_circular(cartesian_target, Via(cartesian_via), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Circular arc (CartesianPose) complete.");
Ok(())
}
/// Relative circular movement.
///
/// Both the target and the via pose are offsets from the current pose.
fn relative_circular(robot: &(impl MoveCircular + MovePtp)) -> Result<()> {
info!("--- Relative circular ---");
robot.move_ptp(poses::home()).start()?.join()?;
robot
.move_circular_relative(
y(Meters(0.4)),
Via(x(Meters(0.2)) + y(Meters(0.2))),
common::TRAVEL_SPEED,
)
.start()?
.join()?;
info!("Relative circular arc complete.");
Ok(())
}
/// Circular motion with blending between arcs.
fn circular_with_blending(robot: &(impl MoveCircular + MovePtp)) -> Result<()> {
info!("--- Circular with blending ---");
robot.move_ptp(poses::home()).start()?.join()?;
let cartesian_target_1 = poses::home_c() + y(Meters(0.2));
let cartesian_target_2 = poses::home_c() + x(Meters(0.2)) + y(Meters(0.2));
let cartesian_via = poses::home_c() + x(Meters(0.1)) + y(Meters(0.1));
robot
.move_circular(cartesian_target_1, Via(cartesian_via), common::TRAVEL_SPEED)
.blending(Percent(50.0))
.move_circular(cartesian_target_2, Via(cartesian_via), common::TRAVEL_SPEED)
.blending(Factor(0.5))
.move_circular(
poses::home_c(),
Via(cartesian_target_1),
common::TRAVEL_SPEED,
)
.start()?
.join()?;
info!("Blending sequence complete.");
Ok(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
circular_with_joint_pose(&robot)?;
circular_with_cartesian_pose(&robot)?;
relative_circular(&robot)?;
circular_with_blending(&robot)?;
info!("Done.");
Ok(())
}
Trait Definition
The trait carries the entry points as default methods, which return a MotionBlock builder; its required method is
what a robot type implements.
MoveCircular
pub trait MoveCircular: BlockQueuing {
/// Queue a fully specified circular motion.
///
/// # Returns
///
/// An [`InstructionHandle`] that resolves when the motion completes.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the motion
/// cannot be queued.
#[doc(hidden)]
fn execute_circular(&self, request: CircularRequest) -> Result<InstructionHandle>;
/// Move the TCP on a circular arc through a via point to an absolute
/// target.
///
/// The robot interpolates in Cartesian space: the tool center point
/// travels on the circular arc that passes through its current pose,
/// the via point and the target, and stops at the target. A plain
/// [`CartesianPose`](crate::types::CartesianPose) is in the robot
/// coordinate system. A [`CartesianTarget`](crate::types::CartesianTarget)
/// names its coordinate system and arriving frame; its configuration
/// vector is ignored, since the path determines the joint configuration.
/// A joint pose is converted to its Cartesian pose by the robot control.
/// The via point is wrapped in [`Via`](crate::types::Via), so target and
/// via cannot be swapped silently; the two convert independently, so a
/// joint target can pass through a Cartesian via.
///
/// The speed is the maximum translational speed of the tool center
/// point along the arc, given as [`MetersPerSecond`](crate::types::MetersPerSecond);
/// a negative or non-finite speed is rejected by
/// [`start`](MotionBlock::start).
///
/// Defaults: no blending and the robot control's motion profile
/// ([`CartesianMotionParameters::DEFAULT`](crate::types::CartesianMotionParameters::DEFAULT)).
/// Change them with [`blending`](MotionBlock::blending), which also
/// accepts a radius in [`Meters`](crate::types::Meters), and
/// [`parameters`](MotionBlock::parameters), chain further motions, and
/// send the block with [`start`](MotionBlock::start) or
/// [`start_awaiting_successor`](MotionBlock::start_awaiting_successor).
/// A blended circular step cannot be followed by a PTP step (see
/// [*Blending*](MoveCircular#blending)).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl MoveCircular, start: CartesianPose, joints: JointPose) -> Result<()> {
/// // A half circle of 20 cm diameter in the x-y plane.
/// let via = start + x(Meters(0.1)) + y(Meters(0.1));
/// let end = start + x(Meters(0.2));
/// robot.move_circular(end, Via(via), MetersPerSecond(0.25)).start()?.join()?;
///
/// // A joint target through a Cartesian via point.
/// robot.move_circular(joints, Via(via), MetersPerSecond(0.1)).start()?.join()?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `target` - The absolute target pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget) (see [`PoseTarget`]).
/// * `via` - The pose the arc passes through, wrapped in
/// [`Via`](crate::types::Via); a `JointPose`, a `CartesianPose` or a
/// `CartesianTarget`, independently of the target's type. Of a
/// `CartesianTarget` via only the coordinate system is used.
/// * `speed` - The maximum translational TCP speed, as
/// [`MetersPerSecond`](crate::types::MetersPerSecond) or a unit that
/// converts into it (see
/// [*Speed*](MoveCircular#speed)); a negative or non-finite value
/// fails at [`start`](MotionBlock::start).
///
/// # Returns
///
/// A block of one circular step, in the circular state.
fn move_circular<T: Into<PoseTarget>, V: Into<PoseTarget>>(
&self,
target: T,
via: Via<V>,
speed: impl Into<MetersPerSecond>,
) -> MotionBlock<'_, Self, state::Circular>
where
Self: Sized,
{
MotionBlock::single(
self,
circular_step(target.into(), via.0.into(), speed, false),
)
}
/// Move the TCP on a circular arc whose via point and target are
/// offsets from the current pose.
///
/// The robot interpolates in Cartesian space, as for
/// [`move_circular`](MoveCircular::move_circular): the tool center point
/// travels on the circular arc through its current pose, the current
/// pose shifted by the via offset and the current pose shifted by the
/// target offset. A [`CartesianTarget`](crate::types::CartesianTarget)
/// offset names the coordinate system it is expressed in. The speed is
/// the maximum translational speed of the tool center point along the
/// arc.
///
/// Defaults: no blending and the robot control's motion profile
/// ([`CartesianMotionParameters::DEFAULT`](crate::types::CartesianMotionParameters::DEFAULT)).
/// Change them with [`blending`](MotionBlock::blending), which also
/// accepts a radius in [`Meters`](crate::types::Meters), and
/// [`parameters`](MotionBlock::parameters), chain further motions, and
/// send the block with [`start`](MotionBlock::start) or
/// [`start_awaiting_successor`](MotionBlock::start_awaiting_successor).
/// A blended circular step cannot be followed by a PTP step (see
/// [*Blending*](MoveCircular#blending)).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl MoveCircular) -> Result<()> {
/// // A quarter circle of 10 cm radius: 10 cm along x and 10 cm along y.
/// robot
/// .move_circular_relative(
/// x(Meters(0.1)) + y(Meters(0.1)),
/// Via(x(Meters(0.1) * (1.0 - FRAC_1_SQRT_2)) + y(Meters(0.1) * FRAC_1_SQRT_2)),
/// MetersPerSecond(0.25),
/// )
/// .start()?
/// .join()?;
/// # Ok(())
/// # }
/// # use std::f64::consts::FRAC_1_SQRT_2;
/// ```
///
/// # Arguments
///
/// * `target` - The offset of the target from the current pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget) (see [`PoseTarget`]).
/// * `via` - The offset of the pose the arc passes through from the
/// current pose, wrapped in [`Via`](crate::types::Via). Of a
/// `CartesianTarget` via only the coordinate system is used.
/// * `speed` - The maximum translational TCP speed, as
/// [`MetersPerSecond`](crate::types::MetersPerSecond) or a unit that
/// converts into it (see
/// [*Speed*](MoveCircular#speed)); a negative or non-finite value
/// fails at [`start`](MotionBlock::start).
///
/// # Returns
///
/// A block of one circular step, in the circular state.
fn move_circular_relative<T: Into<PoseTarget>, V: Into<PoseTarget>>(
&self,
target: T,
via: Via<V>,
speed: impl Into<MetersPerSecond>,
) -> MotionBlock<'_, Self, state::Circular>
where
Self: Sized,
{
MotionBlock::single(
self,
circular_step(target.into(), via.0.into(), speed, true),
)
}
}