Install
Kinematics is not a FastSim base dependency. Install the published extra when the packages are available from your package index:
python -m pip install 'fastsim[kinematics]'
For source checkouts, install the two packages from FastSim-Plugins before FastSim:
python -m pip install ./FastSim-Plugins/packages/fastsim-kinematics-solver-spi
python -m pip install ./FastSim-Plugins/packages/fastsim-kinematics-solver-portable
python -m pip install ./FastSim
Without these optional packages, import fastsim and ordinary Runs continue to
work. The first descriptor or solve call fails with
KinematicsSolveErrorCode.SERVICE_UNAVAILABLE and an installation hint.
Prepare the robot component
FastSim selects the model only from the robot entity already compiled into the
immutable ExecutionPlan. A caller cannot pass a host path. The selected component
must contain one URDF resource. A resource with role: kinematics takes precedence;
otherwise the component must contain exactly one URDF resource.
An Isaac Lab component can therefore use USD for simulation and the same robot's URDF for portable kinematics:
schema: fastsim-component/1
id: robot://example/arm
version: 1.0.0
kind: robot
semantics:
joints: [shoulder, elbow]
joint_units:
shoulder: rad
elbow: rad
groups:
arm: [shoulder, elbow]
frames: [base, tool]
variants:
isaaclab:
resources:
simulation:
uri: arm.usd
format: model/vnd.usd
role: simulation
kinematics:
uri: arm.urdf
format: model/vnd.urdf+xml
role: kinematics
At first use, Core opens the exact compiled file without following symlinks, checks that it is a bounded regular file, and verifies its locked SHA-256 before giving the URDF text to the provider. The public request contains no filesystem path or native simulator handle.
Beginner API
import asyncio
import fastsim
async def main() -> None:
async with fastsim.app("run.yaml") as simulation:
result = await simulation.kinematics.solve(
robot="arm", # Scenario alias or canonical robots.* ID
base_frame="base",
tip_frame="tool",
xyz_m=(0.55, 0.10, 0.42),
quat_xyzw=(0.0, 1.0, 0.0, 0.0),
group="arm", # recommended for multi-group robots
timeout_s=1.0,
)
if result.status.value != "success":
print(result.status.value, result.failure_code, result.message)
return
joints = result.solutions[0].joints
operation = await simulation.control.submit_joints(
actor="arm",
group="arm",
path=[joints.positions],
dt=1.0 / 60.0,
timeout=10.0,
)
print((await operation.result()).status)
await operation.release()
asyncio.run(main())
When start is omitted, Core atomically captures the current joint positions from
the latest committed WorldState in the active Run generation. Joint IDs, order, and
per-axis units come from the compiled component semantics. A multi-arm or
mobile-manipulator component should pass group so the captured state matches the
requested URDF chain exactly.
The default provider implements deterministic portable URDF kinematics. Its first
release supports collision_mode="none". Asking for world or self collision returns
an explicit unsupported provider response; FastSim never silently downgrades the
request.
Advanced API
Advanced callers can construct the immutable types exported lazily by
fastsim.api.kinematics and submit an exact IKRequest:
from fastsim.api.kinematics import IKRequest, Pose
state = await simulation.state()
request = IKRequest(
request_id="layout-check-0042",
run_id=simulation.run_id,
generation=state.world.generation,
robot_entity_id="robots.arm",
base_frame_id="base",
tip_frame_id="tool",
target=Pose("base", (0.55, 0.10, 0.42), (0.0, 1.0, 0.0, 0.0)),
start=None,
timeout_s=1.0,
)
result = await simulation.kinematics.solve_request(request)
request_id is idempotent within one generation. Repeating the same request returns
the cached immutable response; reusing the ID with different input is rejected.
Select a different installed provider when creating the Application:
simulation = fastsim.app(
"run.yaml",
kinematics_solver="kinematics-solver://vendor/gpu-ik",
kinematics_config={"device": "cuda:0"},
)
Provider discovery requires one exact fastsim.kinematics_solvers entry point and
checks its distribution, version, entry-point name/value, service API, and descriptor
identity before use.
Lifecycle and errors
Solver discovery, model loading, executor creation, and provider work begin only on an explicit descriptor or solve call. An unused service imports no solver package, creates no thread, and registers no tick hook.
Every request is checked before provider work and again before result publication. A reset cancels old-generation work and an old result is discarded even if a provider returns later. Provider work runs outside the Application owner loop. Timeouts and caller cancellation set the provider's thread-safe cancellation flag; non-cooperative work remains inside a bounded orphan budget.
Core boundary failures raise KinematicsSolveError with a stable code, request,
Run, generation, and entity context. Ordinary mathematical outcomes such as an
unreachable target, joint-limit infeasibility, unsupported collision mode, or solver
non-convergence remain typed IKSolveResponse values rather than exceptions.
Common Core codes are:
| Code | Meaning |
|---|---|
service_unavailable |
Optional SPI is absent, incompatible, duplicated, or invalid |
solver_not_found |
The explicitly selected provider is not installed |
entity_not_found / entity_not_robot |
The selected compiled entity is absent or is not a robot |
model_unavailable / model_unsupported |
No unambiguous verified URDF exists, or the provider rejects it |
state_unavailable |
No fresh, ordered current joint state can be captured |
stale_generation |
Reset or Run replacement invalidated the request |
request_conflict |
One request ID was reused with different input |
capacity_exhausted |
The bounded active/orphan budget is full |
timeout / cancelled |
Core deadline or caller cancellation ended the request |
provider_failure |
The provider violated or failed outside its ordinary response contract |