-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TOPP-RA and joint trajectory animation
- Loading branch information
1 parent
eea8da3
commit 5678647
Showing
6 changed files
with
224 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import numpy as np | ||
from airo_typing import JointPathType | ||
from pydrake.multibody.optimization import CalcGridPointsOptions, Toppra | ||
from pydrake.multibody.plant import MultibodyPlant | ||
from pydrake.trajectories import PathParameterizedTrajectory, PiecewisePolynomial, Trajectory | ||
|
||
|
||
def time_parametrize_toppra( | ||
plant: MultibodyPlant, | ||
joints: JointPathType | Trajectory, | ||
joint_speed_limit: float = 2.0, # Max 180 degrees/s ~ 3.14 rad/s | ||
joint_acceleration_limit: float = 4.0, # UR recommends < 800 degrees/s^2 ~ 13.9 rad/s^2 | ||
) -> PathParameterizedTrajectory: | ||
"""Recalculate the timing of a path or trajectory to respect joint speed and acceleration limits using TOPP-RA. | ||
Args: | ||
plant: The MultibodyPlant for the robot. | ||
joints: A joint path or trajectory. | ||
joint_speed_limit: The maximum joint speed in rad/s. | ||
joint_acceleration_limit: The maximum joint acceleration in rad/s^2. | ||
""" | ||
if isinstance(joints, Trajectory): | ||
joint_trajectory = joints | ||
n_dofs = joint_trajectory.value(0).shape[0] | ||
else: | ||
joints = np.array(joints).squeeze() | ||
n_dofs = joints.shape[1] | ||
times_dummy = np.linspace(0.0, 1.0, len(joints)) # TOPP-RA will calculate the actual times | ||
joint_trajectory = PiecewisePolynomial.FirstOrderHold(times_dummy, joints.T) | ||
|
||
gridpoints = Toppra.CalcGridPoints(joint_trajectory, CalcGridPointsOptions()) | ||
|
||
acceleration_limits_lower = np.array([-joint_acceleration_limit] * n_dofs) | ||
acceleration_limits_upper = np.array([joint_acceleration_limit] * n_dofs) | ||
velocity_limits_lower = np.array([-joint_speed_limit] * n_dofs) | ||
velocity_limits_upper = np.array([joint_speed_limit] * n_dofs) | ||
|
||
toppra = Toppra(joint_trajectory, plant, gridpoints) | ||
toppra.AddJointAccelerationLimit(acceleration_limits_lower, acceleration_limits_upper) | ||
toppra.AddJointVelocityLimit(velocity_limits_lower, velocity_limits_upper) | ||
time_parametrization = toppra.SolvePathParameterization() | ||
|
||
if time_parametrization is None: | ||
raise ValueError("TOPP-RA failed to find a valid time parametrization.") | ||
|
||
joint_trajectory = PathParameterizedTrajectory(joint_trajectory, time_parametrization) | ||
|
||
return joint_trajectory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import numpy as np | ||
from airo_typing import JointPathContainer, SingleArmTrajectory | ||
from pydrake.trajectories import Trajectory | ||
|
||
|
||
def discretize_drake_joint_trajectory(joint_trajectory: Trajectory, steps: int = 100) -> SingleArmTrajectory: | ||
positions = [] | ||
times_uniform = np.linspace(joint_trajectory.start_time(), joint_trajectory.end_time(), steps) | ||
for t in times_uniform: | ||
position = joint_trajectory.value(t).squeeze() | ||
positions.append(position) | ||
joint_path = JointPathContainer(positions=np.array(positions)) | ||
joint_trajectory_discretized = SingleArmTrajectory(times_uniform, joint_path) | ||
return joint_trajectory_discretized |
75 changes: 57 additions & 18 deletions
75
...ake/visualization/joint_configurations.py → airo_drake/visualization/joints.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,98 @@ | ||
import numpy as np | ||
from airo_typing import JointPathType | ||
from airo_typing import JointPathType, SingleArmTrajectory | ||
from loguru import logger | ||
from pydrake.geometry import Meshcat | ||
from pydrake.multibody.tree import ModelInstanceIndex | ||
from pydrake.planning import RobotDiagram | ||
from pydrake.systems.framework import Context | ||
from pydrake.trajectories import PiecewisePolynomial | ||
from pydrake.trajectories import PiecewisePolynomial, Trajectory | ||
|
||
|
||
def animate_joint_configurations( | ||
def animate_joint_trajectory( | ||
meshcat: Meshcat, | ||
robot_diagram: RobotDiagram, | ||
arm_index: ModelInstanceIndex, | ||
joint_configurations: list[np.ndarray] | JointPathType, | ||
duration: float = 4.0, | ||
time_per_configuration: float = None, | ||
joint_trajectory: SingleArmTrajectory | Trajectory, | ||
context: Context | None = None, | ||
) -> None: | ||
"""Publish a recorded animation to meshcat where the robot arm cycles through the provided joint configurations. | ||
"""Publish a recorded animation to meshcat where the robot arm follows the provided joint trajectory. | ||
Args: | ||
meshcat: The MeshCat instance to add the visualization to. | ||
robot_diagram: The robot diagram. | ||
joint_cofigurations: A list of joint configurations to animate. | ||
joint_trajectory: The joint trajectory to animate. | ||
duration: Total duration of the animation. | ||
time_per_configuration: The time to spend (still) on each joint solution. Will overwrite duration if provided. | ||
context: The context to use for the animation. If None, a new default context will be used (with all other joint angles equal to 0). | ||
""" | ||
if joint_configurations is None or len(joint_configurations) == 0: | ||
logger.warning("Asked to animate joint configurations, but none provided.") | ||
if joint_trajectory is None: | ||
logger.warning("Asked to animate joint trajectory, but none provided.") | ||
|
||
if isinstance(joint_trajectory, SingleArmTrajectory): | ||
positions = joint_trajectory.path.positions | ||
times = joint_trajectory.times | ||
if positions is None: | ||
logger.warning("Asked to animate SingleArmTrajectory, but it has not positions.") | ||
return | ||
joint_trajectory = PiecewisePolynomial.FirstOrderHold(times, positions.T) # linear interpolation | ||
|
||
assert isinstance(joint_trajectory, Trajectory) # For mypy, but extra check we have a Drake Trajectory now | ||
|
||
if context is None: | ||
# Create a new context just for the recording | ||
context = robot_diagram.CreateDefaultContext() | ||
plant = robot_diagram.plant() | ||
plant_context = plant.GetMyContextFromRoot(context) | ||
|
||
joint_configurations = np.array(joint_configurations).squeeze() | ||
fps = 60.0 | ||
|
||
if time_per_configuration is not None: | ||
duration = time_per_configuration * len(joint_configurations) | ||
duration = joint_trajectory.end_time() | ||
|
||
joint_times = np.linspace(0.0, duration, len(joint_configurations)) | ||
joints_interpolated = PiecewisePolynomial.ZeroOrderHold(joint_times, joint_configurations.T) | ||
# TODO can support trajectories that don't start at 0? | ||
if not np.isclose(joint_trajectory.start_time(), 0.0): | ||
logger.warning("Joint trajectory does not start at time 0.0, this is not officially supported.") | ||
|
||
fps = 60.0 | ||
n_frames = int(max(duration * fps, 1)) | ||
frame_times = np.linspace(0.0, duration, n_frames) | ||
|
||
meshcat.StartRecording(set_visualizations_while_recording=False, frames_per_second=fps) | ||
|
||
for t in frame_times: | ||
context.SetTime(t) | ||
plant.SetPositions(plant_context, arm_index, joints_interpolated.value(t).squeeze()) | ||
plant.SetPositions(plant_context, arm_index, joint_trajectory.value(t).squeeze()) | ||
robot_diagram.ForcedPublish(context) | ||
|
||
meshcat.StopRecording() | ||
meshcat.PublishRecording() | ||
|
||
|
||
def animate_joint_configurations( | ||
meshcat: Meshcat, | ||
robot_diagram: RobotDiagram, | ||
arm_index: ModelInstanceIndex, | ||
joint_configurations: list[np.ndarray] | JointPathType, | ||
duration: float = 4.0, | ||
time_per_configuration: float | None = None, | ||
context: Context | None = None, | ||
) -> None: | ||
"""Publish a recorded animation to meshcat where the robot arm cycles through the provided joint configurations. | ||
Args: | ||
meshcat: The MeshCat instance to add the visualization to. | ||
robot_diagram: The robot diagram. | ||
joint_cofigurations: A list of joint configurations to animate. | ||
duration: Total duration of the animation. | ||
time_per_configuration: The time to spend (still) on each joint solution. Will overwrite duration if provided. | ||
context: The context to use for the animation. If None, a new default context will be used (with all other joint angles equal to 0). | ||
""" | ||
if joint_configurations is None or len(joint_configurations) == 0: | ||
logger.warning("Asked to animate joint configurations, but none provided.") | ||
|
||
joint_configurations = np.array(joint_configurations).squeeze() | ||
|
||
if time_per_configuration is not None: | ||
duration = time_per_configuration * len(joint_configurations) | ||
|
||
joint_times = np.linspace(0.0, duration, len(joint_configurations)) | ||
joints_interpolated = PiecewisePolynomial.ZeroOrderHold(joint_times, joint_configurations.T) | ||
|
||
animate_joint_trajectory(meshcat, robot_diagram, arm_index, joints_interpolated, context) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters