PlaCo: Rhoban Planning and Control Library
PlaCo (Planning and Control) is a Python library developed by Rhoban for motion planning and control, particularly in robotics applications. It provides tools for inverse kinematics, contact planning, and motion generation, building upon `pinocchio` and `eigenpy`. The library is currently at version 0.9.20 and has an active but somewhat irregular release cadence, often with minor updates.
Common errors
-
ModuleNotFoundError: No module named 'eigenpy' (or 'pinocchio')
cause The `eigenpy` or `pinocchio` dependency (which are C++ libraries with Python bindings) was not installed correctly or its C++ components failed to build. This is common outside of `conda` environments.fixTry installing PlaCo using `conda` (`conda install -c conda-forge placo`). If using `pip`, ensure all system-level C++ prerequisites for `eigenpy` and `pinocchio` are installed (e.g., `libboost-all-dev`, `libeigen3-dev` on Ubuntu) before retrying `pip install placo`. -
RuntimeError: A robot must be provided (through path to URDF or Pinocchio model)
cause `placo.RobotWrapper` requires a valid path to a URDF file or a `pinocchio.Model` instance to initialize the robot model.fixProvide a correct and accessible path to a URDF file (local or online URL) or pass an already loaded `pinocchio.Model` object. Double-check the path for typos and ensure the file exists/is reachable. -
AttributeError: 'RobotWrapper' object has no attribute 'get_transform' (or similar method missing)
cause An API change occurred between `placo` versions, and the method name or signature you are calling no longer exists or has been renamed.fixConsult the latest `placo` documentation, the GitHub `README`, or `examples` directory for the correct method names and usage patterns corresponding to your installed version. Consider pinning your `placo` version. -
TypeError: argument of type 'NoneType' is not iterable (or similar type-related errors within task setup)
cause A parameter passed to a PlaCo task constructor (e.g., target position, joint names, frame ID) is `None` or of an incorrect Python type, which the underlying C++ code cannot handle.fixCarefully inspect all arguments provided to `placo` classes and methods, especially for tasks (`PositionTask`, `OrientationTask`), to ensure they are of the expected type (e.g., `numpy` arrays for positions, strings for frame IDs) and are not `None`.
Warnings
- breaking PlaCo is pre-1.0, and its API can change between minor versions. Methods and class structures may be refactored, leading to `AttributeError` or `TypeError` if code is not updated.
- gotcha Installation can be complex due to underlying C++ dependencies (`eigenpy`, `pinocchio`). Missing system libraries (e.g., Boost, Eigen) or incorrect environment setup can lead to build failures or `ModuleNotFoundError`.
- gotcha Robotics libraries often use specific coordinate system conventions (e.g., Z-up vs Y-up) or joint orderings that might differ from other tools or your expectations. Misinterpreting these can lead to unexpected robot behavior.
- gotcha While PlaCo provides Python bindings, the core computations are in C++. For very high-frequency or real-time control loops, the Python overhead might introduce latency. Trying to run complex solvers at rates above a few hundred Hz may hit performance limits.
Install
-
pip install placo -
conda install -c conda-forge placo
Imports
- RobotWrapper
import placo robot = placo.RobotWrapper(...)
- KinematicsSolver
import placo solver = placo.KinematicsSolver(...)
- PositionTask
import placo task = placo.PositionTask(...)
Quickstart
import placo
import numpy as np
# Create a robot wrapper from an online URDF (e.g., UR5 robot)
# This avoids local file dependencies for a quick test.
# Ensure internet connectivity for the URDF download.
robot = placo.RobotWrapper(
"https://raw.githubusercontent.com/ros-industrial/universal_robot/kinetic-devel/ur_description/urdf/ur5_robot.urdf",
placo.Flags.FLOATING_BASE # Set to true if the robot base is free-floating (e.g., a humanoid)
)
# Set an initial configuration for the robot's joints
robot.set_joint_positions({
"shoulder_pan_joint": 0.0,
"shoulder_lift_joint": -np.pi / 2,
"elbow_joint": np.pi / 2,
"wrist_1_joint": -np.pi / 2,
"wrist_2_joint": -np.pi / 2,
"wrist_3_joint": 0.0
})
robot.update_kinematics() # Update the robot's internal kinematics based on current joint positions
# Get the current transform (position and orientation) of the 'tool0' end-effector
T_tool0_world = robot.get_transform("tool0")
current_position = T_tool0_world.translation
current_orientation = T_tool0_world.rotation # as a 3x3 rotation matrix
# Define a target for the end-effector: move it slightly forward and up
target_position = current_position + np.array([0.1, 0.0, 0.05]) # Move 10cm in X, 5cm in Z
target_orientation = current_orientation # Keep the current orientation
# Create a kinematics solver instance
solver = placo.KinematicsSolver(robot)
# Add tasks to the solver
# PositionTask: Make the 'tool0' frame reach the target_position
position_task = placo.PositionTask(robot.frame_id("tool0"), target_position)
position_task.set_weight(1.0) # Set task weight (importance)
solver.add_task(position_task)
# OrientationTask: Make the 'tool0' frame match the target_orientation
orientation_task = placo.OrientationTask(robot.frame_id("tool0"), target_orientation)
orientation_task.set_weight(0.5) # Orientation is less critical than position
solver.add_task(orientation_task)
# Solve the inverse kinematics problem
solver.solve(verbose=False) # Set verbose=True for detailed solver output
robot.update_kinematics() # Apply the newly calculated joint positions to the robot model
# Print the results
new_T_tool0_world = robot.get_transform("tool0")
print(f"Initial tool0 position: {current_position.round(3)}")
print(f"Target tool0 position: {target_position.round(3)}")
print(f"Final tool0 position: {new_T_tool0_world.translation.round(3)}")