Source code for isaaclab_contrib.assets.multirotor.multirotor_cfg

# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from collections.abc import Sequence
from dataclasses import MISSING

from isaaclab.assets.articulation import ArticulationCfg
from isaaclab.utils import configclass

from isaaclab_contrib.actuators import ThrusterCfg

from .multirotor import Multirotor


[docs]@configclass class MultirotorCfg(ArticulationCfg): """Configuration parameters for a multirotor articulation. This extends the base articulation configuration to support multirotor-specific settings. """ class_type: type = Multirotor
[docs] @configclass class InitialStateCfg(ArticulationCfg.InitialStateCfg): """Initial state of the multirotor articulation.""" # multirotor-specific initial state rps: dict[str, float] = {".*": 100.0} """Revolutions per second in [1/s] of the thrusters. Defaults to 100.0 for all thrusters."""
# multirotor-specific configuration init_state: InitialStateCfg = InitialStateCfg() """Initial state of the multirotor object.""" actuators: dict[str, ThrusterCfg] = MISSING """Thruster actuators for the multirotor with corresponding thruster names.""" # multirotor force application settings thruster_force_direction: tuple[float, float, float] = (0.0, 0.0, 1.0) """Default force direction in body-local frame for thrusters. Defaults to Z-axis (upward).""" allocation_matrix: Sequence[Sequence[float]] | None = None """allocation matrix for control allocation""" rotor_directions: Sequence[int] | None = None """Sequence of rotor directions, -1 for clockwise, 1 for counter-clockwise.""" def __post_init__(self): """Post initialization validation.""" # Skip validation if actuators is MISSING if self.actuators is MISSING: return # Count the total number of thrusters from all actuator configs num_thrusters = 0 for thruster_cfg in self.actuators.values(): if hasattr(thruster_cfg, "thruster_names_expr") and thruster_cfg.thruster_names_expr is not None: num_thrusters += len(thruster_cfg.thruster_names_expr) # Validate rotor_directions matches number of thrusters if self.rotor_directions is not None: num_rotor_directions = len(self.rotor_directions) if num_thrusters != num_rotor_directions: raise ValueError( f"Mismatch between number of thrusters ({num_thrusters}) and " f"rotor_directions ({num_rotor_directions}). " "They must have the same number of elements." ) # Validate rps explicit entries match number of thrusters # Only validate if rps has explicit entries (not just a wildcard pattern) if hasattr(self.init_state, "rps") and self.init_state.rps is not None: rps_keys = list(self.init_state.rps.keys()) # Check if rps uses a wildcard pattern (single key that's a regex) is_wildcard = len(rps_keys) == 1 and (rps_keys[0] == ".*" or rps_keys[0] == ".*:.*") if not is_wildcard and len(rps_keys) != num_thrusters: raise ValueError( f"Mismatch between number of thrusters ({num_thrusters}) and " f"rps entries ({len(rps_keys)}). " "They must have the same number of elements when using explicit rps keys." ) # Validate allocation_matrix second dimension matches number of thrusters if self.allocation_matrix is not None: if len(self.allocation_matrix) == 0: raise ValueError("Allocation matrix cannot be empty.") # Check that all rows have the same length num_cols = len(self.allocation_matrix[0]) for i, row in enumerate(self.allocation_matrix): if len(row) != num_cols: raise ValueError( f"Allocation matrix row {i} has length {len(row)}, " f"but expected {num_cols} (all rows must have the same length)." ) # Validate that the second dimension (columns) matches number of thrusters if num_cols != num_thrusters: raise ValueError( f"Mismatch between number of thrusters ({num_thrusters}) and " f"allocation matrix columns ({num_cols}). " "The second dimension of the allocation matrix must match the number of thrusters." )