Control
Use Covalent Shaper during runtime by loading the identified model, shaping the desired joint command, and sending the shaped command to the robot manufacturer's SDK.
Before using this page, complete Calibration and Identification so that src/robot/models/current/shaper contains the identified Shaper model files.
Complete Example
The complete runnable example is in:
textsrc/robot/example_usage/shaper/shaper_example_usage.py
Run this example after completing First Steps, with the Python environment active and the package installed.
From the repository root, run:
bashpython3 src/robot/example_usage/shaper/shaper_example_usage.py
The example loads the Shaper model, generates a point-to-point command, compares unshaped and shaped simulated responses, and opens two plots:
- Command position, velocity, and acceleration.
- Simulated plant response for the unshaped and shaped commands using models loaded from robot/example_usage/shaper.
The terminal output should include a summary similar to:
textCovalent Shaper example complete. Dominant simulation mode: wn=... rad/s, zeta=... Unshaped residual vibration: ... rad Always-on Shaper residual vibration: ... rad Residual-tail offline Shaper residual vibration: ... rad Sample-by-sample stream Shaper residual vibration: ... rad
Inputs
The Shaper needs:
- The controller sample time, in seconds.
- The identified model folder, usually
src/robot/models/current/shaper. - The robot URDF file.
- The number of shaped axes.
- The total number of robot joints.
- Desired joint position, velocity, and acceleration commands.
Example 1: Shape a Full Known Trajectory
Use this path when the full joint trajectory is known before the move starts. The Shaper receives the complete command and returns shaped position, velocity, and acceleration arrays.
pythonimport numpy as np import robot_sdk # Replace with the SDK package for the robot you are using. from reforge_core.control.shaper import ShaperInterface sample_time_s = 0.004 num_axes = 3 num_joints = 6 shaper = ShaperInterface( sample_time=sample_time_s, model_directory="src/robot/models/current/shaper", urdf_filepath="src/robot/urdf/<robot>.urdf", num_axes=num_axes, num_joints=num_joints, ) # Desired joint trajectory from your planner or robot SDK: [N, num_joints]. desired_trajectory_rad = np.asarray(robot_sdk.get_planned_joint_trajectory()) time_vector_s = np.arange(desired_trajectory_rad.shape[0]) * sample_time_s desired_velocity_rad_s = np.gradient( desired_trajectory_rad, sample_time_s, axis=0, ) desired_acceleration_rad_s2 = np.gradient( desired_velocity_rad_s, sample_time_s, axis=0, ) shaped_trajectory = shaper.process_trajectory( command=desired_trajectory_rad, command_dot=desired_velocity_rad_s, command_ddot=desired_acceleration_rad_s2, time_vector=list(time_vector_s), vibration_shaping_weight=1.0, vibration_weight_transition_s=0.0, residual_shaping_strategy=None, finalize_tail=False, ) robot_sdk.send_joint_trajectory( positions=shaped_trajectory.positions, velocities=shaped_trajectory.velocities, accelerations=shaped_trajectory.accelerations, sample_time=sample_time_s, )
Use this example when your planner or robot SDK can provide the full command before execution.
Example 2: Shape the Final Part of a Known Trajectory
Use this path when the full trajectory is known, but you want Shaper to focus on the residual vibration near the end of the move. This can reduce unnecessary command delay earlier in the move.
pythonresidual_tail_shaper = ShaperInterface( sample_time=sample_time_s, model_directory="src/robot/models/current/shaper", urdf_filepath="src/robot/urdf/<robot>.urdf", num_axes=num_axes, num_joints=num_joints, ) residual_tail_trajectory = residual_tail_shaper.process_trajectory( command=desired_trajectory_rad, command_dot=desired_velocity_rad_s, command_ddot=desired_acceleration_rad_s2, time_vector=list(time_vector_s), vibration_shaping_weight=1.0, vibration_weight_transition_s=0.0, residual_transition_margin_s=0.0, finalize_tail=False, ) robot_sdk.send_joint_trajectory( positions=residual_tail_trajectory.positions, velocities=residual_tail_trajectory.velocities, accelerations=residual_tail_trajectory.accelerations, sample_time=sample_time_s, )
Use this example when the end of the motion is the main vibration-sensitive part of the move.
Example 3: Shape a Sample-by-Sample Stream
Use this path when the full trajectory is not known ahead of time. The online planner generates one command sample, Shaper modifies that sample, and the shaped command is sent to the robot SDK before the next control cycle.
pythonstreaming_shaper = ShaperInterface( sample_time=sample_time_s, model_directory="src/robot/models/current/shaper", urdf_filepath="src/robot/urdf/<robot>.urdf", num_axes=num_axes, num_joints=num_joints, ) while robot_sdk.control_loop_is_running(): # Replace this with your online planner, joystick command, force controller, # task-space controller, or robot SDK trajectory generator. command_rad, velocity_rad_s, acceleration_rad_s2 = planner.next_joint_sample() shaped_sample = streaming_shaper.process_sample( command=command_rad, command_dot=velocity_rad_s, command_ddot=acceleration_rad_s2, vibration_shaping_weight=1.0, vibration_weight_transition_s=0.0, ) robot_sdk.send_joint_command( positions=shaped_sample.positions, velocities=shaped_sample.velocities, accelerations=shaped_sample.accelerations, )
Use this example when commands are generated online. The Shaper does not receive the complete future trajectory in this mode; it only receives the current command sample.