Create Articulations#

The articulation is a set of links (each of which behaves like a rigid body) connected together with special joints. For instance, a drawer can be connected to a table by a prismatic joint (slider), and a door can be connected to a frame by a revolute joint (hinge). A robot is also an instance of an articulation. Articulations are usually loaded from URDF XML, which we will see in other examples. This tutorial showcases how to create an articulation programmatically by SAPIEN.

Articulations can be controlled by applying torques on their joints. To drive an articulation to a desired state, users can apply a controller to compute corrective torques according to the difference between the actual and the desired.

In this tutorial, you will learn the following:

  • Create Articulation

  • Control the articulation with builtin controllers

  • Get kinematic quantities of the articulation

The example illustrates how to build a controllable toy car from scratch. transforms3d is required.

../../_images/create_articulations.gif

The full script can be downloaded here create_articulations.py

Control an articulation with builtin drives#

After building the articulation, we want to control it by actuating its joints. SAPIEN provides builtin drives (controllers) to control either the position or the speed of a joint.

def get_joints_dict(articulation: sapien.physx.PhysxArticulation):
    joints = articulation.get_joints()
    joint_names =  [joint.name for joint in joints]
    assert len(joint_names) == len(set(joint_names)), 'Joint names are assumed to be unique.'
    return {joint.name: joint for joint in joints}

All the joints of an articulation can be acquired by get_joints().

Note

Although the order of joints returned by get_joints() is fixed, it is recommended to index a joint by its name. Ideally joint names should be unique, but SAPIEN does not enforce it.

    joints = get_joints_dict(car)
    print(joints.keys())
    joints['front_shaft_joint'].set_drive_property(stiffness=1000.0, damping=0.0)  # front shaft
    joints['front_gear'].set_drive_property(0.0, 1000.0)  # front gear
    joints['back_gear'].set_drive_property(0.0, 0.0)  # back gear
    limits = np.rad2deg(joints['front_shaft_joint'].get_limit()[0])

For each active joint (with non-zero degree of freedom), we can set its drive properties: stiffness and damping. They implies the extent to which the drive attempts to achieve the target position and velocity respectively. There do not exist a general rule to set those values and you usually should tune them case by case. If you are familiar with control theory, they correspond to P and D terms in PID controller. The initial target position and velocity of a joint are zero by default. Since our toy car is designed to be a front-wheel drive car, we set both the stiffness and damping as zero for the back gear.

Note

When a non-zero target is set and stiffness/damping is also non-zero, the drive takes effect internally at each simulation step.

We can implement different behaviors when different keys are pressed. set_drive_target(...) and set_drive_velocity_target(...) are called to set the target position and velocity of a joint drive.

    position = 0.0  # position target of joints
    velocity = 0.0  # velocity target of joints
    steps = 0
    last_step = -4
    while not viewer.closed:
        if steps - last_step < 4:
            pass  # prevent multiple changes for one key press
        else:
            last_step = steps
            if viewer.window.key_down('i'):  # accelerate
                velocity += 5.0
                print('velocity increases:', velocity)
                joints['front_gear'].set_drive_velocity_target(velocity)
            elif viewer.window.key_down('k'):  # brake
                velocity -= 5.0
                print('velocity decreases:', velocity)
                joints['front_gear'].set_drive_velocity_target(velocity)
            elif viewer.window.key_down('j'):  # left turn
                position += 2
                position = np.clip(position, *limits)
                joints['front_shaft_joint'].set_drive_target(np.deg2rad(position))
                print('position increases:', position)
            elif viewer.window.key_down('l'):  # right turn
                position -= 2
                position = np.clip(position, *limits)
                print('position decreases:', position)
                joints['front_shaft_joint'].set_drive_target(np.deg2rad(position))
            elif viewer.window.key_down('r'):  # reset
                position = 0
                velocity = 0.0
                print('reset')
                joints['front_shaft_joint'].set_drive_target(position)
                joints['front_gear'].set_drive_velocity_target(velocity)

Get kinematic quantities of the articulation#

The pose of the articulation (frame) in the world frame can be acquired by get_pose(). It is the same as the pose of the root link. Besides, joint positions and velocities can be acquired by get_qpos() and get_qvel(). They both return a list of scalars, the length of which is the total degree of freedom. The order is the same as get_joints().

            print('Pose', car.get_pose())
            print('Joint positions', car.get_qpos())
            print('Joint velocities', car.get_qvel())

Remove an articulation#

Similar to removing an actor, scene.remove_articulation(articulation) will remove it from the scene. Using the articulation or any of its links or joints after removal will result in undefined behavior (usually a crash).

Under the hood implementation#

The articulation builder hides complex logic for managing articulations. This section goes deeper into how PhysxArticulation is managed in SAPIEN for more advanced users.

A PhysXArticulation originates from individual PhysxArticulationLink that can be attached to an Entity just like other SAPIEN Component. Each PhysxArticulationLink may specify a parent PhysxArticulationLink, and all links sharing a common root is considered part of the same articulation. All links of the same articulation share a single PhysxArticulation object, which can be retrieved with PhysxArticulationLink.articulation.

It is possible to dynamically add or remove links to an articulation, however, the associated PhysxArticulation will be invalidated and should be obtained again from a link from the new articulation. This use case is not covered here for simplicity.