diff --git a/source/isaaclab/config/extension.toml b/source/isaaclab/config/extension.toml index 56e7a261123..3b4cd24808c 100644 --- a/source/isaaclab/config/extension.toml +++ b/source/isaaclab/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "4.3.0" +version = "4.4.0" # Description title = "Isaac Lab framework for Robot Learning" @@ -16,6 +16,7 @@ requirements = [ "numpy", "prettytable==3.3.0", "toml", + "lazy_loader>=0.4", "hidapi", "gymnasium==0.29.0", "trimesh", @@ -26,6 +27,7 @@ modules = [ "numpy", "prettytable", "toml", + "lazy_loader", "hid", "gymnasium", "trimesh", diff --git a/source/isaaclab/docs/CHANGELOG.rst b/source/isaaclab/docs/CHANGELOG.rst index bb67104d6eb..75e62a120ca 100644 --- a/source/isaaclab/docs/CHANGELOG.rst +++ b/source/isaaclab/docs/CHANGELOG.rst @@ -1,6 +1,20 @@ Changelog --------- +4.4.0 (2026-02-26) +~~~~~~~~~~~~~~~~~~ + +Changed +^^^^^^^ + +* Refined lazy-loading behavior for config-only import paths to keep environment + config construction backend-free. This includes stricter cascading namespace + resolution in :func:`~isaaclab.utils.module.attach_cascading`, avoiding eager + callable resolution during deepcopy of :class:`~isaaclab.utils.string.ResolvableString`, + and updating locomanipulation MDP exports/import boundaries so + ``test_env_cfg_no_forbidden_imports.py`` passes without importing runtime modules. + + 4.3.0 (2026-02-26) ~~~~~~~~~~~~~~~~~~ diff --git a/source/isaaclab/isaaclab/actuators/__init__.py b/source/isaaclab/isaaclab/actuators/__init__.py index db7d36b00a5..5dc97aed9b3 100644 --- a/source/isaaclab/isaaclab/actuators/__init__.py +++ b/source/isaaclab/isaaclab/actuators/__init__.py @@ -22,15 +22,19 @@ and called by the :class:`isaaclab.assets.Articulation` class. """ -from .actuator_base import ActuatorBase -from .actuator_base_cfg import ActuatorBaseCfg -from .actuator_net import ActuatorNetLSTM, ActuatorNetMLP -from .actuator_net_cfg import ActuatorNetLSTMCfg, ActuatorNetMLPCfg -from .actuator_pd import DCMotor, DelayedPDActuator, IdealPDActuator, ImplicitActuator, RemotizedPDActuator -from .actuator_pd_cfg import ( - DCMotorCfg, - DelayedPDActuatorCfg, - IdealPDActuatorCfg, - ImplicitActuatorCfg, - RemotizedPDActuatorCfg, +from isaaclab.utils.module import lazy_export + +lazy_export( + ("actuator_base", "ActuatorBase"), + ("actuator_base_cfg", "ActuatorBaseCfg"), + ("actuator_net", ["ActuatorNetLSTM", "ActuatorNetMLP"]), + ("actuator_net_cfg", ["ActuatorNetLSTMCfg", "ActuatorNetMLPCfg"]), + ("actuator_pd", ["DCMotor", "DelayedPDActuator", "IdealPDActuator", "ImplicitActuator", "RemotizedPDActuator"]), + ("actuator_pd_cfg", [ + "DCMotorCfg", + "DelayedPDActuatorCfg", + "IdealPDActuatorCfg", + "ImplicitActuatorCfg", + "RemotizedPDActuatorCfg", + ]), ) diff --git a/source/isaaclab/isaaclab/assets/__init__.py b/source/isaaclab/isaaclab/assets/__init__.py index 3eacce88028..f826df9be7d 100644 --- a/source/isaaclab/isaaclab/assets/__init__.py +++ b/source/isaaclab/isaaclab/assets/__init__.py @@ -38,28 +38,18 @@ the corresponding actuator torques. """ -from .articulation import BaseArticulation, BaseArticulationData, Articulation, ArticulationCfg, ArticulationData -from .asset_base import AssetBase -from .asset_base_cfg import AssetBaseCfg -from .rigid_object import BaseRigidObject, BaseRigidObjectData, RigidObject, RigidObjectCfg, RigidObjectData -from .rigid_object_collection import BaseRigidObjectCollection, BaseRigidObjectCollectionData, RigidObjectCollection, RigidObjectCollectionCfg, RigidObjectCollectionData - -__all__ = [ - "AssetBase", - "AssetBaseCfg", - "BaseArticulation", - "BaseArticulationData", - "Articulation", - "ArticulationCfg", - "ArticulationData", - "BaseRigidObject", - "BaseRigidObjectData", - "RigidObject", - "RigidObjectCfg", - "RigidObjectData", - "BaseRigidObjectCollection", - "BaseRigidObjectCollectionData", - "RigidObjectCollection", - "RigidObjectCollectionCfg", - "RigidObjectCollectionData", -] +from isaaclab.utils.module import lazy_export + +lazy_export( + ("asset_base", "AssetBase"), + ("asset_base_cfg", "AssetBaseCfg"), + ("articulation", ["BaseArticulation", "BaseArticulationData", "Articulation", "ArticulationCfg", "ArticulationData"]), + ("rigid_object", ["BaseRigidObject", "BaseRigidObjectData", "RigidObject", "RigidObjectCfg", "RigidObjectData"]), + ("rigid_object_collection", [ + "BaseRigidObjectCollection", + "BaseRigidObjectCollectionData", + "RigidObjectCollection", + "RigidObjectCollectionCfg", + "RigidObjectCollectionData", + ]), +) diff --git a/source/isaaclab/isaaclab/assets/articulation/__init__.py b/source/isaaclab/isaaclab/assets/articulation/__init__.py index 792b7a5454a..50decbd3c08 100644 --- a/source/isaaclab/isaaclab/assets/articulation/__init__.py +++ b/source/isaaclab/isaaclab/assets/articulation/__init__.py @@ -5,16 +5,12 @@ """Sub-module for rigid articulated assets.""" -from .base_articulation import BaseArticulation -from .base_articulation_data import BaseArticulationData -from .articulation import Articulation -from .articulation_cfg import ArticulationCfg -from .articulation_data import ArticulationData +from isaaclab.utils.module import lazy_export -__all__ = [ - "BaseArticulation", - "BaseArticulationData", - "Articulation", - "ArticulationCfg", - "ArticulationData", -] +lazy_export( + ("base_articulation", "BaseArticulation"), + ("base_articulation_data", "BaseArticulationData"), + ("articulation", "Articulation"), + ("articulation_cfg", "ArticulationCfg"), + ("articulation_data", "ArticulationData"), +) diff --git a/source/isaaclab/isaaclab/assets/rigid_object/__init__.py b/source/isaaclab/isaaclab/assets/rigid_object/__init__.py index 9fdde709c8b..763154173c9 100644 --- a/source/isaaclab/isaaclab/assets/rigid_object/__init__.py +++ b/source/isaaclab/isaaclab/assets/rigid_object/__init__.py @@ -5,16 +5,12 @@ """Sub-module for rigid object assets.""" -from .base_rigid_object import BaseRigidObject -from .base_rigid_object_data import BaseRigidObjectData -from .rigid_object import RigidObject -from .rigid_object_cfg import RigidObjectCfg -from .rigid_object_data import RigidObjectData +from isaaclab.utils.module import lazy_export -__all__ = [ - "BaseRigidObject", - "BaseRigidObjectData", - "RigidObject", - "RigidObjectCfg", - "RigidObjectData", -] +lazy_export( + ("base_rigid_object", "BaseRigidObject"), + ("base_rigid_object_data", "BaseRigidObjectData"), + ("rigid_object", "RigidObject"), + ("rigid_object_cfg", "RigidObjectCfg"), + ("rigid_object_data", "RigidObjectData"), +) diff --git a/source/isaaclab/isaaclab/assets/rigid_object_collection/__init__.py b/source/isaaclab/isaaclab/assets/rigid_object_collection/__init__.py index f0fd26363a0..f1ff9384467 100644 --- a/source/isaaclab/isaaclab/assets/rigid_object_collection/__init__.py +++ b/source/isaaclab/isaaclab/assets/rigid_object_collection/__init__.py @@ -5,16 +5,12 @@ """Sub-module for rigid object collection.""" -from .base_rigid_object_collection import BaseRigidObjectCollection -from .base_rigid_object_collection_data import BaseRigidObjectCollectionData -from .rigid_object_collection import RigidObjectCollection -from .rigid_object_collection_cfg import RigidObjectCollectionCfg -from .rigid_object_collection_data import RigidObjectCollectionData +from isaaclab.utils.module import lazy_export -__all__ = [ - "BaseRigidObjectCollection", - "BaseRigidObjectCollectionData", - "RigidObjectCollection", - "RigidObjectCollectionCfg", - "RigidObjectCollectionData", -] +lazy_export( + ("base_rigid_object_collection", "BaseRigidObjectCollection"), + ("base_rigid_object_collection_data", "BaseRigidObjectCollectionData"), + ("rigid_object_collection", "RigidObjectCollection"), + ("rigid_object_collection_cfg", "RigidObjectCollectionCfg"), + ("rigid_object_collection_data", "RigidObjectCollectionData"), +) diff --git a/source/isaaclab/isaaclab/cloner/__init__.py b/source/isaaclab/isaaclab/cloner/__init__.py index 8edaea840c8..1b2c85fa6e3 100644 --- a/source/isaaclab/isaaclab/cloner/__init__.py +++ b/source/isaaclab/isaaclab/cloner/__init__.py @@ -3,6 +3,18 @@ # # SPDX-License-Identifier: BSD-3-Clause -from .cloner_cfg import TemplateCloneCfg -from .cloner_strategies import * -from .cloner_utils import * +"""Sub-package for environment cloning utilities.""" + +from isaaclab.utils.module import lazy_export + +lazy_export( + ("cloner_cfg", "TemplateCloneCfg"), + ("cloner_strategies", ["random", "sequential"]), + ("cloner_utils", [ + "clone_from_template", + "make_clone_plan", + "usd_replicate", + "filter_collisions", + "grid_transforms", + ]), +) diff --git a/source/isaaclab/isaaclab/controllers/__init__.py b/source/isaaclab/isaaclab/controllers/__init__.py index 3a055c508e8..bd42461f620 100644 --- a/source/isaaclab/isaaclab/controllers/__init__.py +++ b/source/isaaclab/isaaclab/controllers/__init__.py @@ -11,7 +11,23 @@ commands to be sent to the robot. """ -from .differential_ik import DifferentialIKController -from .differential_ik_cfg import DifferentialIKControllerCfg -from .operational_space import OperationalSpaceController -from .operational_space_cfg import OperationalSpaceControllerCfg +from isaaclab.utils.module import cascading_export + +cascading_export( + submodules=[ + "differential_ik", + "differential_ik_cfg", + "joint_impedance", + "joint_impedance_cfg", + "operational_space", + "operational_space_cfg", + "pink_ik", + ], +) + +__all__ = [ + "DifferentialIKController", + "DifferentialIKControllerCfg", + "OperationalSpaceController", + "OperationalSpaceControllerCfg", +] diff --git a/source/isaaclab/isaaclab/controllers/joint_impedance.py b/source/isaaclab/isaaclab/controllers/joint_impedance.py index 5baca32385d..0dee9da4898 100644 --- a/source/isaaclab/isaaclab/controllers/joint_impedance.py +++ b/source/isaaclab/isaaclab/controllers/joint_impedance.py @@ -5,58 +5,12 @@ from __future__ import annotations -from collections.abc import Sequence -from dataclasses import MISSING +from typing import TYPE_CHECKING import torch -from isaaclab.utils import configclass - - -@configclass -class JointImpedanceControllerCfg: - """Configuration for joint impedance regulation controller.""" - - command_type: str = "p_abs" - """Type of command: p_abs (absolute) or p_rel (relative).""" - - dof_pos_offset: Sequence[float] | None = None - """Offset to DOF position command given to controller. (default: None). - - If None then position offsets are set to zero. - """ - - impedance_mode: str = MISSING - """Type of gains: "fixed", "variable", "variable_kp".""" - - inertial_compensation: bool = False - """Whether to perform inertial compensation (inverse dynamics).""" - - gravity_compensation: bool = False - """Whether to perform gravity compensation.""" - - stiffness: float | Sequence[float] = MISSING - """The positional gain for determining desired torques based on joint position error.""" - - damping_ratio: float | Sequence[float] | None = None - """The damping ratio is used in-conjunction with positional gain to compute desired torques - based on joint velocity error. - - The following math operation is performed for computing velocity gains: - :math:`d_gains = 2 * sqrt(p_gains) * damping_ratio`. - """ - - stiffness_limits: tuple[float, float] = (0, 300) - """Minimum and maximum values for positional gains. - - Note: Used only when :obj:`impedance_mode` is "variable" or "variable_kp". - """ - - damping_ratio_limits: tuple[float, float] = (0, 100) - """Minimum and maximum values for damping ratios used to compute velocity gains. - - Note: Used only when :obj:`impedance_mode` is "variable". - """ +if TYPE_CHECKING: + from .joint_impedance_cfg import JointImpedanceControllerCfg class JointImpedanceController: diff --git a/source/isaaclab/isaaclab/controllers/joint_impedance_cfg.py b/source/isaaclab/isaaclab/controllers/joint_impedance_cfg.py new file mode 100644 index 00000000000..8245b0e50ab --- /dev/null +++ b/source/isaaclab/isaaclab/controllers/joint_impedance_cfg.py @@ -0,0 +1,60 @@ +# 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 __future__ import annotations + +from collections.abc import Sequence +from dataclasses import MISSING + +from isaaclab.utils import configclass + + +@configclass +class JointImpedanceControllerCfg: + """Configuration for joint impedance regulation controller.""" + + class_type: type | str = "isaaclab.controllers.joint_impedance:JointImpedanceController" + """The associated controller class.""" + + command_type: str = "p_abs" + """Type of command: p_abs (absolute) or p_rel (relative).""" + + dof_pos_offset: Sequence[float] | None = None + """Offset to DOF position command given to controller. (default: None). + + If None then position offsets are set to zero. + """ + + impedance_mode: str = MISSING + """Type of gains: "fixed", "variable", "variable_kp".""" + + inertial_compensation: bool = False + """Whether to perform inertial compensation (inverse dynamics).""" + + gravity_compensation: bool = False + """Whether to perform gravity compensation.""" + + stiffness: float | Sequence[float] = MISSING + """The positional gain for determining desired torques based on joint position error.""" + + damping_ratio: float | Sequence[float] | None = None + """The damping ratio is used in-conjunction with positional gain to compute desired torques + based on joint velocity error. + + The following math operation is performed for computing velocity gains: + :math:`d_gains = 2 * sqrt(p_gains) * damping_ratio`. + """ + + stiffness_limits: tuple[float, float] = (0, 300) + """Minimum and maximum values for positional gains. + + Note: Used only when :obj:`impedance_mode` is "variable" or "variable_kp". + """ + + damping_ratio_limits: tuple[float, float] = (0, 100) + """Minimum and maximum values for damping ratios used to compute velocity gains. + + Note: Used only when :obj:`impedance_mode` is "variable". + """ diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/__init__.py b/source/isaaclab/isaaclab/controllers/pink_ik/__init__.py index 17ed7a67b07..0e5b9508e2b 100644 --- a/source/isaaclab/isaaclab/controllers/pink_ik/__init__.py +++ b/source/isaaclab/isaaclab/controllers/pink_ik/__init__.py @@ -8,6 +8,20 @@ This package provides integration between Pink inverse kinematics solver and IsaacLab. """ -from .null_space_posture_task import NullSpacePostureTask -from .pink_ik import PinkIKController -from .pink_ik_cfg import PinkIKControllerCfg +from isaaclab.utils.module import cascading_export + +cascading_export( + submodules=["pink_task_cfg", "pink_ik_cfg", "pink_ik", "pink_tasks", "null_space_posture_task"], +) + + +__all__ = [ + "NullSpacePostureTask", + "PinkIKController", + "PinkIKControllerCfg", + "PinkIKTaskCfg", + "FrameTaskCfg", + "DampingTaskCfg", + "LocalFrameTaskCfg", + "NullSpacePostureTaskCfg", +] diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/local_frame_task.py b/source/isaaclab/isaaclab/controllers/pink_ik/local_frame_task.py index ff8c6b9b03d..69413e5dbfd 100644 --- a/source/isaaclab/isaaclab/controllers/pink_ik/local_frame_task.py +++ b/source/isaaclab/isaaclab/controllers/pink_ik/local_frame_task.py @@ -3,114 +3,18 @@ # # SPDX-License-Identifier: BSD-3-Clause -from collections.abc import Sequence +"""Deprecated compatibility shim for Pink task imports. -import numpy as np -import pinocchio as pin -from pink.tasks.frame_task import FrameTask +Prefer importing from ``isaaclab.controllers.pink_ik.pink_tasks``. +""" -from .pink_kinematics_configuration import PinkKinematicsConfiguration +from __future__ import annotations +import warnings -class LocalFrameTask(FrameTask): - """ - A task that computes error in a local (custom) frame. - Inherits from FrameTask but overrides compute_error. - """ - - def __init__( - self, - frame: str, - base_link_frame_name: str, - position_cost: float | Sequence[float], - orientation_cost: float | Sequence[float], - lm_damping: float = 0.0, - gain: float = 1.0, - ): - """ - Initialize the LocalFrameTask with configuration. - - This task computes pose errors in a local (custom) frame rather than the world frame, - allowing for more flexible control strategies where the reference frame can be - specified independently. - - Args: - frame: Name of the frame to control (end-effector or target frame). - base_link_frame_name: Name of the base link frame used as reference frame - for computing transforms and errors. - position_cost: Cost weight(s) for position error. Can be a single float - for uniform weighting or a sequence of 3 floats for per-axis weighting. - orientation_cost: Cost weight(s) for orientation error. Can be a single float - for uniform weighting or a sequence of 3 floats for per-axis weighting. - lm_damping: Levenberg-Marquardt damping factor for numerical stability. - Defaults to 0.0 (no damping). - gain: Task gain factor that scales the overall task contribution. - Defaults to 1.0. - """ - super().__init__(frame, position_cost, orientation_cost, lm_damping, gain) - self.base_link_frame_name = base_link_frame_name - self.transform_target_to_base = None - - def set_target(self, transform_target_to_base: pin.SE3) -> None: - """Set task target pose in the world frame. - - Args: - transform_target_to_world: Transform from the task target frame to - the world frame. - """ - self.transform_target_to_base = transform_target_to_base.copy() - - def set_target_from_configuration(self, configuration: PinkKinematicsConfiguration) -> None: - """Set task target pose from a robot configuration. - - Args: - configuration: Robot configuration. - """ - if not isinstance(configuration, PinkKinematicsConfiguration): - raise ValueError("configuration must be a PinkKinematicsConfiguration") - self.set_target(configuration.get_transform(self.frame, self.base_link_frame_name)) - - def compute_error(self, configuration: PinkKinematicsConfiguration) -> np.ndarray: - """ - Compute the error between current and target pose in a local frame. - """ - if not isinstance(configuration, PinkKinematicsConfiguration): - raise ValueError("configuration must be a PinkKinematicsConfiguration") - if self.transform_target_to_base is None: - raise ValueError(f"no target set for frame '{self.frame}'") - - transform_frame_to_base = configuration.get_transform(self.frame, self.base_link_frame_name) - transform_target_to_frame = transform_frame_to_base.actInv(self.transform_target_to_base) - - error_in_frame: np.ndarray = pin.log(transform_target_to_frame).vector - return error_in_frame - - def compute_jacobian(self, configuration: PinkKinematicsConfiguration) -> np.ndarray: - r"""Compute the frame task Jacobian. - - The task Jacobian :math:`J(q) \in \mathbb{R}^{6 \times n_v}` is the - derivative of the task error :math:`e(q) \in \mathbb{R}^6` with respect - to the configuration :math:`q`. The formula for the frame task is: - - .. math:: - - J(q) = -\text{Jlog}_6(T_{tb}) {}_b J_{0b}(q) - - The derivation of the formula for this Jacobian is detailed in - [Caron2023]_. See also - :func:`pink.tasks.task.Task.compute_jacobian` for more context on task - Jacobians. - - Args: - configuration: Robot configuration :math:`q`. - - Returns: - Jacobian matrix :math:`J`, expressed locally in the frame. - """ - if self.transform_target_to_base is None: - raise Exception(f"no target set for frame '{self.frame}'") - transform_frame_to_base = configuration.get_transform(self.frame, self.base_link_frame_name) - transform_frame_to_target = self.transform_target_to_base.actInv(transform_frame_to_base) - jacobian_in_frame = configuration.get_frame_jacobian(self.frame) - J = -pin.Jlog6(transform_frame_to_target) @ jacobian_in_frame - return J +warnings.warn( + "`isaaclab.controllers.pink_ik.local_frame_task` is deprecated; " + "import from `isaaclab.controllers.pink_ik.pink_tasks` instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/null_space_posture_task.py b/source/isaaclab/isaaclab/controllers/pink_ik/null_space_posture_task.py index fbd3b84199a..62d2263643b 100644 --- a/source/isaaclab/isaaclab/controllers/pink_ik/null_space_posture_task.py +++ b/source/isaaclab/isaaclab/controllers/pink_ik/null_space_posture_task.py @@ -5,6 +5,8 @@ from __future__ import annotations +from typing import Any + import numpy as np import pinocchio as pin import scipy.linalg.blas as blas @@ -87,7 +89,7 @@ class NullSpacePostureTask(Task): def __init__( self, - cost: float, + cost: float | Any, lm_damping: float = 0.0, gain: float = 1.0, controlled_frames: list[str] | None = None, @@ -111,6 +113,15 @@ def __init__( controlled_joints: Joint names to control in the posture task. If None or empty, all actuated joints are controlled. """ + # Support class_type(cfg) construction by accepting a config object as first argument. + if not isinstance(cost, (int, float)): + cfg = cost + cost = float(getattr(cfg, "cost")) + lm_damping = float(getattr(cfg, "lm_damping", lm_damping)) + gain = float(getattr(cfg, "gain", gain)) + controlled_frames = getattr(cfg, "controlled_frames", controlled_frames) + controlled_joints = getattr(cfg, "controlled_joints", controlled_joints) + super().__init__(cost=cost, gain=gain, lm_damping=lm_damping) self.target_q: np.ndarray | None = None self.controlled_frames: list[str] = controlled_frames or [] diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py b/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py index 788a5da6705..c4ef2deaeec 100644 --- a/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py +++ b/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik.py @@ -14,17 +14,21 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import numpy as np import torch from pink import solve_ik +from pink.tasks import Task from isaaclab.assets import ArticulationCfg +from isaaclab.controllers import utils as controller_utils +from isaaclab.utils.assets import retrieve_file_path from isaaclab.utils.string import resolve_matching_names_values from .null_space_posture_task import NullSpacePostureTask from .pink_kinematics_configuration import PinkKinematicsConfiguration +from .pink_task_cfg import PinkIKTaskCfg if TYPE_CHECKING: from .pink_ik_cfg import PinkIKControllerCfg @@ -74,10 +78,25 @@ def __init__( # Validate consistency between controlled_joint_indices and configuration self._validate_consistency(cfg, controlled_joint_indices) + # Resolve URDF/mesh paths at runtime. If only usd_path is provided, convert USD→URDF first. + if cfg.urdf_path is None and cfg.usd_path is not None: + import tempfile + + urdf_output_dir = cfg.urdf_output_dir or tempfile.gettempdir() + urdf_path, mesh_path = controller_utils.convert_usd_to_urdf( + cfg.usd_path, urdf_output_dir, force_conversion=True + ) + else: + urdf_path = retrieve_file_path(cfg.urdf_path) if cfg.urdf_path else cfg.urdf_path + mesh_path = retrieve_file_path(cfg.mesh_path) if cfg.mesh_path else cfg.mesh_path + + if urdf_path is None: + raise ValueError("Either urdf_path or usd_path must be provided in the controller configuration") + # Initialize the Kinematics model used by pink IK to control robot self.pink_configuration = PinkKinematicsConfiguration( - urdf_path=cfg.urdf_path, - mesh_path=cfg.mesh_path, + urdf_path=urdf_path, + mesh_path=mesh_path, controlled_joint_names=cfg.joint_names, ) @@ -93,16 +112,19 @@ def __init__( ) self.init_joint_positions = np.zeros(len(pink_joint_names)) self.init_joint_positions[indices] = np.array(values) + self._variable_input_tasks = [task_cfg.class_type(task_cfg) for task_cfg in cfg.variable_input_tasks] + self._fixed_input_tasks = [task_cfg.class_type(task_cfg) for task_cfg in cfg.fixed_input_tasks] + self.cfg.variable_input_tasks = cast(list[Task | PinkIKTaskCfg], self._variable_input_tasks) + self.cfg.fixed_input_tasks = cast(list[Task | PinkIKTaskCfg], self._fixed_input_tasks) - # Set the default targets for each task from the configuration - for task in cfg.variable_input_tasks: + for task in self._variable_input_tasks: # If task is a NullSpacePostureTask, set the target to the initial joint positions if isinstance(task, NullSpacePostureTask): task.set_target(self.init_joint_positions) continue - task.set_target_from_configuration(self.pink_configuration) - for task in cfg.fixed_input_tasks: - task.set_target_from_configuration(self.pink_configuration) + getattr(task, "set_target_from_configuration")(self.pink_configuration) + for task in self._fixed_input_tasks: + getattr(task, "set_target_from_configuration")(self.pink_configuration) # Create joint ordering mappings self._setup_joint_ordering_mappings() @@ -127,6 +149,8 @@ def _validate_consistency(self, cfg: PinkIKControllerCfg, controlled_joint_indic ) # Check: Joint name consistency - verify that the indices point to the expected joint names + if cfg.all_joint_names is None: + raise ValueError("cfg.all_joint_names cannot be None") actual_joint_names = [cfg.all_joint_names[idx] for idx in controlled_joint_indices] if actual_joint_names != cfg.joint_names: mismatches = [] @@ -184,7 +208,7 @@ def update_null_space_joint_targets(self, curr_joint_pos: np.ndarray): Args: curr_joint_pos: The current joint positions of shape (num_joints,). """ - for task in self.cfg.variable_input_tasks: + for task in self._variable_input_tasks: if isinstance(task, NullSpacePostureTask): task.set_target(curr_joint_pos) @@ -220,7 +244,7 @@ def compute( try: velocity = solve_ik( self.pink_configuration, - self.cfg.variable_input_tasks + self.cfg.fixed_input_tasks, + self._variable_input_tasks + self._fixed_input_tasks, dt, solver="daqp", safety_break=self.cfg.fail_on_joint_limit_violation, diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik_cfg.py b/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik_cfg.py index 3eb9fa6c575..8f581242c06 100644 --- a/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik_cfg.py +++ b/source/isaaclab/isaaclab/controllers/pink_ik/pink_ik_cfg.py @@ -7,12 +7,20 @@ from __future__ import annotations -from dataclasses import MISSING - -from pink.tasks import FrameTask +from dataclasses import field +from typing import TYPE_CHECKING from isaaclab.utils import configclass +from .pink_task_cfg import PinkIKTaskCfg + +if TYPE_CHECKING: + from pink.tasks import Task + + +if TYPE_CHECKING: + from pink.tasks import Task + @configclass class PinkIKControllerCfg: @@ -21,9 +29,19 @@ class PinkIKControllerCfg: The Pink IK controller can be found at: https://github.com/stephane-caron/pink """ + usd_path: str | None = None + """Path to the robot's USD file. When set and ``urdf_path`` is None, the controller will automatically + convert the USD to URDF at runtime using ``convert_usd_to_urdf``. Requires Isaac Sim at runtime. + """ + + urdf_output_dir: str | None = None + """Output directory for the USD-to-URDF conversion. Only used when ``usd_path`` is set and + ``urdf_path`` is None. Defaults to ``tempfile.gettempdir()`` if not provided. + """ + urdf_path: str | None = None """Path to the robot's URDF file. This file is used by Pinocchio's ``robot_wrapper.BuildFromURDF`` - to load the robot model. + to load the robot model. If not provided, the URDF is generated from ``usd_path`` at runtime. """ mesh_path: str | None = None @@ -38,7 +56,7 @@ class PinkIKControllerCfg: The last ``num_hand_joints`` values of the action are the hand joint angles. """ - variable_input_tasks: list[FrameTask] = MISSING + variable_input_tasks: list[Task | PinkIKTaskCfg] = field(default_factory=list) """A list of tasks for the Pink IK controller. These tasks are controllable by the environment action. @@ -47,7 +65,7 @@ class PinkIKControllerCfg: For more details, visit: https://github.com/stephane-caron/pink """ - fixed_input_tasks: list[FrameTask] = MISSING + fixed_input_tasks: list[Task | PinkIKTaskCfg] = field(default_factory=list) """ A list of tasks for the Pink IK controller. These tasks are fixed and not controllable by the env action. diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/pink_task_cfg.py b/source/isaaclab/isaaclab/controllers/pink_ik/pink_task_cfg.py new file mode 100644 index 00000000000..0955fba6782 --- /dev/null +++ b/source/isaaclab/isaaclab/controllers/pink_ik/pink_task_cfg.py @@ -0,0 +1,66 @@ +# 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 + +"""Task configuration objects for Pink IK.""" + +from __future__ import annotations + +from dataclasses import MISSING, field +from typing import Any + +from isaaclab.utils import configclass + + +@configclass +class PinkIKTaskCfg: + """Task specification for deferred runtime construction.""" + + class_type: str | type | None = None + """Task builder as ``"module.path:callable"`` or callable object.""" + + +@configclass +class FrameTaskCfg(PinkIKTaskCfg): + """Configuration wrapper for ``pink.tasks.FrameTask``.""" + + frame: Any = MISSING + position_cost: Any = MISSING + orientation_cost: Any = MISSING + lm_damping: float = 0.0 + gain: float = 1.0 + class_type: str | type | None = "isaaclab.controllers.pink_ik.pink_tasks:FrameTask" + + +@configclass +class DampingTaskCfg(PinkIKTaskCfg): + """Configuration wrapper for ``pink.tasks.DampingTask``.""" + + cost: Any = MISSING + class_type: str | type | None = "isaaclab.controllers.pink_ik.pink_tasks:DampingTask" + + +@configclass +class LocalFrameTaskCfg(PinkIKTaskCfg): + """Configuration wrapper for ``LocalFrameTask``.""" + + frame: Any = MISSING + base_link_frame_name: Any = MISSING + position_cost: Any = MISSING + orientation_cost: Any = MISSING + lm_damping: float = 0.0 + gain: float = 1.0 + class_type: str | type | None = "isaaclab.controllers.pink_ik.pink_tasks:LocalFrameTask" + + +@configclass +class NullSpacePostureTaskCfg(PinkIKTaskCfg): + """Configuration wrapper for ``NullSpacePostureTask``.""" + + cost: Any = MISSING + lm_damping: float = 0.0 + gain: float = 1.0 + controlled_frames: list[str] = field(default_factory=list) + controlled_joints: list[str] = field(default_factory=list) + class_type: str | type | None = "isaaclab.controllers.pink_ik.null_space_posture_task:NullSpacePostureTask" diff --git a/source/isaaclab/isaaclab/controllers/pink_ik/pink_tasks.py b/source/isaaclab/isaaclab/controllers/pink_ik/pink_tasks.py new file mode 100644 index 00000000000..bd653d606d6 --- /dev/null +++ b/source/isaaclab/isaaclab/controllers/pink_ik/pink_tasks.py @@ -0,0 +1,122 @@ +# 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 + +import numpy as np +import pinocchio as pin +from pink.tasks import DampingTask as PinkDampingTask +from pink.tasks.frame_task import FrameTask as PinkFrameTask + +from .pink_kinematics_configuration import PinkKinematicsConfiguration + + +class FrameTask(PinkFrameTask): + """Frame task that also supports ``class_type(cfg)`` construction.""" + + def __init__( + self, + cfg_or_frame, + position_cost: float | Sequence[float] | None = None, + orientation_cost: float | Sequence[float] | None = None, + lm_damping: float = 0.0, + gain: float = 1.0, + ): + if isinstance(cfg_or_frame, str): + frame = cfg_or_frame + else: + cfg = cfg_or_frame + frame = cfg.frame + position_cost = cfg.position_cost + orientation_cost = cfg.orientation_cost + lm_damping = cfg.lm_damping + gain = cfg.gain + + if position_cost is None or orientation_cost is None: + raise ValueError("position_cost and orientation_cost must be provided") + + super().__init__( + frame, + position_cost=position_cost, + orientation_cost=orientation_cost, + lm_damping=lm_damping, + gain=gain, + ) + + +class DampingTask(PinkDampingTask): + """Damping task accepting either cfg or direct cost argument.""" + + def __init__(self, cfg_or_cost, cost: float | None = None): + if isinstance(cfg_or_cost, (int, float)): + _cost = float(cfg_or_cost if cost is None else cost) + else: + _cost = cfg_or_cost.cost + super().__init__(cost=_cost) + + +class LocalFrameTask(FrameTask): + """ + A task that computes error in a local (custom) frame. + Inherits from FrameTask but overrides compute_error. + """ + + def __init__( + self, + frame, + base_link_frame_name: str | None = None, + position_cost: float | Sequence[float] | None = None, + orientation_cost: float | Sequence[float] | None = None, + lm_damping: float = 0.0, + gain: float = 1.0, + ): + if isinstance(frame, str): + resolved_frame = frame + if base_link_frame_name is None: + raise ValueError("base_link_frame_name must be provided") + else: + cfg = frame + resolved_frame = cfg.frame + base_link_frame_name = cfg.base_link_frame_name + position_cost = cfg.position_cost + orientation_cost = cfg.orientation_cost + lm_damping = cfg.lm_damping + gain = cfg.gain + + if position_cost is None or orientation_cost is None: + raise ValueError("position_cost and orientation_cost must be provided") + + super().__init__(resolved_frame, position_cost, orientation_cost, lm_damping, gain) + self.base_link_frame_name = base_link_frame_name + self.transform_target_to_base = None + + def set_target(self, transform_target_to_base: pin.SE3) -> None: + self.transform_target_to_base = transform_target_to_base.copy() + + def set_target_from_configuration(self, configuration: PinkKinematicsConfiguration) -> None: + if not isinstance(configuration, PinkKinematicsConfiguration): + raise ValueError("configuration must be a PinkKinematicsConfiguration") + self.set_target(configuration.get_transform(self.frame, self.base_link_frame_name)) + + def compute_error(self, configuration: PinkKinematicsConfiguration) -> np.ndarray: + if not isinstance(configuration, PinkKinematicsConfiguration): + raise ValueError("configuration must be a PinkKinematicsConfiguration") + if self.transform_target_to_base is None: + raise ValueError(f"no target set for frame '{self.frame}'") + + transform_frame_to_base = configuration.get_transform(self.frame, self.base_link_frame_name) + transform_target_to_frame = transform_frame_to_base.actInv(self.transform_target_to_base) + + error_in_frame: np.ndarray = pin.log(transform_target_to_frame).vector + return error_in_frame + + def compute_jacobian(self, configuration: PinkKinematicsConfiguration) -> np.ndarray: + if self.transform_target_to_base is None: + raise Exception(f"no target set for frame '{self.frame}'") + transform_frame_to_base = configuration.get_transform(self.frame, self.base_link_frame_name) + transform_frame_to_target = self.transform_target_to_base.actInv(transform_frame_to_base) + jacobian_in_frame = configuration.get_frame_jacobian(self.frame) + J = -pin.Jlog6(transform_frame_to_target) @ jacobian_in_frame + return J diff --git a/source/isaaclab/isaaclab/controllers/utils.py b/source/isaaclab/isaaclab/controllers/utils.py index 7e72912fdfd..fc091d324af 100644 --- a/source/isaaclab/isaaclab/controllers/utils.py +++ b/source/isaaclab/isaaclab/controllers/utils.py @@ -12,12 +12,6 @@ import os import re -from isaacsim.core.utils.extensions import enable_extension - -enable_extension("isaacsim.asset.exporter.urdf") - -from nvidia.srl.from_usd.to_urdf import UsdToUrdf - # import logger logger = logging.getLogger(__name__) @@ -32,6 +26,12 @@ def convert_usd_to_urdf(usd_path: str, output_path: str, force_conversion: bool Returns: A tuple containing the paths to the URDF file and the mesh directory. """ + from isaacsim.core.utils.extensions import enable_extension + + enable_extension("isaacsim.asset.exporter.urdf") + + from nvidia.srl.from_usd.to_urdf import UsdToUrdf + usd_to_urdf_kwargs = { "node_names_to_remove": None, "edge_names_to_remove": None, diff --git a/source/isaaclab/isaaclab/devices/__init__.py b/source/isaaclab/isaaclab/devices/__init__.py index b2605d39ca1..6dfe04ca327 100644 --- a/source/isaaclab/isaaclab/devices/__init__.py +++ b/source/isaaclab/isaaclab/devices/__init__.py @@ -20,11 +20,16 @@ the peripheral device. """ -from .device_base import DeviceBase, DeviceCfg, DevicesCfg -from .gamepad import Se2Gamepad, Se2GamepadCfg, Se3Gamepad, Se3GamepadCfg -from .haply import HaplyDevice, HaplyDeviceCfg -from .keyboard import Se2Keyboard, Se2KeyboardCfg, Se3Keyboard, Se3KeyboardCfg -from .openxr import ManusVive, ManusViveCfg, OpenXRDevice, OpenXRDeviceCfg -from .retargeter_base import RetargeterBase, RetargeterCfg -from .spacemouse import Se2SpaceMouse, Se2SpaceMouseCfg, Se3SpaceMouse, Se3SpaceMouseCfg -from .teleop_device_factory import create_teleop_device +from isaaclab.utils.module import lazy_export + +lazy_export( + ("device_base", ["DeviceBase", "DeviceCfg", "DevicesCfg"]), + ("gamepad", ["Se2Gamepad", "Se2GamepadCfg", "Se3Gamepad", "Se3GamepadCfg"]), + ("haply", ["HaplyDevice", "HaplyDeviceCfg"]), + ("keyboard", ["Se2Keyboard", "Se2KeyboardCfg", "Se3Keyboard", "Se3KeyboardCfg"]), + ("openxr", ["ManusVive", "ManusViveCfg", "OpenXRDevice", "OpenXRDeviceCfg"]), + ("retargeter_base", ["RetargeterBase", "RetargeterCfg"]), + ("spacemouse", ["Se2SpaceMouse", "Se2SpaceMouseCfg", "Se3SpaceMouse", "Se3SpaceMouseCfg"]), + ("teleop_device_factory", "create_teleop_device"), + submodules=["openxr"], +) diff --git a/source/isaaclab/isaaclab/devices/gamepad/__init__.py b/source/isaaclab/isaaclab/devices/gamepad/__init__.py index 7a4b3d0da2a..9d695f321f4 100644 --- a/source/isaaclab/isaaclab/devices/gamepad/__init__.py +++ b/source/isaaclab/isaaclab/devices/gamepad/__init__.py @@ -5,7 +5,11 @@ """Gamepad device for SE(2) and SE(3) control.""" -from .se2_gamepad import Se2Gamepad -from .se2_gamepad_cfg import Se2GamepadCfg -from .se3_gamepad import Se3Gamepad -from .se3_gamepad_cfg import Se3GamepadCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("se2_gamepad", "Se2Gamepad"), + ("se2_gamepad_cfg", "Se2GamepadCfg"), + ("se3_gamepad", "Se3Gamepad"), + ("se3_gamepad_cfg", "Se3GamepadCfg"), +) diff --git a/source/isaaclab/isaaclab/devices/keyboard/__init__.py b/source/isaaclab/isaaclab/devices/keyboard/__init__.py index 986b7925c4b..5062d0d8fbe 100644 --- a/source/isaaclab/isaaclab/devices/keyboard/__init__.py +++ b/source/isaaclab/isaaclab/devices/keyboard/__init__.py @@ -5,7 +5,11 @@ """Keyboard device for SE(2) and SE(3) control.""" -from .se2_keyboard import Se2Keyboard -from .se2_keyboard_cfg import Se2KeyboardCfg -from .se3_keyboard import Se3Keyboard -from .se3_keyboard_cfg import Se3KeyboardCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("se2_keyboard", "Se2Keyboard"), + ("se2_keyboard_cfg", "Se2KeyboardCfg"), + ("se3_keyboard", "Se3Keyboard"), + ("se3_keyboard_cfg", "Se3KeyboardCfg"), +) diff --git a/source/isaaclab/isaaclab/devices/openxr/__init__.py b/source/isaaclab/isaaclab/devices/openxr/__init__.py index 9d44a65e4ab..91d10997439 100644 --- a/source/isaaclab/isaaclab/devices/openxr/__init__.py +++ b/source/isaaclab/isaaclab/devices/openxr/__init__.py @@ -15,17 +15,31 @@ :class:`DeprecationWarning` at instantiation time. """ -try: - - from isaaclab_teleop.deprecated.openxr import ( # noqa: F401 - ManusVive, - ManusViveCfg, - OpenXRDevice, - OpenXRDeviceCfg, - XrAnchorRotationMode, - XrCfg, - remove_camera_configs, - ) -except ImportError: - print("isaaclab_teleop is not installed. OpenXR teleoperation features will not be available.") - pass +_OPENXR_ATTRS = { + "ManusVive", "ManusViveCfg", + "OpenXRDevice", "OpenXRDeviceCfg", + "XrAnchorRotationMode", "XrCfg", "remove_camera_configs", +} + + +def __getattr__(name: str): + if name in _OPENXR_ATTRS: + try: + import isaaclab_teleop.deprecated.openxr as _openxr + + return getattr(_openxr, name) + except ImportError: + raise AttributeError( + f"module {__name__!r} has no attribute {name!r}. " + "isaaclab_teleop is not installed." + ) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def __dir__(): + try: + import isaaclab_teleop.deprecated.openxr as _openxr + + return list(_OPENXR_ATTRS) + dir(_openxr) + except ImportError: + return list(_OPENXR_ATTRS) diff --git a/source/isaaclab/isaaclab/devices/spacemouse/__init__.py b/source/isaaclab/isaaclab/devices/spacemouse/__init__.py index 55d8df889e7..37231b9fac7 100644 --- a/source/isaaclab/isaaclab/devices/spacemouse/__init__.py +++ b/source/isaaclab/isaaclab/devices/spacemouse/__init__.py @@ -5,7 +5,11 @@ """Spacemouse device for SE(2) and SE(3) control.""" -from .se2_spacemouse import Se2SpaceMouse -from .se2_spacemouse_cfg import Se2SpaceMouseCfg -from .se3_spacemouse import Se3SpaceMouse -from .se3_spacemouse_cfg import Se3SpaceMouseCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("se2_spacemouse", "Se2SpaceMouse"), + ("se2_spacemouse_cfg", "Se2SpaceMouseCfg"), + ("se3_spacemouse", "Se3SpaceMouse"), + ("se3_spacemouse_cfg", "Se3SpaceMouseCfg"), +) diff --git a/source/isaaclab/isaaclab/envs/__init__.py b/source/isaaclab/isaaclab/envs/__init__.py index 543ff2ad4ba..9a280bbad01 100644 --- a/source/isaaclab/isaaclab/envs/__init__.py +++ b/source/isaaclab/isaaclab/envs/__init__.py @@ -42,16 +42,24 @@ .. _`Task Design Workflows`: https://docs.isaacsim.omniverse.nvidia.com/latest/introduction/workflows.html """ -from . import mdp, ui -from .common import VecEnvObs, VecEnvStepReturn, ViewerCfg -from .direct_marl_env import DirectMARLEnv -from .direct_marl_env_cfg import DirectMARLEnvCfg -from .direct_rl_env import DirectRLEnv -from .direct_rl_env_cfg import DirectRLEnvCfg -from .manager_based_env import ManagerBasedEnv -from .manager_based_env_cfg import ManagerBasedEnvCfg -from .manager_based_rl_env import ManagerBasedRLEnv -from .manager_based_rl_env_cfg import ManagerBasedRLEnvCfg -from .manager_based_rl_mimic_env import ManagerBasedRLMimicEnv -from .mimic_env_cfg import * -from .utils.marl import multi_agent_to_single_agent, multi_agent_with_one_agent +from isaaclab.utils.module import lazy_export + +lazy_export( + # Pure data types + ("common", ["VecEnvObs", "VecEnvStepReturn", "ViewerCfg"]), + # Cfg classes — clean after Phase 3/4 + ("direct_rl_env_cfg", "DirectRLEnvCfg"), + ("direct_marl_env_cfg", "DirectMARLEnvCfg"), + ("manager_based_env_cfg", "ManagerBasedEnvCfg"), + ("manager_based_rl_env_cfg", "ManagerBasedRLEnvCfg"), + ("mimic_env_cfg", ["MimicEnvCfg", "MimicObservationGroupCfg", "MimicObservationTermCfg"]), + # Impl classes — deferred until needed + ("direct_rl_env", "DirectRLEnv"), + ("direct_marl_env", "DirectMARLEnv"), + ("manager_based_env", "ManagerBasedEnv"), + ("manager_based_rl_env", "ManagerBasedRLEnv"), + ("manager_based_rl_mimic_env", "ManagerBasedRLMimicEnv"), + # MARL utilities + ("utils.marl", ["multi_agent_to_single_agent", "multi_agent_with_one_agent"]), + submodules=["mdp", "ui"], +) diff --git a/source/isaaclab/isaaclab/envs/direct_rl_env.py b/source/isaaclab/isaaclab/envs/direct_rl_env.py index 4da39201075..d70c11cd442 100644 --- a/source/isaaclab/isaaclab/envs/direct_rl_env.py +++ b/source/isaaclab/isaaclab/envs/direct_rl_env.py @@ -29,15 +29,14 @@ from isaaclab.utils.timer import Timer from isaaclab.utils.version import has_kit +from .common import VecEnvObs, VecEnvStepReturn +from .direct_rl_env_cfg import DirectRLEnvCfg from .ui import ViewportCameraController +from .utils.spaces import sample_space, spec_to_gym_space if has_kit(): import omni.kit.app -from .common import VecEnvObs, VecEnvStepReturn -from .direct_rl_env_cfg import DirectRLEnvCfg -from .utils.spaces import sample_space, spec_to_gym_space - # import logger logger = logging.getLogger(__name__) diff --git a/source/isaaclab/isaaclab/envs/mdp/__init__.py b/source/isaaclab/isaaclab/envs/mdp/__init__.py index bdb507f3eb0..9ad44ca0d58 100644 --- a/source/isaaclab/isaaclab/envs/mdp/__init__.py +++ b/source/isaaclab/isaaclab/envs/mdp/__init__.py @@ -15,11 +15,8 @@ """ -from .actions import * # noqa: F401, F403 -from .commands import * # noqa: F401, F403 -from .curriculums import * # noqa: F401, F403 -from .events import * # noqa: F401, F403 -from .observations import * # noqa: F401, F403 -from .recorders import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export + +cascading_export( + submodules=["actions", "commands", "curriculums", "events", "observations", "recorders", "rewards", "terminations"], +) diff --git a/source/isaaclab/isaaclab/envs/mdp/actions/__init__.py b/source/isaaclab/isaaclab/envs/mdp/actions/__init__.py index 56b3ae25ff4..e670e81e3a5 100644 --- a/source/isaaclab/isaaclab/envs/mdp/actions/__init__.py +++ b/source/isaaclab/isaaclab/envs/mdp/actions/__init__.py @@ -5,9 +5,29 @@ """Various action terms that can be used in the environment.""" -from .actions_cfg import * -from .binary_joint_actions import * -from .joint_actions import * -from .joint_actions_to_limits import * -from .non_holonomic_actions import * -from .surface_gripper_actions import * +from isaaclab.utils.module import lazy_export + +lazy_export( + ("actions_cfg", [ + "JointActionCfg", + "JointPositionActionCfg", + "RelativeJointPositionActionCfg", + "JointVelocityActionCfg", + "JointEffortActionCfg", + "JointPositionToLimitsActionCfg", + "EMAJointPositionToLimitsActionCfg", + "BinaryJointActionCfg", + "BinaryJointPositionActionCfg", + "BinaryJointVelocityActionCfg", + "AbsBinaryJointPositionActionCfg", + "NonHolonomicActionCfg", + "DifferentialInverseKinematicsActionCfg", + "OperationalSpaceControllerActionCfg", + "SurfaceGripperBinaryActionCfg", + ]), + ("binary_joint_actions", ["BinaryJointAction", "BinaryJointPositionAction", "BinaryJointVelocityAction", "AbsBinaryJointPositionAction"]), + ("joint_actions", ["JointAction", "JointPositionAction", "RelativeJointPositionAction", "JointVelocityAction", "JointEffortAction"]), + ("joint_actions_to_limits", ["JointPositionToLimitsAction", "EMAJointPositionToLimitsAction"]), + ("non_holonomic_actions", "NonHolonomicAction"), + ("surface_gripper_actions", "SurfaceGripperBinaryAction"), +) diff --git a/source/isaaclab/isaaclab/envs/mdp/actions/pink_task_space_actions.py b/source/isaaclab/isaaclab/envs/mdp/actions/pink_task_space_actions.py index 7dad8020533..5d7e59418ee 100644 --- a/source/isaaclab/isaaclab/envs/mdp/actions/pink_task_space_actions.py +++ b/source/isaaclab/isaaclab/envs/mdp/actions/pink_task_space_actions.py @@ -15,7 +15,7 @@ import isaaclab.utils.math as math_utils from isaaclab.assets.articulation import Articulation from isaaclab.controllers.pink_ik import PinkIKController -from isaaclab.controllers.pink_ik.local_frame_task import LocalFrameTask +from isaaclab.controllers.pink_ik.pink_tasks import LocalFrameTask from isaaclab.managers.action_manager import ActionTerm if TYPE_CHECKING: diff --git a/source/isaaclab/isaaclab/envs/mdp/commands/__init__.py b/source/isaaclab/isaaclab/envs/mdp/commands/__init__.py index bdfce40473b..9ae8ce862bc 100644 --- a/source/isaaclab/isaaclab/envs/mdp/commands/__init__.py +++ b/source/isaaclab/isaaclab/envs/mdp/commands/__init__.py @@ -5,15 +5,19 @@ """Various command terms that can be used in the environment.""" -from .commands_cfg import ( - NormalVelocityCommandCfg, - NullCommandCfg, - TerrainBasedPose2dCommandCfg, - UniformPose2dCommandCfg, - UniformPoseCommandCfg, - UniformVelocityCommandCfg, +from isaaclab.utils.module import lazy_export + +lazy_export( + ("commands_cfg", [ + "NullCommandCfg", + "UniformVelocityCommandCfg", + "NormalVelocityCommandCfg", + "UniformPoseCommandCfg", + "UniformPose2dCommandCfg", + "TerrainBasedPose2dCommandCfg", + ]), + ("null_command", "NullCommand"), + ("pose_2d_command", ["TerrainBasedPose2dCommand", "UniformPose2dCommand"]), + ("pose_command", "UniformPoseCommand"), + ("velocity_command", ["NormalVelocityCommand", "UniformVelocityCommand"]), ) -from .null_command import NullCommand -from .pose_2d_command import TerrainBasedPose2dCommand, UniformPose2dCommand -from .pose_command import UniformPoseCommand -from .velocity_command import NormalVelocityCommand, UniformVelocityCommand diff --git a/source/isaaclab/isaaclab/envs/ui/__init__.py b/source/isaaclab/isaaclab/envs/ui/__init__.py index 93db88399e4..508a232c5f2 100644 --- a/source/isaaclab/isaaclab/envs/ui/__init__.py +++ b/source/isaaclab/isaaclab/envs/ui/__init__.py @@ -10,7 +10,11 @@ toggling different debug visualization tools, and other user-defined functionalities. """ -from .base_env_window import BaseEnvWindow -from .empty_window import EmptyWindow -from .manager_based_rl_env_window import ManagerBasedRLEnvWindow -from .viewport_camera_controller import ViewportCameraController +from isaaclab.utils.module import lazy_export + +lazy_export( + ("base_env_window", "BaseEnvWindow"), + ("empty_window", "EmptyWindow"), + ("manager_based_rl_env_window", "ManagerBasedRLEnvWindow"), + ("viewport_camera_controller", "ViewportCameraController"), +) diff --git a/source/isaaclab/isaaclab/managers/__init__.py b/source/isaaclab/isaaclab/managers/__init__.py index 4a8266801b4..df6e3034fe9 100644 --- a/source/isaaclab/isaaclab/managers/__init__.py +++ b/source/isaaclab/isaaclab/managers/__init__.py @@ -10,25 +10,31 @@ designed to be modular and can be easily extended to support new functionality. """ -from .action_manager import ActionManager, ActionTerm -from .command_manager import CommandManager, CommandTerm -from .curriculum_manager import CurriculumManager -from .event_manager import EventManager -from .manager_base import ManagerBase, ManagerTermBase -from .manager_term_cfg import ( - ActionTermCfg, - CommandTermCfg, - CurriculumTermCfg, - EventTermCfg, - ManagerTermBaseCfg, - ObservationGroupCfg, - ObservationTermCfg, - RecorderTermCfg, - RewardTermCfg, - TerminationTermCfg, +from isaaclab.utils.module import lazy_export + +lazy_export( + # Term cfg classes — pure dataclasses, no heavy imports + ("manager_term_cfg", [ + "ActionTermCfg", + "CommandTermCfg", + "CurriculumTermCfg", + "EventTermCfg", + "ManagerTermBaseCfg", + "ObservationGroupCfg", + "ObservationTermCfg", + "RecorderTermCfg", + "RewardTermCfg", + "TerminationTermCfg", + ]), + # Manager implementations — deferred + ("manager_base", ["ManagerBase", "ManagerTermBase"]), + ("action_manager", ["ActionManager", "ActionTerm"]), + ("command_manager", ["CommandManager", "CommandTerm"]), + ("curriculum_manager", "CurriculumManager"), + ("event_manager", "EventManager"), + ("observation_manager", "ObservationManager"), + ("recorder_manager", ["DatasetExportMode", "RecorderManager", "RecorderManagerBaseCfg", "RecorderTerm"]), + ("reward_manager", "RewardManager"), + ("scene_entity_cfg", "SceneEntityCfg"), + ("termination_manager", "TerminationManager"), ) -from .observation_manager import ObservationManager -from .recorder_manager import DatasetExportMode, RecorderManager, RecorderManagerBaseCfg, RecorderTerm -from .reward_manager import RewardManager -from .scene_entity_cfg import SceneEntityCfg -from .termination_manager import TerminationManager diff --git a/source/isaaclab/isaaclab/markers/__init__.py b/source/isaaclab/isaaclab/markers/__init__.py index eb7b6976100..4a050acdcfb 100644 --- a/source/isaaclab/isaaclab/markers/__init__.py +++ b/source/isaaclab/isaaclab/markers/__init__.py @@ -21,5 +21,10 @@ """ -from .config import * # noqa: F401, F403 -from .visualization_markers import VisualizationMarkers, VisualizationMarkersCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("visualization_markers", "VisualizationMarkers"), + ("visualization_markers_cfg", "VisualizationMarkersCfg"), + submodules=["config"], +) diff --git a/source/isaaclab/isaaclab/markers/config/__init__.py b/source/isaaclab/isaaclab/markers/config/__init__.py index 54d30051259..54713d65935 100644 --- a/source/isaaclab/isaaclab/markers/config/__init__.py +++ b/source/isaaclab/isaaclab/markers/config/__init__.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause import isaaclab.sim as sim_utils -from isaaclab.markers.visualization_markers import VisualizationMarkersCfg +from isaaclab.markers.visualization_markers_cfg import VisualizationMarkersCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR ## diff --git a/source/isaaclab/isaaclab/scene/__init__.py b/source/isaaclab/isaaclab/scene/__init__.py index 4b614ea4bce..e8aa39aa5c6 100644 --- a/source/isaaclab/isaaclab/scene/__init__.py +++ b/source/isaaclab/isaaclab/scene/__init__.py @@ -25,5 +25,9 @@ :mod:`isaaclab.managers` sub-package for more details. """ -from .interactive_scene import InteractiveScene -from .interactive_scene_cfg import InteractiveSceneCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("interactive_scene", "InteractiveScene"), + ("interactive_scene_cfg", "InteractiveSceneCfg"), +) diff --git a/source/isaaclab/isaaclab/sensors/__init__.py b/source/isaaclab/isaaclab/sensors/__init__.py index f0a5719e505..282b56d6288 100644 --- a/source/isaaclab/isaaclab/sensors/__init__.py +++ b/source/isaaclab/isaaclab/sensors/__init__.py @@ -35,10 +35,78 @@ """ -from .camera import * # noqa: F401, F403 -from .contact_sensor import * # noqa: F401, F403 -from .frame_transformer import * # noqa: F401 -from .imu import * # noqa: F401, F403 -from .ray_caster import * # noqa: F401, F403 -from .sensor_base import SensorBase # noqa: F401 -from .sensor_base_cfg import SensorBaseCfg # noqa: F401 +from isaaclab.utils.module import lazy_export + +lazy_export( + ( + "camera", + [ + "Camera", + "CameraCfg", + "CameraData", + "TiledCamera", + "TiledCameraCfg", + "create_pointcloud_from_depth", + "create_pointcloud_from_rgbd", + "save_images_to_file", + "transform_points", + ], + ), + ( + "contact_sensor", + [ + "ContactSensorCfg", + "ContactSensorData", + "ContactSensor", + "BaseContactSensorData", + "BaseContactSensor", + ], + ), + ( + "frame_transformer", + [ + "BaseFrameTransformer", + "BaseFrameTransformerData", + "FrameTransformer", + "FrameTransformerCfg", + "FrameTransformerData", + "OffsetCfg", + ], + ), + ("imu", ["BaseImu", "BaseImuData", "Imu", "ImuCfg", "ImuData"]), + ( + "ray_caster", + [ + "MultiMeshRayCaster", + "MultiMeshRayCasterCamera", + "MultiMeshRayCasterCameraCfg", + "MultiMeshRayCasterCameraData", + "MultiMeshRayCasterCfg", + "MultiMeshRayCasterData", + "RayCaster", + "RayCasterCamera", + "RayCasterCameraCfg", + "RayCasterCfg", + "RayCasterData", + ], + ), + ("sensor_base", ["SensorBase"]), + ("sensor_base_cfg", ["SensorBaseCfg"]), + submodules=["ray_caster"], +) +_lazy_getattr = __getattr__ +_lazy_dir = __dir__ + + +def __getattr__(name: str): + # ``patterns`` lives at isaaclab.sensors.ray_caster.patterns but many + # callers do ``from isaaclab.sensors import patterns`` for brevity. + if name == "patterns": + from isaaclab.sensors import ray_caster as _rc + + return _rc.patterns + return _lazy_getattr(name) + + +def __dir__(): + return _lazy_dir() + ["patterns"] diff --git a/source/isaaclab/isaaclab/sensors/camera/__init__.py b/source/isaaclab/isaaclab/sensors/camera/__init__.py index f2318067b58..553a080ebbc 100644 --- a/source/isaaclab/isaaclab/sensors/camera/__init__.py +++ b/source/isaaclab/isaaclab/sensors/camera/__init__.py @@ -5,9 +5,13 @@ """Sub-module for camera wrapper around USD camera prim.""" -from .camera import Camera -from .camera_cfg import CameraCfg -from .camera_data import CameraData -from .tiled_camera import TiledCamera -from .tiled_camera_cfg import TiledCameraCfg -from .utils import * # noqa: F401, F403 +from isaaclab.utils.module import lazy_export + +lazy_export( + ("camera", "Camera"), + ("camera_cfg", "CameraCfg"), + ("camera_data", "CameraData"), + ("tiled_camera", "TiledCamera"), + ("tiled_camera_cfg", "TiledCameraCfg"), + ("utils", ["create_pointcloud_from_depth", "create_pointcloud_from_rgbd", "save_images_to_file", "transform_points"]), +) diff --git a/source/isaaclab/isaaclab/sensors/contact_sensor/__init__.py b/source/isaaclab/isaaclab/sensors/contact_sensor/__init__.py index 5f2d3df68a0..62bbad55232 100644 --- a/source/isaaclab/isaaclab/sensors/contact_sensor/__init__.py +++ b/source/isaaclab/isaaclab/sensors/contact_sensor/__init__.py @@ -5,10 +5,12 @@ """Sub-module for rigid contact sensor.""" -from .base_contact_sensor import BaseContactSensor -from .base_contact_sensor_data import BaseContactSensorData -from .contact_sensor import ContactSensor -from .contact_sensor_cfg import ContactSensorCfg -from .contact_sensor_data import ContactSensorData +from isaaclab.utils.module import lazy_export -__all__ = ["BaseContactSensor", "BaseContactSensorData", "ContactSensor", "ContactSensorCfg", "ContactSensorData"] +lazy_export( + ("contact_sensor", "ContactSensor"), + ("contact_sensor_cfg", "ContactSensorCfg"), + ("contact_sensor_data", "ContactSensorData"), + ("base_contact_sensor", "BaseContactSensor"), + ("base_contact_sensor_data", "BaseContactSensorData"), +) diff --git a/source/isaaclab/isaaclab/sensors/frame_transformer/__init__.py b/source/isaaclab/isaaclab/sensors/frame_transformer/__init__.py index 664a91e4eb0..c625dcec815 100644 --- a/source/isaaclab/isaaclab/sensors/frame_transformer/__init__.py +++ b/source/isaaclab/isaaclab/sensors/frame_transformer/__init__.py @@ -5,10 +5,12 @@ """Sub-module for frame transformer sensor.""" -from .base_frame_transformer import BaseFrameTransformer -from .base_frame_transformer_data import BaseFrameTransformerData -from .frame_transformer import FrameTransformer -from .frame_transformer_cfg import FrameTransformerCfg, OffsetCfg -from .frame_transformer_data import FrameTransformerData +from isaaclab.utils.module import lazy_export -__all__ = ["BaseFrameTransformer", "BaseFrameTransformerData", "FrameTransformer", "FrameTransformerCfg", "FrameTransformerData", "OffsetCfg"] +lazy_export( + ("base_frame_transformer", "BaseFrameTransformer"), + ("base_frame_transformer_data", "BaseFrameTransformerData"), + ("frame_transformer", "FrameTransformer"), + ("frame_transformer_cfg", ["FrameTransformerCfg", "OffsetCfg"]), + ("frame_transformer_data", "FrameTransformerData"), +) diff --git a/source/isaaclab/isaaclab/sensors/imu/__init__.py b/source/isaaclab/isaaclab/sensors/imu/__init__.py index 2f372fade3a..810517e4e01 100644 --- a/source/isaaclab/isaaclab/sensors/imu/__init__.py +++ b/source/isaaclab/isaaclab/sensors/imu/__init__.py @@ -7,10 +7,12 @@ Imu Sensor """ -from .base_imu import BaseImu -from .base_imu_data import BaseImuData -from .imu import Imu -from .imu_cfg import ImuCfg -from .imu_data import ImuData +from isaaclab.utils.module import lazy_export -__all__ = ["BaseImu", "BaseImuData", "Imu", "ImuCfg", "ImuData"] +lazy_export( + ("base_imu", "BaseImu"), + ("base_imu_data", "BaseImuData"), + ("imu", "Imu"), + ("imu_cfg", "ImuCfg"), + ("imu_data", "ImuData"), +) diff --git a/source/isaaclab/isaaclab/sensors/ray_caster/__init__.py b/source/isaaclab/isaaclab/sensors/ray_caster/__init__.py index 06f479ed2ee..44ce81bd519 100644 --- a/source/isaaclab/isaaclab/sensors/ray_caster/__init__.py +++ b/source/isaaclab/isaaclab/sensors/ray_caster/__init__.py @@ -15,15 +15,19 @@ the same ray-casting operations as the sensor implementations, but return the results as images. """ -from . import patterns -from .multi_mesh_ray_caster import MultiMeshRayCaster -from .multi_mesh_ray_caster_camera import MultiMeshRayCasterCamera -from .multi_mesh_ray_caster_camera_cfg import MultiMeshRayCasterCameraCfg -from .multi_mesh_ray_caster_camera_data import MultiMeshRayCasterCameraData -from .multi_mesh_ray_caster_cfg import MultiMeshRayCasterCfg -from .multi_mesh_ray_caster_data import MultiMeshRayCasterData -from .ray_caster import RayCaster -from .ray_caster_camera import RayCasterCamera -from .ray_caster_camera_cfg import RayCasterCameraCfg -from .ray_caster_cfg import RayCasterCfg -from .ray_caster_data import RayCasterData +from isaaclab.utils.module import lazy_export + +lazy_export( + ("multi_mesh_ray_caster", "MultiMeshRayCaster"), + ("multi_mesh_ray_caster_camera", "MultiMeshRayCasterCamera"), + ("multi_mesh_ray_caster_camera_cfg", "MultiMeshRayCasterCameraCfg"), + ("multi_mesh_ray_caster_camera_data", "MultiMeshRayCasterCameraData"), + ("multi_mesh_ray_caster_cfg", "MultiMeshRayCasterCfg"), + ("multi_mesh_ray_caster_data", "MultiMeshRayCasterData"), + ("ray_caster", "RayCaster"), + ("ray_caster_camera", "RayCasterCamera"), + ("ray_caster_camera_cfg", "RayCasterCameraCfg"), + ("ray_caster_cfg", "RayCasterCfg"), + ("ray_caster_data", "RayCasterData"), + submodules=["patterns"], +) diff --git a/source/isaaclab/isaaclab/sim/__init__.py b/source/isaaclab/isaaclab/sim/__init__.py index 9cb5fdc13aa..8d9dd39db13 100644 --- a/source/isaaclab/isaaclab/sim/__init__.py +++ b/source/isaaclab/isaaclab/sim/__init__.py @@ -26,32 +26,61 @@ """ -import warnings - -from .converters import * # noqa: F401, F403 -from .schemas import * # noqa: F401, F403 -from .simulation_cfg import RenderCfg, SimulationCfg # noqa: F401, F403 -from .simulation_context import SimulationContext, build_simulation_context # noqa: F401, F403 -from .spawners import * # noqa: F401, F403 -from .utils import * # noqa: F401, F403 -from .views import * # noqa: F401, F403 - -# Deprecated alias for PhysxCfg -> PhysxCfg -# This supports old code that uses `from isaaclab.sim import PhysxCfg` -try: - from isaaclab_physx.physics import PhysxCfg as _PhysxCfg - - class PhysxCfg(_PhysxCfg): - """DEPRECATED: Use PhysxCfg from isaaclab_physx.physics instead.""" - - def __init__(self, *args, **kwargs): - warnings.warn( - "PhysxCfg is deprecated. Use PhysxCfg from isaaclab_physx.physics instead.", - DeprecationWarning, - stacklevel=2, - ) - super().__init__(*args, **kwargs) - -except ImportError: - # isaaclab_physx not installed - PhysxCfg = None # type: ignore +from isaaclab.utils.module import lazy_export + +# submodules lets `from isaaclab.sim import schemas` / `converters` etc. resolve +# directly as subpackage objects without falling through to the _utils fallback. +lazy_export( + ("simulation_cfg", ["RenderCfg", "SimulationCfg"]), + ("simulation_context", ["SimulationContext", "build_simulation_context"]), + submodules=["converters", "schemas", "spawners", "utils", "views"], +) + +_lazy_getattr = __getattr__ +# Keep pure-data cfg resolution ahead of pxr-heavy utility modules. +# - `spawners` for SpawnerCfg and file/material cfgs +# - `schemas` for physics schema cfgs (e.g., RigidBodyPropertiesCfg) +_SUBPACKAGES = ("spawners", "schemas", "converters", "views", "utils") + + +def __getattr__(name: str): + # Backward-compat deprecated alias. + if name == "PhysxCfg": + import warnings + + try: + from isaaclab_physx.physics import PhysxCfg as _PhysxCfg + + class PhysxCfg(_PhysxCfg): + """DEPRECATED: Use PhysxCfg from isaaclab_physx.physics instead.""" + + def __init__(self, *args, **kwargs): + warnings.warn( + "PhysxCfg is deprecated. Use PhysxCfg from isaaclab_physx.physics instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) + + globals()["PhysxCfg"] = PhysxCfg + return PhysxCfg + except ImportError: + return None + + # Try lazy_loader first (submodules + submod_attrs). + try: + return _lazy_getattr(name) + except AttributeError: + pass + + # Fallback: search each subpackage for the symbol. + import importlib + + for subpkg in _SUBPACKAGES: + try: + submod = importlib.import_module(f"{__name__}.{subpkg}") + return getattr(submod, name) + except (ImportError, AttributeError): + continue + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/source/isaaclab/isaaclab/sim/converters/__init__.py b/source/isaaclab/isaaclab/sim/converters/__init__.py index 7503c53bdd8..006522fc356 100644 --- a/source/isaaclab/isaaclab/sim/converters/__init__.py +++ b/source/isaaclab/isaaclab/sim/converters/__init__.py @@ -16,11 +16,15 @@ """ -from .asset_converter_base import AssetConverterBase -from .asset_converter_base_cfg import AssetConverterBaseCfg -from .mesh_converter import MeshConverter -from .mesh_converter_cfg import MeshConverterCfg -from .mjcf_converter import MjcfConverter -from .mjcf_converter_cfg import MjcfConverterCfg -from .urdf_converter import UrdfConverter -from .urdf_converter_cfg import UrdfConverterCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("asset_converter_base", "AssetConverterBase"), + ("asset_converter_base_cfg", "AssetConverterBaseCfg"), + ("mesh_converter", "MeshConverter"), + ("mesh_converter_cfg", "MeshConverterCfg"), + ("mjcf_converter", "MjcfConverter"), + ("mjcf_converter_cfg", "MjcfConverterCfg"), + ("urdf_converter", "UrdfConverter"), + ("urdf_converter_cfg", "UrdfConverterCfg"), +) diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.py b/source/isaaclab/isaaclab/sim/schemas/__init__.py index c8402fdb13c..23451c22206 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.py +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.py @@ -32,96 +32,46 @@ """ -from .schemas import ( - MESH_APPROXIMATION_TOKENS, - PHYSX_MESH_COLLISION_CFGS, - USD_MESH_COLLISION_CFGS, - activate_contact_sensors, - define_articulation_root_properties, - define_collision_properties, - define_deformable_body_properties, - define_mass_properties, - define_mesh_collision_properties, - define_rigid_body_properties, - modify_articulation_root_properties, - modify_collision_properties, - modify_deformable_body_properties, - modify_fixed_tendon_properties, - modify_joint_drive_properties, - modify_mass_properties, - modify_mesh_collision_properties, - modify_rigid_body_properties, - modify_spatial_tendon_properties, -) -from .schemas_cfg import ( - ArticulationRootPropertiesCfg, - BoundingCubePropertiesCfg, - BoundingSpherePropertiesCfg, - CollisionPropertiesCfg, - ConvexDecompositionPropertiesCfg, - ConvexHullPropertiesCfg, - DeformableBodyPropertiesCfg, - FixedTendonPropertiesCfg, - JointDrivePropertiesCfg, - MassPropertiesCfg, - MeshCollisionPropertiesCfg, - RigidBodyPropertiesCfg, - SDFMeshPropertiesCfg, - SpatialTendonPropertiesCfg, - TriangleMeshPropertiesCfg, - TriangleMeshSimplificationPropertiesCfg, -) +from isaaclab.utils.module import lazy_export -__all__ = [ - # articulation root - "ArticulationRootPropertiesCfg", - "define_articulation_root_properties", - "modify_articulation_root_properties", - # rigid bodies - "RigidBodyPropertiesCfg", - "define_rigid_body_properties", - "modify_rigid_body_properties", - "activate_contact_sensors", - # colliders - "CollisionPropertiesCfg", - "define_collision_properties", - "modify_collision_properties", - # deformables - "DeformableBodyPropertiesCfg", - "define_deformable_body_properties", - "modify_deformable_body_properties", - # joints - "JointDrivePropertiesCfg", - "modify_joint_drive_properties", - # mass - "MassPropertiesCfg", - "define_mass_properties", - "modify_mass_properties", - # mesh colliders - "MeshCollisionPropertiesCfg", - "define_mesh_collision_properties", - "modify_mesh_collision_properties", - # bounding cube - "BoundingCubePropertiesCfg", - # bounding sphere - "BoundingSpherePropertiesCfg", - # convex decomposition - "ConvexDecompositionPropertiesCfg", - # convex hull - "ConvexHullPropertiesCfg", - # sdf mesh - "SDFMeshPropertiesCfg", - # triangle mesh - "TriangleMeshPropertiesCfg", - # triangle mesh simplification - "TriangleMeshSimplificationPropertiesCfg", - # tendons - "FixedTendonPropertiesCfg", - "SpatialTendonPropertiesCfg", - "modify_fixed_tendon_properties", - "modify_spatial_tendon_properties", - # Constants for configs that use PhysX vs USD API - "PHYSX_MESH_COLLISION_CFGS", - "USD_MESH_COLLISION_CFGS", - "MESH_APPROXIMATION_TOKENS", -] +lazy_export( + ("schemas", [ + "MESH_APPROXIMATION_TOKENS", + "PHYSX_MESH_COLLISION_CFGS", + "USD_MESH_COLLISION_CFGS", + "activate_contact_sensors", + "define_articulation_root_properties", + "define_collision_properties", + "define_deformable_body_properties", + "define_mass_properties", + "define_mesh_collision_properties", + "define_rigid_body_properties", + "modify_articulation_root_properties", + "modify_collision_properties", + "modify_deformable_body_properties", + "modify_fixed_tendon_properties", + "modify_joint_drive_properties", + "modify_mass_properties", + "modify_mesh_collision_properties", + "modify_rigid_body_properties", + "modify_spatial_tendon_properties", + ]), + ("schemas_cfg", [ + "ArticulationRootPropertiesCfg", + "BoundingCubePropertiesCfg", + "BoundingSpherePropertiesCfg", + "CollisionPropertiesCfg", + "ConvexDecompositionPropertiesCfg", + "ConvexHullPropertiesCfg", + "DeformableBodyPropertiesCfg", + "FixedTendonPropertiesCfg", + "JointDrivePropertiesCfg", + "MassPropertiesCfg", + "MeshCollisionPropertiesCfg", + "RigidBodyPropertiesCfg", + "SDFMeshPropertiesCfg", + "SpatialTendonPropertiesCfg", + "TriangleMeshPropertiesCfg", + "TriangleMeshSimplificationPropertiesCfg", + ]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/__init__.py b/source/isaaclab/isaaclab/sim/spawners/__init__.py index 916141906e1..268422f0245 100644 --- a/source/isaaclab/isaaclab/sim/spawners/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/__init__.py @@ -54,11 +54,78 @@ class and the function call in a single line of code. """ -from .from_files import * # noqa: F401, F403 -from .lights import * # noqa: F401, F403 -from .materials import * # noqa: F401, F403 -from .meshes import * # noqa: F401, F403 -from .sensors import * # noqa: F401, F403 -from .shapes import * # noqa: F401, F403 -from .spawner_cfg import * # noqa: F401, F403 -from .wrappers import * # noqa: F401, F403 +from isaaclab.utils.module import lazy_export + +lazy_export( + # spawner base cfg classes + ("spawner_cfg", ["SpawnerCfg", "RigidObjectSpawnerCfg", "DeformableObjectSpawnerCfg"]), + # from_files + ("from_files", [ + "spawn_from_mjcf", + "spawn_from_urdf", + "spawn_from_usd", + "spawn_from_usd_with_compliant_contact_material", + "spawn_ground_plane", + "GroundPlaneCfg", + "MjcfFileCfg", + "UrdfFileCfg", + "UsdFileCfg", + "UsdFileWithCompliantContactCfg", + ]), + # lights + ("lights", [ + "spawn_light", + "CylinderLightCfg", + "DiskLightCfg", + "DistantLightCfg", + "DomeLightCfg", + "LightCfg", + "SphereLightCfg", + ]), + # materials + ("materials", [ + "spawn_deformable_body_material", + "spawn_rigid_body_material", + "DeformableBodyMaterialCfg", + "PhysicsMaterialCfg", + "RigidBodyMaterialCfg", + "spawn_from_mdl_file", + "spawn_preview_surface", + "GlassMdlCfg", + "MdlFileCfg", + "PreviewSurfaceCfg", + "VisualMaterialCfg", + ]), + # meshes + ("meshes", [ + "spawn_mesh_capsule", + "spawn_mesh_cone", + "spawn_mesh_cuboid", + "spawn_mesh_cylinder", + "spawn_mesh_sphere", + "MeshCapsuleCfg", + "MeshCfg", + "MeshConeCfg", + "MeshCuboidCfg", + "MeshCylinderCfg", + "MeshSphereCfg", + ]), + # sensors + ("sensors", ["spawn_camera", "FisheyeCameraCfg", "PinholeCameraCfg"]), + # shapes + ("shapes", [ + "spawn_capsule", + "spawn_cone", + "spawn_cuboid", + "spawn_cylinder", + "spawn_sphere", + "CapsuleCfg", + "ConeCfg", + "CuboidCfg", + "CylinderCfg", + "ShapeCfg", + "SphereCfg", + ]), + # wrappers + ("wrappers", ["spawn_multi_asset", "spawn_multi_usd_file", "MultiAssetSpawnerCfg", "MultiUsdFileCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/__init__.py b/source/isaaclab/isaaclab/sim/spawners/from_files/__init__.py index a95ac491b0a..34591fa261f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/__init__.py @@ -13,11 +13,21 @@ """ -from .from_files import ( - spawn_from_mjcf, - spawn_from_urdf, - spawn_from_usd, - spawn_from_usd_with_compliant_contact_material, - spawn_ground_plane, +from isaaclab.utils.module import lazy_export + +lazy_export( + ("from_files", [ + "spawn_from_mjcf", + "spawn_from_urdf", + "spawn_from_usd", + "spawn_from_usd_with_compliant_contact_material", + "spawn_ground_plane", + ]), + ("from_files_cfg", [ + "GroundPlaneCfg", + "MjcfFileCfg", + "UrdfFileCfg", + "UsdFileCfg", + "UsdFileWithCompliantContactCfg", + ]), ) -from .from_files_cfg import GroundPlaneCfg, MjcfFileCfg, UrdfFileCfg, UsdFileCfg, UsdFileWithCompliantContactCfg diff --git a/source/isaaclab/isaaclab/sim/spawners/lights/__init__.py b/source/isaaclab/isaaclab/sim/spawners/lights/__init__.py index df0c638f58f..13aeb4484e9 100644 --- a/source/isaaclab/isaaclab/sim/spawners/lights/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/lights/__init__.py @@ -10,5 +10,9 @@ `_. """ -from .lights import spawn_light -from .lights_cfg import CylinderLightCfg, DiskLightCfg, DistantLightCfg, DomeLightCfg, LightCfg, SphereLightCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("lights", "spawn_light"), + ("lights_cfg", ["CylinderLightCfg", "DiskLightCfg", "DistantLightCfg", "DomeLightCfg", "LightCfg", "SphereLightCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.py b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.py index 0b0e7851494..e3233300946 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.py @@ -52,7 +52,11 @@ .. _Physics Scene: https://openusd.org/dev/api/usd_physics_page_front.html """ -from .physics_materials import spawn_deformable_body_material, spawn_rigid_body_material -from .physics_materials_cfg import DeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg -from .visual_materials import spawn_from_mdl_file, spawn_preview_surface -from .visual_materials_cfg import GlassMdlCfg, MdlFileCfg, PreviewSurfaceCfg, VisualMaterialCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("physics_materials", ["spawn_deformable_body_material", "spawn_rigid_body_material"]), + ("physics_materials_cfg", ["DeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg"]), + ("visual_materials", ["spawn_from_mdl_file", "spawn_preview_surface"]), + ("visual_materials_cfg", ["GlassMdlCfg", "MdlFileCfg", "PreviewSurfaceCfg", "VisualMaterialCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.py b/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.py index 49836dc5cbd..6fdfe8e10cf 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.py @@ -20,5 +20,15 @@ .. _USDGeomMesh: https://openusd.org/release/api/class_usd_geom_mesh.html """ -from .meshes import spawn_mesh_capsule, spawn_mesh_cone, spawn_mesh_cuboid, spawn_mesh_cylinder, spawn_mesh_sphere -from .meshes_cfg import MeshCapsuleCfg, MeshCfg, MeshConeCfg, MeshCuboidCfg, MeshCylinderCfg, MeshSphereCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("meshes", [ + "spawn_mesh_capsule", + "spawn_mesh_cone", + "spawn_mesh_cuboid", + "spawn_mesh_cylinder", + "spawn_mesh_sphere", + ]), + ("meshes_cfg", ["MeshCapsuleCfg", "MeshCfg", "MeshConeCfg", "MeshCuboidCfg", "MeshCylinderCfg", "MeshSphereCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/sensors/__init__.py b/source/isaaclab/isaaclab/sim/spawners/sensors/__init__.py index ac61868c025..f5e0721654a 100644 --- a/source/isaaclab/isaaclab/sim/spawners/sensors/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/sensors/__init__.py @@ -11,5 +11,9 @@ """ -from .sensors import spawn_camera -from .sensors_cfg import FisheyeCameraCfg, PinholeCameraCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("sensors", "spawn_camera"), + ("sensors_cfg", ["FisheyeCameraCfg", "PinholeCameraCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/__init__.py b/source/isaaclab/isaaclab/sim/spawners/shapes/__init__.py index 8f6cab9439c..9f7fa8cc329 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/__init__.py @@ -14,5 +14,9 @@ """ -from .shapes import spawn_capsule, spawn_cone, spawn_cuboid, spawn_cylinder, spawn_sphere -from .shapes_cfg import CapsuleCfg, ConeCfg, CuboidCfg, CylinderCfg, ShapeCfg, SphereCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("shapes", ["spawn_capsule", "spawn_cone", "spawn_cuboid", "spawn_cylinder", "spawn_sphere"]), + ("shapes_cfg", ["CapsuleCfg", "ConeCfg", "CuboidCfg", "CylinderCfg", "ShapeCfg", "SphereCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/spawners/wrappers/__init__.py b/source/isaaclab/isaaclab/sim/spawners/wrappers/__init__.py index 4006fa1a6ab..467e8befdf3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/wrappers/__init__.py +++ b/source/isaaclab/isaaclab/sim/spawners/wrappers/__init__.py @@ -10,5 +10,9 @@ different configurations. """ -from .wrappers import spawn_multi_asset, spawn_multi_usd_file -from .wrappers_cfg import MultiAssetSpawnerCfg, MultiUsdFileCfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("wrappers", ["spawn_multi_asset", "spawn_multi_usd_file"]), + ("wrappers_cfg", ["MultiAssetSpawnerCfg", "MultiUsdFileCfg"]), +) diff --git a/source/isaaclab/isaaclab/sim/utils/__init__.py b/source/isaaclab/isaaclab/sim/utils/__init__.py index 3a85ae44c2f..ede99ab0f24 100644 --- a/source/isaaclab/isaaclab/sim/utils/__init__.py +++ b/source/isaaclab/isaaclab/sim/utils/__init__.py @@ -5,9 +5,9 @@ """Utilities built around USD operations.""" -from .legacy import * # noqa: F401, F403 -from .prims import * # noqa: F401, F403 -from .queries import * # noqa: F401, F403 -from .semantics import * # noqa: F401, F403 -from .stage import * # noqa: F401, F403 -from .transforms import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export + +cascading_export( + # Octi: Legacy Last :). + submodules=["stage", "queries", "semantics", "transforms", "prims", "legacy"], +) diff --git a/source/isaaclab/isaaclab/sim/views/__init__.py b/source/isaaclab/isaaclab/sim/views/__init__.py index eb5bea7690c..621fd4f8734 100644 --- a/source/isaaclab/isaaclab/sim/views/__init__.py +++ b/source/isaaclab/isaaclab/sim/views/__init__.py @@ -5,4 +5,8 @@ """Views for manipulating USD prims.""" -from .xform_prim_view import XformPrimView +from isaaclab.utils.module import lazy_export + +lazy_export( + ("xform_prim_view", "XformPrimView"), +) diff --git a/source/isaaclab/isaaclab/terrains/__init__.py b/source/isaaclab/isaaclab/terrains/__init__.py index 6f0b5018557..3a8d1052dc6 100644 --- a/source/isaaclab/isaaclab/terrains/__init__.py +++ b/source/isaaclab/isaaclab/terrains/__init__.py @@ -19,11 +19,50 @@ * :meth:`TerrainImporter.import_usd`: spawn a prim as reference to input USD file. """ -from .height_field import * # noqa: F401, F403 -from .sub_terrain_cfg import FlatPatchSamplingCfg, SubTerrainBaseCfg -from .terrain_generator import TerrainGenerator -from .terrain_generator_cfg import TerrainGeneratorCfg -from .terrain_importer import TerrainImporter -from .terrain_importer_cfg import TerrainImporterCfg -from .trimesh import * # noqa: F401, F403 -from .utils import color_meshes_by_height, create_prim_from_mesh + +from isaaclab.utils.module import lazy_export + +lazy_export( + # cfg classes + ("sub_terrain_cfg", ["FlatPatchSamplingCfg", "SubTerrainBaseCfg"]), + ("terrain_generator_cfg", "TerrainGeneratorCfg"), + ("terrain_importer_cfg", "TerrainImporterCfg"), + # height field cfg classes (re-exported from sub-package) + ( + "height_field", + [ + "HfDiscreteObstaclesTerrainCfg", + "HfInvertedPyramidSlopedTerrainCfg", + "HfInvertedPyramidStairsTerrainCfg", + "HfPyramidSlopedTerrainCfg", + "HfPyramidStairsTerrainCfg", + "HfRandomUniformTerrainCfg", + "HfSteppingStonesTerrainCfg", + "HfTerrainBaseCfg", + "HfWaveTerrainCfg", + ], + ), + # trimesh cfg classes (re-exported from sub-package) + ( + "trimesh", + [ + "MeshBoxTerrainCfg", + "MeshFloatingRingTerrainCfg", + "MeshGapTerrainCfg", + "MeshInvertedPyramidStairsTerrainCfg", + "MeshPitTerrainCfg", + "MeshPlaneTerrainCfg", + "MeshPyramidStairsTerrainCfg", + "MeshRailsTerrainCfg", + "MeshRandomGridTerrainCfg", + "MeshRepeatedBoxesTerrainCfg", + "MeshRepeatedCylindersTerrainCfg", + "MeshRepeatedPyramidsTerrainCfg", + "MeshStarTerrainCfg", + ], + ), + # impl classes — deferred + ("terrain_generator", "TerrainGenerator"), + ("terrain_importer", "TerrainImporter"), + ("utils", ["color_meshes_by_height", "create_prim_from_mesh", "find_flat_patches"]), +) diff --git a/source/isaaclab/isaaclab/terrains/height_field/__init__.py b/source/isaaclab/isaaclab/terrains/height_field/__init__.py index 3bc28ba3ccf..7117a527c6e 100644 --- a/source/isaaclab/isaaclab/terrains/height_field/__init__.py +++ b/source/isaaclab/isaaclab/terrains/height_field/__init__.py @@ -25,14 +25,34 @@ """ -from .hf_terrains_cfg import ( - HfDiscreteObstaclesTerrainCfg, - HfInvertedPyramidSlopedTerrainCfg, - HfInvertedPyramidStairsTerrainCfg, - HfPyramidSlopedTerrainCfg, - HfPyramidStairsTerrainCfg, - HfRandomUniformTerrainCfg, - HfSteppingStonesTerrainCfg, - HfTerrainBaseCfg, - HfWaveTerrainCfg, +from isaaclab.utils.module import lazy_export + +lazy_export( + ( + "hf_terrains_cfg", + [ + "HfDiscreteObstaclesTerrainCfg", + "HfInvertedPyramidSlopedTerrainCfg", + "HfInvertedPyramidStairsTerrainCfg", + "HfPyramidSlopedTerrainCfg", + "HfPyramidStairsTerrainCfg", + "HfRandomUniformTerrainCfg", + "HfSteppingStonesTerrainCfg", + "HfTerrainBaseCfg", + "HfWaveTerrainCfg", + ], + ), + ( + "hf_terrains", + [ + "hf_discrete_obstacles_terrain", + "hf_inverted_pyramid_sloped_terrain", + "hf_inverted_pyramid_stairs_terrain", + "hf_pyramid_sloped_terrain", + "hf_pyramid_stairs_terrain", + "hf_random_uniform_terrain", + "hf_stepping_stones_terrain", + "hf_wave_terrain", + ], + ), ) diff --git a/source/isaaclab/isaaclab/terrains/trimesh/__init__.py b/source/isaaclab/isaaclab/terrains/trimesh/__init__.py index b27b7a92110..200da36024d 100644 --- a/source/isaaclab/isaaclab/terrains/trimesh/__init__.py +++ b/source/isaaclab/isaaclab/terrains/trimesh/__init__.py @@ -12,18 +12,41 @@ efficient than the height-field representation, but it is not as flexible. """ -from .mesh_terrains_cfg import ( - MeshBoxTerrainCfg, - MeshFloatingRingTerrainCfg, - MeshGapTerrainCfg, - MeshInvertedPyramidStairsTerrainCfg, - MeshPitTerrainCfg, - MeshPlaneTerrainCfg, - MeshPyramidStairsTerrainCfg, - MeshRailsTerrainCfg, - MeshRandomGridTerrainCfg, - MeshRepeatedBoxesTerrainCfg, - MeshRepeatedCylindersTerrainCfg, - MeshRepeatedPyramidsTerrainCfg, - MeshStarTerrainCfg, +from isaaclab.utils.module import lazy_export + +lazy_export( + ( + "mesh_terrains_cfg", + [ + "MeshBoxTerrainCfg", + "MeshFloatingRingTerrainCfg", + "MeshGapTerrainCfg", + "MeshInvertedPyramidStairsTerrainCfg", + "MeshPitTerrainCfg", + "MeshPlaneTerrainCfg", + "MeshPyramidStairsTerrainCfg", + "MeshRailsTerrainCfg", + "MeshRandomGridTerrainCfg", + "MeshRepeatedBoxesTerrainCfg", + "MeshRepeatedCylindersTerrainCfg", + "MeshRepeatedPyramidsTerrainCfg", + "MeshStarTerrainCfg", + ], + ), + ( + "mesh_terrains", + [ + "flat_terrain", + "pyramid_stairs_terrain", + "inverted_pyramid_stairs_terrain", + "random_grid_terrain", + "rails_terrain", + "pit_terrain", + "box_terrain", + "gap_terrain", + "floating_ring_terrain", + "star_terrain", + "repeated_objects_terrain", + ], + ), ) diff --git a/source/isaaclab/isaaclab/ui/widgets/__init__.py b/source/isaaclab/isaaclab/ui/widgets/__init__.py index cfd6de31fa2..481cbb188fb 100644 --- a/source/isaaclab/isaaclab/ui/widgets/__init__.py +++ b/source/isaaclab/isaaclab/ui/widgets/__init__.py @@ -3,7 +3,11 @@ # # SPDX-License-Identifier: BSD-3-Clause -from .image_plot import ImagePlot -from .line_plot import LiveLinePlot -from .manager_live_visualizer import ManagerLiveVisualizer -from .ui_visualizer_base import UiVisualizerBase +from isaaclab.utils.module import lazy_export + +lazy_export( + ("image_plot", "ImagePlot"), + ("line_plot", "LiveLinePlot"), + ("manager_live_visualizer", "ManagerLiveVisualizer"), + ("ui_visualizer_base", "UiVisualizerBase"), +) diff --git a/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py b/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py index 0b20b10dabc..cc0a2186c89 100644 --- a/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py +++ b/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py @@ -12,8 +12,6 @@ import numpy -import omni.kit.app - from isaaclab.managers import ManagerBase from isaaclab.sim import SimulationContext from isaaclab.utils import configclass @@ -196,6 +194,8 @@ def _set_debug_vis_impl(self, debug_vis: bool): if debug_vis: # if enabled create a subscriber for the post update event if it doesn't exist if not hasattr(self, "_debug_vis_handle") or self._debug_vis_handle is None: + import omni.kit.app + app_interface = omni.kit.app.get_app_interface() self._debug_vis_handle = app_interface.get_post_update_event_stream().create_subscription_to_pop( lambda event, obj=weakref.proxy(self): obj._debug_vis_callback(event) diff --git a/source/isaaclab/isaaclab/utils/__init__.py b/source/isaaclab/isaaclab/utils/__init__.py index 1295715857f..f9b333daf36 100644 --- a/source/isaaclab/isaaclab/utils/__init__.py +++ b/source/isaaclab/isaaclab/utils/__init__.py @@ -5,15 +5,96 @@ """Sub-package containing utilities for common operations and helper functions.""" -from .array import * -from .buffers import * + + +# Import configclass directly — its name matches its submodule name, which +# confuses lazy_loader's attribute-caching logic (the import machinery resets +# the parent attribute to the submodule object after each submodule import). +# configclass.py only depends on stdlib and local utils, so eager import is fine. from .configclass import configclass -from .dict import * -from .interpolation import * -from .logger import * -from .mesh import * -from .modifiers import * -from .string import * -from .timer import Timer -from .types import * -from .version import * +from .module import lazy_export + +lazy_export( + ( + "array", + [ + "TensorData", + "TENSOR_TYPES", + "TENSOR_TYPE_CONVERSIONS", + "convert_to_torch", + ], + ), + ( + "buffers", + [ + "CircularBuffer", + "DelayBuffer", + "TimestampedBuffer", + "TimestampedBufferWarp", + ], + ), + ( + "dict", + [ + "class_to_dict", + "update_class_from_dict", + "dict_to_md5_hash", + "convert_dict_to_backend", + "update_dict", + "replace_slices_with_strings", + "replace_strings_with_slices", + "print_dict", + ], + ), + ("interpolation", ["LinearInterpolation"]), + ( + "logger", + [ + "configure_logging", + "ColoredFormatter", + "RateLimitFilter", + ], + ), + ( + "mesh", + [ + "create_trimesh_from_geom_mesh", + "create_trimesh_from_geom_shape", + "convert_faces_to_triangles", + "PRIMITIVE_MESH_TYPES", + ], + ), + ( + "modifiers", + [ + "ModifierCfg", + "ModifierBase", + "DigitalFilter", + "DigitalFilterCfg", + "Integrator", + "IntegratorCfg", + "bias", + "clip", + "scale", + ], + ), + ( + "string", + [ + "to_camel_case", + "to_snake_case", + "string_to_slice", + "is_lambda_expression", + "callable_to_string", + "string_to_callable", + "resolve_matching_names", + "resolve_matching_names_values", + "find_unique_string_name", + "find_root_prim_path_from_regex", + ], + ), + ("timer", ["Timer", "TimerError"]), + ("types", ["ArticulationActions"]), + ("version", ["has_kit", "get_isaac_sim_version", "compare_versions"]), + ("module", ["attach_cascading", "cascading_export", "lazy_export"]), +) diff --git a/source/isaaclab/isaaclab/utils/module.py b/source/isaaclab/isaaclab/utils/module.py index ee9b7acfa27..aaecfa15b17 100644 --- a/source/isaaclab/isaaclab/utils/module.py +++ b/source/isaaclab/isaaclab/utils/module.py @@ -9,6 +9,9 @@ import importlib import sys +from collections.abc import Callable, Iterable + +import lazy_loader as lazy def attach_cascading( @@ -48,39 +51,73 @@ def attach_cascading( def __getattr__(name: str): for mod_name in submodules: - try: - mod = importlib.import_module(f"{package_name}.{mod_name}") - if hasattr(mod, name): - val = getattr(mod, name) - sys.modules[package_name].__dict__[name] = val - return val - except ImportError: - pass + full_mod_name = f"{package_name}.{mod_name}" + mod = importlib.import_module(full_mod_name) + if hasattr(mod, name): + val = getattr(mod, name) + sys.modules[package_name].__dict__[name] = val + return val for pkg in packages: - try: - mod = importlib.import_module(pkg) - if hasattr(mod, name): - val = getattr(mod, name) - sys.modules[package_name].__dict__[name] = val - return val - except ImportError: - pass + mod = importlib.import_module(pkg) + if hasattr(mod, name): + val = getattr(mod, name) + sys.modules[package_name].__dict__[name] = val + return val raise AttributeError(f"module {package_name!r} has no attribute {name!r}") def __dir__(): names: list[str] = [] for mod_name in submodules: - try: - mod = importlib.import_module(f"{package_name}.{mod_name}") - names.extend(n for n in dir(mod) if not n.startswith("_")) - except ImportError: - pass + full_mod_name = f"{package_name}.{mod_name}" + mod = importlib.import_module(full_mod_name) + names.extend(n for n in dir(mod) if not n.startswith("_")) for pkg in packages: - try: - mod = importlib.import_module(pkg) - names.extend(n for n in dir(mod) if not n.startswith("_")) - except ImportError: - pass + mod = importlib.import_module(pkg) + names.extend(n for n in dir(mod) if not n.startswith("_")) return sorted(set(names)) return __getattr__, __dir__ + + +def cascading_export( + submodules: list[str], + packages: list[str] | tuple[str, ...] = (), +) -> tuple[Callable[[str], object], Callable[[], list[str]]]: + """Register cascading imports for the calling package's ``__init__.py``. + + This helper mirrors :func:`lazy_export` ergonomics by inferring the caller's + package name and installing ``__getattr__`` and ``__dir__`` on that module. + """ + caller_globals = sys._getframe(1).f_globals + package_name = caller_globals["__name__"] + __getattr__, __dir__ = attach_cascading(package_name, submodules=submodules, packages=packages) + + mod = sys.modules[package_name] + setattr(mod, "__getattr__", __getattr__) + setattr(mod, "__dir__", __dir__) + return __getattr__, __dir__ + + +def lazy_export( + *imports: tuple[str, str | Iterable[str]], + submodules: list[str] | None = None, +) -> tuple[Callable[[str], object], Callable[[], list[str]], list[str]]: + """Register lazy imports for the calling package's ``__init__.py``. + + This helper wraps :func:`lazy_loader.attach` and installs ``__getattr__``, + ``__dir__`` and ``__all__`` on the calling module. + """ + caller_globals = sys._getframe(1).f_globals + package_name = caller_globals["__name__"] + + submod_attrs: dict[str, list[str]] = {} + for submod, names in imports: + submod_attrs[submod] = [names] if isinstance(names, str) else list(names) + + __getattr__, __dir__, __all__ = lazy.attach(package_name, submodules=submodules or [], submod_attrs=submod_attrs) + + mod = sys.modules[package_name] + setattr(mod, "__getattr__", __getattr__) + setattr(mod, "__dir__", __dir__) + setattr(mod, "__all__", __all__) + return __getattr__, __dir__, __all__ diff --git a/source/isaaclab/setup.py b/source/isaaclab/setup.py index 06fca3a4b85..f5370c609f1 100644 --- a/source/isaaclab/setup.py +++ b/source/isaaclab/setup.py @@ -55,6 +55,7 @@ "rerun-sdk>=0.29.0", # Required by pydantic-core/imgui_bundle on Python 3.12 (Sentinel symbol). "typing_extensions>=4.14.0", + "lazy_loader>=0.4", ] # Append Linux x86_64 and ARM64 deps via PEP 508 markers diff --git a/source/isaaclab/test/controllers/test_local_frame_task.py b/source/isaaclab/test/controllers/test_local_frame_task.py index cf832f32a93..e3ebec56c27 100644 --- a/source/isaaclab/test/controllers/test_local_frame_task.py +++ b/source/isaaclab/test/controllers/test_local_frame_task.py @@ -16,8 +16,8 @@ import pinocchio as pin import pytest -from isaaclab.controllers.pink_ik.local_frame_task import LocalFrameTask from isaaclab.controllers.pink_ik.pink_kinematics_configuration import PinkKinematicsConfiguration +from isaaclab.controllers.pink_ik.pink_tasks import LocalFrameTask # class TestLocalFrameTask: # """Test suite for LocalFrameTask class.""" diff --git a/source/isaaclab/test/controllers/test_pink_ik.py b/source/isaaclab/test/controllers/test_pink_ik.py index 99a8603089b..9913c76f7dc 100644 --- a/source/isaaclab/test/controllers/test_pink_ik.py +++ b/source/isaaclab/test/controllers/test_pink_ik.py @@ -94,9 +94,10 @@ def env_and_cfg(request): env, env_cfg = create_test_env(env_name, num_envs=1) - # Get only the FrameTasks from variable_input_tasks + # Read instantiated task objects from the live action term/controller, not raw cfg wrappers. + action_term = env.action_manager.get_term(name="upper_body_ik") variable_input_tasks = [ - task for task in env_cfg.actions.upper_body_ik.controller.variable_input_tasks if isinstance(task, FrameTask) + task for task in action_term._ik_controllers[0].cfg.variable_input_tasks if isinstance(task, FrameTask) ] assert len(variable_input_tasks) == 2, "Expected exactly two FrameTasks (left and right hand)." frames = [task.frame for task in variable_input_tasks] diff --git a/source/isaaclab_assets/isaaclab_assets/__init__.py b/source/isaaclab_assets/isaaclab_assets/__init__.py index d83e15466fc..3d8f227d478 100644 --- a/source/isaaclab_assets/isaaclab_assets/__init__.py +++ b/source/isaaclab_assets/isaaclab_assets/__init__.py @@ -6,6 +6,7 @@ import os import toml +from isaaclab.utils.module import cascading_export # Conveniences to other module directories via relative paths ISAACLAB_ASSETS_EXT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) @@ -20,5 +21,4 @@ # Configure the module-level variables __version__ = ISAACLAB_ASSETS_METADATA["package"]["version"] -from .robots import * -from .sensors import * +cascading_export(submodules=["robots", "sensors"]) diff --git a/source/isaaclab_assets/isaaclab_assets/robots/__init__.py b/source/isaaclab_assets/isaaclab_assets/robots/__init__.py index 77bcf04d0a3..cdf88ab445d 100644 --- a/source/isaaclab_assets/isaaclab_assets/robots/__init__.py +++ b/source/isaaclab_assets/isaaclab_assets/robots/__init__.py @@ -3,30 +3,38 @@ # # SPDX-License-Identifier: BSD-3-Clause -## -# Configuration for different assets. -## +"""Robot configuration definitions. -from .agibot import * -from .agility import * -from .allegro import * -from .ant import * -from .anymal import * -from .cart_double_pendulum import * -from .cartpole import * -from .cassie import * -from .fourier import * -from .franka import * -from .galbot import * -from .humanoid import * -from .humanoid_28 import * -from .kinova import * -from .kuka_allegro import * -from .pick_and_place import * -from .quadcopter import * -from .ridgeback_franka import * -from .sawyer import * -from .shadow_hand import * -from .spot import * -from .unitree import * -from .universal_robots import * +This package intentionally avoids eager ``from .module import *`` so that importing +``isaaclab_assets.robots.`` does not execute every robot config module. +""" + +from isaaclab.utils.module import cascading_export + +cascading_export( + submodules=[ + "anymal", + "unitree", + "agility", + "franka", + "spot", + "cassie", + "agibot", + "allegro", + "ant", + "cart_double_pendulum", + "cartpole", + "fourier", + "galbot", + "humanoid", + "humanoid_28", + "kinova", + "kuka_allegro", + "pick_and_place", + "quadcopter", + "ridgeback_franka", + "sawyer", + "shadow_hand", + "universal_robots", + ], +) diff --git a/source/isaaclab_assets/isaaclab_assets/sensors/__init__.py b/source/isaaclab_assets/isaaclab_assets/sensors/__init__.py index f5f6c6ac116..c626a738d40 100644 --- a/source/isaaclab_assets/isaaclab_assets/sensors/__init__.py +++ b/source/isaaclab_assets/isaaclab_assets/sensors/__init__.py @@ -3,9 +3,11 @@ # # SPDX-License-Identifier: BSD-3-Clause -## -# Configuration for different assets. -## +"""Configuration for different assets.""" -from .gelsight import * -from .velodyne import * +from isaaclab.utils.module import lazy_export + +lazy_export( + ("gelsight", ["GELSIGHT_R15_CFG", "GELSIGHT_MINI_CFG"]), + ("velodyne", "VELODYNE_VLP_16_RAYCASTER_CFG"), +) diff --git a/source/isaaclab_contrib/isaaclab_contrib/assets/__init__.py b/source/isaaclab_contrib/isaaclab_contrib/assets/__init__.py index 8c40124e72a..a1fde6b46d5 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/assets/__init__.py +++ b/source/isaaclab_contrib/isaaclab_contrib/assets/__init__.py @@ -11,4 +11,8 @@ contributed by the community to extend the capabilities of Isaac Lab. """ -from .multirotor import Multirotor, MultirotorCfg, MultirotorData +from isaaclab.utils.module import lazy_export + +lazy_export( + ("multirotor", ["Multirotor", "MultirotorCfg", "MultirotorData"]), +) diff --git a/source/isaaclab_contrib/isaaclab_contrib/assets/multirotor/__init__.py b/source/isaaclab_contrib/isaaclab_contrib/assets/multirotor/__init__.py index 3ef1b482d05..64f4382a8d3 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/assets/multirotor/__init__.py +++ b/source/isaaclab_contrib/isaaclab_contrib/assets/multirotor/__init__.py @@ -41,6 +41,10 @@ - :mod:`isaaclab_contrib.mdp.actions`: Thrust action terms for RL """ -from .multirotor import Multirotor -from .multirotor_cfg import MultirotorCfg -from .multirotor_data import MultirotorData +from isaaclab.utils.module import lazy_export + +lazy_export( + ("multirotor", "Multirotor"), + ("multirotor_cfg", "MultirotorCfg"), + ("multirotor_data", "MultirotorData"), +) diff --git a/source/isaaclab_contrib/isaaclab_contrib/sensors/__init__.py b/source/isaaclab_contrib/isaaclab_contrib/sensors/__init__.py index a7ea884318a..e47305c8df8 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/sensors/__init__.py +++ b/source/isaaclab_contrib/isaaclab_contrib/sensors/__init__.py @@ -22,4 +22,8 @@ """ -from .tacsl_sensor import * +from isaaclab.utils.module import lazy_export + +lazy_export( + ("tacsl_sensor", ["VisuoTactileSensor", "GelSightRenderCfg", "VisuoTactileSensorCfg", "VisuoTactileSensorData"]), +) diff --git a/source/isaaclab_contrib/isaaclab_contrib/sensors/tacsl_sensor/__init__.py b/source/isaaclab_contrib/isaaclab_contrib/sensors/tacsl_sensor/__init__.py index 869b233d166..a8714ccece5 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/sensors/tacsl_sensor/__init__.py +++ b/source/isaaclab_contrib/isaaclab_contrib/sensors/tacsl_sensor/__init__.py @@ -5,6 +5,10 @@ """TacSL Tactile Sensor implementation for IsaacLab.""" -from .visuotactile_sensor import VisuoTactileSensor -from .visuotactile_sensor_cfg import GelSightRenderCfg, VisuoTactileSensorCfg -from .visuotactile_sensor_data import VisuoTactileSensorData +from isaaclab.utils.module import lazy_export + +lazy_export( + ("visuotactile_sensor", "VisuoTactileSensor"), + ("visuotactile_sensor_cfg", ["GelSightRenderCfg", "VisuoTactileSensorCfg"]), + ("visuotactile_sensor_data", "VisuoTactileSensorData"), +) diff --git a/source/isaaclab_newton/isaaclab_newton/cloner/__init__.py b/source/isaaclab_newton/isaaclab_newton/cloner/__init__.py index 3460eea6864..fa977971287 100644 --- a/source/isaaclab_newton/isaaclab_newton/cloner/__init__.py +++ b/source/isaaclab_newton/isaaclab_newton/cloner/__init__.py @@ -3,6 +3,10 @@ # # SPDX-License-Identifier: BSD-3-Clause -from .newton_replicate import newton_replicate +"""Newton cloner utilities.""" -__all__ = ["newton_replicate"] +from isaaclab.utils.module import lazy_export + +lazy_export( + ("newton_replicate", "newton_replicate"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/__init__.py b/source/isaaclab_physx/isaaclab_physx/assets/__init__.py index 3b31699c1f7..7f188526da8 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/__init__.py @@ -38,22 +38,12 @@ the corresponding actuator torques. """ -from .articulation import Articulation, ArticulationData -from .deformable_object import DeformableObject, DeformableObjectCfg, DeformableObjectData -from .rigid_object import RigidObject, RigidObjectData -from .rigid_object_collection import RigidObjectCollection, RigidObjectCollectionData -from .surface_gripper import SurfaceGripper, SurfaceGripperCfg - -__all__ = [ - "Articulation", - "ArticulationData", - "DeformableObject", - "DeformableObjectCfg", - "DeformableObjectData", - "RigidObject", - "RigidObjectData", - "RigidObjectCollection", - "RigidObjectCollectionData", - "SurfaceGripper", - "SurfaceGripperCfg", -] +from isaaclab.utils.module import lazy_export + +lazy_export( + ("articulation", ["Articulation", "ArticulationData"]), + ("deformable_object", ["DeformableObject", "DeformableObjectCfg", "DeformableObjectData"]), + ("rigid_object", ["RigidObject", "RigidObjectData"]), + ("rigid_object_collection", ["RigidObjectCollection", "RigidObjectCollectionData"]), + ("surface_gripper", ["SurfaceGripper", "SurfaceGripperCfg"]), +) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/articulation/__init__.py b/source/isaaclab_physx/isaaclab_physx/assets/articulation/__init__.py index 6efcec972dc..ce621159839 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/articulation/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/articulation/__init__.py @@ -5,10 +5,9 @@ """Sub-module for rigid articulated assets.""" -from .articulation import Articulation -from .articulation_data import ArticulationData +from isaaclab.utils.module import lazy_export -__all__ = [ - "Articulation", - "ArticulationData", -] +lazy_export( + ("articulation", "Articulation"), + ("articulation_data", "ArticulationData"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/__init__.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/__init__.py index 34ebd7892ff..1d53fc9be8e 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/__init__.py @@ -5,12 +5,10 @@ """Sub-module for deformable object assets.""" -from .deformable_object import DeformableObject -from .deformable_object_cfg import DeformableObjectCfg -from .deformable_object_data import DeformableObjectData +from isaaclab.utils.module import lazy_export -__all__ = [ - "DeformableObject", - "DeformableObjectCfg", - "DeformableObjectData", -] +lazy_export( + ("deformable_object", "DeformableObject"), + ("deformable_object_cfg", "DeformableObjectCfg"), + ("deformable_object_data", "DeformableObjectData"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object/__init__.py b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object/__init__.py index a6a074e9618..96468beaf60 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object/__init__.py @@ -5,10 +5,9 @@ """Sub-module for rigid object assets.""" -from .rigid_object import RigidObject -from .rigid_object_data import RigidObjectData +from isaaclab.utils.module import lazy_export -__all__ = [ - "RigidObject", - "RigidObjectData", -] +lazy_export( + ("rigid_object", "RigidObject"), + ("rigid_object_data", "RigidObjectData"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/__init__.py b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/__init__.py index d5969bf393a..b906028178e 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/__init__.py @@ -5,10 +5,9 @@ """Sub-module for rigid object collection.""" -from .rigid_object_collection import RigidObjectCollection -from .rigid_object_collection_data import RigidObjectCollectionData +from isaaclab.utils.module import lazy_export -__all__ = [ - "RigidObjectCollection", - "RigidObjectCollectionData", -] +lazy_export( + ("rigid_object_collection", "RigidObjectCollection"), + ("rigid_object_collection_data", "RigidObjectCollectionData"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/surface_gripper/__init__.py b/source/isaaclab_physx/isaaclab_physx/assets/surface_gripper/__init__.py index 1c7771cc5bc..50e9200fd7e 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/surface_gripper/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/surface_gripper/__init__.py @@ -5,10 +5,9 @@ """Sub-module for surface_gripper assets.""" -from .surface_gripper import SurfaceGripper -from .surface_gripper_cfg import SurfaceGripperCfg +from isaaclab.utils.module import lazy_export -__all__ = [ - "SurfaceGripper", - "SurfaceGripperCfg", -] +lazy_export( + ("surface_gripper", "SurfaceGripper"), + ("surface_gripper_cfg", "SurfaceGripperCfg"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/cloner/__init__.py b/source/isaaclab_physx/isaaclab_physx/cloner/__init__.py index df60869211e..ecfdd312a0e 100644 --- a/source/isaaclab_physx/isaaclab_physx/cloner/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/cloner/__init__.py @@ -3,6 +3,10 @@ # # SPDX-License-Identifier: BSD-3-Clause -from .physx_replicate import physx_replicate +"""PhysX cloner utilities.""" -__all__ = ["physx_replicate"] +from isaaclab.utils.module import lazy_export + +lazy_export( + ("physx_replicate", "physx_replicate"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/physics/__init__.py b/source/isaaclab_physx/isaaclab_physx/physics/__init__.py index cc84bb7e38a..12ad2da6669 100644 --- a/source/isaaclab_physx/isaaclab_physx/physics/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/physics/__init__.py @@ -5,12 +5,9 @@ """Implementation backends for simulation interfaces.""" -from .physx_manager import PhysxManager, IsaacEvents -from .physx_manager_cfg import PhysxCfg +from isaaclab.utils.module import lazy_export - -__all__ = [ - "PhysxManager", - "IsaacEvents", - "PhysxCfg", -] +lazy_export( + ("physx_manager", ["PhysxManager", "IsaacEvents"]), + ("physx_manager_cfg", "PhysxCfg"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/renderers/__init__.py b/source/isaaclab_physx/isaaclab_physx/renderers/__init__.py index 1efb09ae878..a76cf3f3ced 100644 --- a/source/isaaclab_physx/isaaclab_physx/renderers/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/renderers/__init__.py @@ -5,13 +5,8 @@ """Sub-module for PhysX renderer backends (Isaac RTX / Omniverse Replicator).""" -from .isaac_rtx_renderer import IsaacRtxRenderer -from .isaac_rtx_renderer_cfg import IsaacRtxRendererCfg +from isaaclab.utils.module import cascading_export -Renderer = IsaacRtxRenderer - -__all__ = [ - "IsaacRtxRenderer", - "IsaacRtxRendererCfg", - "Renderer", -] +cascading_export( + submodules=["isaac_rtx_renderer", "isaac_rtx_renderer_cfg"], +) diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/__init__.py b/source/isaaclab_physx/isaaclab_physx/sensors/__init__.py index dde43d8ce3f..0e50b734c10 100644 --- a/source/isaaclab_physx/isaaclab_physx/sensors/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/sensors/__init__.py @@ -5,15 +5,10 @@ """Sub-package containing PhysX-specific sensor implementations.""" -from .contact_sensor import ContactSensor, ContactSensorData -from .frame_transformer import FrameTransformer, FrameTransformerData -from .imu import Imu, ImuData +from isaaclab.utils.module import lazy_export -__all__ = [ - "ContactSensor", - "ContactSensorData", - "FrameTransformer", - "FrameTransformerData", - "Imu", - "ImuData", -] +lazy_export( + ("contact_sensor", ["ContactSensor", "ContactSensorCfg", "ContactSensorData"]), + ("frame_transformer", ["FrameTransformer", "FrameTransformerData"]), + ("imu", ["Imu", "ImuData"]), +) diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/__init__.py b/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/__init__.py index e2f5f2b7772..24ecb4658df 100644 --- a/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/__init__.py @@ -5,7 +5,10 @@ """Sub-module for PhysX rigid contact sensor.""" -from .contact_sensor import ContactSensor -from .contact_sensor_data import ContactSensorData +from isaaclab.utils.module import lazy_export -__all__ = ["ContactSensor", "ContactSensorData"] +lazy_export( + ("contact_sensor", "ContactSensor"), + ("contact_sensor_cfg", "ContactSensorCfg"), + ("contact_sensor_data", "ContactSensorData"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/contact_sensor_cfg.py b/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/contact_sensor_cfg.py new file mode 100644 index 00000000000..8950342a0f3 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/contact_sensor_cfg.py @@ -0,0 +1,19 @@ +# 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 typing import TYPE_CHECKING + +from isaaclab.sensors.contact_sensor.contact_sensor_cfg import ContactSensorCfg as _BaseContactSensorCfg +from isaaclab.utils import configclass + +if TYPE_CHECKING: + from .contact_sensor import ContactSensor + + +@configclass +class ContactSensorCfg(_BaseContactSensorCfg): + """PhysX contact sensor configuration.""" + + class_type: type["ContactSensor"] | str = "{DIR}.contact_sensor:ContactSensor" diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/__init__.py b/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/__init__.py index dde1954a4ad..cef89f0f6bb 100644 --- a/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/__init__.py @@ -5,7 +5,9 @@ """Sub-module for PhysX frame transformer sensor.""" -from .frame_transformer import FrameTransformer -from .frame_transformer_data import FrameTransformerData +from isaaclab.utils.module import lazy_export -__all__ = ["FrameTransformer", "FrameTransformerData"] +lazy_export( + ("frame_transformer", "FrameTransformer"), + ("frame_transformer_data", "FrameTransformerData"), +) diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/imu/__init__.py b/source/isaaclab_physx/isaaclab_physx/sensors/imu/__init__.py index d1c50358796..3fd8a36279f 100644 --- a/source/isaaclab_physx/isaaclab_physx/sensors/imu/__init__.py +++ b/source/isaaclab_physx/isaaclab_physx/sensors/imu/__init__.py @@ -5,7 +5,9 @@ """Sub-module for PhysX IMU sensor.""" -from .imu import Imu -from .imu_data import ImuData +from isaaclab.utils.module import lazy_export -__all__ = ["Imu", "ImuData"] +lazy_export( + ("imu", "Imu"), + ("imu_data", "ImuData"), +) diff --git a/source/isaaclab_rl/isaaclab_rl/rsl_rl/__init__.py b/source/isaaclab_rl/isaaclab_rl/rsl_rl/__init__.py index 3d8c6296899..d8df29b61e3 100644 --- a/source/isaaclab_rl/isaaclab_rl/rsl_rl/__init__.py +++ b/source/isaaclab_rl/isaaclab_rl/rsl_rl/__init__.py @@ -15,9 +15,24 @@ """ -from .distillation_cfg import * -from .exporter import export_policy_as_jit, export_policy_as_onnx -from .rl_cfg import * -from .rnd_cfg import RslRlRndCfg -from .symmetry_cfg import RslRlSymmetryCfg -from .vecenv_wrapper import RslRlVecEnvWrapper +from isaaclab.utils.module import lazy_export + +lazy_export( + ("rl_cfg", [ + "RslRlPpoActorCriticCfg", + "RslRlPpoActorCriticRecurrentCfg", + "RslRlPpoAlgorithmCfg", + "RslRlBaseRunnerCfg", + "RslRlOnPolicyRunnerCfg", + ]), + ("distillation_cfg", [ + "RslRlDistillationStudentTeacherCfg", + "RslRlDistillationStudentTeacherRecurrentCfg", + "RslRlDistillationAlgorithmCfg", + "RslRlDistillationRunnerCfg", + ]), + ("rnd_cfg", "RslRlRndCfg"), + ("symmetry_cfg", "RslRlSymmetryCfg"), + ("exporter", ["export_policy_as_jit", "export_policy_as_onnx"]), + ("vecenv_wrapper", "RslRlVecEnvWrapper"), +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/direct/automate/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/direct/automate/__init__.py index 9da64598c0c..ef9160ac3e0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/direct/automate/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/direct/automate/__init__.py @@ -16,7 +16,7 @@ entry_point=f"{__name__}.assembly_env:AssemblyEnv", disable_env_checker=True, kwargs={ - "env_cfg_entry_point": f"{__name__}.assembly_env:AssemblyEnvCfg", + "env_cfg_entry_point": f"{__name__}.assembly_env_cfg:AssemblyEnvCfg", "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", }, ) @@ -27,7 +27,7 @@ entry_point=f"{__name__}.disassembly_env:DisassemblyEnv", disable_env_checker=True, kwargs={ - "env_cfg_entry_point": f"{__name__}.disassembly_env:DisassemblyEnvCfg", + "env_cfg_entry_point": f"{__name__}.disassembly_env_cfg:DisassemblyEnvCfg", "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", }, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/cartpole/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/cartpole/mdp/__init__.py index 155079c558f..57e31ba73bb 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/cartpole/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/cartpole/mdp/__init__.py @@ -5,6 +5,9 @@ """This sub-module contains the functions that are specific to the cartpole environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .rewards import * # noqa: F401, F403 +cascading_export( + submodules=["rewards"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/humanoid/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/humanoid/mdp/__init__.py index 9fd05f55635..f837c61e4e1 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/humanoid/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/classic/humanoid/mdp/__init__.py @@ -5,7 +5,9 @@ """This sub-module contains the functions that are specific to the humanoid environment.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .observations import * -from .rewards import * +cascading_export( + submodules=["observations", "rewards"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/__init__.py index bc4d65f5b1f..42ac8be97da 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/__init__.py @@ -5,10 +5,9 @@ """This sub-module contains the functions that are specific to the drone ARL environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from isaaclab_contrib.mdp import * # noqa: F401, F403 - -from .commands import * # noqa: F401, F403 -from .observations import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 +cascading_export( + submodules=["commands", "observations", "rewards"], + packages=["isaaclab.envs.mdp", "isaaclab_contrib.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/commands/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/commands/__init__.py index a7386d3ce53..6658444f0e7 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/commands/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/commands/__init__.py @@ -5,5 +5,9 @@ """Various command terms that can be used in the environment.""" -from .commands_cfg import DroneUniformPoseCommandCfg -from .drone_pose_command import DroneUniformPoseCommand +from isaaclab.utils.module import lazy_export + +lazy_export( + ("commands_cfg", "DroneUniformPoseCommandCfg"), + ("drone_pose_command", "DroneUniformPoseCommand"), +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/configs/pink_controller_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/configs/pink_controller_cfg.py index 488d22c137b..51d69f92eed 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/configs/pink_controller_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/configs/pink_controller_cfg.py @@ -9,9 +9,7 @@ including both fixed base and mobile configurations for upper body manipulation. """ -from isaaclab.controllers.pink_ik.local_frame_task import LocalFrameTask -from isaaclab.controllers.pink_ik.null_space_posture_task import NullSpacePostureTask -from isaaclab.controllers.pink_ik.pink_ik_cfg import PinkIKControllerCfg +from isaaclab.controllers.pink_ik import LocalFrameTaskCfg, NullSpacePostureTaskCfg, PinkIKControllerCfg from isaaclab.envs.mdp.actions.pink_actions_cfg import PinkInverseKinematicsActionCfg ## @@ -25,23 +23,23 @@ show_ik_warnings=True, fail_on_joint_limit_violation=False, variable_input_tasks=[ - LocalFrameTask( - "g1_29dof_with_hand_rev_1_0_left_wrist_yaw_link", + LocalFrameTaskCfg( + frame="g1_29dof_with_hand_rev_1_0_left_wrist_yaw_link", base_link_frame_name="g1_29dof_with_hand_rev_1_0_pelvis", position_cost=8.0, # [cost] / [m] orientation_cost=2.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - LocalFrameTask( - "g1_29dof_with_hand_rev_1_0_right_wrist_yaw_link", + LocalFrameTaskCfg( + frame="g1_29dof_with_hand_rev_1_0_right_wrist_yaw_link", base_link_frame_name="g1_29dof_with_hand_rev_1_0_pelvis", position_cost=8.0, # [cost] / [m] orientation_cost=2.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - NullSpacePostureTask( + NullSpacePostureTaskCfg( cost=0.5, lm_damping=1, controlled_frames=[ diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/fixed_base_upper_body_ik_g1_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/fixed_base_upper_body_ik_g1_env_cfg.py index e225660e8e8..c6c115ed679 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/fixed_base_upper_body_ik_g1_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/fixed_base_upper_body_ik_g1_env_cfg.py @@ -4,16 +4,8 @@ # SPDX-License-Identifier: BSD-3-Clause -import logging - -try: - import isaacteleop # noqa: F401 -- pipeline builders need isaacteleop at runtime - from isaaclab_teleop import IsaacTeleopCfg, XrCfg - - _TELEOP_AVAILABLE = True -except ImportError: - _TELEOP_AVAILABLE = False - logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.") +from isaaclab_teleop.isaac_teleop_cfg import IsaacTeleopCfg +from isaaclab_teleop.xr_cfg import XrCfg import isaaclab.envs.mdp as base_mdp import isaaclab.sim as sim_utils @@ -26,7 +18,7 @@ from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg, UsdFileCfg from isaaclab.utils import configclass -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR, retrieve_file_path +from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from isaaclab_tasks.manager_based.locomanipulation.pick_place import mdp as locomanip_mdp from isaaclab_tasks.manager_based.manipulation.pick_place import mdp as manip_mdp @@ -393,20 +385,16 @@ def __post_init__(self): # Set the URDF and mesh paths for the IK controller urdf_omniverse_path = f"{ISAACLAB_NUCLEUS_DIR}/Controllers/LocomanipulationAssets/unitree_g1_kinematics_asset/g1_29dof_with_hand_only_kinematics.urdf" # noqa: E501 - # Retrieve local paths for the URDF and mesh files. Will be cached for call after the first time. - self.actions.upper_body_ik.controller.urdf_path = retrieve_file_path(urdf_omniverse_path) - - # IsaacTeleop-based teleoperation pipeline - # Build the pipeline and extract SE3 retargeters for UI parameter tuning - if _TELEOP_AVAILABLE: - self.xr = XrCfg( - anchor_pos=(0.0, 0.0, -0.45), - anchor_rot=(0.0, 0.0, 0.0, 1.0), - ) - pipeline, se3_retargeters = _build_g1_upper_body_pipeline() - self.isaac_teleop = IsaacTeleopCfg( - pipeline_builder=lambda: pipeline, - # retargeters_to_tune=lambda: se3_retargeters, - sim_device=self.sim.device, - xr_cfg=self.xr, - ) + # Defer Nucleus path resolution to controller initialization at runtime. + self.actions.upper_body_ik.controller.urdf_path = urdf_omniverse_path + + # IsaacTeleop-based teleoperation pipeline (resolved lazily at runtime). + self.xr = XrCfg( + anchor_pos=(0.0, 0.0, -0.45), + anchor_rot=(0.0, 0.0, 0.0, 1.0), + ) + self.isaac_teleop = IsaacTeleopCfg( + pipeline_builder=_build_g1_upper_body_pipeline, + sim_device=self.sim.device, + xr_cfg=self.xr, + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/locomanipulation_g1_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/locomanipulation_g1_env_cfg.py index 8fc02ccf6a4..47b9a22aa16 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/locomanipulation_g1_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/locomanipulation_g1_env_cfg.py @@ -25,7 +25,7 @@ from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg, UsdFileCfg from isaaclab.utils import configclass -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR, retrieve_file_path +from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from isaaclab_tasks.manager_based.locomanipulation.pick_place import mdp as locomanip_mdp from isaaclab_tasks.manager_based.locomanipulation.pick_place.configs.action_cfg import AgileBasedLowerBodyActionCfg @@ -437,11 +437,8 @@ def __post_init__(self): # scene settings self.scene.replicate_physics = False - # Set the URDF and mesh paths for the IK controller - urdf_omniverse_path = f"{ISAACLAB_NUCLEUS_DIR}/Controllers/LocomanipulationAssets/unitree_g1_kinematics_asset/g1_29dof_with_hand_only_kinematics.urdf" # noqa: E501 - - # Retrieve local paths for the URDF and mesh files. Will be cached for call after the first time. - self.actions.upper_body_ik.controller.urdf_path = retrieve_file_path(urdf_omniverse_path) + # Set the URDF path for the IK controller. Path resolution (Nucleus → local) happens at runtime. + self.actions.upper_body_ik.controller.urdf_path = f"{ISAACLAB_NUCLEUS_DIR}/Controllers/LocomanipulationAssets/unitree_g1_kinematics_asset/g1_29dof_with_hand_only_kinematics.urdf" # noqa: E501 if _TELEOP_AVAILABLE: self.xr = XrCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/__init__.py index d0275ceb7be..45d3dd265d0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/__init__.py @@ -3,11 +3,11 @@ # # SPDX-License-Identifier: BSD-3-Clause - """This sub-module contains the functions that are specific to the locomanipulation environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .actions import * # noqa: F401, F403 -from .observations import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["actions", "observations", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/observations.py index 376f196cba8..5e39567efa5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/observations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/observations.py @@ -3,10 +3,16 @@ # # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + +from typing import TYPE_CHECKING + import torch import warp as wp -from isaaclab.envs import ManagerBasedRLEnv +if TYPE_CHECKING: + from isaaclab.envs import ManagerBasedRLEnv + from isaaclab.managers import SceneEntityCfg diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/terminations.py index c2f3fd0f8b0..ec42385377c 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomanipulation/pick_place/mdp/terminations.py @@ -14,7 +14,6 @@ from isaaclab.assets import RigidObject from isaaclab.managers import SceneEntityCfg -from isaaclab.sim.views import XformPrimView from isaaclab.utils.math import quat_apply_inverse if TYPE_CHECKING: @@ -70,8 +69,9 @@ def task_done_pick_place_table_frame( object: RigidObject = env.scene[object_cfg.name] table = env.scene[table_cfg.name] - if not isinstance(table, XformPrimView): - raise TypeError(f"Expected table '{table_cfg.name}' to be an XformPrimView, got {type(table)}") + # Avoid importing sim views at module-load time for pure cfg loading. + if not hasattr(table, "get_world_poses"): + raise TypeError(f"Expected table '{table_cfg.name}' to expose get_world_poses(), got {type(table)}") # Get table world pose table_pos_w, table_quat_w = table.get_world_poses() diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/mdp/__init__.py index 6f6cad00712..33b8dbedda3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/mdp/__init__.py @@ -5,8 +5,9 @@ """This sub-module contains the functions that are specific to the locomotion environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .curriculums import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["curriculums", "rewards", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/cabinet/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/cabinet/mdp/__init__.py index 79a9af2f736..ea31c86b7cb 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/cabinet/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/cabinet/mdp/__init__.py @@ -5,7 +5,9 @@ """This sub-module contains the functions that are specific to the cabinet environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .observations import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 +cascading_export( + submodules=["observations", "rewards"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/deploy/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/deploy/mdp/__init__.py index 10ab3ea7e7f..dba18553826 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/deploy/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/deploy/mdp/__init__.py @@ -1,14 +1,13 @@ -# Copyright (c) 2025-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# 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 """This sub-module contains the functions that are specific to the locomotion environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .events import * # noqa: F401, F403 -from .noise_models import * # noqa: F401, F403 -from .observations import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["events", "noise_models", "observations", "rewards", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/dexsuite/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/dexsuite/mdp/__init__.py index a6537b1a5e1..0865a0c4296 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/dexsuite/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/dexsuite/mdp/__init__.py @@ -3,10 +3,11 @@ # # SPDX-License-Identifier: BSD-3-Clause -from isaaclab.envs.mdp import * # noqa: F401, F403 +"""This sub-module contains the functions that are specific to the dexsuite environments.""" -from .commands import * # noqa: F401, F403 -from .curriculums import * # noqa: F401, F403 -from .observations import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export + +cascading_export( + submodules=["commands", "curriculums", "observations", "rewards", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/__init__.py index 0fafe450036..820061dc7b9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/__init__.py @@ -5,10 +5,9 @@ """This sub-module contains the functions that are specific to the in-hand manipulation environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .commands import * # noqa: F401, F403 -from .events import * # noqa: F401, F403 -from .observations import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["commands", "events", "observations", "rewards", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/commands/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/commands/__init__.py index ab3a9cf3e11..b75622b10f9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/commands/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/inhand/mdp/commands/__init__.py @@ -5,5 +5,9 @@ """Sub-module containing command terms for 3D orientation goals.""" -from .commands_cfg import InHandReOrientationCommandCfg # noqa: F401 -from .orientation_command import InHandReOrientationCommand # noqa: F401 +from isaaclab.utils.module import lazy_export + +lazy_export( + ("commands_cfg", "InHandReOrientationCommandCfg"), + ("orientation_command", "InHandReOrientationCommand"), +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/lift/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/lift/mdp/__init__.py index f3dd0fecdf8..6e56ac1fc57 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/lift/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/lift/mdp/__init__.py @@ -5,8 +5,9 @@ """This sub-module contains the functions that are specific to the lift environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .observations import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["observations", "rewards", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/exhaustpipe_gr1t2_pink_ik_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/exhaustpipe_gr1t2_pink_ik_env_cfg.py index 4d22c35e1f4..3dd523de27f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/exhaustpipe_gr1t2_pink_ik_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/exhaustpipe_gr1t2_pink_ik_env_cfg.py @@ -3,20 +3,9 @@ # # SPDX-License-Identifier: BSD-3-Clause -import logging +from isaaclab_teleop.isaac_teleop_cfg import IsaacTeleopCfg -from pink.tasks import DampingTask, FrameTask - -try: - from isaaclab_teleop import IsaacTeleopCfg - - _TELEOP_AVAILABLE = True -except ImportError: - _TELEOP_AVAILABLE = False - logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.") - -import isaaclab.controllers.utils as ControllerUtils -from isaaclab.controllers.pink_ik import NullSpacePostureTask, PinkIKControllerCfg +from isaaclab.controllers.pink_ik import DampingTaskCfg, FrameTaskCfg, NullSpacePostureTaskCfg, PinkIKControllerCfg from isaaclab.envs.mdp.actions.pink_actions_cfg import PinkInverseKinematicsActionCfg from isaaclab.utils import configclass @@ -92,24 +81,24 @@ def __post_init__(self): # Determines whether Pink IK solver will fail due to a joint limit violation fail_on_joint_limit_violation=False, variable_input_tasks=[ - FrameTask( - "GR1T2_fourier_hand_6dof_left_hand_pitch_link", + FrameTaskCfg( + frame="GR1T2_fourier_hand_6dof_left_hand_pitch_link", position_cost=8.0, # [cost] / [m] orientation_cost=1.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - FrameTask( - "GR1T2_fourier_hand_6dof_right_hand_pitch_link", + FrameTaskCfg( + frame="GR1T2_fourier_hand_6dof_right_hand_pitch_link", position_cost=8.0, # [cost] / [m] orientation_cost=1.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - DampingTask( + DampingTaskCfg( cost=0.5, # [cost] * [s] / [rad] ), - NullSpacePostureTask( + NullSpacePostureTaskCfg( cost=0.2, lm_damping=1, controlled_frames=[ @@ -134,20 +123,13 @@ def __post_init__(self): fixed_input_tasks=[], ), ) - # Convert USD to URDF and change revolute joints to fixed - temp_urdf_output_path, temp_urdf_meshes_output_path = ControllerUtils.convert_usd_to_urdf( - self.scene.robot.spawn.usd_path, self.temp_urdf_dir, force_conversion=True - ) - - # Set the URDF and mesh paths for the IK controller - self.actions.gr1_action.controller.urdf_path = temp_urdf_output_path - self.actions.gr1_action.controller.mesh_path = temp_urdf_meshes_output_path + # Defer USD→URDF conversion to controller initialization (requires Isaac Sim at runtime). + self.actions.gr1_action.controller.usd_path = self.scene.robot.spawn.usd_path + self.actions.gr1_action.controller.urdf_output_dir = self.temp_urdf_dir - # IsaacTeleop-based teleoperation pipeline - if _TELEOP_AVAILABLE: - pipeline = _build_gr1t2_pickplace_pipeline() - self.isaac_teleop = IsaacTeleopCfg( - pipeline_builder=lambda: pipeline, - sim_device=self.sim.device, - xr_cfg=self.xr, - ) + # IsaacTeleop-based teleoperation pipeline. + self.isaac_teleop = IsaacTeleopCfg( + pipeline_builder=lambda: _build_gr1t2_pickplace_pipeline()[0], + sim_device=self.sim.device, + xr_cfg=self.xr, + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/mdp/__init__.py index 555bfb7cbe8..4e32930882f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/mdp/__init__.py @@ -5,8 +5,9 @@ """This sub-module contains the functions that are specific to the lift environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .observations import * # noqa: F401, F403 -from .pick_place_events import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["observations", "pick_place_events", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/nutpour_gr1t2_pink_ik_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/nutpour_gr1t2_pink_ik_env_cfg.py index 1e05d8b8589..db242eee50a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/nutpour_gr1t2_pink_ik_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/nutpour_gr1t2_pink_ik_env_cfg.py @@ -3,20 +3,9 @@ # # SPDX-License-Identifier: BSD-3-Clause -import logging +from isaaclab_teleop.isaac_teleop_cfg import IsaacTeleopCfg -from pink.tasks import DampingTask, FrameTask - -try: - from isaaclab_teleop import IsaacTeleopCfg - - _TELEOP_AVAILABLE = True -except ImportError: - _TELEOP_AVAILABLE = False - logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.") - -import isaaclab.controllers.utils as ControllerUtils -from isaaclab.controllers.pink_ik import NullSpacePostureTask, PinkIKControllerCfg +from isaaclab.controllers.pink_ik import DampingTaskCfg, FrameTaskCfg, NullSpacePostureTaskCfg, PinkIKControllerCfg from isaaclab.envs.mdp.actions.pink_actions_cfg import PinkInverseKinematicsActionCfg from isaaclab.utils import configclass @@ -90,24 +79,24 @@ def __post_init__(self): # Determines whether Pink IK solver will fail due to a joint limit violation fail_on_joint_limit_violation=False, variable_input_tasks=[ - FrameTask( - "GR1T2_fourier_hand_6dof_left_hand_pitch_link", + FrameTaskCfg( + frame="GR1T2_fourier_hand_6dof_left_hand_pitch_link", position_cost=8.0, # [cost] / [m] orientation_cost=1.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - FrameTask( - "GR1T2_fourier_hand_6dof_right_hand_pitch_link", + FrameTaskCfg( + frame="GR1T2_fourier_hand_6dof_right_hand_pitch_link", position_cost=8.0, # [cost] / [m] orientation_cost=1.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - DampingTask( + DampingTaskCfg( cost=0.5, # [cost] * [s] / [rad] ), - NullSpacePostureTask( + NullSpacePostureTaskCfg( cost=0.2, lm_damping=1, controlled_frames=[ @@ -132,20 +121,13 @@ def __post_init__(self): fixed_input_tasks=[], ), ) - # Convert USD to URDF and change revolute joints to fixed - temp_urdf_output_path, temp_urdf_meshes_output_path = ControllerUtils.convert_usd_to_urdf( - self.scene.robot.spawn.usd_path, self.temp_urdf_dir, force_conversion=True - ) - - # Set the URDF and mesh paths for the IK controller - self.actions.gr1_action.controller.urdf_path = temp_urdf_output_path - self.actions.gr1_action.controller.mesh_path = temp_urdf_meshes_output_path + # Defer USD→URDF conversion to controller initialization (requires Isaac Sim at runtime). + self.actions.gr1_action.controller.usd_path = self.scene.robot.spawn.usd_path + self.actions.gr1_action.controller.urdf_output_dir = self.temp_urdf_dir - # IsaacTeleop-based teleoperation pipeline - if _TELEOP_AVAILABLE: - pipeline = _build_gr1t2_pickplace_pipeline() - self.isaac_teleop = IsaacTeleopCfg( - pipeline_builder=lambda: pipeline, - sim_device=self.sim.device, - xr_cfg=self.xr, - ) + # IsaacTeleop-based teleoperation pipeline. + self.isaac_teleop = IsaacTeleopCfg( + pipeline_builder=lambda: _build_gr1t2_pickplace_pipeline()[0], + sim_device=self.sim.device, + xr_cfg=self.xr, + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_env_cfg.py index dd1edbf7d8b..76739b4b8ae 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_env_cfg.py @@ -3,27 +3,15 @@ # # SPDX-License-Identifier: BSD-3-Clause -import logging import os import tempfile import torch -from pink.tasks import DampingTask, FrameTask -try: - import isaacteleop # noqa: F401 -- pipeline builders need isaacteleop at runtime - from isaaclab_teleop import IsaacTeleopCfg, XrCfg - - _TELEOP_AVAILABLE = True -except ImportError: - _TELEOP_AVAILABLE = False - logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.") - -import isaaclab.controllers.utils as ControllerUtils import isaaclab.envs.mdp as base_mdp import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg -from isaaclab.controllers.pink_ik import NullSpacePostureTask, PinkIKControllerCfg +from isaaclab.controllers.pink_ik import DampingTaskCfg, FrameTaskCfg, NullSpacePostureTaskCfg, PinkIKControllerCfg from isaaclab.envs import ManagerBasedRLEnvCfg from isaaclab.envs.mdp.actions.pink_actions_cfg import PinkInverseKinematicsActionCfg from isaaclab.managers import EventTermCfg as EventTerm @@ -39,6 +27,8 @@ from . import mdp from isaaclab_assets.robots.fourier import GR1T2_HIGH_PD_CFG # isort: skip +from isaaclab_teleop.isaac_teleop_cfg import IsaacTeleopCfg # isort: skip +from isaaclab_teleop.xr_cfg import XrCfg # isort: skip def _build_gr1t2_pickplace_pipeline(): @@ -115,9 +105,21 @@ def _build_gr1t2_pickplace_pipeline(): # DexHand Retargeters (left and right hands) # ------------------------------------------------------------------------- # Resolve dex-retargeting YAML config paths from IsaacLab's retargeter data directory - import isaaclab.devices.openxr.retargeters.humanoid.fourier.gr1_t2_dex_retargeting_utils as _dex_utils - - _data_dir = os.path.abspath(os.path.join(os.path.dirname(_dex_utils.__file__), "data")) + import isaaclab_teleop.isaac_teleop_cfg as _teleop_cfg_mod + + _teleop_cfg_file = _teleop_cfg_mod.__file__ + if _teleop_cfg_file is None: + raise RuntimeError("Could not resolve isaaclab_teleop package path for dex-retargeting configs.") + _teleop_pkg_dir = os.path.dirname(_teleop_cfg_file) + _data_dir = os.path.join( + _teleop_pkg_dir, + "deprecated", + "openxr", + "retargeters", + "humanoid", + "fourier", + "data", + ) _config_dir = os.path.join(_data_dir, "configs", "dex-retargeting") left_yaml_path = os.path.join(_config_dir, "fourier_hand_left_dexpilot.yml") right_yaml_path = os.path.join(_config_dir, "fourier_hand_right_dexpilot.yml") @@ -401,24 +403,24 @@ class ActionsCfg: # Determines whether Pink IK solver will fail due to a joint limit violation fail_on_joint_limit_violation=False, variable_input_tasks=[ - FrameTask( - "GR1T2_fourier_hand_6dof_left_hand_pitch_link", + FrameTaskCfg( + frame="GR1T2_fourier_hand_6dof_left_hand_pitch_link", position_cost=8.0, # [cost] / [m] orientation_cost=1.0, # [cost] / [rad] lm_damping=12, # dampening for solver for step jumps gain=0.5, ), - FrameTask( - "GR1T2_fourier_hand_6dof_right_hand_pitch_link", + FrameTaskCfg( + frame="GR1T2_fourier_hand_6dof_right_hand_pitch_link", position_cost=8.0, # [cost] / [m] orientation_cost=1.0, # [cost] / [rad] lm_damping=12, # dampening for solver for step jumps gain=0.5, ), - DampingTask( + DampingTaskCfg( cost=0.5, # [cost] * [s] / [rad] ), - NullSpacePostureTask( + NullSpacePostureTaskCfg( cost=0.5, lm_damping=1, controlled_frames=[ @@ -597,27 +599,17 @@ def __post_init__(self): # scene settings self.scene.replicate_physics = False - # Convert USD to URDF and change revolute joints to fixed - temp_urdf_output_path, temp_urdf_meshes_output_path = ControllerUtils.convert_usd_to_urdf( - self.scene.robot.spawn.usd_path, self.temp_urdf_dir, force_conversion=True - ) + # Defer USD→URDF conversion to controller initialization (requires Isaac Sim at runtime). + self.actions.upper_body_ik.controller.usd_path = self.scene.robot.spawn.usd_path + self.actions.upper_body_ik.controller.urdf_output_dir = self.temp_urdf_dir - # Set the URDF and mesh paths for the IK controller - self.actions.upper_body_ik.controller.urdf_path = temp_urdf_output_path - self.actions.upper_body_ik.controller.mesh_path = temp_urdf_meshes_output_path - - # IsaacTeleop-based teleoperation pipeline - # Both are wrapped in lambdas so they survive @configclass deepcopy - # (retargeters contain non-picklable SWIG handles). - if _TELEOP_AVAILABLE: - self.xr = XrCfg( - anchor_pos=(0.0, 0.0, 0.0), - anchor_rot=(0.0, 0.0, 0.0, 1.0), - ) - pipeline, retargeters = _build_gr1t2_pickplace_pipeline() - self.isaac_teleop = IsaacTeleopCfg( - pipeline_builder=lambda: pipeline, - # retargeters_to_tune=lambda: retargeters, - sim_device=self.sim.device, - xr_cfg=self.xr, - ) + # IsaacTeleop-based teleoperation pipeline. + self.xr = XrCfg( + anchor_pos=(0.0, 0.0, 0.0), + anchor_rot=(0.0, 0.0, 0.0, 1.0), + ) + self.isaac_teleop = IsaacTeleopCfg( + pipeline_builder=lambda: _build_gr1t2_pickplace_pipeline()[0], + sim_device=self.sim.device, + xr_cfg=self.xr, + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_waist_enabled_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_waist_enabled_env_cfg.py index ba27679831f..1c9f19bb302 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_waist_enabled_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_gr1t2_waist_enabled_env_cfg.py @@ -3,18 +3,11 @@ # # SPDX-License-Identifier: BSD-3-Clause -import logging import tempfile -try: - from isaaclab_teleop import IsaacTeleopCfg, XrCfg +from isaaclab_teleop.isaac_teleop_cfg import IsaacTeleopCfg +from isaaclab_teleop.xr_cfg import XrCfg - _TELEOP_AVAILABLE = True -except ImportError: - _TELEOP_AVAILABLE = False - logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.") - -import isaaclab.controllers.utils as ControllerUtils from isaaclab.envs import ManagerBasedRLEnvCfg from isaaclab.utils import configclass @@ -63,25 +56,17 @@ def __post_init__(self): for joint_name in waist_joint_names: self.actions.upper_body_ik.pink_controlled_joint_names.append(joint_name) - # Convert USD to URDF and change revolute joints to fixed - temp_urdf_output_path, temp_urdf_meshes_output_path = ControllerUtils.convert_usd_to_urdf( - self.scene.robot.spawn.usd_path, self.temp_urdf_dir, force_conversion=True - ) - - # Set the URDF and mesh paths for the IK controller - self.actions.upper_body_ik.controller.urdf_path = temp_urdf_output_path - self.actions.upper_body_ik.controller.mesh_path = temp_urdf_meshes_output_path + # Defer USD→URDF conversion to controller initialization (requires Isaac Sim at runtime). + self.actions.upper_body_ik.controller.usd_path = self.scene.robot.spawn.usd_path + self.actions.upper_body_ik.controller.urdf_output_dir = self.temp_urdf_dir - # IsaacTeleop-based teleoperation pipeline - if _TELEOP_AVAILABLE: - self.xr = XrCfg( - anchor_pos=(0.0, 0.0, 0.0), - anchor_rot=(0.0, 0.0, 0.0, 1.0), - ) - pipeline, retargeters = _build_gr1t2_pickplace_pipeline() - self.isaac_teleop = IsaacTeleopCfg( - pipeline_builder=lambda: pipeline, - # retargeters_to_tune=lambda: retargeters, - sim_device=self.sim.device, - xr_cfg=self.xr, - ) + # IsaacTeleop-based teleoperation pipeline. + self.xr = XrCfg( + anchor_pos=(0.0, 0.0, 0.0), + anchor_rot=(0.0, 0.0, 0.0, 1.0), + ) + self.isaac_teleop = IsaacTeleopCfg( + pipeline_builder=lambda: _build_gr1t2_pickplace_pipeline()[0], + sim_device=self.sim.device, + xr_cfg=self.xr, + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_unitree_g1_inspire_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_unitree_g1_inspire_hand_env_cfg.py index 9f7fb2289fe..6622f8c21c3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_unitree_g1_inspire_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/pickplace_unitree_g1_inspire_hand_env_cfg.py @@ -2,27 +2,17 @@ # All rights reserved. # # SPDX-License-Identifier: BSD-3-Clause -import logging import os import tempfile import torch -from pink.tasks import FrameTask +from isaaclab_teleop.isaac_teleop_cfg import IsaacTeleopCfg +from isaaclab_teleop.xr_cfg import XrCfg -try: - import isaacteleop # noqa: F401 -- pipeline builders need isaacteleop at runtime - from isaaclab_teleop import IsaacTeleopCfg, XrCfg - - _TELEOP_AVAILABLE = True -except ImportError: - _TELEOP_AVAILABLE = False - logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.") - -import isaaclab.controllers.utils as ControllerUtils import isaaclab.envs.mdp as base_mdp import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg -from isaaclab.controllers.pink_ik import NullSpacePostureTask, PinkIKControllerCfg +from isaaclab.controllers.pink_ik import FrameTaskCfg, NullSpacePostureTaskCfg, PinkIKControllerCfg from isaaclab.envs import ManagerBasedRLEnvCfg from isaaclab.envs.mdp.actions.pink_actions_cfg import PinkInverseKinematicsActionCfg from isaaclab.managers import EventTermCfg as EventTerm @@ -406,21 +396,21 @@ class ActionsCfg: show_ik_warnings=False, fail_on_joint_limit_violation=False, variable_input_tasks=[ - FrameTask( - "g1_29dof_rev_1_0_left_wrist_yaw_link", + FrameTaskCfg( + frame="g1_29dof_rev_1_0_left_wrist_yaw_link", position_cost=8.0, # [cost] / [m] orientation_cost=2.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - FrameTask( - "g1_29dof_rev_1_0_right_wrist_yaw_link", + FrameTaskCfg( + frame="g1_29dof_rev_1_0_right_wrist_yaw_link", position_cost=8.0, # [cost] / [m] orientation_cost=2.0, # [cost] / [rad] lm_damping=10, # dampening for solver for step jumps gain=0.5, ), - NullSpacePostureTask( + NullSpacePostureTaskCfg( cost=0.5, lm_damping=1, controlled_frames=[ @@ -596,24 +586,17 @@ def __post_init__(self): self.sim.dt = 1 / 120 # 120Hz self.sim.render_interval = 2 - # Convert USD to URDF and change revolute joints to fixed - temp_urdf_output_path, temp_urdf_meshes_output_path = ControllerUtils.convert_usd_to_urdf( - self.scene.robot.spawn.usd_path, self.temp_urdf_dir, force_conversion=True - ) + # Defer USD→URDF conversion to controller initialization (requires Isaac Sim at runtime). + self.actions.pink_ik_cfg.controller.usd_path = self.scene.robot.spawn.usd_path + self.actions.pink_ik_cfg.controller.urdf_output_dir = self.temp_urdf_dir - # Set the URDF and mesh paths for the IK controller - self.actions.pink_ik_cfg.controller.urdf_path = temp_urdf_output_path - self.actions.pink_ik_cfg.controller.mesh_path = temp_urdf_meshes_output_path - - # IsaacTeleop-based teleoperation pipeline - if _TELEOP_AVAILABLE: - self.xr = XrCfg( - anchor_pos=(0.0, 0.0, 0.0), - anchor_rot=(0.0, 0.0, 0.0, 1.0), - ) - pipeline = _build_g1_inspire_pickplace_pipeline() - self.isaac_teleop = IsaacTeleopCfg( - pipeline_builder=lambda: pipeline, - sim_device=self.sim.device, - xr_cfg=self.xr, - ) + # IsaacTeleop-based teleoperation pipeline (resolved lazily at runtime). + self.xr = XrCfg( + anchor_pos=(0.0, 0.0, 0.0), + anchor_rot=(0.0, 0.0, 0.0, 1.0), + ) + self.isaac_teleop = IsaacTeleopCfg( + pipeline_builder=_build_g1_inspire_pickplace_pipeline, + sim_device=self.sim.device, + xr_cfg=self.xr, + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/place/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/place/mdp/__init__.py index 41f76fcdb1b..fe74f121523 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/place/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/place/mdp/__init__.py @@ -5,7 +5,9 @@ """This sub-module contains the functions that are specific to the pick and place environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .observations import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["observations", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/mdp/__init__.py index 3fec83fe70a..9f430ae72d5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/mdp/__init__.py @@ -5,6 +5,9 @@ """This sub-module contains the functions that are specific to the locomotion environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .rewards import * # noqa: F401, F403 +cascading_export( + submodules=["rewards"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/mdp/__init__.py index ea04fcc468e..b39cf0c7b5d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/mdp/__init__.py @@ -5,7 +5,9 @@ """This sub-module contains the functions that are specific to the lift environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .observations import * # noqa: F401, F403 -from .terminations import * # noqa: F401, F403 +cascading_export( + submodules=["observations", "terminations"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/manager_based/navigation/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/manager_based/navigation/mdp/__init__.py index 677e395f1b6..9425ec8b943 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/manager_based/navigation/mdp/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/manager_based/navigation/mdp/__init__.py @@ -5,7 +5,9 @@ """This sub-module contains the functions that are specific to the locomotion environments.""" -from isaaclab.envs.mdp import * # noqa: F401, F403 +from isaaclab.utils.module import cascading_export -from .pre_trained_policy_action_cfg import * # noqa: F401, F403 -from .rewards import * # noqa: F401, F403 +cascading_export( + submodules=["pre_trained_policy_action_cfg", "rewards"], + packages=["isaaclab.envs.mdp"], +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/utils/__init__.py index 495b207c319..8877c459ef5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/__init__.py @@ -5,5 +5,9 @@ """Sub-package with utilities, data collectors and environment wrappers.""" -from .importer import import_packages -from .parse_cfg import get_checkpoint_path, load_cfg_from_registry, parse_env_cfg +from isaaclab.utils.module import lazy_export + +lazy_export( + ("importer", ["import_packages"]), + ("parse_cfg", ["get_checkpoint_path", "load_cfg_from_registry", "parse_env_cfg"]), +) diff --git a/source/isaaclab_tasks/test/test_env_cfg_no_forbidden_imports.py b/source/isaaclab_tasks/test/test_env_cfg_no_forbidden_imports.py new file mode 100644 index 00000000000..a738755481a --- /dev/null +++ b/source/isaaclab_tasks/test/test_env_cfg_no_forbidden_imports.py @@ -0,0 +1,188 @@ +# 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 + +"""Test that env_cfg construction does not import forbidden backend modules. + +``load_cfg_from_registry`` runs BEFORE SimulationApp is launched to inspect +the physics backend and decide whether Kit is needed at all. Config classes +are pure data — they must be constructable without any runtime dependencies +that conflict with Kit's internal ``fork()`` or that require a running +simulator. + +Forbidden categories +-------------------- +1. **Backend / simulator runtime** (``pxr``, ``omni``, ``carb``, ``isaacsim``) + — require SimulationApp / Kit to be initialized first. +2. **SciPy** — loads OpenBLAS which registers ``atfork`` handlers that crash + Kit's internal ``fork()`` during startup. + +Remediation patterns +-------------------- +* Use ``lazy_loader`` (``lazy.attach``) in ``__init__.py`` files so that + implementation modules are only imported when first accessed. +* Guard annotation-only imports with ``TYPE_CHECKING``. +* Store ``class_type`` / ``func`` fields as fully-qualified strings + (e.g. ``"isaaclab.assets.articulation:Articulation"``); ``cfg.validate()`` + resolves them to callables after Kit has launched. +* Use local ``# noqa: PLC0415`` imports inside functions for Kit-dependent + symbols that cannot be imported at module level before Kit is running. + +Performance note +---------------- +All task checks are batched into a **single subprocess** so that +``import isaaclab_tasks`` (~1.6 s) is paid only once instead of once per test. +Results are returned as JSON and cached for the parametrized test functions. +""" + +import json +import subprocess +import sys +import textwrap + +import gymnasium +import pytest + +import isaaclab_tasks # noqa: F401 -- triggers task registration + +# Forbidden module prefixes -- these must NOT appear in sys.modules after +# config loading because they require SimulationApp / a specific physics +# backend to be started first, or because they are heavyweight runtime +# libraries that should never be needed to construct pure-data config objects. +_FORBIDDEN_PREFIXES = ( + # Backend / simulator runtime (require SimulationApp / Kit) + "pxr", # USD Python bindings + "omni", # Omniverse runtime + "carb", # Carbonite framework + "isaacsim", # Isaac Sim modules + # SciPy loads OpenBLAS which crashes Kit's fork() + "scipy", +) + +_ALL_ISAAC_TASKS = sorted(name for name in gymnasium.registry if name.startswith("Isaac-")) + +# --------------------------------------------------------------------------- +# Batch subprocess: run all checks in one Python process so we only pay the +# `import isaaclab_tasks` cost once (~1.6 s) instead of once per test. +# --------------------------------------------------------------------------- + + +def _build_batch_script(task_names: list[str]) -> str: + return textwrap.dedent(f"""\ + import sys, traceback, json + + FORBIDDEN = {list(_FORBIDDEN_PREFIXES)!r} + task_names = {task_names!r} + + import isaaclab_tasks # noqa: F401 + from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry + + results = {{}} + + for task_name in task_names: + violations = {{}} + load_error = None + + _orig_import = __builtins__.__import__ + + def _hook(name, *args, **kw): + top = name.split('.')[0] + if top in FORBIDDEN and top not in violations: + violations[top] = ''.join(traceback.format_stack()) + return _orig_import(name, *args, **kw) + + __builtins__.__import__ = _hook + try: + cfg = load_cfg_from_registry(task_name, "env_cfg_entry_point") + except Exception as exc: + load_error = str(exc) + finally: + __builtins__.__import__ = _orig_import + + # Note: we intentionally do NOT clean up sys.modules between tasks. + # The import hook intercepts every __import__ call regardless of + # whether the module is already cached, so it reliably catches + # violations even across tasks. Cleaning up sys.modules would force + # re-importing shared modules (e.g. velocity_env_cfg, which is + # shared by 40+ locomotion tasks) and turn a 3 s run into 17 s. + + results[task_name] = {{ + 'load_error': load_error, + 'violations': violations, + }} + + # Use a sentinel so the parser can find the JSON even when + # load_cfg_from_registry prints [INFO] lines to stdout. + print("__RESULTS__" + json.dumps(results)) + """) + + +@pytest.fixture(scope="session") +def all_cfg_check_results() -> dict: + """Run all task cfg checks in a single subprocess and return results dict.""" + script = _build_batch_script(_ALL_ISAAC_TASKS) + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=300, + ) + # Find the sentinel line (load_cfg_from_registry emits [INFO] lines to stdout) + json_line = None + for line in result.stdout.splitlines(): + if line.startswith("__RESULTS__"): + json_line = line[len("__RESULTS__") :] + break + + if json_line is None: + return { + "__subprocess_crash__": ( + f"Batch subprocess did not produce results.\n" + f"--- stdout ---\n{result.stdout}\n" + f"--- stderr ---\n{result.stderr}" + ) + } + try: + return json.loads(json_line) + except json.JSONDecodeError as exc: + return {"__json_error__": str(exc), "__raw__": json_line[:500]} + + +@pytest.mark.parametrize("task_name", _ALL_ISAAC_TASKS) +def test_config_load_does_not_import_backend_modules(task_name: str, all_cfg_check_results: dict): + """Config loading must not import forbidden runtime modules. + + Config classes are pure data. They must not pull in backend modules + (pxr, omni, carb, isaacsim) or heavyweight libraries (scipy). + + Fix: use lazy_loader (lazy.attach) in __init__.py files, TYPE_CHECKING + guards for annotation-only imports, and string references for + class_type/func fields in cfg files. + """ + if "__subprocess_crash__" in all_cfg_check_results: + pytest.fail(f"Batch check subprocess crashed:\n{all_cfg_check_results['__subprocess_crash__']}") + + if task_name not in all_cfg_check_results: + pytest.fail(f"No result for '{task_name}' - batch subprocess may have crashed.") + + info = all_cfg_check_results[task_name] + load_error = info.get("load_error") + violations = info.get("violations", {}) + + messages = [] + if load_error: + messages.append(f"ERROR: config load crashed: {load_error}") + if violations: + messages.append(f"FAIL: {len(violations)} forbidden top-level module(s) imported:") + for mod, stack in sorted(violations.items()): + messages.append(f"\n=== {mod} ===\n{stack}") + + assert not violations and not load_error, ( + f"Config loading for '{task_name}' imported forbidden backend modules.\n" + f"Forbidden prefixes: {_FORBIDDEN_PREFIXES}\n" + + "\n".join(messages) + + "\n\nFix: use lazy_loader (lazy.attach) in the offending __init__.py, " + "or move the import under TYPE_CHECKING and use a string reference " + "for isinstance checks." + ) diff --git a/source/isaaclab_teleop/isaaclab_teleop/__init__.py b/source/isaaclab_teleop/isaaclab_teleop/__init__.py index 018d25a3145..4ef33967008 100644 --- a/source/isaaclab_teleop/isaaclab_teleop/__init__.py +++ b/source/isaaclab_teleop/isaaclab_teleop/__init__.py @@ -19,7 +19,11 @@ # Configure the module-level variables __version__ = ISAACLAB_TELEOP_METADATA["package"]["version"] -from .isaac_teleop_cfg import IsaacTeleopCfg -from .isaac_teleop_device import IsaacTeleopDevice, create_isaac_teleop_device -from .xr_anchor_utils import XrAnchorSynchronizer -from .xr_cfg import XrAnchorRotationMode, XrCfg, remove_camera_configs +from isaaclab.utils.module import lazy_export + +lazy_export( + ("isaac_teleop_cfg", "IsaacTeleopCfg"), + ("isaac_teleop_device", ["IsaacTeleopDevice", "create_isaac_teleop_device"]), + ("xr_anchor_utils", "XrAnchorSynchronizer"), + ("xr_cfg", ["XrAnchorRotationMode", "XrCfg", "remove_camera_configs"]), +) diff --git a/source/isaaclab_teleop/isaaclab_teleop/deprecated/openxr/__init__.py b/source/isaaclab_teleop/isaaclab_teleop/deprecated/openxr/__init__.py index 1eee45cc4c3..c840983b36d 100644 --- a/source/isaaclab_teleop/isaaclab_teleop/deprecated/openxr/__init__.py +++ b/source/isaaclab_teleop/isaaclab_teleop/deprecated/openxr/__init__.py @@ -16,6 +16,10 @@ :mod:`isaaclab_teleop.xr_anchor_utils`. """ -from .manus_vive import ManusVive, ManusViveCfg -from .openxr_device import OpenXRDevice, OpenXRDeviceCfg -from .xr_cfg import XrAnchorRotationMode, XrCfg, remove_camera_configs +from isaaclab.utils.module import lazy_export + +lazy_export( + ("manus_vive", ["ManusVive", "ManusViveCfg"]), + ("openxr_device", ["OpenXRDevice", "OpenXRDeviceCfg"]), + ("xr_cfg", ["XrAnchorRotationMode", "XrCfg", "remove_camera_configs"]), +)