Articulation#
The Articulation class represents the fundamental physics entity for articulated objects (e.g., robots, grippers, cabinets, doors) in EmbodiChain.
Configuration#
Articulations are configured using the ArticulationCfg dataclass.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Path to the asset file (URDF/USD). |
|
|
|
Initial root position |
|
|
|
Initial root rotation |
|
|
|
Fixed-base/self-collision are portable; root sleep and paired solver iterations are Default-only and ignored by Newton. Explicit |
|
|
|
Preserve source link/joint physics, or apply explicitly configured overlays after source resolution. |
|
|
|
Initial joint positions. |
|
|
|
Override limits by flattened source-resolved DOF order or joint-name/regex rules before backend build. |
|
|
|
Scaling factors for the articulation links. |
|
|
|
Optional sparse joint drive, limit, friction, and armature overlay. |
|
|
empty groups |
Grouped rigid-body physics applied to all links. |
|
|
|
Optional per-link overrides keyed by group name; each group matches link names via regex. |
At runtime, call articulation.set_gravity(...) to change gravity for every
environment or for a selected set of environment indices.
Combined state writes#
Use Articulation.set_state (also inherited by Robot) to reset selected
articulations in one Scene batch operation:
robot.set_state(
env_ids=env_ids,
joint_ids=policy_joint_ids,
root_pose=pose, # (N, 7): environment-local xyz + xyzw
qpos=joint_position, # (N, J), clipped to the selected joint limits
target_qpos=joint_position,
clear_dynamics=True,
)
All supplied joint fields share the selected row and joint order. Optional
qvel, target_qvel, qf, and root_velocity fields override reset defaults.
clear_dynamics=True clears velocities, forces, external wrenches and solver
history, and holds the final joint positions where no target is supplied.
Newton/MJWarp requires all articulations in each affected solver world when
clearing solver history. Other environments retain their state.
The Scene batch validates shapes and selections before writes, then propagates final joint kinematics once. Native execution errors are reported to the caller. Single-field joint setters use this same writer; target-only writes do not recompute kinematics. State changes follow the existing render-frame publication lifecycle.
Root velocity writes#
Articulation.set_root_velocity(velocity, env_ids=None) writes root-link velocities
in world coordinates. The tensor has shape (N, 6): linear velocity in m/s,
followed by angular velocity in rad/s. Omitting env_ids selects all environments;
selected writes use the requested environment order and preserve other rows.
Robot inherits this method. Writes use the existing Scene batch APIs.
Per-link physics (link_attrs)#
By default, attrs applies the same rigid-body physics to every link. Use link_attrs to
override specific links (matched by regex, same rules as joint drive dict keys):
from embodichain.lab.sim.cfg import (
ArticulationCfg,
CollisionPropertiesCfg,
LinkPhysicsOverrideCfg,
RigidBodyMaterialCfg,
RigidBodyPhysicsCfg,
)
art_cfg = ArticulationCfg(
fpath="path/to/robot.urdf",
asset_physics_mode="overlay",
attrs=RigidBodyPhysicsCfg(
material_props=RigidBodyMaterialCfg(static_friction=0.5),
),
link_attrs={
"eef": LinkPhysicsOverrideCfg(
link_names_expr=[".*(hand|finger|ee).*"],
attrs=RigidBodyPhysicsCfg(
material_props=RigidBodyMaterialCfg(static_friction=0.95),
collision_props=CollisionPropertiesCfg(contact_offset=0.001),
),
),
},
)
At runtime, use articulation.set_link_physical_attr(...) and get_link_physical_attr(...)
for the same partial-override behavior.
Source mass properties#
For URDF-backed articulations, MassPropertiesCfg.recompute_inertia is the
only switch that permits geometry-derived mass properties to replace the
asset’s authored inertia. It defaults to False (or None, which resolves to
the same behavior), so an overlay that only configures joint drives or other
unrelated attributes retains the source mass, inertia, and center of mass in
both backends. Set it to True only when collision geometry should be used to
derive a new tensor. A positive mass can be overridden while retaining the
source tensor; density requires recompute_inertia=True when the source
already provides a valid tensor. An all-zero or otherwise invalid source
tensor is not preserved: when the link has collision geometry, both backends
derive a fallback tensor from that geometry.
Reading current mass properties#
articulation.body_data.read_physical_properties() refreshes and returns mass,
principal inertia and local center-of-mass (COM) pose tensors. Shapes are
(N, num_links), (N, num_links, 3) and (N, num_links, 7) respectively, in
the public link_names order. COM poses use (x, y, z, qx, qy, qz, qw).
On Newton, this read uses DexSim’s batch property APIs and reuses the output buffers, avoiding per-link transfers to the host. Clone returned tensors when retaining a snapshot across subsequent reads. Other backends retain their existing property read path.
set_inertia() keeps the current principal frame; set_com_pose() keeps the
current principal moments and changes their orientation and COM position.
EmbodiChain composes R diag(I) R.T before writing DexSim’s body-frame mat33.
get_newton_link_properties() exposes that matrix plus com_position, without
a separate quaternion. Principal axes can differ by ordering or quaternion sign;
compare reconstructed matrices when checking physical equivalence.
Initialization defaults remain separate. reset(env_ids=...) restores the
saved inertia/frame pair and COM position only for selected environments.
Drive Configuration#
The joint_drive_props parameter uses JointDrivePropertiesCfg for sparse
joint-property overlays. Each field defaults to None, preserving its source
or backend value. Generic articulations default to
asset_physics_mode="preserve"; explicit drive changes require "overlay".
Robot configurations default to "overlay". An unspecified drive is not an
instruction to remove an existing source-authored actuator.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Position gain; force-drive units are N/m or N·m/rad. |
|
|
|
Velocity gain; force-drive units are N·s/m or N·m·s/rad. |
|
|
|
Authored effort limit in N or N·m; solver enforcement varies. |
|
|
|
Authored speed limit in m/s or rad/s; solver enforcement varies. |
|
|
|
Passive joint friction; interpretation depends on backend/solver. |
|
|
|
Added joint-space inertia in kg for prismatic joints or kg·m² for revolute joints. |
|
|
|
|
|
|
|
Target components: |
None is accepted for every field in this table. Gain units shown apply to
force/torque drives; Default acceleration drives use a mass-independent
response. Newton rejects active acceleration drives. Unless target_mode is
explicit, drive_type="force" or "acceleration" selects
position_velocity, while drive_type="none" selects none.
Default implements target components through effective gains. Newton authors
its target mode and uses gain-based fallbacks for solvers without native mode
support: none and effort clear both gains, and velocity clears position
gain. Non-MuJoCo Newton position mode assumes a zero velocity target.
Newton support for effort/velocity limits, passive friction, and armature is solver-dependent; storing a value is not proof that the solver enforces it. Inspect the resolved properties and test the response after changing solvers.
Joint Position Limits#
When binding a finalized Scene, ArticulationData initializes position,
velocity and effort limits through one DexSim ArticulationBatch property
read. Each environment retains its own values in public DOF order. The
joint_stiffness, joint_damping, joint_friction and joint_armature
properties also use this batch interface and return independent snapshots of
the current model. Retained descriptor edits that require a Scene rebuild
become visible after that rebuild.
Use qpos_limits to override the limits defined in the asset file. This is the
articulation’s effective physical limit in simulation, so it is also the range
used when set_qpos(...) clamps requested joint positions.
from embodichain.lab.sim.cfg import ArticulationCfg
import torch
# Override specific joints by name or regex at initialization.
art_cfg = ArticulationCfg(
fpath="assets/robots/franka/franka.urdf",
qpos_limits={
"panda_joint1": [-2.5, 2.5],
"panda_joint[2-4]": [-1.5, 1.5],
},
)
# Or provide a (dof, 2) tensor/array applied to all environments.
dof = 7
art_cfg = ArticulationCfg(
fpath="assets/robots/franka/franka.urdf",
qpos_limits=torch.tensor([[-2.0, 2.0]] * dof),
)
You can also change the active limits at runtime:
# Tighten limits for joints 0 and 1 on all environments.
new_limits = torch.tensor([[-0.1, 0.1], [-0.2, 0.2]], device=device)
articulation.set_qpos_limits(new_limits, joint_ids=[0, 1])
# Query the effective limits.
effective_limits = articulation.get_qpos_limits(joint_ids=[0, 1])
If you need solver-only planning limits that are tighter than the physical
articulation limits, configure them on the solver with
SolverCfg.user_qpos_limits; that behavior lives in the solver layer, not the
articulation layer.
Setup & Initialization#
import torch
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
from embodichain.lab.sim.cfg import ArticulationCfg, ArticulationRootPropertiesCfg
from embodichain.lab.sim.objects import Articulation
# 1. Initialize Simulation
device = "cuda" if torch.cuda.is_available() else "cpu"
sim_cfg = SimulationManagerCfg(device=device)
sim = SimulationManager(sim_config=sim_cfg)
# 2. Configure Articulation
art_cfg = ArticulationCfg(
fpath="assets/robots/franka/franka.urdf",
init_pos=(0, 0, 0.5),
root_props=ArticulationRootPropertiesCfg(fixed_base=True),
)
# 3. Spawn Articulation
# Note: The method is 'add_articulation'
articulation: Articulation = sim.add_articulation(cfg=art_cfg)
# 4. Reset Simulation
# This performs a global reset of the simulation state
sim.reset_objects_state()
USD Import#
You can import USD files (.usd, .usda, .usdc) as articulations:
from embodichain.data import get_data_path
# Import USD with properties from file
usd_art_cfg = ArticulationCfg(
fpath=get_data_path("path/to/robot.usd"),
init_pos=(0, 0, 0.5),
asset_physics_mode="preserve",
)
usd_robot = sim.add_articulation(cfg=usd_art_cfg)
# Or override USD properties with config (URDF behavior)
usd_art_cfg_override = ArticulationCfg(
fpath=get_data_path("path/to/robot.usd"),
init_pos=(0, 0, 0.5),
asset_physics_mode="overlay",
joint_drive_props=JointDrivePropertiesCfg(stiffness=5000, damping=500),
)
robot = sim.add_articulation(cfg=usd_art_cfg_override)
Articulation Class#
State data is accessed via getter methods that return batched tensors (N environments). Certain static properties are available as standard class properties.
Property |
Type |
Description |
|---|---|---|
|
|
Number of simulation environments this articulation is instantiated in. |
|
|
Degrees of freedom (number of actuated joints). |
|
|
Names of all movable joints. |
|
|
Names of all rigid links. |
|
|
Total mass of the articulation per environment |
Method |
Shape / Return Type |
Description |
|---|---|---|
|
|
Root link pose |
|
|
Specific link pose |
|
|
Current joint positions (or joint targets if |
|
|
Current joint velocities (or velocity targets if |
|
|
Returns |
|
|
Backend drive type for each selected environment and joint. |
# Example: Accessing state
print(f"Degrees of freedom: {articulation.dof}")
print(f"Current Joint Positions: {articulation.get_qpos()}")
print(f"End Effector Pose: {articulation.get_link_pose('ee_link')}")
Atomic Action Geometry Adapter#
Articulation exposes deterministic, domain-neutral facts through APIs such as
get_link_vert_face(), compute_fk(), and get_parent_joint_chain(). Atomic
Action integrations compose those facts with initial-state mesh transforms,
Open3D surface sampling, and affordance-specific geometry metadata through
sample_initial_articulation_geometry() in
embodichain.lab.sim.atomic_actions. The adapter returns a typed value; call
to_object_geometry() only at the ObjectSemantics.geometry boundary.
Keeping this stochastic conversion in the Atomic Action layer means simulation objects do not own private point-cloud keys or affordance interpretation.
Visual Appearance#
Asset materials are wrapped automatically during articulation construction. Materials are organized by environment and link:
materials = articulation.get_visual_material_inst()
base_material = materials[0].get("base_link")
if base_material is not None:
base_material.set_roughness(0.5)
Method |
Return / Args |
Description |
|---|---|---|
|
|
Create and assign material instances to selected links. |
|
- |
Restore the asset’s original per-link, per-segment materials. |
|
|
Get representative materials by environment and link. Missing materials are omitted from each dictionary. |
|
|
Retain each original segment instance and create a working instance from the link’s existing material template. |
For links with multiple mesh segments, get_visual_material_inst() exposes the first valid material. Use get_existing_visual_material() for per-segment operations. Segments on the same link share one working instance, while each link keeps an independent working instance. shared=True builds link state from the first environment for reuse across the batch. reset() restores the original materials for every link in each selected environment before resetting articulation state.
Control & Dynamics#
You can control the articulation by setting target states or directly applying forces.
Joint Control#
# Set joint position targets (PD Control)
# Get current qpos to create a target tensor of correct shape
current_qpos = articulation.get_qpos()
target_qpos = torch.zeros_like(current_qpos)
# Set target position
# target=True: Sets the drive target. The physics engine applies forces to reach this position.
# target=False: Instantly resets/teleports joints to this position (ignoring physics).
articulation.set_qpos(target_qpos, target=True)
# Set target velocities
target_qvel = torch.zeros_like(current_qpos)
articulation.set_qvel(target_qvel, target=True)
# Apply forces directly
# Sets an external force tensor (N, dof) applied at the degree of freedom.
target_qf = torch.ones_like(current_qpos) * 10.0
articulation.set_qf(target_qf)
# Important: Step simulation to apply control
sim.update()
Pose Control#
# Teleport the articulation root to a new pose
# shape: (N, 7) formatted as [x, y, z, qx, qy, qz, qw]
new_root_pose = torch.tensor([[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]], device=device).repeat(sim.num_envs, 1)
articulation.set_local_pose(new_root_pose)
Drive Configuration#
Dynamically adjust drive properties.
# Set stiffness for all joints
articulation.set_joint_drive(
stiffness=torch.tensor([100.0], device=device),
damping=torch.tensor([10.0], device=device)
)
Kinematics#
Supports differentiable Forward Kinematics (FK) and Jacobian computation.
# Compute Forward Kinematics
# Note: Ensure `build_pk_chain=True` in cfg
if getattr(art_cfg, 'build_pk_chain', False):
# Returns (batch_size, 4, 4) homogeneous transformation matrix
ee_pose = articulation.compute_fk(
qpos=articulation.get_qpos(),
end_link_name="ee_link" # Replace with actual link name
)
# Or return a dictionary of multiple link transforms (pytorch_kinematics Transform3d objects)
link_poses = articulation.compute_fk(
qpos=articulation.get_qpos(),
link_names=["link1", "link2"],
to_dict=True
)
State Reset#
Resetting an articulation returns it to its initial state properties.
# Clear the physical dynamics and velocities
articulation.clear_dynamics()
# Reset the articulation entirely (resets pose, velocities, and root states to config defaults)
articulation.reset()