Skip to main content

Overview

The KinCharModel class represents a kinematic character model with hierarchical joint structure. It provides functionality for loading character definitions from XML files, performing forward kinematics, converting between rotation representations, and managing joint constraints.

Enums

JointType

Defines the types of joints supported by the character model.
class JointType(enum.Enum):
    ROOT = 0       # Root joint (no DOF)
    HINGE = 1      # 1-DOF revolute joint
    SPHERICAL = 2  # 3-DOF ball joint
    FIXED = 3      # Fixed joint (no DOF)

GeomType

Defines the types of geometric shapes for collision and visualization.
class GeomType(enum.Enum):
    BOX = 0
    SPHERE = 1
    CAPSULE = 2
    CYLINDER = 3
    MESH = 4

Classes

Joint

Represents a single joint in the character.
Joint(name: str, joint_type: JointType, axis: torch.Tensor, limits=None)
name
str
required
Name of the joint.
joint_type
JointType
required
Type of joint (ROOT, HINGE, SPHERICAL, or FIXED).
axis
torch.Tensor
Axis of rotation for HINGE joints (None for other types).
limits
torch.Tensor
default:"None"
Joint limits in radians.

Methods

get_dof_dim
Get the number of degrees of freedom for this joint.
get_dof_dim() -> int
return
int
Number of DOFs (0 for ROOT/FIXED, 1 for HINGE, 3 for SPHERICAL).
dof_to_rot
Convert DOF values to quaternion rotation.
dof_to_rot(dof: torch.Tensor) -> torch.Tensor
rot_to_dof
Convert quaternion rotation to DOF values.
rot_to_dof(rot: torch.Tensor) -> torch.Tensor

Geom

Represents a geometric shape attached to a body.
Geom(
    shape_type: GeomType,
    offset: torch.Tensor | list,
    dims: torch.Tensor | list,
    device,
    quat=None,
    radius=None,
    mesh_name=None,
    name=None
)

KinCharModel

Main class for kinematic character modeling.

Initialization

Constructor

KinCharModel(device)
device
str | torch.device
required
Device (CPU or GPU) for storing character data.

init

Initialize the character model with body hierarchy.
init(
    body_names: list[str],
    parent_indices: list[int] | torch.Tensor,
    local_translation: list | torch.Tensor,
    local_rotation: list | torch.Tensor,
    joints: list[Joint],
    geoms: list[list[Geom]] = None,
    contact_body_names: list[str] = None
)
body_names
list[str]
required
Names of all bodies in the character.
parent_indices
list[int] | torch.Tensor
required
Parent index for each body (-1 for root).
local_translation
list | torch.Tensor
required
Local translation of each body relative to parent (shape: [num_bodies, 3]).
local_rotation
list | torch.Tensor
required
Local rotation of each body as quaternions (shape: [num_bodies, 4]).
joints
list[Joint]
required
Joint definitions for each body.
geoms
list[list[Geom]]
default:"None"
Geometric shapes for each body (for collision/visualization).
contact_body_names
list[str]
default:"None"
Names of bodies that can make contact. If None, all bodies are contact bodies.

load_char_file

Load character definition from a MuJoCo XML file.
load_char_file(char_file: str)
char_file
str
required
Path to the MuJoCo XML character definition file.

Core Methods

forward_kinematics

Compute world-space positions and rotations for all bodies.
forward_kinematics(
    root_pos: torch.Tensor,
    root_rot: torch.Tensor,
    joint_rot: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]
root_pos
torch.Tensor
required
Root body position (shape: […, 3]).
root_rot
torch.Tensor
required
Root body rotation as quaternion (shape: […, 4]).
joint_rot
torch.Tensor
required
Joint rotations as quaternions (shape: […, num_joints-1, 4]).
return
tuple[torch.Tensor, torch.Tensor]
Tuple containing:
  • body_pos (torch.Tensor): World positions of all bodies (shape: […, num_bodies, 3])
  • body_rot (torch.Tensor): World rotations of all bodies (shape: […, num_bodies, 4])

dof_to_rot

Convert DOF values to joint rotations.
dof_to_rot(dof: torch.Tensor) -> torch.Tensor
dof
torch.Tensor
required
DOF values (shape: […, dof_size]).
return
torch.Tensor
Joint rotations as quaternions (shape: […, num_joints-1, 4]).

rot_to_dof

Convert joint rotations to DOF values.
rot_to_dof(rot: torch.Tensor) -> torch.Tensor
rot
torch.Tensor
required
Joint rotations as quaternions (shape: […, num_joints-1, 4]).
return
torch.Tensor
DOF values (shape: […, dof_size]).

compute_dof_vel

Compute DOF velocities from two consecutive joint rotation frames.
compute_dof_vel(
    joint_rot0: torch.Tensor,
    joint_rot1: torch.Tensor,
    dt: float
) -> torch.Tensor
joint_rot0
torch.Tensor
required
Joint rotations at time t (shape: […, num_joints-1, 4]).
joint_rot1
torch.Tensor
required
Joint rotations at time t+dt (shape: […, num_joints-1, 4]).
dt
float
required
Time step between frames.
return
torch.Tensor
DOF velocities (shape: […, dof_size]).

