Skip to content

simulo.OperationalSpaceController

Operational space controller for simultaneous motion and force control.

Computes joint effort/torque commands to achieve task-space motion and/or force objectives. Supports impedance control with configurable stiffness and damping, plus nullspace control for redundant manipulators.

This is useful for:

  • Contact-rich manipulation (e.g., wiping a surface)
  • Compliant motion control
  • Force-controlled tasks

For common use cases, use the factory methods:

  • for_compliant_motion() - Pure motion control with compliance
  • for_force_control() - Force control on specified axis

Example:

import simulo
class OSCScenario(simulo.Scenario):
def build(self, scene):
self.robot = simulo.Robot(asset=simulo.Asset.usd("builtin://franka_panda_effort"))
scene.add(self.robot, at="/World/Robot")
def on_start(self):
arm_joints = self.robot.find_joints("panda_joint.*")
# Simple: use factory method
self.osc = simulo.OperationalSpaceController.for_force_control(
robot=self.robot,
end_effector="panda_hand",
joints=arm_joints,
force_axis="z",
)
self.target = simulo.Pose(position=[0.5, 0.3, 0.7], orientation=[1,0,0,0])
def on_step(self):
# Single call - like IK's move_to()
self.osc.move_to(self.target, force=10.0) # 10N in z-axis

Attributes:

  • robot — The robot this controller is attached to
  • end_effector — Name of the end-effector body/link
  • joints — List of joint indices being controlled
  • contact_sensor — Optional contact sensor for force feedback

Full constructor (uncommon — see the factory methods above for the common cases):

simulo.OperationalSpaceController(
robot: Robot,
end_effector: str,
joints: List[int],
contact_sensor: Optional[Any] = None,
stiffness: float = 400.0,
damping_ratio: float = 1.0,
force_axis: Optional[str] = None,
nullspace_control: bool = True,
config: Optional[OSCConfig] = None,
)
OperationalSpaceController.for_compliant_motion(
robot: Robot,
end_effector: str,
joints: List[int],
stiffness: float = 400.0,
damping_ratio: float = 1.0,
nullspace_control: bool = True,
) -> OperationalSpaceController

classmethod

Create OSC for compliant motion control (no force control).

Use this for tasks requiring soft, compliant end-effector motion.

Args:

  • robot — The robot to control
  • end_effector — Name of the end-effector body/link
  • joints — List of joint indices to control
  • stiffness — Task-space stiffness in N/m (default: 400)
  • damping_ratio — Damping ratio (1.0 = critically damped)
  • nullspace_control — Keep joints centered when possible

Returns:

Configured OperationalSpaceController

Example:

osc = OperationalSpaceController.for_compliant_motion( … robot, “panda_hand”, arm_joints, stiffness=200.0 … ) osc.move_to(target_pose)

OperationalSpaceController.for_force_control(
robot: Robot,
end_effector: str,
joints: List[int],
force_axis: str = 'z',
contact_sensor: Optional[Any] = None,
stiffness: float = 400.0,
damping_ratio: float = 1.0,
nullspace_control: bool = True,
) -> OperationalSpaceController

classmethod

Create OSC for hybrid motion/force control.

Use this for contact tasks like pushing, wiping, or polishing. Motion is controlled on 5 axes while force is controlled on 1 axis.

Args:

  • robot — The robot to control
  • end_effector — Name of the end-effector body/link
  • joints — List of joint indices to control
  • force_axis — Axis for force control (“x”, “y”, or “z”)
  • contact_sensor — Optional ContactSensor for closed-loop force control
  • stiffness — Task-space stiffness in N/m for motion axes
  • damping_ratio — Damping ratio (1.0 = critically damped)
  • nullspace_control — Keep joints centered when possible

Returns:

Configured OperationalSpaceController

Example:

osc = OperationalSpaceController.for_force_control( … robot, “panda_hand”, arm_joints, … force_axis=“z”, contact_sensor=wall_sensor … ) osc.move_to(target_pose, force=10.0) # 10N in z

OperationalSpaceController.move_to(
target: Union[Pose, torch.Tensor, List[float]],
force: Optional[float] = None,
wrench: Optional[Union[torch.Tensor, List[float]]] = None,
stiffness: Optional[Union[float, List[float]]] = None,
) -> Optional[torch.Tensor]

Move end-effector toward target pose, optionally applying force.

Call this every simulation step. The controller computes joint efforts and applies them to the robot automatically.

Args:

  • target — Target pose in robot base frame. Can be: - simulo.Pose object - Tensor of shape (7,) or (num_envs, 7) with [x, y, z, qw, qx, qy, qz] - List [x, y, z, qw, qx, qy, qz]
  • force — Force to apply in the force-controlled axis (Newtons). Simple scalar for single-axis force control.
  • wrench — Full 6-DOF wrench [fx, fy, fz, tx, ty, tz] for power users. Overrides force if both provided.
  • stiffness — Task-space stiffness. Can be: - float: Same stiffness for all axes (e.g., 400.0) - List of 6 floats: Per-axis stiffness If None, uses default stiffness from constructor.

Returns:

Joint effort tensor (num_envs, num_joints) or None if computation failed.

Example:

# Simple motion control
osc.move_to(target_pose)
# Motion with force in z-axis
osc.move_to(target_pose, force=10.0)
# With custom stiffness
osc.move_to(target_pose, force=10.0, stiffness=300.0)
OperationalSpaceController.reset() -> None

Reset controller state.

Call this when resetting the robot to a new state.

OperationalSpaceController.ee_body_idx: Optional[int]

property

Get the end-effector body index.

OperationalSpaceController.action_dim: int

property

Get the action dimension (command size).