Moving a Robot on a Linear Path
A linear motion is a movement where the robot travels to a target position along a straight-line path in Cartesian space. Unlike point-to-point (PTP) motion, which follows the shortest path in joint space, a linear 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 linear movements. For detailed information on how to use these methods synchronously or asynchronously, see the asynchronous instructions section and the instructions chapter.
Performing a Linear Movement
A linear movement is started with move_linear() from the MoveLinear trait. It moves the tool center point on a
straight line to the target and requires two arguments: a target pose of type JointPose, CartesianPose or
CartesianTarget, and the maximum translational speed of the tool center point along the path, written as
MetersPerSecond(0.25). By default the motion does not blend; .blending() changes that, further motions can be
chained, and start() sends the motion to the robot.
The speed is a MetersPerSecond value, or any unit that converts into it, and a maximum: the direction comes from the
target, so a negative or non-finite speed is rejected by start() with a UnitLimitError, like every other invalid
motion parameter. A literal is written inline as MetersPerSecond(0.25). A constant uses
MetersPerSecond::from_static(0.25), which is const and asserts, so an invalid value in a const item or in an
inline const { .. } block does not compile. A speed computed at runtime is MetersPerSecond::new(value)?, which
returns the UnitLimitError for a non-finite value. A bare f64 is not a MetersPerSecond and is rejected by the
compiler.
The speed is a required argument, not an optional modifier. A PTP motion can default to the maximum speed of the axes, but the speed of the tool center point on a linear path depends on the robot, the tool, and the application, so there is no default that is safe everywhere. Every call site states the speed it wants. Name the value once per application rather than repeating a magic number, as the examples do:
/// Cartesian speed used by the examples for linear and circular motions.
///
/// Those motions have no default speed: the safe value depends on the
/// application, so the API makes every call site say what it wants. Naming
/// the value here is the pattern to copy — one constant per application
/// rather than a magic number at each call. The speed is a `MetersPerSecond`;
/// a constant uses `from_static`, which is `const` and asserts, so an
/// invalid constant (or an inline `const { .. }`) does not compile. A
/// speed computed at runtime uses `MetersPerSecond::new(..)?`, and a
/// negative speed fails at `start()`.
pub const TRAVEL_SPEED: MetersPerSecond = MetersPerSecond::from_static(1.0);
/// A deliberately slow Cartesian speed, for motions that should be observable.
pub const SLOW_TRAVEL_SPEED: MetersPerSecond = MetersPerSecond::from_static(0.25);
Perform a Linear Movement Using a CartesianPose
To perform a linear movement to a target defined in Cartesian space, pass a CartesianPose:
let target = CartesianPose::new(
Meters(0.1382),
Meters(0.550),
Meters(0.296),
Radians(PI),
Radians(0.0),
Radians(0.0),
);
robot
.move_linear(target, common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Cartesian target reached via linear interpolation.");
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. To move to a pose in another coordinate system, or
to let another frame arrive at it, pass a CartesianTarget instead; see
Coordinate Systems. A linear motion interpolates the path, so it ignores the configuration
vector of a CartesianTarget.
Perform a Linear Movement Using a JointPose
To perform a linear movement to a target defined in joint space, pass a JointPose. The robot control resolves it
to a Cartesian pose and moves there on a straight line:
let target = JointPose::new(
Radians(FRAC_PI_2),
Radians(-FRAC_PI_2),
Radians(FRAC_PI_2),
Radians(-FRAC_PI_2),
Radians(-FRAC_PI_2),
Radians(0.0),
);
robot
.move_linear(target, common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Joint target reached via linear interpolation.");
Perform a Relative Linear Movement
For moving linearly relative to the current pose, use move_linear_relative(). Other than that, it accepts the same
arguments as move_linear().
In this example, the robot moves 10 cm in the positive z-direction from its current pose. The z() helper builds a
CartesianPose in which every component is zero except z:
robot
.move_linear_relative(z(Meters(0.1)), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Moved +10cm in Z (relative linear).");
Perform a Linear Movement with Blending
Linear movements can use blending by chaining .blending() with a Factor or Percent, or with a radius in
Meters (see Blending Radius and Motion Profile). 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. .blending() is the transition into the next step, and the block is sent as a whole by start():
fn linear_with_blending(robot: &impl MoveLinear) -> Result<()> {
info!("--- Linear with blending ---");
robot
.move_linear(poses::blending_1(), common::TRAVEL_SPEED)
.blending(Percent(50.0))
.move_linear(poses::blending_2(), common::TRAVEL_SPEED)
.blending(Factor(0.5))
.move_linear(poses::blending_3(), 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.
Motion types can be mixed within a block, for example a PTP approach blended into a linear contact move:
fn mixed_block(robot: &(impl MovePtp + MoveLinear)) -> Result<()> {
info!("--- Mixed PTP/linear block ---");
robot
.move_ptp(poses::home_c())
.blending(Percent(20.0))
.move_linear(poses::blending_1(), common::SLOW_TRAVEL_SPEED)
.start()?
.join()?;
info!("Mixed block complete.");
Ok(())
}
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
A linear step accepts two more modifiers:
.blending(Meters(0.05)), an absolute blending radius in meters;MillimetersandCentimetersconvert into it. It needs a linear or circular successor: before a PTP step it is dropped with a warning, so blend into a PTP step withFactororPercentinstead..parameters(CartesianMotionParameters), a motion profile with the maximum translational acceleration (max_acceleration, aMetersPerSecondSquared), the maximum rotational speed and acceleration (max_rotation_speed, aRadiansPerSecond, andmax_rotation_acceleration, aRadiansPerSecondSquared), and orientation interpolation. The maxima are validated bystart(): a negative or non-finite value is rejected. Without it, the motion uses the robot control’s defaults,CartesianMotionParameters::DEFAULT. Build a profile from that constant with struct update syntax. The translational speed is not part of the profile; it stays the argument of the motion.
/// A gentler motion profile for linear motions.
///
/// Built with struct-update syntax from the control defaults.
const GENTLE: CartesianMotionParameters = CartesianMotionParameters {
max_acceleration: MetersPerSecondSquared(0.5),
..CartesianMotionParameters::DEFAULT
};
/// Linear motion with a motion profile and an absolute blending radius.
///
/// The radius needs a linear or circular successor: a PTP step has no
/// blending radius.
fn linear_with_parameters(robot: &impl MoveLinear) -> Result<()> {
info!("--- Linear with a motion profile ---");
robot
.move_linear(poses::blending_1(), common::SLOW_TRAVEL_SPEED)
.parameters(GENTLE)
.blending(Meters(0.05))
.move_linear(poses::blending_2(), common::SLOW_TRAVEL_SPEED)
.start()?
.join()?;
info!("Linear motion with a profile complete.");
Ok(())
}
Full Example
Linear motion example
//! Linear motion examples.
//!
//! Demonstrates linear movements using joint poses, Cartesian poses,
//! relative movements, and blending.
mod common;
use common::poses;
use std::f64::consts::{FRAC_PI_2, PI};
use tracing::info;
use voraus_robot_arm::prelude::*;
use voraus_robot_arm::types::coordinates::z;
/// Linear movement using a `JointPose` target.
fn linear_with_joint_pose(robot: &impl MoveLinear) -> Result<()> {
info!("--- Linear with JointPose ---");
let target = JointPose::new(
Radians(FRAC_PI_2),
Radians(-FRAC_PI_2),
Radians(FRAC_PI_2),
Radians(-FRAC_PI_2),
Radians(-FRAC_PI_2),
Radians(0.0),
);
robot
.move_linear(target, common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Joint target reached via linear interpolation.");
Ok(())
}
/// Linear movement using a `CartesianPose` target.
fn linear_with_cartesian_pose(robot: &impl MoveLinear) -> Result<()> {
info!("--- Linear with CartesianPose ---");
let target = CartesianPose::new(
Meters(0.1382),
Meters(0.550),
Meters(0.296),
Radians(PI),
Radians(0.0),
Radians(0.0),
);
robot
.move_linear(target, common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Cartesian target reached via linear interpolation.");
Ok(())
}
/// Relative linear movement (10 cm up in Z).
fn relative_linear(robot: &impl MoveLinear) -> Result<()> {
info!("--- Relative linear ---");
robot
.move_linear_relative(z(Meters(0.1)), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Moved +10cm in Z (relative linear).");
Ok(())
}
/// Linear motion with blending between waypoints.
///
/// Relative blending by `Factor` or `Percent`; see
/// `linear_with_parameters` for a radius in `Meters`.
fn linear_with_blending(robot: &impl MoveLinear) -> Result<()> {
info!("--- Linear with blending ---");
robot
.move_linear(poses::blending_1(), common::TRAVEL_SPEED)
.blending(Percent(50.0))
.move_linear(poses::blending_2(), common::TRAVEL_SPEED)
.blending(Factor(0.5))
.move_linear(poses::blending_3(), common::TRAVEL_SPEED)
.start()?
.join()?;
info!("Blending sequence complete.");
Ok(())
}
/// Mixed block: PTP approach, linear contact move, with blending between.
fn mixed_block(robot: &(impl MovePtp + MoveLinear)) -> Result<()> {
info!("--- Mixed PTP/linear block ---");
robot
.move_ptp(poses::home_c())
.blending(Percent(20.0))
.move_linear(poses::blending_1(), common::SLOW_TRAVEL_SPEED)
.start()?
.join()?;
info!("Mixed block complete.");
Ok(())
}
/// A gentler motion profile for linear motions.
///
/// Built with struct-update syntax from the control defaults.
const GENTLE: CartesianMotionParameters = CartesianMotionParameters {
max_acceleration: MetersPerSecondSquared(0.5),
..CartesianMotionParameters::DEFAULT
};
/// Linear motion with a motion profile and an absolute blending radius.
///
/// The radius needs a linear or circular successor: a PTP step has no
/// blending radius.
fn linear_with_parameters(robot: &impl MoveLinear) -> Result<()> {
info!("--- Linear with a motion profile ---");
robot
.move_linear(poses::blending_1(), common::SLOW_TRAVEL_SPEED)
.parameters(GENTLE)
.blending(Meters(0.05))
.move_linear(poses::blending_2(), common::SLOW_TRAVEL_SPEED)
.start()?
.join()?;
info!("Linear motion with a profile complete.");
Ok(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
// First move to a known starting pose.
robot.move_ptp(poses::home()).start()?.join()?;
linear_with_joint_pose(&robot)?;
linear_with_cartesian_pose(&robot)?;
relative_linear(&robot)?;
linear_with_blending(&robot)?;
mixed_block(&robot)?;
linear_with_parameters(&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.
MoveLinear
pub trait MoveLinear: BlockQueuing {
/// Queue a fully specified linear 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_linear(&self, request: LinearRequest) -> Result<InstructionHandle>;
/// Move the TCP on a straight line to an absolute target.
///
/// The robot interpolates in Cartesian space: the tool center point
/// travels on a straight line from its current pose to 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 target is converted to its Cartesian pose by the robot control.
///
/// The speed is the maximum translational speed of the tool center
/// point along the path, 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).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl MoveLinear, joints: JointPose, pose: CartesianPose) -> Result<()> {
/// robot.move_linear(pose, MetersPerSecond(0.25)).start()?.join()?;
/// robot.move_linear(joints, 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`]).
/// * `speed` - The maximum translational TCP speed, as
/// [`MetersPerSecond`](crate::types::MetersPerSecond) or a unit that
/// converts into it (see
/// [*Speed*](MoveLinear#speed)); a negative or non-finite value
/// fails at [`start`](MotionBlock::start).
///
/// # Returns
///
/// A block of one linear step, in the linear state.
fn move_linear(
&self,
target: impl Into<PoseTarget>,
speed: impl Into<MetersPerSecond>,
) -> MotionBlock<'_, Self, state::Linear>
where
Self: Sized,
{
MotionBlock::single(self, linear_step(target.into(), speed, false))
}
/// Move the TCP on a straight line by an offset from the current pose.
///
/// The robot interpolates in Cartesian space, as for
/// [`move_linear`](MoveLinear::move_linear): the tool center point travels
/// on a straight line to the current pose shifted by the 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 path.
///
/// 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).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl MoveLinear) -> Result<()> {
/// // Move 10 cm up on a straight line.
/// robot
/// .move_linear_relative(z(Meters(0.1)), MetersPerSecond(0.25))
/// .start()?
/// .join()?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `offset` - The offset from the current pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget) (see [`PoseTarget`]).
/// * `speed` - The maximum translational TCP speed, as
/// [`MetersPerSecond`](crate::types::MetersPerSecond) or a unit that
/// converts into it (see
/// [*Speed*](MoveLinear#speed)); a negative or non-finite value
/// fails at [`start`](MotionBlock::start).
///
/// # Returns
///
/// A block of one linear step, in the linear state.
fn move_linear_relative(
&self,
offset: impl Into<PoseTarget>,
speed: impl Into<MetersPerSecond>,
) -> MotionBlock<'_, Self, state::Linear>
where
Self: Sized,
{
MotionBlock::single(self, linear_step(offset.into(), speed, true))
}
}