Install

Kinematics is not a FastSim base dependency. Install the published extra when the packages are available from your package index:

bash
python -m pip install 'fastsim[kinematics]'

For source checkouts, install the two packages from FastSim-Plugins before FastSim:

bash
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:

yaml
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

python
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:

python
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:

python
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