compute_frame_dof_vel

Compute DOF velocities for a sequence of motion frames.
compute_frame_dof_vel(
    joint_rot: torch.Tensor,
    dt: float
) -> torch.Tensor
joint_rot
torch.Tensor
required
Sequence of joint rotations (shape: […, num_frames, num_joints-1, 4]).
dt
float
required
Time step between frames.
return
torch.Tensor
DOF velocities for all frames (shape: […, num_frames, dof_size]).

apply_joint_dof_limits

Clamp DOF values to joint limits.
apply_joint_dof_limits(joint_dofs: torch.Tensor) -> torch.Tensor
joint_dofs
torch.Tensor
required
DOF values to clamp (shape: […, dof_size]).
return
torch.Tensor
Clamped DOF values within joint limits.

construct_frame_data

Combine root and joint data into a single motion frame tensor.
construct_frame_data(
    root_pos: torch.Tensor,
    root_rot_quat: torch.Tensor,
    joint_rot: torch.Tensor
) -> torch.Tensor
root_pos
torch.Tensor
required
Root positions.
root_rot_quat
torch.Tensor
required
Root rotations as quaternions.
joint_rot
torch.Tensor
required
Joint rotations as quaternions.
return
torch.Tensor
Combined motion frame with root_pos, root_rot (exp map), and joint_dof.

Body and Joint Queries

get_num_bodies

get_num_bodies() -> int
return
int
Total number of bodies in the character.

get_num_joints

get_num_joints() -> int
return
int
Total number of joints (including root).

get_num_non_root_joints

get_num_non_root_joints() -> int
return
int
Number of non-root joints.

get_dof_size

get_dof_size() -> int
return
int
Total number of degrees of freedom.

get_body_names

get_body_names() -> list[str]
return
list[str]
List of all body names.

get_body_name

get_body_name(body_id: int) -> str
body_id
int
required
Body index.
return
str
Name of the body.

get_body_id

get_body_id(body_name: str) -> int
body_name
str
required
Name of the body.
return
int
Index of the body.

get_joint_id

get_joint_id(body_name: str) -> int
body_name
str
required
Name of the body.
return
int
Joint index (body index - 1, since joint arrays exclude root).

get_joint

get_joint(j: int) -> Joint
j
int
required
Joint index (must be > 0).
return
Joint
Joint object at the specified index.

get_parent_id

get_parent_id(j: int) -> int
j
int
required
Body/joint index.
return
int
Index of the parent body (-1 for root).

get_joint_dof_idx

get_joint_dof_idx(j: int) -> int
j
int
required
Joint index.
return
int
Starting index of this joint’s DOFs in the DOF vector.

get_joint_dof_dim

get_joint_dof_dim(j: int) -> int
j
int
required
Joint index.
return
int
Number of DOFs for this joint.

Contact Bodies

get_num_contact_bodies

get_num_contact_bodies() -> int
return
int
Number of bodies designated for contact detection.

is_contact_body

is_contact_body(body_name: str) -> bool
body_name
str
required
Name of the body.
return
bool
True if the body is a contact body.

get_contact_body_id

get_contact_body_id(body_name: str) -> int
body_name
str
required
Name of the contact body.
return
int
Index within the contact body array.

get_contact_body_ids

get_contact_body_ids() -> torch.Tensor
return
torch.Tensor
Tensor of contact body indices.

get_contact_body_name

get_contact_body_name(body_id: int) -> str
body_id
int
required
Contact body index.
return
str
Name of the contact body.

create_full_contact_tensor

Expand a contact tensor from contact bodies to all bodies.
create_full_contact_tensor(contact_tensor: torch.Tensor) -> torch.Tensor
contact_tensor
torch.Tensor
required
Contact labels for contact bodies (shape: […, num_contact_bodies]).
return
torch.Tensor
Contact labels for all bodies (shape: […, num_bodies]). Non-contact bodies have label 0.

Geometry Methods

get_geoms

get_geoms(body_id: int) -> list[Geom]
body_id
int
required
Body index.
return
list[Geom]
List of geometric shapes attached to the body.

get_body_geom_names

get_body_geom_names() -> list[str]
return
list[str]
List of all geometry names in the character.

find_lowest_point

Find the lowest point on the character given body poses.
find_lowest_point(
    body_pos: torch.Tensor,
    body_rot: torch.Tensor
) -> torch.Tensor
body_pos
torch.Tensor
required
World positions of all bodies (shape: […, num_bodies, 3]).
body_rot
torch.Tensor
required
World rotations of all bodies (shape: […, num_bodies, 4]).
return
torch.Tensor
3D position of the lowest point (shape: […, 3]).

Bone Length Methods

get_bone_length

Get the length of a bone (distance from parent joint).
get_bone_length(body: int | str) -> float
body
int | str
required
Body index or name.
return
float
Length of the bone in meters.

