diff --git a/docs/conf.py b/docs/conf.py index d75475634e30..a52ff90d31cc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -203,6 +203,7 @@ "toml", "pink", "pinocchio", + "qpsolvers", "nvidia.srl", "flatdict", "filelock", diff --git a/source/isaaclab/changelog.d/fix-pink-ik-daqp-install.rst b/source/isaaclab/changelog.d/fix-pink-ik-daqp-install.rst new file mode 100644 index 000000000000..e4d25c7ba957 --- /dev/null +++ b/source/isaaclab/changelog.d/fix-pink-ik-daqp-install.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed Pink IK setup checks to reinstall and report the required ``daqp`` + solver when it is missing or incompatible. diff --git a/source/isaaclab/isaaclab/cli/commands/install.py b/source/isaaclab/isaaclab/cli/commands/install.py index ba9735b92a6d..46125ae46f1e 100644 --- a/source/isaaclab/isaaclab/cli/commands/install.py +++ b/source/isaaclab/isaaclab/cli/commands/install.py @@ -159,22 +159,22 @@ def _maybe_uninstall_prebundled_torch( ) -# Pinocchio stack required by isaaclab.controllers.pink_ik. Installed via the cmeel -# ``pin`` wheel, which provides the ``pinocchio`` Python module under +# Dependency stack required by isaaclab.controllers.pink_ik. Pinocchio is installed +# via the cmeel ``pin`` wheel, which provides the ``pinocchio`` Python module under # ``cmeel.prefix/lib/python3.12/site-packages/`` and registers it on sys.path via a -# ``cmeel.pth`` hook. -_PINOCCHIO_STACK = ("pin", "pin-pink==3.1.0", "daqp==0.7.2") +# ``cmeel.pth`` hook. DAQP provides the QP solver selected by the Pink IK controller. +_PINK_IK_STACK = ("pin", "pin-pink==3.1.0", "daqp==0.8.5") -def _ensure_pinocchio_installed(python_exe: str, pip_cmd: list[str], *, probe_env: dict[str, str]) -> None: - """Ensure ``pinocchio`` is importable, force-installing the cmeel pin stack if not. +def _ensure_pink_ik_dependencies_installed(python_exe: str, pip_cmd: list[str], *, probe_env: dict[str, str]) -> None: + """Ensure the Pink IK dependency stack is importable, force-installing it if not. Recent Isaac Sim base images preinstall ``pin-pink`` into the kit's bundled ``site-packages`` without its ``pin`` (cmeel pinocchio) dependency. Pip then treats the ``pin-pink`` requirement as satisfied and never resolves the - transitive ``pin`` dep, leaving ``import pinocchio`` broken. This probes - for ``pinocchio`` at runtime and force-installs the cmeel stack when needed - so the pink IK controller and its tests work out of the box. + transitive ``pin`` dep, leaving ``import pinocchio`` broken. This checks + the runtime dependencies and force-installs the cmeel stack when needed so + the pink IK controller and its tests work out of the box. Only runs on Linux x86_64 / aarch64 — the same platforms that have pinocchio listed in :mod:`isaaclab`'s ``setup.py`` install requirements. @@ -194,7 +194,13 @@ def _ensure_pinocchio_installed(python_exe: str, pip_cmd: list[str], *, probe_en return probe_result = run_command( - [python_exe, "-c", "import pinocchio"], + [ + python_exe, + "-c", + "import inspect, pinocchio, daqp, qpsolvers; " + "assert 'daqp' in qpsolvers.available_solvers; " + "assert 'primal_start' in inspect.signature(daqp.solve).parameters", + ], env=probe_env, check=False, capture_output=True, @@ -203,19 +209,16 @@ def _ensure_pinocchio_installed(python_exe: str, pip_cmd: list[str], *, probe_en if probe_result.returncode == 0: return - print_info( - "``import pinocchio`` failed — the kit-bundled ``pin-pink`` likely shipped without its" - " ``pin`` dep. Force-installing the cmeel pinocchio stack." - ) + print_info("Pink IK dependency probe failed. Force-installing the cmeel pinocchio and DAQP stack.") install_result = run_command( - pip_cmd + ["install", "--upgrade", "--force-reinstall", *_PINOCCHIO_STACK], + pip_cmd + ["install", "--upgrade", "--force-reinstall", *_PINK_IK_STACK], check=False, ) if install_result.returncode != 0: print_warning( - "Force-installing the cmeel pinocchio stack failed (returncode " + "Force-installing the cmeel pinocchio and DAQP stack failed (returncode " f"{install_result.returncode}). The pink IK controller and its tests will not be" - " usable until ``pin pin-pink==3.1.0 daqp==0.7.2`` is installed manually." + " usable until ``pin pin-pink==3.1.0 daqp==0.8.5`` is installed manually." ) @@ -735,10 +738,10 @@ def command_install(install_type: str = "all") -> None: # Can prevent that from happening. _ensure_cuda_torch() - # Ensure ``pinocchio`` is actually importable. The kit-bundled ``pin-pink`` in recent - # Isaac Sim images ships without its cmeel ``pin`` dependency, so the transitive - # requirement from ``pip install -e source/isaaclab`` can be silently skipped. - _ensure_pinocchio_installed(python_exe, pip_cmd, probe_env=probe_env) + # Ensure Pink IK's runtime dependencies are actually importable. The kit-bundled + # ``pin-pink`` in recent Isaac Sim images can cause transitive dependencies from + # ``pip install -e source/isaaclab`` to be silently skipped. + _ensure_pink_ik_dependencies_installed(python_exe, pip_cmd, probe_env=probe_env) # Repoint prebundled packages in Isaac Sim to the environment's copies so # the active venv/conda versions are always loaded regardless of PYTHONPATH diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py b/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py index d1162b480910..1520afdb0177 100644 --- a/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py +++ b/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py @@ -20,6 +20,7 @@ import torch from pink import solve_ik from pink.tasks import Task +from qpsolvers.exceptions import SolverNotFound from isaaclab.assets import ArticulationCfg from isaaclab.controllers import utils as controller_utils @@ -34,6 +35,9 @@ from .pink_ik_cfg import PinkIKControllerCfg +_QP_SOLVER = "daqp" + + class PinkIKController: """Integration of Pink IK controller with Isaac Lab. @@ -240,30 +244,45 @@ def compute( # Update Pink's robot configuration with the current joint positions self.pink_configuration.update(joint_positions_pink) + def _return_current_joint_positions(error: Exception) -> torch.Tensor: + if self.cfg.show_ik_warnings: + print( + "Warning: IK quadratic solver could not find a solution! Did not update the target joint" + f" positions.\nError: {error}" + ) + + if self.cfg.xr_enabled: + from isaaclab.ui.xr_widgets import XRVisualization + + XRVisualization.push_event("ik_error", {"error": error}) + return torch.tensor(curr_controlled_joint_pos, device=self.device, dtype=torch.float32) + # Solve IK using Pink's solver try: velocity = solve_ik( self.pink_configuration, self._variable_input_tasks + self._fixed_input_tasks, dt, - solver="daqp", + solver=_QP_SOLVER, safety_break=self.cfg.fail_on_joint_limit_violation, ) assert not np.isnan(velocity).any(), "Solution to IK contains NaN." joint_angle_changes = velocity * dt + except SolverNotFound as e: + raise RuntimeError( + f"Pink IK requires the '{_QP_SOLVER}' QP solver. Install the Pink IK stack with " + "``./isaaclab.sh -i`` or manually install ``pin pin-pink==3.1.0 daqp==0.8.5``." + ) from e + except TypeError as e: + if "primal_start" in str(e): + raise RuntimeError( + "Pink IK requires a DAQP version compatible with qpsolvers warm-start arguments. " + "Install the Pink IK stack with ``./isaaclab.sh -i`` or manually install " + "``pin pin-pink==3.1.0 daqp==0.8.5``." + ) from e + return _return_current_joint_positions(e) except (AssertionError, Exception) as e: - # Print warning and return the current joint positions as the target - if self.cfg.show_ik_warnings: - print( - "Warning: IK quadratic solver could not find a solution! Did not update the target joint" - f" positions.\nError: {e}" - ) - - if self.cfg.xr_enabled: - from isaaclab.ui.xr_widgets import XRVisualization - - XRVisualization.push_event("ik_error", {"error": e}) - return torch.tensor(curr_controlled_joint_pos, device=self.device, dtype=torch.float32) + return _return_current_joint_positions(e) # Reorder the joint angle changes back to Isaac Lab conventions joint_vel_isaac_lab = torch.tensor( diff --git a/source/isaaclab/setup.py b/source/isaaclab/setup.py index d56f73a34225..cfced25a24aa 100644 --- a/source/isaaclab/setup.py +++ b/source/isaaclab/setup.py @@ -69,7 +69,7 @@ # required by isaaclab.isaaclab.controllers.pink_ik f"pin ; platform_system == 'Linux' and ({SUPPORTED_ARCHS_ARM})", f"pin-pink==3.1.0 ; platform_system == 'Linux' and ({SUPPORTED_ARCHS_ARM})", - f"daqp==0.7.2 ; platform_system == 'Linux' and ({SUPPORTED_ARCHS_ARM})", + f"daqp==0.8.5 ; platform_system == 'Linux' and ({SUPPORTED_ARCHS_ARM})", ] # Adds OpenUSD dependencies based on architecture for Kit less mode. INSTALL_REQUIRES += [