set_bone_length

Set the length of a bone.
set_bone_length(
    body: int | str,
    new_length: float,
    update_original: bool = True,
    update_geoms: bool = True
)
body
int | str
required
Body index or name.
new_length
float
required
New bone length in meters.
update_original
bool
default:"True"
Whether to update the stored original length.
update_geoms
bool
default:"True"
Whether to scale attached geometries.

scale_bone_length

Scale the length of a bone by a multiplicative factor.
scale_bone_length(
    body: int | str,
    scale: float,
    update_original: bool = True,
    update_geoms: bool = True
)
body
int | str
required
Body index or name.
scale
float
required
Multiplicative scale factor.
update_original
bool
default:"True"
Whether to update the stored original length.
update_geoms
bool
default:"True"
Whether to scale attached geometries.

Utility Methods

get_copy

Create a deep copy of the character model.
get_copy(new_device=None) -> KinCharModel
new_device
str | torch.device
default:"None"
Device for the copy. If None, uses the same device.
return
KinCharModel
Deep copy of the character model.

output_xml

Export the character to a MuJoCo XML file.
output_xml(output_file: str)
output_file
str
required
Path where the XML file should be written.

Usage Examples

Loading a Character

import torch
from parc.anim.kin_char_model import KinCharModel

# Create and load character model
char_model = KinCharModel(device="cuda:0")
char_model.load_char_file("path/to/character.xml")

# Query character properties
print(f"Number of bodies: {char_model.get_num_bodies()}")
print(f"Number of joints: {char_model.get_num_joints()}")
print(f"DOF size: {char_model.get_dof_size()}")
print(f"Body names: {char_model.get_body_names()}")

Forward Kinematics

import torch

# Define root pose and joint rotations
batch_size = 10
num_joints = char_model.get_num_joints() - 1

root_pos = torch.zeros(batch_size, 3, device="cuda:0")
root_pos[:, 2] = 1.0  # 1 meter above ground

root_rot = torch.tensor([0, 0, 0, 1], device="cuda:0").expand(batch_size, 4)
joint_rot = torch.zeros(batch_size, num_joints, 4, device="cuda:0")
joint_rot[..., 3] = 1.0  # Identity quaternions

# Compute forward kinematics
body_pos, body_rot = char_model.forward_kinematics(root_pos, root_rot, joint_rot)

print(f"Body positions shape: {body_pos.shape}")  # [10, num_bodies, 3]
print(f"Body rotations shape: {body_rot.shape}")  # [10, num_bodies, 4]

DOF Conversion

# Convert joint rotations to DOF values
dof_values = char_model.rot_to_dof(joint_rot)
print(f"DOF shape: {dof_values.shape}")  # [10, dof_size]

# Apply joint limits
clamped_dofs = char_model.apply_joint_dof_limits(dof_values)

# Convert back to rotations
joint_rot_new = char_model.dof_to_rot(clamped_dofs)

Working with Contact Bodies

# Get contact body information
num_contact = char_model.get_num_contact_bodies()
contact_ids = char_model.get_contact_body_ids()

print(f"Number of contact bodies: {num_contact}")
for i in range(num_contact):
    name = char_model.get_contact_body_name(i)
    print(f"Contact body {i}: {name}")

# Expand contact tensor to all bodies
contact_labels = torch.randn(batch_size, num_contact, device="cuda:0")
full_contacts = char_model.create_full_contact_tensor(contact_labels)
print(f"Full contact tensor shape: {full_contacts.shape}")  # [10, num_bodies]

Modifying Bone Lengths

# Get current bone length
body_name = "left_shin"
original_length = char_model.get_bone_length(body_name)
print(f"Original length: {original_length:.3f}m")

# Scale the bone
char_model.scale_bone_length(body_name, scale=1.2)
new_length = char_model.get_bone_length(body_name)
print(f"New length: {new_length:.3f}m")

# Or set absolute length
char_model.set_bone_length(body_name, new_length=0.5)

Computing Velocities

# Create two consecutive frames
num_frames = 100
joint_rot_sequence = torch.randn(num_frames, num_joints, 4, device="cuda:0")
joint_rot_sequence = joint_rot_sequence / joint_rot_sequence.norm(dim=-1, keepdim=True)

# Compute velocities for entire sequence
dt = 1.0 / 30.0  # 30 FPS
dof_vel = char_model.compute_frame_dof_vel(joint_rot_sequence, dt)
print(f"DOF velocities shape: {dof_vel.shape}")  # [100, dof_size]

Finding Lowest Point

# Get body poses from forward kinematics
body_pos, body_rot = char_model.forward_kinematics(root_pos, root_rot, joint_rot)

# Find the lowest point (useful for ground contact)
lowest_point = char_model.find_lowest_point(body_pos, body_rot)
print(f"Lowest point: {lowest_point}")
print(f"Height above ground: {lowest_point[..., 2]}")

Build docs developers (and LLMs) love