diff --git a/.github/workflows/sync-and-translate.yml b/.github/workflows/sync-and-translate.yml index 354b7df8cf..f5024e0326 100644 --- a/.github/workflows/sync-and-translate.yml +++ b/.github/workflows/sync-and-translate.yml @@ -68,7 +68,6 @@ jobs: - name: Commit and push gettext and po files to main branch run: | - git add -f docs/_build/html/* git add -f docs/locale/zh_CN/LC_MESSAGES/**/*.po git commit -m "Update gettext and po files" git push origin main diff --git a/docs/_build/html/_images/a1_flat.jpg b/docs/_build/html/_images/a1_flat.jpg deleted file mode 100644 index c47be98f18..0000000000 Binary files a/docs/_build/html/_images/a1_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/a1_rough.jpg b/docs/_build/html/_images/a1_rough.jpg deleted file mode 100644 index fdee8d2981..0000000000 Binary files a/docs/_build/html/_images/a1_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/a3c-dark.svg b/docs/_build/html/_images/a3c-dark.svg deleted file mode 100644 index 48cdb84914..0000000000 --- a/docs/_build/html/_images/a3c-dark.svg +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_build/html/_images/a3c-light.svg b/docs/_build/html/_images/a3c-light.svg deleted file mode 100644 index d24bfc20b7..0000000000 --- a/docs/_build/html/_images/a3c-light.svg +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_build/html/_images/actuator-dark.svg b/docs/_build/html/_images/actuator-dark.svg deleted file mode 100644 index b9e0682395..0000000000 --- a/docs/_build/html/_images/actuator-dark.svg +++ /dev/null @@ -1,522 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - DC Motor - Actuator Net(MLP/LSTM) - - Gripper - - - Arm - Base - Mimic Group - - - - open/close (1) - joint position(6) - joint position(12) - joint torque(12) - joint torque(6) - joint velocity(6) - - Simulation - - - - Actions - - - - - - - - - - - diff --git a/docs/_build/html/_images/actuator-light.svg b/docs/_build/html/_images/actuator-light.svg deleted file mode 100644 index 214b5a7fee..0000000000 --- a/docs/_build/html/_images/actuator-light.svg +++ /dev/null @@ -1,10214 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - DC Motor - Actuator Net(MLP/LSTM) - - Gripper - - - Arm - Base - Mimic Group - - - - open/close (1) - joint position(6) - joint position(12) - joint torque(12) - joint torque(6) - joint velocity(6) - - Simulation - - - - Actions - - - - - - - - - - - diff --git a/docs/_build/html/_images/allegro_cube.jpg b/docs/_build/html/_images/allegro_cube.jpg deleted file mode 100644 index 7ada7d8c1a..0000000000 Binary files a/docs/_build/html/_images/allegro_cube.jpg and /dev/null differ diff --git a/docs/_build/html/_images/ant.jpg b/docs/_build/html/_images/ant.jpg deleted file mode 100644 index d5eadb6b8e..0000000000 Binary files a/docs/_build/html/_images/ant.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_b_flat.jpg b/docs/_build/html/_images/anymal_b_flat.jpg deleted file mode 100644 index f2b6f0bfef..0000000000 Binary files a/docs/_build/html/_images/anymal_b_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_b_rough.jpg b/docs/_build/html/_images/anymal_b_rough.jpg deleted file mode 100644 index f00c50761e..0000000000 Binary files a/docs/_build/html/_images/anymal_b_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_c_flat.jpg b/docs/_build/html/_images/anymal_c_flat.jpg deleted file mode 100644 index 295f441f2c..0000000000 Binary files a/docs/_build/html/_images/anymal_c_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_c_nav.jpg b/docs/_build/html/_images/anymal_c_nav.jpg deleted file mode 100644 index 12766e6075..0000000000 Binary files a/docs/_build/html/_images/anymal_c_nav.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_c_rough.jpg b/docs/_build/html/_images/anymal_c_rough.jpg deleted file mode 100644 index 92ebc2fc42..0000000000 Binary files a/docs/_build/html/_images/anymal_c_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_d_flat.jpg b/docs/_build/html/_images/anymal_d_flat.jpg deleted file mode 100644 index 1e33d34c7d..0000000000 Binary files a/docs/_build/html/_images/anymal_d_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/anymal_d_rough.jpg b/docs/_build/html/_images/anymal_d_rough.jpg deleted file mode 100644 index fd57324fdd..0000000000 Binary files a/docs/_build/html/_images/anymal_d_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/box_terrain.jpg b/docs/_build/html/_images/box_terrain.jpg deleted file mode 100644 index 2694433c06..0000000000 Binary files a/docs/_build/html/_images/box_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/box_terrain_with_two_boxes.jpg b/docs/_build/html/_images/box_terrain_with_two_boxes.jpg deleted file mode 100644 index c2715442a7..0000000000 Binary files a/docs/_build/html/_images/box_terrain_with_two_boxes.jpg and /dev/null differ diff --git a/docs/_build/html/_images/cartpole.jpg b/docs/_build/html/_images/cartpole.jpg deleted file mode 100644 index 4eabbb80a7..0000000000 Binary files a/docs/_build/html/_images/cartpole.jpg and /dev/null differ diff --git a/docs/_build/html/_images/discrete_obstacles_terrain.jpg b/docs/_build/html/_images/discrete_obstacles_terrain.jpg deleted file mode 100644 index ad56e56e55..0000000000 Binary files a/docs/_build/html/_images/discrete_obstacles_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/flat_terrain.jpg b/docs/_build/html/_images/flat_terrain.jpg deleted file mode 100644 index 5464cc9117..0000000000 Binary files a/docs/_build/html/_images/flat_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/floating_ring_terrain.jpg b/docs/_build/html/_images/floating_ring_terrain.jpg deleted file mode 100644 index e00a7afbd7..0000000000 Binary files a/docs/_build/html/_images/floating_ring_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/franka_lift.jpg b/docs/_build/html/_images/franka_lift.jpg deleted file mode 100644 index 1784ced522..0000000000 Binary files a/docs/_build/html/_images/franka_lift.jpg and /dev/null differ diff --git a/docs/_build/html/_images/franka_open_drawer.jpg b/docs/_build/html/_images/franka_open_drawer.jpg deleted file mode 100644 index 42249d5aff..0000000000 Binary files a/docs/_build/html/_images/franka_open_drawer.jpg and /dev/null differ diff --git a/docs/_build/html/_images/franka_reach.jpg b/docs/_build/html/_images/franka_reach.jpg deleted file mode 100644 index 19f140c167..0000000000 Binary files a/docs/_build/html/_images/franka_reach.jpg and /dev/null differ diff --git a/docs/_build/html/_images/g1_flat.jpg b/docs/_build/html/_images/g1_flat.jpg deleted file mode 100644 index cf9e1b1482..0000000000 Binary files a/docs/_build/html/_images/g1_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/g1_rough.jpg b/docs/_build/html/_images/g1_rough.jpg deleted file mode 100644 index 25ed68d6e1..0000000000 Binary files a/docs/_build/html/_images/g1_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/gap_terrain.jpg b/docs/_build/html/_images/gap_terrain.jpg deleted file mode 100644 index 22e024455b..0000000000 Binary files a/docs/_build/html/_images/gap_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/go1_flat.jpg b/docs/_build/html/_images/go1_flat.jpg deleted file mode 100644 index 4c9319d61b..0000000000 Binary files a/docs/_build/html/_images/go1_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/go1_rough.jpg b/docs/_build/html/_images/go1_rough.jpg deleted file mode 100644 index 6a4e832bd2..0000000000 Binary files a/docs/_build/html/_images/go1_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/go2_flat.jpg b/docs/_build/html/_images/go2_flat.jpg deleted file mode 100644 index 0a42c60b7a..0000000000 Binary files a/docs/_build/html/_images/go2_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/go2_rough.jpg b/docs/_build/html/_images/go2_rough.jpg deleted file mode 100644 index 2baa1eeaf9..0000000000 Binary files a/docs/_build/html/_images/go2_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/h1_flat.jpg b/docs/_build/html/_images/h1_flat.jpg deleted file mode 100644 index 2c7cb58bd8..0000000000 Binary files a/docs/_build/html/_images/h1_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/h1_rough.jpg b/docs/_build/html/_images/h1_rough.jpg deleted file mode 100644 index dc6052fc94..0000000000 Binary files a/docs/_build/html/_images/h1_rough.jpg and /dev/null differ diff --git a/docs/_build/html/_images/humanoid.jpg b/docs/_build/html/_images/humanoid.jpg deleted file mode 100644 index 84dc55d2b2..0000000000 Binary files a/docs/_build/html/_images/humanoid.jpg and /dev/null differ diff --git a/docs/_build/html/_images/inverted_pyramid_sloped_terrain.jpg b/docs/_build/html/_images/inverted_pyramid_sloped_terrain.jpg deleted file mode 100644 index 9ca4ece222..0000000000 Binary files a/docs/_build/html/_images/inverted_pyramid_sloped_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/inverted_pyramid_stairs_terrain.jpg b/docs/_build/html/_images/inverted_pyramid_stairs_terrain.jpg deleted file mode 100644 index 47ff07070d..0000000000 Binary files a/docs/_build/html/_images/inverted_pyramid_stairs_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/inverted_pyramid_stairs_terrain1.jpg b/docs/_build/html/_images/inverted_pyramid_stairs_terrain1.jpg deleted file mode 100644 index 3b92cc216a..0000000000 Binary files a/docs/_build/html/_images/inverted_pyramid_stairs_terrain1.jpg and /dev/null differ diff --git a/docs/_build/html/_images/inverted_pyramid_stairs_terrain_with_holes.jpg b/docs/_build/html/_images/inverted_pyramid_stairs_terrain_with_holes.jpg deleted file mode 100644 index 77120d29b6..0000000000 Binary files a/docs/_build/html/_images/inverted_pyramid_stairs_terrain_with_holes.jpg and /dev/null differ diff --git a/docs/_build/html/_images/isaaclab.jpg b/docs/_build/html/_images/isaaclab.jpg deleted file mode 100644 index 16072e704a..0000000000 Binary files a/docs/_build/html/_images/isaaclab.jpg and /dev/null differ diff --git a/docs/_build/html/_images/pit_terrain.jpg b/docs/_build/html/_images/pit_terrain.jpg deleted file mode 100644 index 6456c2d1f1..0000000000 Binary files a/docs/_build/html/_images/pit_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/pit_terrain_with_two_levels.jpg b/docs/_build/html/_images/pit_terrain_with_two_levels.jpg deleted file mode 100644 index d71f828895..0000000000 Binary files a/docs/_build/html/_images/pit_terrain_with_two_levels.jpg and /dev/null differ diff --git a/docs/_build/html/_images/pyramid_sloped_terrain.jpg b/docs/_build/html/_images/pyramid_sloped_terrain.jpg deleted file mode 100644 index eccf3884ed..0000000000 Binary files a/docs/_build/html/_images/pyramid_sloped_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/pyramid_stairs_terrain.jpg b/docs/_build/html/_images/pyramid_stairs_terrain.jpg deleted file mode 100644 index eec9f84de6..0000000000 Binary files a/docs/_build/html/_images/pyramid_stairs_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/pyramid_stairs_terrain1.jpg b/docs/_build/html/_images/pyramid_stairs_terrain1.jpg deleted file mode 100644 index 8c77619e2f..0000000000 Binary files a/docs/_build/html/_images/pyramid_stairs_terrain1.jpg and /dev/null differ diff --git a/docs/_build/html/_images/pyramid_stairs_terrain_with_holes.jpg b/docs/_build/html/_images/pyramid_stairs_terrain_with_holes.jpg deleted file mode 100644 index cac7e9671e..0000000000 Binary files a/docs/_build/html/_images/pyramid_stairs_terrain_with_holes.jpg and /dev/null differ diff --git a/docs/_build/html/_images/quadcopter.jpg b/docs/_build/html/_images/quadcopter.jpg deleted file mode 100644 index 1b62fd8ed6..0000000000 Binary files a/docs/_build/html/_images/quadcopter.jpg and /dev/null differ diff --git a/docs/_build/html/_images/rails_terrain.jpg b/docs/_build/html/_images/rails_terrain.jpg deleted file mode 100644 index 89c2b21bb5..0000000000 Binary files a/docs/_build/html/_images/rails_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/random_grid_terrain.jpg b/docs/_build/html/_images/random_grid_terrain.jpg deleted file mode 100644 index fb0eac1234..0000000000 Binary files a/docs/_build/html/_images/random_grid_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/random_grid_terrain_with_holes.jpg b/docs/_build/html/_images/random_grid_terrain_with_holes.jpg deleted file mode 100644 index 57fa80efc6..0000000000 Binary files a/docs/_build/html/_images/random_grid_terrain_with_holes.jpg and /dev/null differ diff --git a/docs/_build/html/_images/random_uniform_terrain.jpg b/docs/_build/html/_images/random_uniform_terrain.jpg deleted file mode 100644 index 5ea4796cd0..0000000000 Binary files a/docs/_build/html/_images/random_uniform_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/repeated_objects_box_terrain.jpg b/docs/_build/html/_images/repeated_objects_box_terrain.jpg deleted file mode 100644 index 3c42279ec6..0000000000 Binary files a/docs/_build/html/_images/repeated_objects_box_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/repeated_objects_cylinder_terrain.jpg b/docs/_build/html/_images/repeated_objects_cylinder_terrain.jpg deleted file mode 100644 index 2864e86469..0000000000 Binary files a/docs/_build/html/_images/repeated_objects_cylinder_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/repeated_objects_pyramid_terrain.jpg b/docs/_build/html/_images/repeated_objects_pyramid_terrain.jpg deleted file mode 100644 index 3c7585d7d5..0000000000 Binary files a/docs/_build/html/_images/repeated_objects_pyramid_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/shadow_cube.jpg b/docs/_build/html/_images/shadow_cube.jpg deleted file mode 100644 index ba30f096e4..0000000000 Binary files a/docs/_build/html/_images/shadow_cube.jpg and /dev/null differ diff --git a/docs/_build/html/_images/spot_flat.jpg b/docs/_build/html/_images/spot_flat.jpg deleted file mode 100644 index 5176156596..0000000000 Binary files a/docs/_build/html/_images/spot_flat.jpg and /dev/null differ diff --git a/docs/_build/html/_images/star_terrain.jpg b/docs/_build/html/_images/star_terrain.jpg deleted file mode 100644 index 86e92aefe8..0000000000 Binary files a/docs/_build/html/_images/star_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/stepping_stones_terrain.jpg b/docs/_build/html/_images/stepping_stones_terrain.jpg deleted file mode 100644 index ecc08fd788..0000000000 Binary files a/docs/_build/html/_images/stepping_stones_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_images/tasks.jpg b/docs/_build/html/_images/tasks.jpg deleted file mode 100644 index 8aa96c179c..0000000000 Binary files a/docs/_build/html/_images/tasks.jpg and /dev/null differ diff --git a/docs/_build/html/_images/ur10_reach.jpg b/docs/_build/html/_images/ur10_reach.jpg deleted file mode 100644 index f32a241272..0000000000 Binary files a/docs/_build/html/_images/ur10_reach.jpg and /dev/null differ diff --git a/docs/_build/html/_images/vscode_tasks.png b/docs/_build/html/_images/vscode_tasks.png deleted file mode 100644 index 41d5cdb471..0000000000 Binary files a/docs/_build/html/_images/vscode_tasks.png and /dev/null differ diff --git a/docs/_build/html/_images/wave_terrain.jpg b/docs/_build/html/_images/wave_terrain.jpg deleted file mode 100644 index 2b759f113c..0000000000 Binary files a/docs/_build/html/_images/wave_terrain.jpg and /dev/null differ diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html deleted file mode 100644 index 6913aa8f0d..0000000000 --- a/docs/_build/html/_modules/index.html +++ /dev/null @@ -1,693 +0,0 @@ - - - - - - - - - - - 概览:模块代码 — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

代码可用的所有模块

- - -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_base.html b/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_base.html deleted file mode 100644 index f62cf27b7a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_base.html +++ /dev/null @@ -1,811 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.actuators.actuator_base — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.actuators.actuator_base 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from abc import ABC, abstractmethod
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-from omni.isaac.core.utils.types import ArticulationActions
-
-import omni.isaac.lab.utils.string as string_utils
-
-if TYPE_CHECKING:
-    from .actuator_cfg import ActuatorBaseCfg
-
-
-
[文档]class ActuatorBase(ABC): - """Base class for actuator models over a collection of actuated joints in an articulation. - - Actuator models augment the simulated articulation joints with an external drive dynamics model. - The model is used to convert the user-provided joint commands (positions, velocities and efforts) - into the desired joint positions, velocities and efforts that are applied to the simulated articulation. - - The base class provides the interface for the actuator models. It is responsible for parsing the - actuator parameters from the configuration and storing them as buffers. It also provides the - interface for resetting the actuator state and computing the desired joint commands for the simulation. - - For each actuator model, a corresponding configuration class is provided. The configuration class - is used to parse the actuator parameters from the configuration. It also specifies the joint names - for which the actuator model is applied. These names can be specified as regular expressions, which - are matched against the joint names in the articulation. - - To see how the class is used, check the :class:`omni.isaac.lab.assets.Articulation` class. - """ - - computed_effort: torch.Tensor - """The computed effort for the actuator group. Shape is (num_envs, num_joints).""" - applied_effort: torch.Tensor - """The applied effort for the actuator group. Shape is (num_envs, num_joints).""" - effort_limit: torch.Tensor - """The effort limit for the actuator group. Shape is (num_envs, num_joints).""" - velocity_limit: torch.Tensor - """The velocity limit for the actuator group. Shape is (num_envs, num_joints).""" - stiffness: torch.Tensor - """The stiffness (P gain) of the PD controller. Shape is (num_envs, num_joints).""" - damping: torch.Tensor - """The damping (D gain) of the PD controller. Shape is (num_envs, num_joints).""" - armature: torch.Tensor - """The armature of the actuator joints. Shape is (num_envs, num_joints).""" - friction: torch.Tensor - """The joint friction of the actuator joints. Shape is (num_envs, num_joints).""" - -
[文档] def __init__( - self, - cfg: ActuatorBaseCfg, - joint_names: list[str], - joint_ids: slice | Sequence[int], - num_envs: int, - device: str, - stiffness: torch.Tensor | float = 0.0, - damping: torch.Tensor | float = 0.0, - armature: torch.Tensor | float = 0.0, - friction: torch.Tensor | float = 0.0, - effort_limit: torch.Tensor | float = torch.inf, - velocity_limit: torch.Tensor | float = torch.inf, - ): - """Initialize the actuator. - - Note: - The actuator parameters are parsed from the configuration and stored as buffers. If the parameters - are not specified in the configuration, then the default values provided in the arguments are used. - - Args: - cfg: The configuration of the actuator model. - joint_names: The joint names in the articulation. - joint_ids: The joint indices in the articulation. If :obj:`slice(None)`, then all - the joints in the articulation are part of the group. - num_envs: Number of articulations in the view. - device: Device used for processing. - stiffness: The default joint stiffness (P gain). Defaults to 0.0. - If a tensor, then the shape is (num_envs, num_joints). - damping: The default joint damping (D gain). Defaults to 0.0. - If a tensor, then the shape is (num_envs, num_joints). - armature: The default joint armature. Defaults to 0.0. - If a tensor, then the shape is (num_envs, num_joints). - friction: The default joint friction. Defaults to 0.0. - If a tensor, then the shape is (num_envs, num_joints). - effort_limit: The default effort limit. Defaults to infinity. - If a tensor, then the shape is (num_envs, num_joints). - velocity_limit: The default velocity limit. Defaults to infinity. - If a tensor, then the shape is (num_envs, num_joints). - """ - # save parameters - self.cfg = cfg - self._num_envs = num_envs - self._device = device - self._joint_names = joint_names - self._joint_indices = joint_ids - - # parse joint stiffness and damping - self.stiffness = self._parse_joint_parameter(self.cfg.stiffness, stiffness) - self.damping = self._parse_joint_parameter(self.cfg.damping, damping) - # parse joint armature and friction - self.armature = self._parse_joint_parameter(self.cfg.armature, armature) - self.friction = self._parse_joint_parameter(self.cfg.friction, friction) - # parse joint limits - # note: for velocity limits, we don't have USD parameter, so default is infinity - self.effort_limit = self._parse_joint_parameter(self.cfg.effort_limit, effort_limit) - self.velocity_limit = self._parse_joint_parameter(self.cfg.velocity_limit, velocity_limit) - - # create commands buffers for allocation - self.computed_effort = torch.zeros(self._num_envs, self.num_joints, device=self._device) - self.applied_effort = torch.zeros_like(self.computed_effort)
- - def __str__(self) -> str: - """Returns: A string representation of the actuator group.""" - # resolve joint indices for printing - joint_indices = self.joint_indices - if joint_indices == slice(None): - joint_indices = list(range(self.num_joints)) - return ( - f"<class {self.__class__.__name__}> object:\n" - f"\tNumber of joints : {self.num_joints}\n" - f"\tJoint names expression: {self.cfg.joint_names_expr}\n" - f"\tJoint names : {self.joint_names}\n" - f"\tJoint indices : {joint_indices}\n" - ) - - """ - Properties. - """ - - @property - def num_joints(self) -> int: - """Number of actuators in the group.""" - return len(self._joint_names) - - @property - def joint_names(self) -> list[str]: - """Articulation's joint names that are part of the group.""" - return self._joint_names - - @property - def joint_indices(self) -> slice | Sequence[int]: - """Articulation's joint indices that are part of the group. - - Note: - If :obj:`slice(None)` is returned, then the group contains all the joints in the articulation. - We do this to avoid unnecessary indexing of the joints for performance reasons. - """ - return self._joint_indices - - """ - Operations. - """ - -
[文档] @abstractmethod - def reset(self, env_ids: Sequence[int]): - """Reset the internals within the group. - - Args: - env_ids: List of environment IDs to reset. - """ - raise NotImplementedError
- -
[文档] @abstractmethod - def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - """Process the actuator group actions and compute the articulation actions. - - It computes the articulation actions based on the actuator model type - - Args: - control_action: The joint action instance comprising of the desired joint positions, joint velocities - and (feed-forward) joint efforts. - joint_pos: The current joint positions of the joints in the group. Shape is (num_envs, num_joints). - joint_vel: The current joint velocities of the joints in the group. Shape is (num_envs, num_joints). - - Returns: - The computed desired joint positions, joint velocities and joint efforts. - """ - raise NotImplementedError
- - """ - Helper functions. - """ - - def _parse_joint_parameter( - self, cfg_value: float | dict[str, float] | None, default_value: float | torch.Tensor | None - ) -> torch.Tensor: - """Parse the joint parameter from the configuration. - - Args: - cfg_value: The parameter value from the configuration. If None, then use the default value. - default_value: The default value to use if the parameter is None. If it is also None, - then an error is raised. - - Returns: - The parsed parameter value. - - Raises: - TypeError: If the parameter value is not of the expected type. - TypeError: If the default value is not of the expected type. - ValueError: If the parameter value is None and no default value is provided. - """ - # create parameter buffer - param = torch.zeros(self._num_envs, self.num_joints, device=self._device) - # parse the parameter - if cfg_value is not None: - if isinstance(cfg_value, (float, int)): - # if float, then use the same value for all joints - param[:] = float(cfg_value) - elif isinstance(cfg_value, dict): - # if dict, then parse the regular expression - indices, _, values = string_utils.resolve_matching_names_values(cfg_value, self.joint_names) - # note: need to specify type to be safe (e.g. values are ints, but we want floats) - param[:, indices] = torch.tensor(values, dtype=torch.float, device=self._device) - else: - raise TypeError(f"Invalid type for parameter value: {type(cfg_value)}. Expected float or dict.") - elif default_value is not None: - if isinstance(default_value, (float, int)): - # if float, then use the same value for all joints - param[:] = float(default_value) - elif isinstance(default_value, torch.Tensor): - # if tensor, then use the same tensor for all joints - param[:] = default_value.float() - else: - raise TypeError(f"Invalid type for default value: {type(default_value)}. Expected float or Tensor.") - else: - raise ValueError("The parameter value is None and no default value is provided.") - - return param - - def _clip_effort(self, effort: torch.Tensor) -> torch.Tensor: - """Clip the desired torques based on the motor limits. - - Args: - desired_torques: The desired torques to clip. - - Returns: - The clipped torques. - """ - return torch.clip(effort, min=-self.effort_limit, max=self.effort_limit)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_cfg.html deleted file mode 100644 index a87a4116ac..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_cfg.html +++ /dev/null @@ -1,752 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.actuators.actuator_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.actuators.actuator_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-from collections.abc import Iterable
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-from . import actuator_net, actuator_pd
-from .actuator_base import ActuatorBase
-
-
-
[文档]@configclass -class ActuatorBaseCfg: - """Configuration for default actuators in an articulation.""" - - class_type: type[ActuatorBase] = MISSING - """The associated actuator class. - - The class should inherit from :class:`omni.isaac.lab.actuators.ActuatorBase`. - """ - - joint_names_expr: list[str] = MISSING - """Articulation's joint names that are part of the group. - - Note: - This can be a list of joint names or a list of regex expressions (e.g. ".*"). - """ - - effort_limit: dict[str, float] | float | None = None - """Force/Torque limit of the joints in the group. Defaults to None. - - If None, the limit is set to the value specified in the USD joint prim. - """ - - velocity_limit: dict[str, float] | float | None = None - """Velocity limit of the joints in the group. Defaults to None. - - If None, the limit is set to the value specified in the USD joint prim. - """ - - stiffness: dict[str, float] | float | None = MISSING - """Stiffness gains (also known as p-gain) of the joints in the group. - - If None, the stiffness is set to the value from the USD joint prim. - """ - - damping: dict[str, float] | float | None = MISSING - """Damping gains (also known as d-gain) of the joints in the group. - - If None, the damping is set to the value from the USD joint prim. - """ - - armature: dict[str, float] | float | None = None - """Armature of the joints in the group. Defaults to None. - - If None, the armature is set to the value from the USD joint prim. - """ - - friction: dict[str, float] | float | None = None - """Joint friction of the joints in the group. Defaults to None. - - If None, the joint friction is set to the value from the USD joint prim. - """
- - -""" -Implicit Actuator Models. -""" - - -
[文档]@configclass -class ImplicitActuatorCfg(ActuatorBaseCfg): - """Configuration for an implicit actuator. - - Note: - The PD control is handled implicitly by the simulation. - """ - - class_type: type = actuator_pd.ImplicitActuator
- - -""" -Explicit Actuator Models. -""" - - -
[文档]@configclass -class IdealPDActuatorCfg(ActuatorBaseCfg): - """Configuration for an ideal PD actuator.""" - - class_type: type = actuator_pd.IdealPDActuator
- - -
[文档]@configclass -class DCMotorCfg(IdealPDActuatorCfg): - """Configuration for direct control (DC) motor actuator model.""" - - class_type: type = actuator_pd.DCMotor - - saturation_effort: float = MISSING - """Peak motor force/torque of the electric DC motor (in N-m)."""
- - -
[文档]@configclass -class ActuatorNetLSTMCfg(DCMotorCfg): - """Configuration for LSTM-based actuator model.""" - - class_type: type = actuator_net.ActuatorNetLSTM - # we don't use stiffness and damping for actuator net - stiffness = None - damping = None - - network_file: str = MISSING - """Path to the file containing network weights."""
- - -
[文档]@configclass -class ActuatorNetMLPCfg(DCMotorCfg): - """Configuration for MLP-based actuator model.""" - - class_type: type = actuator_net.ActuatorNetMLP - # we don't use stiffness and damping for actuator net - stiffness = None - damping = None - - network_file: str = MISSING - """Path to the file containing network weights.""" - - pos_scale: float = MISSING - """Scaling of the joint position errors input to the network.""" - vel_scale: float = MISSING - """Scaling of the joint velocities input to the network.""" - torque_scale: float = MISSING - """Scaling of the joint efforts output from the network.""" - - input_order: Literal["pos_vel", "vel_pos"] = MISSING - """Order of the inputs to the network. - - The order can be one of the following: - - * ``"pos_vel"``: joint position errors followed by joint velocities - * ``"vel_pos"``: joint velocities followed by joint position errors - """ - - input_idx: Iterable[int] = MISSING - """ - Indices of the actuator history buffer passed as inputs to the network. - - The index *0* corresponds to current time-step, while *n* corresponds to n-th - time-step in the past. The allocated history length is `max(input_idx) + 1`. - """
- - -
[文档]@configclass -class DelayedPDActuatorCfg(IdealPDActuatorCfg): - """Configuration for a delayed PD actuator.""" - - class_type: type = actuator_pd.DelayedPDActuator - - min_delay: int = 0 - """Minimum number of physics time-steps with which the actuator command may be delayed. Defaults to 0.""" - - max_delay: int = 0 - """Maximum number of physics time-steps with which the actuator command may be delayed. Defaults to 0."""
- - -
[文档]@configclass -class RemotizedPDActuatorCfg(DelayedPDActuatorCfg): - """Configuration for a remotized PD actuator. - - Note: - The torque output limits for this actuator is derived from a linear interpolation of a lookup table - in :attr:`joint_parameter_lookup`. This table describes the relationship between joint angles and - the output torques. - """ - - class_type: type = actuator_pd.RemotizedPDActuator - - joint_parameter_lookup: torch.Tensor = MISSING - """Joint parameter lookup table. Shape is (num_lookup_points, 3). - - This tensor describes the relationship between the joint angle (rad), the transmission ratio (in/out), - and the output torque (N*m). The table is used to interpolate the output torque based on the joint angle. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_net.html b/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_net.html deleted file mode 100644 index 620c973f6f..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_net.html +++ /dev/null @@ -1,752 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.actuators.actuator_net — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.actuators.actuator_net 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Neural network models for actuators.
-
-Currently, the following models are supported:
-
-* Multi-Layer Perceptron (MLP)
-* Long Short-Term Memory (LSTM)
-
-"""
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-from omni.isaac.core.utils.types import ArticulationActions
-
-from omni.isaac.lab.utils.assets import read_file
-
-from .actuator_pd import DCMotor
-
-if TYPE_CHECKING:
-    from .actuator_cfg import ActuatorNetLSTMCfg, ActuatorNetMLPCfg
-
-
-
[文档]class ActuatorNetLSTM(DCMotor): - """Actuator model based on recurrent neural network (LSTM). - - Unlike the MLP implementation :cite:t:`hwangbo2019learning`, this class implements - the learned model as a temporal neural network (LSTM) based on the work from - :cite:t:`rudin2022learning`. This removes the need of storing a history as the - hidden states of the recurrent network captures the history. - - Note: - Only the desired joint positions are used as inputs to the network. - """ - - cfg: ActuatorNetLSTMCfg - """The configuration of the actuator model.""" - -
[文档] def __init__(self, cfg: ActuatorNetLSTMCfg, *args, **kwargs): - super().__init__(cfg, *args, **kwargs) - - # load the model from JIT file - file_bytes = read_file(self.cfg.network_file) - self.network = torch.jit.load(file_bytes, map_location=self._device) - - # extract number of lstm layers and hidden dim from the shape of weights - num_layers = len(self.network.lstm.state_dict()) // 4 - hidden_dim = self.network.lstm.state_dict()["weight_hh_l0"].shape[1] - # create buffers for storing LSTM inputs - self.sea_input = torch.zeros(self._num_envs * self.num_joints, 1, 2, device=self._device) - self.sea_hidden_state = torch.zeros( - num_layers, self._num_envs * self.num_joints, hidden_dim, device=self._device - ) - self.sea_cell_state = torch.zeros(num_layers, self._num_envs * self.num_joints, hidden_dim, device=self._device) - # reshape via views (doesn't change the actual memory layout) - layer_shape_per_env = (num_layers, self._num_envs, self.num_joints, hidden_dim) - self.sea_hidden_state_per_env = self.sea_hidden_state.view(layer_shape_per_env) - self.sea_cell_state_per_env = self.sea_cell_state.view(layer_shape_per_env)
- - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int]): - # reset the hidden and cell states for the specified environments - with torch.no_grad(): - self.sea_hidden_state_per_env[:, env_ids] = 0.0 - self.sea_cell_state_per_env[:, env_ids] = 0.0
- -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - # compute network inputs - self.sea_input[:, 0, 0] = (control_action.joint_positions - joint_pos).flatten() - self.sea_input[:, 0, 1] = joint_vel.flatten() - # save current joint vel for dc-motor clipping - self._joint_vel[:] = joint_vel - - # run network inference - with torch.inference_mode(): - torques, (self.sea_hidden_state[:], self.sea_cell_state[:]) = self.network( - self.sea_input, (self.sea_hidden_state, self.sea_cell_state) - ) - self.computed_effort = torques.reshape(self._num_envs, self.num_joints) - - # clip the computed effort based on the motor limits - self.applied_effort = self._clip_effort(self.computed_effort) - - # return torques - control_action.joint_efforts = self.applied_effort - control_action.joint_positions = None - control_action.joint_velocities = None - return control_action
- - -
[文档]class ActuatorNetMLP(DCMotor): - """Actuator model based on multi-layer perceptron and joint history. - - Many times the analytical model is not sufficient to capture the actuator dynamics, the - delay in the actuator response, or the non-linearities in the actuator. In these cases, - a neural network model can be used to approximate the actuator dynamics. This model is - trained using data collected from the physical actuator and maps the joint state and the - desired joint command to the produced torque by the actuator. - - This class implements the learned model as a neural network based on the work from - :cite:t:`hwangbo2019learning`. The class stores the history of the joint positions errors - and velocities which are used to provide input to the neural network. The model is loaded - as a TorchScript. - - Note: - Only the desired joint positions are used as inputs to the network. - - """ - - cfg: ActuatorNetMLPCfg - """The configuration of the actuator model.""" - -
[文档] def __init__(self, cfg: ActuatorNetMLPCfg, *args, **kwargs): - super().__init__(cfg, *args, **kwargs) - - # load the model from JIT file - file_bytes = read_file(self.cfg.network_file) - self.network = torch.jit.load(file_bytes, map_location=self._device) - - # create buffers for MLP history - history_length = max(self.cfg.input_idx) + 1 - self._joint_pos_error_history = torch.zeros( - self._num_envs, history_length, self.num_joints, device=self._device - ) - self._joint_vel_history = torch.zeros(self._num_envs, history_length, self.num_joints, device=self._device)
- - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int]): - # reset the history for the specified environments - self._joint_pos_error_history[env_ids] = 0.0 - self._joint_vel_history[env_ids] = 0.0
- -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - # move history queue by 1 and update top of history - # -- positions - self._joint_pos_error_history = self._joint_pos_error_history.roll(1, 1) - self._joint_pos_error_history[:, 0] = control_action.joint_positions - joint_pos - # -- velocity - self._joint_vel_history = self._joint_vel_history.roll(1, 1) - self._joint_vel_history[:, 0] = joint_vel - # save current joint vel for dc-motor clipping - self._joint_vel[:] = joint_vel - - # compute network inputs - # -- positions - pos_input = torch.cat([self._joint_pos_error_history[:, i].unsqueeze(2) for i in self.cfg.input_idx], dim=2) - pos_input = pos_input.view(self._num_envs * self.num_joints, -1) - # -- velocity - vel_input = torch.cat([self._joint_vel_history[:, i].unsqueeze(2) for i in self.cfg.input_idx], dim=2) - vel_input = vel_input.view(self._num_envs * self.num_joints, -1) - # -- scale and concatenate inputs - if self.cfg.input_order == "pos_vel": - network_input = torch.cat([pos_input * self.cfg.pos_scale, vel_input * self.cfg.vel_scale], dim=1) - elif self.cfg.input_order == "vel_pos": - network_input = torch.cat([vel_input * self.cfg.vel_scale, pos_input * self.cfg.pos_scale], dim=1) - else: - raise ValueError( - f"Invalid input order for MLP actuator net: {self.cfg.input_order}. Must be 'pos_vel' or 'vel_pos'." - ) - - # run network inference - torques = self.network(network_input).view(self._num_envs, self.num_joints) - self.computed_effort = torques.view(self._num_envs, self.num_joints) * self.cfg.torque_scale - - # clip the computed effort based on the motor limits - self.applied_effort = self._clip_effort(self.computed_effort) - - # return torques - control_action.joint_efforts = self.applied_effort - control_action.joint_positions = None - control_action.joint_velocities = None - return control_action
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_pd.html b/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_pd.html deleted file mode 100644 index 56235db42a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/actuators/actuator_pd.html +++ /dev/null @@ -1,912 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.actuators.actuator_pd — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.actuators.actuator_pd 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-from omni.isaac.core.utils.types import ArticulationActions
-
-from omni.isaac.lab.utils import DelayBuffer, LinearInterpolation
-
-from .actuator_base import ActuatorBase
-
-if TYPE_CHECKING:
-    from .actuator_cfg import (
-        DCMotorCfg,
-        DelayedPDActuatorCfg,
-        IdealPDActuatorCfg,
-        ImplicitActuatorCfg,
-        RemotizedPDActuatorCfg,
-    )
-
-
-"""
-Implicit Actuator Models.
-"""
-
-
-
[文档]class ImplicitActuator(ActuatorBase): - """Implicit actuator model that is handled by the simulation. - - This performs a similar function as the :class:`IdealPDActuator` class. However, the PD control is handled - implicitly by the simulation which performs continuous-time integration of the PD control law. This is - generally more accurate than the explicit PD control law used in :class:`IdealPDActuator` when the simulation - time-step is large. - - .. note:: - - The articulation class sets the stiffness and damping parameters from the configuration into the simulation. - Thus, the parameters are not used in this class. - - .. caution:: - - The class is only provided for consistency with the other actuator models. It does not implement any - functionality and should not be used. All values should be set to the simulation directly. - """ - - cfg: ImplicitActuatorCfg - """The configuration for the actuator model.""" - - """ - Operations. - """ - -
[文档] def reset(self, *args, **kwargs): - # This is a no-op. There is no state to reset for implicit actuators. - pass
- -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - """Compute the aproximmate torques for the actuated joint (physX does not compute this explicitly).""" - # store approximate torques for reward computation - error_pos = control_action.joint_positions - joint_pos - error_vel = control_action.joint_velocities - joint_vel - self.computed_effort = self.stiffness * error_pos + self.damping * error_vel + control_action.joint_efforts - # clip the torques based on the motor limits - self.applied_effort = self._clip_effort(self.computed_effort) - return control_action
- - -""" -Explicit Actuator Models. -""" - - -
[文档]class IdealPDActuator(ActuatorBase): - r"""Ideal torque-controlled actuator model with a simple saturation model. - - It employs the following model for computing torques for the actuated joint :math:`j`: - - .. math:: - - \tau_{j, computed} = k_p * (q - q_{des}) + k_d * (\dot{q} - \dot{q}_{des}) + \tau_{ff} - - where, :math:`k_p` and :math:`k_d` are joint stiffness and damping gains, :math:`q` and :math:`\dot{q}` - are the current joint positions and velocities, :math:`q_{des}`, :math:`\dot{q}_{des}` and :math:`\tau_{ff}` - are the desired joint positions, velocities and torques commands. - - The clipping model is based on the maximum torque applied by the motor. It is implemented as: - - .. math:: - - \tau_{j, max} & = \gamma \times \tau_{motor, max} \\ - \tau_{j, applied} & = clip(\tau_{computed}, -\tau_{j, max}, \tau_{j, max}) - - where the clipping function is defined as :math:`clip(x, x_{min}, x_{max}) = min(max(x, x_{min}), x_{max})`. - The parameters :math:`\gamma` is the gear ratio of the gear box connecting the motor and the actuated joint ends, - and :math:`\tau_{motor, max}` is the maximum motor effort possible. These parameters are read from - the configuration instance passed to the class. - """ - - cfg: IdealPDActuatorCfg - """The configuration for the actuator model.""" - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int]): - pass
- -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - # compute errors - error_pos = control_action.joint_positions - joint_pos - error_vel = control_action.joint_velocities - joint_vel - # calculate the desired joint torques - self.computed_effort = self.stiffness * error_pos + self.damping * error_vel + control_action.joint_efforts - # clip the torques based on the motor limits - self.applied_effort = self._clip_effort(self.computed_effort) - # set the computed actions back into the control action - control_action.joint_efforts = self.applied_effort - control_action.joint_positions = None - control_action.joint_velocities = None - return control_action
- - -
[文档]class DCMotor(IdealPDActuator): - r"""Direct control (DC) motor actuator model with velocity-based saturation model. - - It uses the same model as the :class:`IdealActuator` for computing the torques from input commands. - However, it implements a saturation model defined by DC motor characteristics. - - A DC motor is a type of electric motor that is powered by direct current electricity. In most cases, - the motor is connected to a constant source of voltage supply, and the current is controlled by a rheostat. - Depending on various design factors such as windings and materials, the motor can draw a limited maximum power - from the electronic source, which limits the produced motor torque and speed. - - A DC motor characteristics are defined by the following parameters: - - * Continuous-rated speed (:math:`\dot{q}_{motor, max}`) : The maximum-rated speed of the motor. - * Continuous-stall torque (:math:`\tau_{motor, max}`): The maximum-rated torque produced at 0 speed. - * Saturation torque (:math:`\tau_{motor, sat}`): The maximum torque that can be outputted for a short period. - - Based on these parameters, the instantaneous minimum and maximum torques are defined as follows: - - .. math:: - - \tau_{j, max}(\dot{q}) & = clip \left (\tau_{j, sat} \times \left(1 - - \frac{\dot{q}}{\dot{q}_{j, max}}\right), 0.0, \tau_{j, max} \right) \\ - \tau_{j, min}(\dot{q}) & = clip \left (\tau_{j, sat} \times \left( -1 - - \frac{\dot{q}}{\dot{q}_{j, max}}\right), - \tau_{j, max}, 0.0 \right) - - where :math:`\gamma` is the gear ratio of the gear box connecting the motor and the actuated joint ends, - :math:`\dot{q}_{j, max} = \gamma^{-1} \times \dot{q}_{motor, max}`, :math:`\tau_{j, max} = - \gamma \times \tau_{motor, max}` and :math:`\tau_{j, peak} = \gamma \times \tau_{motor, peak}` - are the maximum joint velocity, maximum joint torque and peak torque, respectively. These parameters - are read from the configuration instance passed to the class. - - Using these values, the computed torques are clipped to the minimum and maximum values based on the - instantaneous joint velocity: - - .. math:: - - \tau_{j, applied} = clip(\tau_{computed}, \tau_{j, min}(\dot{q}), \tau_{j, max}(\dot{q})) - - """ - - cfg: DCMotorCfg - """The configuration for the actuator model.""" - -
[文档] def __init__(self, cfg: DCMotorCfg, *args, **kwargs): - super().__init__(cfg, *args, **kwargs) - # parse configuration - if self.cfg.saturation_effort is not None: - self._saturation_effort = self.cfg.saturation_effort - else: - self._saturation_effort = torch.inf - # prepare joint vel buffer for max effort computation - self._joint_vel = torch.zeros_like(self.computed_effort) - # create buffer for zeros effort - self._zeros_effort = torch.zeros_like(self.computed_effort) - # check that quantities are provided - if self.cfg.velocity_limit is None: - raise ValueError("The velocity limit must be provided for the DC motor actuator model.")
- - """ - Operations. - """ - -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - # save current joint vel - self._joint_vel[:] = joint_vel - # calculate the desired joint torques - return super().compute(control_action, joint_pos, joint_vel)
- - """ - Helper functions. - """ - - def _clip_effort(self, effort: torch.Tensor) -> torch.Tensor: - # compute torque limits - # -- max limit - max_effort = self._saturation_effort * (1.0 - self._joint_vel / self.velocity_limit) - max_effort = torch.clip(max_effort, min=self._zeros_effort, max=self.effort_limit) - # -- min limit - min_effort = self._saturation_effort * (-1.0 - self._joint_vel / self.velocity_limit) - min_effort = torch.clip(min_effort, min=-self.effort_limit, max=self._zeros_effort) - - # clip the torques based on the motor limits - return torch.clip(effort, min=min_effort, max=max_effort)
- - -
[文档]class DelayedPDActuator(IdealPDActuator): - """Ideal PD actuator with delayed command application. - - This class extends the :class:`IdealPDActuator` class by adding a delay to the actuator commands. The delay - is implemented using a circular buffer that stores the actuator commands for a certain number of physics steps. - The most recent actuation value is pushed to the buffer at every physics step, but the final actuation value - applied to the simulation is lagged by a certain number of physics steps. - - The amount of time lag is configurable and can be set to a random value between the minimum and maximum time - lag bounds at every reset. The minimum and maximum time lag values are set in the configuration instance passed - to the class. - """ - - cfg: DelayedPDActuatorCfg - """The configuration for the actuator model.""" - -
[文档] def __init__(self, cfg: DelayedPDActuatorCfg, *args, **kwargs): - super().__init__(cfg, *args, **kwargs) - # instantiate the delay buffers - self.positions_delay_buffer = DelayBuffer(cfg.max_delay, self._num_envs, device=self._device) - self.velocities_delay_buffer = DelayBuffer(cfg.max_delay, self._num_envs, device=self._device) - self.efforts_delay_buffer = DelayBuffer(cfg.max_delay, self._num_envs, device=self._device) - # all of the envs - self._ALL_INDICES = torch.arange(self._num_envs, dtype=torch.long, device=self._device)
- -
[文档] def reset(self, env_ids: Sequence[int]): - super().reset(env_ids) - # number of environments (since env_ids can be a slice) - if env_ids is None or env_ids == slice(None): - num_envs = self._num_envs - else: - num_envs = len(env_ids) - # set a new random delay for environments in env_ids - time_lags = torch.randint( - low=self.cfg.min_delay, - high=self.cfg.max_delay + 1, - size=(num_envs,), - dtype=torch.int, - device=self._device, - ) - # set delays - self.positions_delay_buffer.set_time_lag(time_lags, env_ids) - self.velocities_delay_buffer.set_time_lag(time_lags, env_ids) - self.efforts_delay_buffer.set_time_lag(time_lags, env_ids) - # reset buffers - self.positions_delay_buffer.reset(env_ids) - self.velocities_delay_buffer.reset(env_ids) - self.efforts_delay_buffer.reset(env_ids)
- -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - # apply delay based on the delay the model for all the setpoints - control_action.joint_positions = self.positions_delay_buffer.compute(control_action.joint_positions) - control_action.joint_velocities = self.velocities_delay_buffer.compute(control_action.joint_velocities) - control_action.joint_efforts = self.efforts_delay_buffer.compute(control_action.joint_efforts) - # compte actuator model - return super().compute(control_action, joint_pos, joint_vel)
- - -
[文档]class RemotizedPDActuator(DelayedPDActuator): - """Ideal PD actuator with angle-dependent torque limits. - - This class extends the :class:`DelayedPDActuator` class by adding angle-dependent torque limits to the actuator. - The torque limits are applied by querying a lookup table describing the relationship between the joint angle - and the maximum output torque. The lookup table is provided in the configuration instance passed to the class. - - The torque limits are interpolated based on the current joint positions and applied to the actuator commands. - """ - -
[文档] def __init__( - self, - cfg: RemotizedPDActuatorCfg, - joint_names: list[str], - joint_ids: Sequence[int], - num_envs: int, - device: str, - stiffness: torch.Tensor | float = 0.0, - damping: torch.Tensor | float = 0.0, - armature: torch.Tensor | float = 0.0, - friction: torch.Tensor | float = 0.0, - effort_limit: torch.Tensor | float = torch.inf, - velocity_limit: torch.Tensor | float = torch.inf, - ): - # remove effort and velocity box constraints from the base class - cfg.effort_limit = torch.inf - cfg.velocity_limit = torch.inf - # call the base method and set default effort_limit and velocity_limit to inf - super().__init__( - cfg, joint_names, joint_ids, num_envs, device, stiffness, damping, armature, friction, torch.inf, torch.inf - ) - self._joint_parameter_lookup = cfg.joint_parameter_lookup.to(device=device) - # define remotized joint torque limit - self._torque_limit = LinearInterpolation(self.angle_samples, self.max_torque_samples, device=device)
- - """ - Properties. - """ - - @property - def angle_samples(self) -> torch.Tensor: - return self._joint_parameter_lookup[:, 0] - - @property - def transmission_ratio_samples(self) -> torch.Tensor: - return self._joint_parameter_lookup[:, 1] - - @property - def max_torque_samples(self) -> torch.Tensor: - return self._joint_parameter_lookup[:, 2] - - """ - Operations. - """ - -
[文档] def compute( - self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor - ) -> ArticulationActions: - # call the base method - control_action = super().compute(control_action, joint_pos, joint_vel) - # compute the absolute torque limits for the current joint positions - abs_torque_limits = self._torque_limit.compute(joint_pos) - # apply the limits - control_action.joint_efforts = torch.clamp( - control_action.joint_efforts, min=-abs_torque_limits, max=abs_torque_limits - ) - self.applied_effort = control_action.joint_efforts - return control_action
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/app/app_launcher.html b/docs/_build/html/_modules/omni/isaac/lab/app/app_launcher.html deleted file mode 100644 index 968028a90a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/app/app_launcher.html +++ /dev/null @@ -1,1194 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.app.app_launcher — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.app.app_launcher 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-package with the utility class to configure the :class:`omni.isaac.kit.SimulationApp`.
-
-The :class:`AppLauncher` parses environment variables and input CLI arguments to launch the simulator in
-various different modes. This includes with or without GUI and switching between different Omniverse remote
-clients. Some of these require the extensions to be loaded in a specific order, otherwise a segmentation
-fault occurs. The launched :class:`omni.isaac.kit.SimulationApp` instance is accessible via the
-:attr:`AppLauncher.app` property.
-"""
-
-import argparse
-import contextlib
-import os
-import re
-import signal
-import sys
-from typing import Any, Literal
-
-with contextlib.suppress(ModuleNotFoundError):
-    import isaacsim  # noqa: F401
-
-from omni.isaac.kit import SimulationApp
-
-
-
[文档]class AppLauncher: - """A utility class to launch Isaac Sim application based on command-line arguments and environment variables. - - The class resolves the simulation app settings that appear through environments variables, - command-line arguments (CLI) or as input keyword arguments. Based on these settings, it launches the - simulation app and configures the extensions to load (as a part of post-launch setup). - - The input arguments provided to the class are given higher priority than the values set - from the corresponding environment variables. This provides flexibility to deal with different - users' preferences. - - .. note:: - Explicitly defined arguments are only given priority when their value is set to something outside - their default configuration. For example, the ``livestream`` argument is -1 by default. It only - overrides the ``LIVESTREAM`` environment variable when ``livestream`` argument is set to a - value >-1. In other words, if ``livestream=-1``, then the value from the environment variable - ``LIVESTREAM`` is used. - - """ - -
[文档] def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwargs): - """Create a `SimulationApp`_ instance based on the input settings. - - Args: - launcher_args: Input arguments to parse using the AppLauncher and set into the SimulationApp. - Defaults to None, which is equivalent to passing an empty dictionary. A detailed description of - the possible arguments is available in the `SimulationApp`_ documentation. - **kwargs : Additional keyword arguments that will be merged into :attr:`launcher_args`. - They serve as a convenience for those who want to pass some arguments using the argparse - interface and others directly into the AppLauncher. Duplicated arguments with - the :attr:`launcher_args` will raise a ValueError. - - Raises: - ValueError: If there are common/duplicated arguments between ``launcher_args`` and ``kwargs``. - ValueError: If combination of ``launcher_args`` and ``kwargs`` are missing the necessary arguments - that are needed by the AppLauncher to resolve the desired app configuration. - ValueError: If incompatible or undefined values are assigned to relevant environment values, - such as ``LIVESTREAM``. - - .. _argparse.Namespace: https://docs.python.org/3/library/argparse.html?highlight=namespace#argparse.Namespace - .. _SimulationApp: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.kit/docs/index.html - """ - # We allow users to pass either a dict or an argparse.Namespace into - # __init__, anticipating that these will be all of the argparse arguments - # used by the calling script. Those which we appended via add_app_launcher_args - # will be used to control extension loading logic. Additional arguments are allowed, - # and will be passed directly to the SimulationApp initialization. - # - # We could potentially require users to enter each argument they want passed here - # as a kwarg, but this would require them to pass livestream, headless, and - # any other options we choose to add here explicitly, and with the correct keywords. - # - # @hunter: I feel that this is cumbersome and could introduce error, and would prefer to do - # some sanity checking in the add_app_launcher_args function - if launcher_args is None: - launcher_args = {} - elif isinstance(launcher_args, argparse.Namespace): - launcher_args = launcher_args.__dict__ - - # Check that arguments are unique - if len(kwargs) > 0: - if not set(kwargs.keys()).isdisjoint(launcher_args.keys()): - overlapping_args = set(kwargs.keys()).intersection(launcher_args.keys()) - raise ValueError( - f"Input `launcher_args` and `kwargs` both provided common attributes: {overlapping_args}." - " Please ensure that each argument is supplied to only one of them, as the AppLauncher cannot" - " discern priority between them." - ) - launcher_args.update(kwargs) - - # Define config members that are read from env-vars or keyword args - self._headless: bool # 0: GUI, 1: Headless - self._livestream: Literal[0, 1, 2] # 0: Disabled, 1: Native, 2: WebRTC - self._offscreen_render: bool # 0: Disabled, 1: Enabled - self._sim_experience_file: str # Experience file to load - - # Exposed to train scripts - self.device_id: int # device ID for GPU simulation (defaults to 0) - self.local_rank: int # local rank of GPUs in the current node - self.global_rank: int # global rank for multi-node training - - # Integrate env-vars and input keyword args into simulation app config - self._config_resolution(launcher_args) - # Create SimulationApp, passing the resolved self._config to it for initialization - self._create_app() - # Load IsaacSim extensions - self._load_extensions() - # Hide the stop button in the toolbar - self._hide_stop_button() - - # Set up signal handlers for graceful shutdown - # -- during interrupts - signal.signal(signal.SIGINT, self._interrupt_signal_handle_callback) - # -- during explicit `kill` commands - signal.signal(signal.SIGTERM, self._abort_signal_handle_callback) - # -- during segfaults - signal.signal(signal.SIGABRT, self._abort_signal_handle_callback) - signal.signal(signal.SIGSEGV, self._abort_signal_handle_callback)
- - """ - Properties. - """ - - @property - def app(self) -> SimulationApp: - """The launched SimulationApp.""" - if self._app is not None: - return self._app - else: - raise RuntimeError("The `AppLauncher.app` member cannot be retrieved until the class is initialized.") - - """ - Operations. - """ - -
[文档] @staticmethod - def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: - """Utility function to configure AppLauncher arguments with an existing argument parser object. - - This function takes an ``argparse.ArgumentParser`` object and does some sanity checking on the existing - arguments for ingestion by the SimulationApp. It then appends custom command-line arguments relevant - to the SimulationApp to the input :class:`argparse.ArgumentParser` instance. This allows overriding the - environment variables using command-line arguments. - - Currently, it adds the following parameters to the argparser object: - - * ``headless`` (bool): If True, the app will be launched in headless (no-gui) mode. The values map the same - as that for the ``HEADLESS`` environment variable. If False, then headless mode is determined by the - ``HEADLESS`` environment variable. - * ``livestream`` (int): If one of {0, 1, 2}, then livestreaming and headless mode is enabled. The values - map the same as that for the ``LIVESTREAM`` environment variable. If :obj:`-1`, then livestreaming is - determined by the ``LIVESTREAM`` environment variable. - * ``enable_cameras`` (bool): If True, the app will enable camera sensors and render them, even when in - headless mode. This flag must be set to True if the environments contains any camera sensors. - The values map the same as that for the ``ENABLE_CAMERAS`` environment variable. - If False, then enable_cameras mode is determined by the ``ENABLE_CAMERAS`` environment variable. - * ``device_id`` (int): If specified, simulation will run on the specified GPU device. - * ``experience`` (str): The experience file to load when launching the SimulationApp. If a relative path - is provided, it is resolved relative to the ``apps`` folder in Isaac Sim and Isaac Lab (in that order). - - If provided as an empty string, the experience file is determined based on the headless flag: - - * If headless and enable_cameras are True, the experience file is set to ``isaaclab.python.headless.rendering.kit``. - * If headless is False and enable_cameras is True, the experience file is set to ``isaaclab.python.rendering.kit``. - * If headless is False and enable_cameras is False, the experience file is set to ``isaaclab.python.kit``. - * If headless is True and enable_cameras is False, the experience file is set to ``isaaclab.python.headless.kit``. - - Args: - parser: An argument parser instance to be extended with the AppLauncher specific options. - """ - # If the passed parser has an existing _HelpAction when passed, - # we here remove the options which would invoke it, - # to be added back after the additional AppLauncher args - # have been added. This is equivalent to - # initially constructing the ArgParser with add_help=False, - # but this means we don't have to require that behavior - # in users and can handle it on our end. - # We do this because calling parse_known_args() will handle - # any -h/--help options being passed and then exit immediately, - # before the additional arguments can be added to the help readout. - parser_help = None - if len(parser._actions) > 0 and isinstance(parser._actions[0], argparse._HelpAction): # type: ignore - parser_help = parser._actions[0] - parser._option_string_actions.pop("-h") - parser._option_string_actions.pop("--help") - - # Parse known args for potential name collisions/type mismatches - # between the config fields SimulationApp expects and the ArgParse - # arguments that the user passed. - known, _ = parser.parse_known_args() - config = vars(known) - if len(config) == 0: - print( - "[WARN][AppLauncher]: There are no arguments attached to the ArgumentParser object." - " If you have your own arguments, please load your own arguments before calling the" - " `AppLauncher.add_app_launcher_args` method. This allows the method to check the validity" - " of the arguments and perform checks for argument names." - ) - else: - AppLauncher._check_argparser_config_params(config) - - # Add custom arguments to the parser - arg_group = parser.add_argument_group( - "app_launcher arguments", - description="Arguments for the AppLauncher. For more details, please check the documentation.", - ) - arg_group.add_argument( - "--headless", - action="store_true", - default=AppLauncher._APPLAUNCHER_CFG_INFO["headless"][1], - help="Force display off at all times.", - ) - arg_group.add_argument( - "--livestream", - type=int, - default=AppLauncher._APPLAUNCHER_CFG_INFO["livestream"][1], - choices={0, 1, 2}, - help="Force enable livestreaming. Mapping corresponds to that for the `LIVESTREAM` environment variable.", - ) - arg_group.add_argument( - "--enable_cameras", - action="store_true", - default=AppLauncher._APPLAUNCHER_CFG_INFO["enable_cameras"][1], - help="Enable camera sensors and relevant extension dependencies.", - ) - arg_group.add_argument( - "--device_id", - type=int, - default=AppLauncher._APPLAUNCHER_CFG_INFO["device_id"][1], - help="GPU device ID used for running simulation.", - ) - arg_group.add_argument( - "--verbose", # Note: This is read by SimulationApp through sys.argv - action="store_true", - help="Enable verbose terminal output from the SimulationApp.", - ) - arg_group.add_argument( - "--experience", - type=str, - default="", - help=( - "The experience file to load when launching the SimulationApp. If an empty string is provided," - " the experience file is determined based on the headless flag. If a relative path is provided," - " it is resolved relative to the `apps` folder in Isaac Sim and Isaac Lab (in that order)." - ), - ) - - # Corresponding to the beginning of the function, - # if we have removed -h/--help handling, we add it back. - if parser_help is not None: - parser._option_string_actions["-h"] = parser_help - parser._option_string_actions["--help"] = parser_help
- - """ - Internal functions. - """ - - _APPLAUNCHER_CFG_INFO: dict[str, tuple[list[type], Any]] = { - "headless": ([bool], False), - "livestream": ([int], -1), - "enable_cameras": ([bool], False), - "device_id": ([int], 0), - "experience": ([str], ""), - } - """A dictionary of arguments added manually by the :meth:`AppLauncher.add_app_launcher_args` method. - - The values are a tuple of the expected type and default value. This is used to check against name collisions - for arguments passed to the :class:`AppLauncher` class as well as for type checking. - - They have corresponding environment variables as detailed in the documentation. - """ - - # TODO: Find some internally managed NVIDIA list of these types. - # SimulationApp.DEFAULT_LAUNCHER_CONFIG almost works, except that - # it is ambiguous where the default types are None - _SIM_APP_CFG_TYPES: dict[str, list[type]] = { - "headless": [bool], - "hide_ui": [bool, type(None)], - "active_gpu": [int, type(None)], - "physics_gpu": [int], - "multi_gpu": [bool], - "sync_loads": [bool], - "width": [int], - "height": [int], - "window_width": [int], - "window_height": [int], - "display_options": [int], - "subdiv_refinement_level": [int], - "renderer": [str], - "anti_aliasing": [int], - "samples_per_pixel_per_frame": [int], - "denoiser": [bool], - "max_bounces": [int], - "max_specular_transmission_bounces": [int], - "max_volume_bounces": [int], - "open_usd": [str, type(None)], - "livesync_usd": [str, type(None)], - "fast_shutdown": [bool], - "experience": [str], - } - """A dictionary containing the type of arguments passed to SimulationApp. - - This is used to check against name collisions for arguments passed to the :class:`AppLauncher` class - as well as for type checking. It corresponds closely to the :attr:`SimulationApp.DEFAULT_LAUNCHER_CONFIG`, - but specifically denotes where None types are allowed. - """ - - @staticmethod - def _check_argparser_config_params(config: dict) -> None: - """Checks that input argparser object has parameters with valid settings with no name conflicts. - - First, we inspect the dictionary to ensure that the passed ArgParser object is not attempting to add arguments - which should be assigned by calling :meth:`AppLauncher.add_app_launcher_args`. - - Then, we check that if the key corresponds to a config setting expected by SimulationApp, then the type of - that key's value corresponds to the type expected by the SimulationApp. If it passes the check, the function - prints out that the setting with be passed to the SimulationApp. Otherwise, we raise a ValueError exception. - - Args: - config: A configuration parameters which will be passed to the SimulationApp constructor. - - Raises: - ValueError: If a key is an already existing field in the configuration parameters but - should be added by calling the :meth:`AppLauncher.add_app_launcher_args. - ValueError: If keys corresponding to those used to initialize SimulationApp - (as found in :attr:`_SIM_APP_CFG_TYPES`) are of the wrong value type. - """ - # check that no config key conflicts with AppLauncher config names - applauncher_keys = set(AppLauncher._APPLAUNCHER_CFG_INFO.keys()) - for key, value in config.items(): - if key in applauncher_keys: - raise ValueError( - f"The passed ArgParser object already has the field '{key}'. This field will be added by" - " `AppLauncher.add_app_launcher_args()`, and should not be added directly. Please remove the" - " argument or rename it to a non-conflicting name." - ) - # check that type of the passed keys are valid - simulationapp_keys = set(AppLauncher._SIM_APP_CFG_TYPES.keys()) - for key, value in config.items(): - if key in simulationapp_keys: - given_type = type(value) - expected_types = AppLauncher._SIM_APP_CFG_TYPES[key] - if type(value) not in set(expected_types): - raise ValueError( - f"Invalid value type for the argument '{key}': {given_type}. Expected one of {expected_types}," - " if intended to be ingested by the SimulationApp object. Please change the type if this" - " intended for the SimulationApp or change the name of the argument to avoid name conflicts." - ) - # Print out values which will be used - print(f"[INFO][AppLauncher]: The argument '{key}' will be used to configure the SimulationApp.") - - def _config_resolution(self, launcher_args: dict): - """Resolve the input arguments and environment variables. - - Args: - launcher_args: A dictionary of all input arguments passed to the class object. - """ - # Handle all control logic resolution - - # --LIVESTREAM logic-- - # - livestream_env = int(os.environ.get("LIVESTREAM", 0)) - livestream_arg = launcher_args.pop("livestream", AppLauncher._APPLAUNCHER_CFG_INFO["livestream"][1]) - livestream_valid_vals = {0, 1, 2} - # Value checking on LIVESTREAM - if livestream_env not in livestream_valid_vals: - raise ValueError( - f"Invalid value for environment variable `LIVESTREAM`: {livestream_env} ." - f" Expected: {livestream_valid_vals}." - ) - # We allow livestream kwarg to supersede LIVESTREAM envvar - if livestream_arg >= 0: - if livestream_arg in livestream_valid_vals: - self._livestream = livestream_arg - # print info that we overrode the env-var - print( - f"[INFO][AppLauncher]: Input keyword argument `livestream={livestream_arg}` has overridden" - f" the environment variable `LIVESTREAM={livestream_env}`." - ) - else: - raise ValueError( - f"Invalid value for input keyword argument `livestream`: {livestream_arg} ." - f" Expected: {livestream_valid_vals}." - ) - else: - self._livestream = livestream_env - - # --HEADLESS logic-- - # - # Resolve headless execution of simulation app - # HEADLESS is initially passed as an int instead of - # the bool of headless_arg to avoid messy string processing, - headless_env = int(os.environ.get("HEADLESS", 0)) - headless_arg = launcher_args.pop("headless", AppLauncher._APPLAUNCHER_CFG_INFO["headless"][1]) - headless_valid_vals = {0, 1} - # Value checking on HEADLESS - if headless_env not in headless_valid_vals: - raise ValueError( - f"Invalid value for environment variable `HEADLESS`: {headless_env} . Expected: {headless_valid_vals}." - ) - # We allow headless kwarg to supersede HEADLESS envvar if headless_arg does not have the default value - # Note: Headless is always true when livestreaming - if headless_arg is True: - self._headless = headless_arg - elif self._livestream in {1, 2}: - # we are always headless on the host machine - self._headless = True - # inform who has toggled the headless flag - if self._livestream == livestream_arg: - print( - f"[INFO][AppLauncher]: Input keyword argument `livestream={self._livestream}` has implicitly" - f" overridden the environment variable `HEADLESS={headless_env}` to True." - ) - elif self._livestream == livestream_env: - print( - f"[INFO][AppLauncher]: Environment variable `LIVESTREAM={self._livestream}` has implicitly" - f" overridden the environment variable `HEADLESS={headless_env}` to True." - ) - else: - # Headless needs to be a bool to be ingested by SimulationApp - self._headless = bool(headless_env) - # Headless needs to be passed to the SimulationApp so we keep it here - launcher_args["headless"] = self._headless - - # --enable_cameras logic-- - # - enable_cameras_env = int(os.environ.get("ENABLE_CAMERAS", 0)) - enable_cameras_arg = launcher_args.pop("enable_cameras", AppLauncher._APPLAUNCHER_CFG_INFO["enable_cameras"][1]) - enable_cameras_valid_vals = {0, 1} - if enable_cameras_env not in enable_cameras_valid_vals: - raise ValueError( - f"Invalid value for environment variable `ENABLE_CAMERAS`: {enable_cameras_env} ." - f"Expected: {enable_cameras_valid_vals} ." - ) - # We allow enable_cameras kwarg to supersede ENABLE_CAMERAS envvar - if enable_cameras_arg is True: - self._enable_cameras = enable_cameras_arg - else: - self._enable_cameras = bool(enable_cameras_env) - self._offscreen_render = False - if self._enable_cameras and self._headless: - self._offscreen_render = True - # hide_ui flag - launcher_args["hide_ui"] = False - if self._headless and not self._livestream: - launcher_args["hide_ui"] = True - - # --simulation GPU device logic -- - self.device_id = launcher_args.pop("device_id", AppLauncher._APPLAUNCHER_CFG_INFO["device_id"][1]) - if "distributed" in launcher_args: - distributed_train = launcher_args["distributed"] - # local rank (GPU id) in a current multi-gpu mode - self.local_rank = int(os.getenv("LOCAL_RANK", "0")) - # global rank (GPU id) in multi-gpu multi-node mode - self.global_rank = int(os.getenv("RANK", "0")) - if distributed_train: - self.device_id = self.local_rank - launcher_args["multi_gpu"] = False - # set physics and rendering device - launcher_args["physics_gpu"] = self.device_id - launcher_args["active_gpu"] = self.device_id - - # Check if input keywords contain an 'experience' file setting - # Note: since experience is taken as a separate argument by Simulation App, we store it separately - self._sim_experience_file = launcher_args.pop("experience", "") - - # If nothing is provided resolve the experience file based on the headless flag - kit_app_exp_path = os.environ["EXP_PATH"] - isaaclab_app_exp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), *[".."] * 6, "apps") - if self._sim_experience_file == "": - # check if the headless flag is setS - if self._enable_cameras: - if self._headless and not self._livestream: - self._sim_experience_file = os.path.join( - isaaclab_app_exp_path, "isaaclab.python.headless.rendering.kit" - ) - else: - self._sim_experience_file = os.path.join(isaaclab_app_exp_path, "isaaclab.python.rendering.kit") - elif self._headless and not self._livestream: - self._sim_experience_file = os.path.join(isaaclab_app_exp_path, "isaaclab.python.headless.kit") - else: - self._sim_experience_file = os.path.join(isaaclab_app_exp_path, "isaaclab.python.kit") - elif not os.path.isabs(self._sim_experience_file): - option_1_app_exp_path = os.path.join(kit_app_exp_path, self._sim_experience_file) - option_2_app_exp_path = os.path.join(isaaclab_app_exp_path, self._sim_experience_file) - if os.path.exists(option_1_app_exp_path): - self._sim_experience_file = option_1_app_exp_path - elif os.path.exists(option_2_app_exp_path): - self._sim_experience_file = option_2_app_exp_path - else: - raise FileNotFoundError( - f"Invalid value for input keyword argument `experience`: {self._sim_experience_file}." - "\n No such file exists in either the Kit or Isaac Lab experience paths. Checked paths:" - f"\n\t [1]: {option_1_app_exp_path}" - f"\n\t [2]: {option_2_app_exp_path}" - ) - elif not os.path.exists(self._sim_experience_file): - raise FileNotFoundError( - f"Invalid value for input keyword argument `experience`: {self._sim_experience_file}." - " The file does not exist." - ) - - print(f"[INFO][AppLauncher]: Loading experience file: {self._sim_experience_file}") - # Remove all values from input keyword args which are not meant for SimulationApp - # Assign all the passed settings to a dictionary for the simulation app - self._sim_app_config = { - key: launcher_args[key] for key in set(AppLauncher._SIM_APP_CFG_TYPES.keys()) & set(launcher_args.keys()) - } - - def _create_app(self): - """Launch and create the SimulationApp based on the parsed simulation config.""" - # Initialize SimulationApp - # hack sys module to make sure that the SimulationApp is initialized correctly - # this is to avoid the warnings from the simulation app about not ok modules - r = re.compile(".*lab.*") - found_modules = list(filter(r.match, list(sys.modules.keys()))) - found_modules += ["omni.isaac.kit.app_framework"] - # remove Isaac Lab modules from sys.modules - hacked_modules = dict() - for key in found_modules: - hacked_modules[key] = sys.modules[key] - del sys.modules[key] - # launch simulation app - self._app = SimulationApp(self._sim_app_config, experience=self._sim_experience_file) - # add Isaac Lab modules back to sys.modules - for key, value in hacked_modules.items(): - sys.modules[key] = value - - def _rendering_enabled(self) -> bool: - """Check if rendering is required by the app.""" - # Indicates whether rendering is required by the app. - # Extensions required for rendering bring startup and simulation costs, so we do not enable them if not required. - return not self._headless or self._livestream >= 1 or self._enable_cameras - - def _load_extensions(self): - """Load correct extensions based on AppLauncher's resolved config member variables.""" - # These have to be loaded after SimulationApp is initialized - import carb - import omni.physx.bindings._physx as physx_impl - from omni.isaac.core.utils.extensions import enable_extension - - # Retrieve carb settings for modification - carb_settings_iface = carb.settings.get_settings() - - if self._livestream >= 1: - # Ensure that a viewport exists in case an experience has been - # loaded which does not load it by default - enable_extension("omni.kit.viewport.window") - # Set carb settings to allow for livestreaming - carb_settings_iface.set_bool("/app/livestream/enabled", True) - carb_settings_iface.set_bool("/app/window/drawMouse", True) - carb_settings_iface.set_bool("/ngx/enabled", False) - carb_settings_iface.set_string("/app/livestream/proto", "ws") - carb_settings_iface.set_int("/app/livestream/websocket/framerate_limit", 120) - # Note: Only one livestream extension can be enabled at a time - if self._livestream == 1: - # Enable Native Livestream extension - # Default App: Streaming Client from the Omniverse Launcher - enable_extension("omni.kit.streamsdk.plugins-3.2.1") - enable_extension("omni.kit.livestream.core-3.2.0") - enable_extension("omni.kit.livestream.native-4.1.0") - elif self._livestream == 2: - # Enable WebRTC Livestream extension - # Default URL: http://localhost:8211/streaming/webrtc-client/ - enable_extension("omni.services.streamclient.webrtc") - else: - raise ValueError(f"Invalid value for livestream: {self._livestream}. Expected: 1, 2 .") - else: - carb_settings_iface.set_bool("/app/livestream/enabled", False) - - # set carb setting to indicate Isaac Lab's offscreen_render pipeline should be enabled - # this flag is used by the SimulationContext class to enable the offscreen_render pipeline - # when the render() method is called. - carb_settings_iface.set_bool("/isaaclab/render/offscreen", self._offscreen_render) - - # set carb setting to indicate no RTX sensors are used - # this flag is set to True when an RTX-rendering related sensor is created - # for example: the `Camera` sensor class - carb_settings_iface.set_bool("/isaaclab/render/rtx_sensors", False) - - # set fabric update flag to disable updating transforms when rendering is disabled - carb_settings_iface.set_bool("/physics/fabricUpdateTransformations", self._rendering_enabled()) - - # set the nucleus directory manually to the latest published Nucleus - # note: this is done to ensure prior versions of Isaac Sim still use the latest assets - assets_path = "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/4.0" - carb_settings_iface.set_string("/persistent/isaac/asset_root/default", assets_path) - carb_settings_iface.set_string("/persistent/isaac/asset_root/cloud", assets_path) - carb_settings_iface.set_string("/persistent/isaac/asset_root/nvidia", assets_path) - - # disable physics backwards compatibility check - carb_settings_iface.set_int(physx_impl.SETTING_BACKWARD_COMPATIBILITY, 0) - - def _hide_stop_button(self): - """Hide the stop button in the toolbar. - - For standalone executions, having a stop button is confusing since it invalidates the whole simulation. - Thus, we hide the button so that users don't accidentally click it. - """ - # when we are truly headless, then we can't import the widget toolbar - # thus, we only hide the stop button when we are not headless (i.e. GUI is enabled) - if self._livestream >= 1 or not self._headless: - import omni.kit.widget.toolbar - - # grey out the stop button because we don't want to stop the simulation manually in standalone mode - toolbar = omni.kit.widget.toolbar.get_instance() - play_button_group = toolbar._builtin_tools._play_button_group # type: ignore - if play_button_group is not None: - play_button_group._stop_button.visible = False # type: ignore - play_button_group._stop_button.enabled = False # type: ignore - play_button_group._stop_button = None # type: ignore - - def _interrupt_signal_handle_callback(self, signal, frame): - """Handle the interrupt signal from the keyboard.""" - # close the app - self._app.close() - # raise the error for keyboard interrupt - raise KeyboardInterrupt - - def _abort_signal_handle_callback(self, signal, frame): - """Handle the abort/segmentation/kill signals.""" - # close the app - self._app.close()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation.html b/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation.html deleted file mode 100644 index 877597dd59..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation.html +++ /dev/null @@ -1,1824 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.articulation.articulation — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.articulation.articulation 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# Flag for pyright to ignore type errors in this file.
-# pyright: reportPrivateUsage=false
-
-from __future__ import annotations
-
-import torch
-import warnings
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-import carb
-import omni.isaac.core.utils.stage as stage_utils
-import omni.physics.tensors.impl.api as physx
-from omni.isaac.core.utils.types import ArticulationActions
-from pxr import PhysxSchema, UsdPhysics
-
-import omni.isaac.lab.sim as sim_utils
-import omni.isaac.lab.utils.math as math_utils
-import omni.isaac.lab.utils.string as string_utils
-from omni.isaac.lab.actuators import ActuatorBase, ActuatorBaseCfg, ImplicitActuator
-
-from ..rigid_object import RigidObject
-from .articulation_data import ArticulationData
-
-if TYPE_CHECKING:
-    from .articulation_cfg import ArticulationCfg
-
-
-
[文档]class Articulation(RigidObject): - """An articulation asset class. - - An articulation is a collection of rigid bodies connected by joints. The joints can be either - fixed or actuated. The joints can be of different types, such as revolute, prismatic, D-6, etc. - However, the articulation class has currently been tested with revolute and prismatic joints. - The class supports both floating-base and fixed-base articulations. The type of articulation - is determined based on the root joint of the articulation. If the root joint is fixed, then - the articulation is considered a fixed-base system. Otherwise, it is considered a floating-base - system. This can be checked using the :attr:`Articulation.is_fixed_base` attribute. - - For an asset to be considered an articulation, the root prim of the asset must have the - `USD ArticulationRootAPI`_. This API is used to define the sub-tree of the articulation using - the reduced coordinate formulation. On playing the simulation, the physics engine parses the - articulation root prim and creates the corresponding articulation in the physics engine. The - articulation root prim can be specified using the :attr:`AssetBaseCfg.prim_path` attribute. - - The articulation class is a subclass of the :class:`RigidObject` class. Therefore, it inherits - all the functionality of the rigid object class. In case of an articulation, the :attr:`root_physx_view` - attribute corresponds to the articulation root view and can be used to access the articulation - related data. - - The articulation class also provides the functionality to augment the simulation of an articulated - system with custom actuator models. These models can either be explicit or implicit, as detailed in - the :mod:`omni.isaac.lab.actuators` module. The actuator models are specified using the - :attr:`ArticulationCfg.actuators` attribute. These are then parsed and used to initialize the - corresponding actuator models, when the simulation is played. - - During the simulation step, the articulation class first applies the actuator models to compute - the joint commands based on the user-specified targets. These joint commands are then applied - into the simulation. The joint commands can be either position, velocity, or effort commands. - As an example, the following snippet shows how this can be used for position commands: - - .. code-block:: python - - # an example instance of the articulation class - my_articulation = Articulation(cfg) - - # set joint position targets - my_articulation.set_joint_position_target(position) - # propagate the actuator models and apply the computed commands into the simulation - my_articulation.write_data_to_sim() - - # step the simulation using the simulation context - sim_context.step() - - # update the articulation state, where dt is the simulation time step - my_articulation.update(dt) - - .. _`USD ArticulationRootAPI`: https://openusd.org/dev/api/class_usd_physics_articulation_root_a_p_i.html - - """ - - cfg: ArticulationCfg - """Configuration instance for the articulations.""" - - actuators: dict[str, ActuatorBase] - """Dictionary of actuator instances for the articulation. - - The keys are the actuator names and the values are the actuator instances. The actuator instances - are initialized based on the actuator configurations specified in the :attr:`ArticulationCfg.actuators` - attribute. They are used to compute the joint commands during the :meth:`write_data_to_sim` function. - """ - -
[文档] def __init__(self, cfg: ArticulationCfg): - """Initialize the articulation. - - Args: - cfg: A configuration instance. - """ - super().__init__(cfg)
- - """ - Properties - """ - - @property - def data(self) -> ArticulationData: - return self._data - - @property - def is_fixed_base(self) -> bool: - """Whether the articulation is a fixed-base or floating-base system.""" - return self.root_physx_view.shared_metatype.fixed_base - - @property - def num_joints(self) -> int: - """Number of joints in articulation.""" - return self.root_physx_view.shared_metatype.dof_count - - @property - def num_fixed_tendons(self) -> int: - """Number of fixed tendons in articulation.""" - return self.root_physx_view.max_fixed_tendons - - @property - def num_bodies(self) -> int: - """Number of bodies in articulation.""" - return self.root_physx_view.shared_metatype.link_count - - @property - def joint_names(self) -> list[str]: - """Ordered names of joints in articulation.""" - return self.root_physx_view.shared_metatype.dof_names - - @property - def fixed_tendon_names(self) -> list[str]: - """Ordered names of fixed tendons in articulation.""" - return self._fixed_tendon_names - - @property - def body_names(self) -> list[str]: - """Ordered names of bodies in articulation.""" - return self.root_physx_view.shared_metatype.link_names - - @property - def root_physx_view(self) -> physx.ArticulationView: - """Articulation view for the asset (PhysX). - - Note: - Use this view with caution. It requires handling of tensors in a specific way. - """ - return self._root_physx_view - - @property - def body_physx_view(self) -> physx.RigidBodyView: - """Rigid body view for the asset (PhysX). - - .. deprecated:: v0.3.0 - - In previous versions, this attribute returned the rigid body view over all the links of the articulation. - However, this led to confusion with the link ordering as they were not ordered in the same way as the - articulation view. - - Therefore, this attribute will be removed in v0.4.0. Please use the :attr:`root_physx_view` attribute - instead. - - """ - dep_msg = "The attribute 'body_physx_view' will be removed in v0.4.0. Please use 'root_physx_view' instead." - warnings.warn(dep_msg, DeprecationWarning) - carb.log_error(dep_msg) - return self._body_physx_view - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - super().reset(env_ids) - # use ellipses object to skip initial indices. - if env_ids is None: - env_ids = slice(None) - # reset actuators - for actuator in self.actuators.values(): - actuator.reset(env_ids)
- -
[文档] def write_data_to_sim(self): - """Write external wrenches and joint commands to the simulation. - - If any explicit actuators are present, then the actuator models are used to compute the - joint commands. Otherwise, the joint commands are directly set into the simulation. - """ - # write external wrench - if self.has_external_wrench: - # apply external forces and torques - self._body_physx_view.apply_forces_and_torques_at_position( - force_data=self._external_force_body_view_b.view(-1, 3), - torque_data=self._external_torque_body_view_b.view(-1, 3), - position_data=None, - indices=self._ALL_BODY_INDICES, - is_global=False, - ) - - # apply actuator models - self._apply_actuator_model() - # write actions into simulation - self.root_physx_view.set_dof_actuation_forces(self._joint_effort_target_sim, self._ALL_INDICES) - # position and velocity targets only for implicit actuators - if self._has_implicit_actuators: - self.root_physx_view.set_dof_position_targets(self._joint_pos_target_sim, self._ALL_INDICES) - self.root_physx_view.set_dof_velocity_targets(self._joint_vel_target_sim, self._ALL_INDICES)
- -
[文档] def find_joints( - self, name_keys: str | Sequence[str], joint_subset: list[str] | None = None, preserve_order: bool = False - ) -> tuple[list[int], list[str]]: - """Find joints in the articulation based on the name keys. - - Please see the :func:`omni.isaac.lab.utils.string.resolve_matching_names` function for more information - on the name matching. - - Args: - name_keys: A regular expression or a list of regular expressions to match the joint names. - joint_subset: A subset of joints to search for. Defaults to None, which means all joints - in the articulation are searched. - preserve_order: Whether to preserve the order of the name keys in the output. Defaults to False. - - Returns: - A tuple of lists containing the joint indices and names. - """ - if joint_subset is None: - joint_subset = self.joint_names - # find joints - return string_utils.resolve_matching_names(name_keys, joint_subset, preserve_order)
- -
[文档] def find_fixed_tendons( - self, name_keys: str | Sequence[str], tendon_subsets: list[str] | None = None, preserve_order: bool = False - ) -> tuple[list[int], list[str]]: - """Find fixed tendons in the articulation based on the name keys. - - Please see the :func:`omni.isaac.lab.utils.string.resolve_matching_names` function for more information - on the name matching. - - Args: - name_keys: A regular expression or a list of regular expressions to match the joint - names with fixed tendons. - tendon_subsets: A subset of joints with fixed tendons to search for. Defaults to None, which means - all joints in the articulation are searched. - preserve_order: Whether to preserve the order of the name keys in the output. Defaults to False. - - Returns: - A tuple of lists containing the tendon indices and names. - """ - if tendon_subsets is None: - # tendons follow the joint names they are attached to - tendon_subsets = self.fixed_tendon_names - # find tendons - return string_utils.resolve_matching_names(name_keys, tendon_subsets, preserve_order)
- - """ - Operations - Setters. - """ - -
[文档] def set_external_force_and_torque( - self, - forces: torch.Tensor, - torques: torch.Tensor, - body_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - # call parent to set the external forces and torques into buffers - super().set_external_force_and_torque(forces, torques, body_ids, env_ids) - # reordering of the external forces and torques to match the body view ordering - if self.has_external_wrench: - self._external_force_body_view_b = self._external_force_b[:, self._body_view_ordering] - self._external_torque_body_view_b = self._external_torque_b[:, self._body_view_ordering]
- - """ - Operations - Writers. - """ - -
[文档] def write_root_pose_to_sim(self, root_pose: torch.Tensor, env_ids: Sequence[int] | None = None): - # resolve all indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - # note: we need to do this here since tensors are not set into simulation until step. - # set into internal buffers - self._data.root_state_w[env_ids, :7] = root_pose.clone() - # convert root quaternion from wxyz to xyzw - root_poses_xyzw = self._data.root_state_w[:, :7].clone() - root_poses_xyzw[:, 3:] = math_utils.convert_quat(root_poses_xyzw[:, 3:], to="xyzw") - # set into simulation - self.root_physx_view.set_root_transforms(root_poses_xyzw, indices=physx_env_ids)
- -
[文档] def write_root_velocity_to_sim(self, root_velocity: torch.Tensor, env_ids: Sequence[int] | None = None): - # resolve all indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - # note: we need to do this here since tensors are not set into simulation until step. - # set into internal buffers - self._data.root_state_w[env_ids, 7:] = root_velocity.clone() - self._data.body_acc_w[env_ids] = 0.0 - # set into simulation - self.root_physx_view.set_root_velocities(self._data.root_state_w[:, 7:], indices=physx_env_ids)
- -
[文档] def write_joint_state_to_sim( - self, - position: torch.Tensor, - velocity: torch.Tensor, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | slice | None = None, - ): - """Write joint positions and velocities to the simulation. - - Args: - position: Joint positions. Shape is (len(env_ids), len(joint_ids)). - velocity: Joint velocities. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the targets for. Defaults to None (all joints). - env_ids: The environment indices to set the targets for. Defaults to None (all environments). - """ - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set into internal buffers - self._data.joint_pos[env_ids, joint_ids] = position - self._data.joint_vel[env_ids, joint_ids] = velocity - self._data._previous_joint_vel[env_ids, joint_ids] = velocity - self._data.joint_acc[env_ids, joint_ids] = 0.0 - # set into simulation - self.root_physx_view.set_dof_positions(self._data.joint_pos, indices=physx_env_ids) - self.root_physx_view.set_dof_velocities(self._data.joint_vel, indices=physx_env_ids)
- -
[文档] def write_joint_stiffness_to_sim( - self, - stiffness: torch.Tensor | float, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write joint stiffness into the simulation. - - Args: - stiffness: Joint stiffness. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the stiffness for. Defaults to None (all joints). - env_ids: The environment indices to set the stiffness for. Defaults to None (all environments). - """ - # note: This function isn't setting the values for actuator models. (#128) - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set into internal buffers - self._data.joint_stiffness[env_ids, joint_ids] = stiffness - # set into simulation - self.root_physx_view.set_dof_stiffnesses(self._data.joint_stiffness.cpu(), indices=physx_env_ids.cpu())
- -
[文档] def write_joint_damping_to_sim( - self, - damping: torch.Tensor | float, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write joint damping into the simulation. - - Args: - damping: Joint damping. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the damping for. - Defaults to None (all joints). - env_ids: The environment indices to set the damping for. - Defaults to None (all environments). - """ - # note: This function isn't setting the values for actuator models. (#128) - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set into internal buffers - self._data.joint_damping[env_ids, joint_ids] = damping - # set into simulation - self.root_physx_view.set_dof_dampings(self._data.joint_damping.cpu(), indices=physx_env_ids.cpu())
- -
[文档] def write_joint_effort_limit_to_sim( - self, - limits: torch.Tensor | float, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write joint effort limits into the simulation. - - Args: - limits: Joint torque limits. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the joint torque limits for. Defaults to None (all joints). - env_ids: The environment indices to set the joint torque limits for. Defaults to None (all environments). - """ - # note: This function isn't setting the values for actuator models. (#128) - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # move tensor to cpu if needed - if isinstance(limits, torch.Tensor): - limits = limits.cpu() - # set into internal buffers - torque_limit_all = self.root_physx_view.get_dof_max_forces() - torque_limit_all[env_ids, joint_ids] = limits - # set into simulation - self.root_physx_view.set_dof_max_forces(torque_limit_all.cpu(), indices=physx_env_ids.cpu())
- -
[文档] def write_joint_armature_to_sim( - self, - armature: torch.Tensor | float, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write joint armature into the simulation. - - Args: - armature: Joint armature. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the joint torque limits for. Defaults to None (all joints). - env_ids: The environment indices to set the joint torque limits for. Defaults to None (all environments). - """ - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set into internal buffers - self._data.joint_armature[env_ids, joint_ids] = armature - # set into simulation - self.root_physx_view.set_dof_armatures(self._data.joint_armature.cpu(), indices=physx_env_ids.cpu())
- -
[文档] def write_joint_friction_to_sim( - self, - joint_friction: torch.Tensor | float, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write joint friction into the simulation. - - Args: - joint_friction: Joint friction. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the joint torque limits for. Defaults to None (all joints). - env_ids: The environment indices to set the joint torque limits for. Defaults to None (all environments). - """ - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set into internal buffers - self._data.joint_friction[env_ids, joint_ids] = joint_friction - # set into simulation - self.root_physx_view.set_dof_friction_coefficients(self._data.joint_friction.cpu(), indices=physx_env_ids.cpu())
- -
[文档] def write_joint_limits_to_sim( - self, - limits: torch.Tensor | float, - joint_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write joint limits into the simulation. - - Args: - limits: Joint limits. Shape is (len(env_ids), len(joint_ids), 2). - joint_ids: The joint indices to set the limits for. Defaults to None (all joints). - env_ids: The environment indices to set the limits for. Defaults to None (all environments). - """ - # note: This function isn't setting the values for actuator models. (#128) - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set into internal buffers - self._data.joint_limits[env_ids, joint_ids] = limits - # set into simulation - self.root_physx_view.set_dof_limits(self._data.joint_limits.cpu(), indices=physx_env_ids.cpu())
- - """ - Operations - State. - """ - -
[文档] def set_joint_position_target( - self, target: torch.Tensor, joint_ids: Sequence[int] | slice | None = None, env_ids: Sequence[int] | None = None - ): - """Set joint position targets into internal buffers. - - .. note:: - This function does not apply the joint targets to the simulation. It only fills the buffers with - the desired values. To apply the joint targets, call the :meth:`write_data_to_sim` function. - - Args: - target: Joint position targets. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the targets for. Defaults to None (all joints). - env_ids: The environment indices to set the targets for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set targets - self._data.joint_pos_target[env_ids, joint_ids] = target
- -
[文档] def set_joint_velocity_target( - self, target: torch.Tensor, joint_ids: Sequence[int] | slice | None = None, env_ids: Sequence[int] | None = None - ): - """Set joint velocity targets into internal buffers. - - .. note:: - This function does not apply the joint targets to the simulation. It only fills the buffers with - the desired values. To apply the joint targets, call the :meth:`write_data_to_sim` function. - - Args: - target: Joint velocity targets. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the targets for. Defaults to None (all joints). - env_ids: The environment indices to set the targets for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set targets - self._data.joint_vel_target[env_ids, joint_ids] = target
- -
[文档] def set_joint_effort_target( - self, target: torch.Tensor, joint_ids: Sequence[int] | slice | None = None, env_ids: Sequence[int] | None = None - ): - """Set joint efforts into internal buffers. - - .. note:: - This function does not apply the joint targets to the simulation. It only fills the buffers with - the desired values. To apply the joint targets, call the :meth:`write_data_to_sim` function. - - Args: - target: Joint effort targets. Shape is (len(env_ids), len(joint_ids)). - joint_ids: The joint indices to set the targets for. Defaults to None (all joints). - env_ids: The environment indices to set the targets for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if joint_ids is None: - joint_ids = slice(None) - if env_ids != slice(None) and joint_ids != slice(None): - env_ids = env_ids[:, None] - # set targets - self._data.joint_effort_target[env_ids, joint_ids] = target
- - """ - Operations - Tendons. - """ - -
[文档] def set_fixed_tendon_stiffness( - self, - stiffness: torch.Tensor, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set fixed tendon stiffness into internal buffers. - - .. note:: - This function does not apply the tendon stiffness to the simulation. It only fills the buffers with - the desired values. To apply the tendon stiffness, call the :meth:`write_fixed_tendon_properties_to_sim` function. - - Args: - stiffness: Fixed tendon stiffness. Shape is (len(env_ids), len(fixed_tendon_ids)). - fixed_tendon_ids: The tendon indices to set the stiffness for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the stiffness for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - if env_ids != slice(None) and fixed_tendon_ids != slice(None): - env_ids = env_ids[:, None] - # set stiffness - self._data.fixed_tendon_stiffness[env_ids, fixed_tendon_ids] = stiffness
- -
[文档] def set_fixed_tendon_damping( - self, - damping: torch.Tensor, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set fixed tendon damping into internal buffers. - - .. note:: - This function does not apply the tendon damping to the simulation. It only fills the buffers with - the desired values. To apply the tendon damping, call the :meth:`write_fixed_tendon_properties_to_sim` function. - - Args: - damping: Fixed tendon damping. Shape is (len(env_ids), len(fixed_tendon_ids)). - fixed_tendon_ids: The tendon indices to set the damping for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the damping for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - if env_ids != slice(None) and fixed_tendon_ids != slice(None): - env_ids = env_ids[:, None] - # set damping - self._data.fixed_tendon_damping[env_ids, fixed_tendon_ids] = damping
- -
[文档] def set_fixed_tendon_limit_stiffness( - self, - limit_stiffness: torch.Tensor, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set fixed tendon limit stiffness efforts into internal buffers. - - .. note:: - This function does not apply the tendon limit stiffness to the simulation. It only fills the buffers with - the desired values. To apply the tendon limit stiffness, call the :meth:`write_fixed_tendon_properties_to_sim` function. - - Args: - limit_stiffness: Fixed tendon limit stiffness. Shape is (len(env_ids), len(fixed_tendon_ids)). - fixed_tendon_ids: The tendon indices to set the limit stiffness for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the limit stiffness for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - if env_ids != slice(None) and fixed_tendon_ids != slice(None): - env_ids = env_ids[:, None] - # set limit_stiffness - self._data.fixed_tendon_limit_stiffness[env_ids, fixed_tendon_ids] = limit_stiffness
- -
[文档] def set_fixed_tendon_limit( - self, - limit: torch.Tensor, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set fixed tendon limit efforts into internal buffers. - - .. note:: - This function does not apply the tendon limit to the simulation. It only fills the buffers with - the desired values. To apply the tendon limit, call the :meth:`write_fixed_tendon_properties_to_sim` function. - - Args: - limit: Fixed tendon limit. Shape is (len(env_ids), len(fixed_tendon_ids)). - fixed_tendon_ids: The tendon indices to set the limit for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the limit for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - if env_ids != slice(None) and fixed_tendon_ids != slice(None): - env_ids = env_ids[:, None] - # set limit - self._data.fixed_tendon_limit[env_ids, fixed_tendon_ids] = limit
- -
[文档] def set_fixed_tendon_rest_length( - self, - rest_length: torch.Tensor, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set fixed tendon rest length efforts into internal buffers. - - .. note:: - This function does not apply the tendon rest length to the simulation. It only fills the buffers with - the desired values. To apply the tendon rest length, call the :meth:`write_fixed_tendon_properties_to_sim` function. - - Args: - rest_length: Fixed tendon rest length. Shape is (len(env_ids), len(fixed_tendon_ids)). - fixed_tendon_ids: The tendon indices to set the rest length for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the rest length for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - if env_ids != slice(None) and fixed_tendon_ids != slice(None): - env_ids = env_ids[:, None] - # set rest_length - self._data.fixed_tendon_rest_length[env_ids, fixed_tendon_ids] = rest_length
- -
[文档] def set_fixed_tendon_offset( - self, - offset: torch.Tensor, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set fixed tendon offset efforts into internal buffers. - - .. note:: - This function does not apply the tendon offset to the simulation. It only fills the buffers with - the desired values. To apply the tendon offset, call the :meth:`write_fixed_tendon_properties_to_sim` function. - - Args: - offset: Fixed tendon offset. Shape is (len(env_ids), len(fixed_tendon_ids)). - fixed_tendon_ids: The tendon indices to set the offset for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the offset for. Defaults to None (all environments). - """ - # resolve indices - if env_ids is None: - env_ids = slice(None) - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - if env_ids != slice(None) and fixed_tendon_ids != slice(None): - env_ids = env_ids[:, None] - # set offset - self._data.fixed_tendon_offset[env_ids, fixed_tendon_ids] = offset
- -
[文档] def write_fixed_tendon_properties_to_sim( - self, - fixed_tendon_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Write fixed tendon properties into the simulation. - - Args: - fixed_tendon_ids: The fixed tendon indices to set the limits for. Defaults to None (all fixed tendons). - env_ids: The environment indices to set the limits for. Defaults to None (all environments). - """ - # resolve indices - physx_env_ids = env_ids - if env_ids is None: - physx_env_ids = self._ALL_INDICES - if fixed_tendon_ids is None: - fixed_tendon_ids = slice(None) - - # set into simulation - self.root_physx_view.set_fixed_tendon_properties( - self._data.fixed_tendon_stiffness, - self._data.fixed_tendon_damping, - self._data.fixed_tendon_limit_stiffness, - self._data.fixed_tendon_limit, - self._data.fixed_tendon_rest_length, - self._data.fixed_tendon_offset, - indices=physx_env_ids, - )
- - """ - Internal helper. - """ - - def _initialize_impl(self): - # create simulation view - self._physics_sim_view = physx.create_simulation_view(self._backend) - self._physics_sim_view.set_subspace_roots("/") - # obtain the first prim in the regex expression (all others are assumed to be a copy of this) - template_prim = sim_utils.find_first_matching_prim(self.cfg.prim_path) - if template_prim is None: - raise RuntimeError(f"Failed to find prim for expression: '{self.cfg.prim_path}'.") - template_prim_path = template_prim.GetPath().pathString - - # find articulation root prims - root_prims = sim_utils.get_all_matching_child_prims( - template_prim_path, predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI) - ) - if len(root_prims) == 0: - raise RuntimeError( - f"Failed to find an articulation when resolving '{self.cfg.prim_path}'." - " Please ensure that the prim has 'USD ArticulationRootAPI' applied." - ) - if len(root_prims) > 1: - raise RuntimeError( - f"Failed to find a single articulation when resolving '{self.cfg.prim_path}'." - f" Found multiple '{root_prims}' under '{template_prim_path}'." - " Please ensure that there is only one articulation in the prim path tree." - ) - - # resolve articulation root prim back into regex expression - root_prim_path = root_prims[0].GetPath().pathString - root_prim_path_expr = self.cfg.prim_path + root_prim_path[len(template_prim_path) :] - # -- articulation - self._root_physx_view = self._physics_sim_view.create_articulation_view(root_prim_path_expr.replace(".*", "*")) - # -- link views - # note: we use the root view to get the body names, but we use the body view to get the - # actual data. This is mainly needed to apply external forces to the bodies. - physx_body_names = self.root_physx_view.shared_metatype.link_names - body_names_regex = r"(" + "|".join(physx_body_names) + r")" - body_names_regex = f"{self.cfg.prim_path}/{body_names_regex}" - self._body_physx_view = self._physics_sim_view.create_rigid_body_view(body_names_regex.replace(".*", "*")) - - # create ordering from articulation view to body view for body names - # note: we need to do this since the body view is not ordered in the same way as the articulation view - # -- root view - root_view_body_names = self.body_names - # -- body view - prim_paths = self._body_physx_view.prim_paths[: self.num_bodies] - body_view_body_names = [path.split("/")[-1] for path in prim_paths] - # -- mapping from articulation view to body view - self._body_view_ordering = [root_view_body_names.index(name) for name in body_view_body_names] - self._body_view_ordering = torch.tensor(self._body_view_ordering, dtype=torch.long, device=self.device) - - # log information about the articulation - carb.log_info(f"Articulation initialized at: {self.cfg.prim_path} with root '{root_prim_path_expr}'.") - carb.log_info(f"Is fixed root: {self.is_fixed_base}") - carb.log_info(f"Number of bodies: {self.num_bodies}") - carb.log_info(f"Body names: {self.body_names}") - carb.log_info(f"Number of joints: {self.num_joints}") - carb.log_info(f"Joint names: {self.joint_names}") - carb.log_info(f"Number of fixed tendons: {self.num_fixed_tendons}") - # -- assert that parsing was successful - if set(physx_body_names) != set(self.body_names): - raise RuntimeError("Failed to parse all bodies properly in the articulation.") - - # container for data access - self._data = ArticulationData(self.root_physx_view, self.device) - - # create buffers - self._create_buffers() - # process configuration - self._process_cfg() - self._process_actuators_cfg() - self._process_fixed_tendons() - # validate configuration - self._validate_cfg() - # update the robot data - self.update(0.0) - # log joint information - self._log_articulation_joint_info() - - def _create_buffers(self): - # allocate buffers - super()._create_buffers() - - # asset data - # -- properties - self._data.joint_names = self.joint_names - - # -- default joint state - self._data.default_joint_pos = torch.zeros(self.num_instances, self.num_joints, device=self.device) - self._data.default_joint_vel = torch.zeros_like(self._data.default_joint_pos) - - # -- joint commands - self._data.joint_pos_target = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_vel_target = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_effort_target = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_stiffness = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_damping = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_armature = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_friction = torch.zeros_like(self._data.default_joint_pos) - self._data.joint_limits = torch.zeros(self.num_instances, self.num_joints, 2, device=self.device) - - # -- joint commands (explicit) - self._data.computed_torque = torch.zeros_like(self._data.default_joint_pos) - self._data.applied_torque = torch.zeros_like(self._data.default_joint_pos) - - # -- tendons - if self.num_fixed_tendons > 0: - self._data.fixed_tendon_stiffness = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.fixed_tendon_damping = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.fixed_tendon_limit_stiffness = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.fixed_tendon_limit = torch.zeros( - self.num_instances, self.num_fixed_tendons, 2, device=self.device - ) - self._data.fixed_tendon_rest_length = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.fixed_tendon_offset = torch.zeros(self.num_instances, self.num_fixed_tendons, device=self.device) - - # -- other data - self._data.soft_joint_pos_limits = torch.zeros(self.num_instances, self.num_joints, 2, device=self.device) - self._data.soft_joint_vel_limits = torch.zeros(self.num_instances, self.num_joints, device=self.device) - self._data.gear_ratio = torch.ones(self.num_instances, self.num_joints, device=self.device) - - # -- initialize default buffers related to joint properties - self._data.default_joint_stiffness = torch.zeros(self.num_instances, self.num_joints, device=self.device) - self._data.default_joint_damping = torch.zeros(self.num_instances, self.num_joints, device=self.device) - self._data.default_joint_armature = torch.zeros(self.num_instances, self.num_joints, device=self.device) - self._data.default_joint_friction = torch.zeros(self.num_instances, self.num_joints, device=self.device) - self._data.default_joint_limits = torch.zeros(self.num_instances, self.num_joints, 2, device=self.device) - - # -- initialize default buffers related to fixed tendon properties - if self.num_fixed_tendons > 0: - self._data.default_fixed_tendon_stiffness = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.default_fixed_tendon_damping = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.default_fixed_tendon_limit_stiffness = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.default_fixed_tendon_limit = torch.zeros( - self.num_instances, self.num_fixed_tendons, 2, device=self.device - ) - self._data.default_fixed_tendon_rest_length = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - self._data.default_fixed_tendon_offset = torch.zeros( - self.num_instances, self.num_fixed_tendons, device=self.device - ) - - # soft joint position limits (recommended not to be too close to limits). - joint_pos_limits = self.root_physx_view.get_dof_limits() - joint_pos_mean = (joint_pos_limits[..., 0] + joint_pos_limits[..., 1]) / 2 - joint_pos_range = joint_pos_limits[..., 1] - joint_pos_limits[..., 0] - soft_limit_factor = self.cfg.soft_joint_pos_limit_factor - # add to data - self._data.soft_joint_pos_limits[..., 0] = joint_pos_mean - 0.5 * joint_pos_range * soft_limit_factor - self._data.soft_joint_pos_limits[..., 1] = joint_pos_mean + 0.5 * joint_pos_range * soft_limit_factor - - # create buffers to store processed actions from actuator models - self._joint_pos_target_sim = torch.zeros_like(self._data.joint_pos_target) - self._joint_vel_target_sim = torch.zeros_like(self._data.joint_pos_target) - self._joint_effort_target_sim = torch.zeros_like(self._data.joint_pos_target) - - def _process_cfg(self): - """Post processing of configuration parameters.""" - # default state - super()._process_cfg() - # -- joint state - # joint pos - indices_list, _, values_list = string_utils.resolve_matching_names_values( - self.cfg.init_state.joint_pos, self.joint_names - ) - self._data.default_joint_pos[:, indices_list] = torch.tensor(values_list, device=self.device) - # joint vel - indices_list, _, values_list = string_utils.resolve_matching_names_values( - self.cfg.init_state.joint_vel, self.joint_names - ) - self._data.default_joint_vel[:, indices_list] = torch.tensor(values_list, device=self.device) - - # -- joint limits - self._data.default_joint_limits = self.root_physx_view.get_dof_limits().to(device=self.device).clone() - self._data.joint_limits = self._data.default_joint_limits.clone() - - """ - Internal helpers -- Actuators. - """ - - def _process_actuators_cfg(self): - """Process and apply articulation joint properties.""" - # create actuators - self.actuators = dict() - # flag for implicit actuators - # if this is false, we by-pass certain checks when doing actuator-related operations - self._has_implicit_actuators = False - - # cache the values coming from the usd - usd_stiffness = self.root_physx_view.get_dof_stiffnesses().clone() - usd_damping = self.root_physx_view.get_dof_dampings().clone() - usd_armature = self.root_physx_view.get_dof_armatures().clone() - usd_friction = self.root_physx_view.get_dof_friction_coefficients().clone() - usd_effort_limit = self.root_physx_view.get_dof_max_forces().clone() - usd_velocity_limit = self.root_physx_view.get_dof_max_velocities().clone() - - # iterate over all actuator configurations - for actuator_name, actuator_cfg in self.cfg.actuators.items(): - # type annotation for type checkers - actuator_cfg: ActuatorBaseCfg - # create actuator group - joint_ids, joint_names = self.find_joints(actuator_cfg.joint_names_expr) - # check if any joints are found - if len(joint_names) == 0: - raise ValueError( - f"No joints found for actuator group: {actuator_name} with joint name expression:" - f" {actuator_cfg.joint_names_expr}." - ) - # create actuator collection - # note: for efficiency avoid indexing when over all indices - actuator: ActuatorBase = actuator_cfg.class_type( - cfg=actuator_cfg, - joint_names=joint_names, - joint_ids=slice(None) if len(joint_names) == self.num_joints else joint_ids, - num_envs=self.num_instances, - device=self.device, - stiffness=usd_stiffness[:, joint_ids], - damping=usd_damping[:, joint_ids], - armature=usd_armature[:, joint_ids], - friction=usd_friction[:, joint_ids], - effort_limit=usd_effort_limit[:, joint_ids], - velocity_limit=usd_velocity_limit[:, joint_ids], - ) - # log information on actuator groups - carb.log_info( - f"Actuator collection: {actuator_name} with model '{actuator_cfg.class_type.__name__}' and" - f" joint names: {joint_names} [{joint_ids}]." - ) - # store actuator group - self.actuators[actuator_name] = actuator - # set the passed gains and limits into the simulation - if isinstance(actuator, ImplicitActuator): - self._has_implicit_actuators = True - # the gains and limits are set into the simulation since actuator model is implicit - self.write_joint_stiffness_to_sim(actuator.stiffness, joint_ids=actuator.joint_indices) - self.write_joint_damping_to_sim(actuator.damping, joint_ids=actuator.joint_indices) - self.write_joint_effort_limit_to_sim(actuator.effort_limit, joint_ids=actuator.joint_indices) - self.write_joint_armature_to_sim(actuator.armature, joint_ids=actuator.joint_indices) - self.write_joint_friction_to_sim(actuator.friction, joint_ids=actuator.joint_indices) - else: - # the gains and limits are processed by the actuator model - # we set gains to zero, and torque limit to a high value in simulation to avoid any interference - self.write_joint_stiffness_to_sim(0.0, joint_ids=actuator.joint_indices) - self.write_joint_damping_to_sim(0.0, joint_ids=actuator.joint_indices) - self.write_joint_effort_limit_to_sim(1.0e9, joint_ids=actuator.joint_indices) - self.write_joint_armature_to_sim(actuator.armature, joint_ids=actuator.joint_indices) - self.write_joint_friction_to_sim(actuator.friction, joint_ids=actuator.joint_indices) - - # set the default joint parameters based on the changes from the actuators - self._data.default_joint_stiffness = self.root_physx_view.get_dof_stiffnesses().to(device=self.device).clone() - self._data.default_joint_damping = self.root_physx_view.get_dof_dampings().to(device=self.device).clone() - self._data.default_joint_armature = self.root_physx_view.get_dof_armatures().to(device=self.device).clone() - self._data.default_joint_friction = ( - self.root_physx_view.get_dof_friction_coefficients().to(device=self.device).clone() - ) - - # perform some sanity checks to ensure actuators are prepared correctly - total_act_joints = sum(actuator.num_joints for actuator in self.actuators.values()) - if total_act_joints != (self.num_joints - self.num_fixed_tendons): - carb.log_warn( - "Not all actuators are configured! Total number of actuated joints not equal to number of" - f" joints available: {total_act_joints} != {self.num_joints}." - ) - - def _process_fixed_tendons(self): - """Process fixed tendons.""" - # create a list to store the fixed tendon names - self._fixed_tendon_names = list() - - # parse fixed tendons properties if they exist - if self.num_fixed_tendons > 0: - stage = stage_utils.get_current_stage() - - # iterate over all joints to find tendons attached to them - for j in range(self.num_joints): - usd_joint_path = self.root_physx_view.dof_paths[0][j] - # check whether joint has tendons - tendon name follows the joint name it is attached to - joint = UsdPhysics.Joint.Get(stage, usd_joint_path) - if joint.GetPrim().HasAPI(PhysxSchema.PhysxTendonAxisRootAPI): - joint_name = usd_joint_path.split("/")[-1] - self._fixed_tendon_names.append(joint_name) - - self._data.fixed_tendon_names = self._fixed_tendon_names - self._data.default_fixed_tendon_stiffness = self.root_physx_view.get_fixed_tendon_stiffnesses().clone() - self._data.default_fixed_tendon_damping = self.root_physx_view.get_fixed_tendon_dampings().clone() - self._data.default_fixed_tendon_limit_stiffness = ( - self.root_physx_view.get_fixed_tendon_limit_stiffnesses().clone() - ) - self._data.default_fixed_tendon_limit = self.root_physx_view.get_fixed_tendon_limits().clone() - self._data.default_fixed_tendon_rest_length = self.root_physx_view.get_fixed_tendon_rest_lengths().clone() - self._data.default_fixed_tendon_offset = self.root_physx_view.get_fixed_tendon_offsets().clone() - - def _apply_actuator_model(self): - """Processes joint commands for the articulation by forwarding them to the actuators. - - The actions are first processed using actuator models. Depending on the robot configuration, - the actuator models compute the joint level simulation commands and sets them into the PhysX buffers. - """ - # process actions per group - for actuator in self.actuators.values(): - # prepare input for actuator model based on cached data - # TODO : A tensor dict would be nice to do the indexing of all tensors together - control_action = ArticulationActions( - joint_positions=self._data.joint_pos_target[:, actuator.joint_indices], - joint_velocities=self._data.joint_vel_target[:, actuator.joint_indices], - joint_efforts=self._data.joint_effort_target[:, actuator.joint_indices], - joint_indices=actuator.joint_indices, - ) - # compute joint command from the actuator model - control_action = actuator.compute( - control_action, - joint_pos=self._data.joint_pos[:, actuator.joint_indices], - joint_vel=self._data.joint_vel[:, actuator.joint_indices], - ) - # update targets (these are set into the simulation) - if control_action.joint_positions is not None: - self._joint_pos_target_sim[:, actuator.joint_indices] = control_action.joint_positions - if control_action.joint_velocities is not None: - self._joint_vel_target_sim[:, actuator.joint_indices] = control_action.joint_velocities - if control_action.joint_efforts is not None: - self._joint_effort_target_sim[:, actuator.joint_indices] = control_action.joint_efforts - # update state of the actuator model - # -- torques - self._data.computed_torque[:, actuator.joint_indices] = actuator.computed_effort - self._data.applied_torque[:, actuator.joint_indices] = actuator.applied_effort - # -- actuator data - self._data.soft_joint_vel_limits[:, actuator.joint_indices] = actuator.velocity_limit - # TODO: find a cleaner way to handle gear ratio. Only needed for variable gear ratio actuators. - if hasattr(actuator, "gear_ratio"): - self._data.gear_ratio[:, actuator.joint_indices] = actuator.gear_ratio - - """ - Internal helpers -- Debugging. - """ - - def _validate_cfg(self): - """Validate the configuration after processing. - - Note: - This function should be called only after the configuration has been processed and the buffers have been - created. Otherwise, some settings that are altered during processing may not be validated. - For instance, the actuator models may change the joint max velocity limits. - """ - # check that the default values are within the limits - joint_pos_limits = self.root_physx_view.get_dof_limits()[0].to(self.device) - out_of_range = self._data.default_joint_pos[0] < joint_pos_limits[:, 0] - out_of_range |= self._data.default_joint_pos[0] > joint_pos_limits[:, 1] - violated_indices = torch.nonzero(out_of_range, as_tuple=False).squeeze(-1) - # throw error if any of the default joint positions are out of the limits - if len(violated_indices) > 0: - # prepare message for violated joints - msg = "The following joints have default positions out of the limits: \n" - for idx in violated_indices: - joint_name = self.data.joint_names[idx] - joint_limits = joint_pos_limits[idx] - joint_pos = self.data.default_joint_pos[0, idx] - # add to message - msg += f"\t- '{joint_name}': {joint_pos:.3f} not in [{joint_limits[0]:.3f}, {joint_limits[1]:.3f}]\n" - raise ValueError(msg) - - # check that the default joint velocities are within the limits - joint_max_vel = self.root_physx_view.get_dof_max_velocities()[0].to(self.device) - out_of_range = torch.abs(self._data.default_joint_vel[0]) > joint_max_vel - violated_indices = torch.nonzero(out_of_range, as_tuple=False).squeeze(-1) - if len(violated_indices) > 0: - # prepare message for violated joints - msg = "The following joints have default velocities out of the limits: \n" - for idx in violated_indices: - joint_name = self.data.joint_names[idx] - joint_limits = [-joint_max_vel[idx], joint_max_vel[idx]] - joint_vel = self.data.default_joint_vel[0, idx] - # add to message - msg += f"\t- '{joint_name}': {joint_vel:.3f} not in [{joint_limits[0]:.3f}, {joint_limits[1]:.3f}]\n" - raise ValueError(msg) - - def _log_articulation_joint_info(self): - """Log information about the articulation's simulated joints.""" - # read out all joint parameters from simulation - # -- gains - stiffnesses = self.root_physx_view.get_dof_stiffnesses()[0].tolist() - dampings = self.root_physx_view.get_dof_dampings()[0].tolist() - # -- properties - armatures = self.root_physx_view.get_dof_armatures()[0].tolist() - frictions = self.root_physx_view.get_dof_friction_coefficients()[0].tolist() - # -- limits - position_limits = self.root_physx_view.get_dof_limits()[0].tolist() - velocity_limits = self.root_physx_view.get_dof_max_velocities()[0].tolist() - effort_limits = self.root_physx_view.get_dof_max_forces()[0].tolist() - # create table for term information - table = PrettyTable(float_format=".3f") - table.title = f"Simulation Joint Information (Prim path: {self.cfg.prim_path})" - table.field_names = [ - "Index", - "Name", - "Stiffness", - "Damping", - "Armature", - "Friction", - "Position Limits", - "Velocity Limits", - "Effort Limits", - ] - # set alignment of table columns - table.align["Name"] = "l" - # add info on each term - for index, name in enumerate(self.joint_names): - table.add_row([ - index, - name, - stiffnesses[index], - dampings[index], - armatures[index], - frictions[index], - position_limits[index], - velocity_limits[index], - effort_limits[index], - ]) - # convert table to string - carb.log_info(f"Simulation parameters for joints in {self.cfg.prim_path}:\n" + table.get_string()) - - # read out all tendon parameters from simulation - if self.num_fixed_tendons > 0: - # -- gains - ft_stiffnesses = self.root_physx_view.get_fixed_tendon_stiffnesses()[0].tolist() - ft_dampings = self.root_physx_view.get_fixed_tendon_dampings()[0].tolist() - # -- limits - ft_limit_stiffnesses = self.root_physx_view.get_fixed_tendon_limit_stiffnesses()[0].tolist() - ft_limits = self.root_physx_view.get_fixed_tendon_limits()[0].tolist() - ft_rest_lengths = self.root_physx_view.get_fixed_tendon_rest_lengths()[0].tolist() - ft_offsets = self.root_physx_view.get_fixed_tendon_offsets()[0].tolist() - # create table for term information - tendon_table = PrettyTable(float_format=".3f") - tendon_table.title = f"Simulation Tendon Information (Prim path: {self.cfg.prim_path})" - tendon_table.field_names = [ - "Index", - "Stiffness", - "Damping", - "Limit Stiffness", - "Limit", - "Rest Length", - "Offset", - ] - # add info on each term - for index in range(self.num_fixed_tendons): - tendon_table.add_row([ - index, - ft_stiffnesses[index], - ft_dampings[index], - ft_limit_stiffnesses[index], - ft_limits[index], - ft_rest_lengths[index], - ft_offsets[index], - ]) - # convert table to string - carb.log_info(f"Simulation parameters for tendons in {self.cfg.prim_path}:\n" + tendon_table.get_string())
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation_cfg.html deleted file mode 100644 index f2702eba3d..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation_cfg.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.articulation.articulation_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.articulation.articulation_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.actuators import ActuatorBaseCfg
-from omni.isaac.lab.utils import configclass
-
-from ..rigid_object import RigidObjectCfg
-from .articulation import Articulation
-
-
-
[文档]@configclass -class ArticulationCfg(RigidObjectCfg): - """Configuration parameters for an articulation.""" - - class_type: type = Articulation - -
[文档] @configclass - class InitialStateCfg(RigidObjectCfg.InitialStateCfg): - """Initial state of the articulation.""" - - # root position - joint_pos: dict[str, float] = {".*": 0.0} - """Joint positions of the joints. Defaults to 0.0 for all joints.""" - joint_vel: dict[str, float] = {".*": 0.0} - """Joint velocities of the joints. Defaults to 0.0 for all joints."""
- - ## - # Initialize configurations. - ## - - init_state: InitialStateCfg = InitialStateCfg() - """Initial state of the articulated object. Defaults to identity pose with zero velocity and zero joint state.""" - soft_joint_pos_limit_factor: float = 1.0 - """Fraction specifying the range of DOF position limits (parsed from the asset) to use. - Defaults to 1.0.""" - actuators: dict[str, ActuatorBaseCfg] = MISSING - """Actuators for the robot with corresponding joint names."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation_data.html b/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation_data.html deleted file mode 100644 index 9dc8f2eb99..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/articulation/articulation_data.html +++ /dev/null @@ -1,850 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.articulation.articulation_data — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.articulation.articulation_data 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-
-import omni.physics.tensors.impl.api as physx
-
-import omni.isaac.lab.utils.math as math_utils
-from omni.isaac.lab.utils.buffers import TimestampedBuffer
-
-from ..rigid_object import RigidObjectData
-
-
-
[文档]class ArticulationData(RigidObjectData): - """Data container for an articulation. - - This class extends the :class:`RigidObjectData` class to provide additional data for - an articulation mainly related to the joints and tendons. - """ - - _root_physx_view: physx.ArticulationView - """The root articulation view of the object. - - Note: - Internally, this is stored as a weak reference to avoid circular references between the asset class - and the data container. This is important to avoid memory leaks. - """ - - def __init__(self, root_physx_view: physx.ArticulationView, device: str): - # Initialize the parent class - super().__init__(root_physx_view, device) # type: ignore - - # Initialize history for finite differencing - self._previous_joint_vel = self._root_physx_view.get_dof_velocities().clone() - - # Initialize the lazy buffers. - self._body_state_w = TimestampedBuffer() - self._joint_pos = TimestampedBuffer() - self._joint_acc = TimestampedBuffer() - self._joint_vel = TimestampedBuffer() - -
[文档] def update(self, dt: float): - self._sim_timestamp += dt - # Trigger an update of the joint acceleration buffer at a higher frequency - # since we do finite differencing. - self.joint_acc
- - ## - # Names. - ## - - joint_names: list[str] = None - """Joint names in the order parsed by the simulation view.""" - - fixed_tendon_names: list[str] = None - """Fixed tendon names in the order parsed by the simulation view.""" - - ## - # Defaults. - ## - - default_joint_pos: torch.Tensor = None - """Default joint positions of all joints. Shape is (num_instances, num_joints).""" - - default_joint_vel: torch.Tensor = None - """Default joint velocities of all joints. Shape is (num_instances, num_joints).""" - - default_joint_stiffness: torch.Tensor = None - """Default joint stiffness of all joints. Shape is (num_instances, num_joints).""" - - default_joint_damping: torch.Tensor = None - """Default joint damping of all joints. Shape is (num_instances, num_joints).""" - - default_joint_armature: torch.Tensor = None - """Default joint armature of all joints. Shape is (num_instances, num_joints).""" - - default_joint_friction: torch.Tensor = None - """Default joint friction of all joints. Shape is (num_instances, num_joints).""" - - default_joint_limits: torch.Tensor = None - """Default joint limits of all joints. Shape is (num_instances, num_joints, 2).""" - - default_fixed_tendon_stiffness: torch.Tensor = None - """Default tendon stiffness of all tendons. Shape is (num_instances, num_fixed_tendons).""" - - default_fixed_tendon_damping: torch.Tensor = None - """Default tendon damping of all tendons. Shape is (num_instances, num_fixed_tendons).""" - - default_fixed_tendon_limit_stiffness: torch.Tensor = None - """Default tendon limit stiffness of all tendons. Shape is (num_instances, num_fixed_tendons).""" - - default_fixed_tendon_rest_length: torch.Tensor = None - """Default tendon rest length of all tendons. Shape is (num_instances, num_fixed_tendons).""" - - default_fixed_tendon_offset: torch.Tensor = None - """Default tendon offset of all tendons. Shape is (num_instances, num_fixed_tendons).""" - - default_fixed_tendon_limit: torch.Tensor = None - """Default tendon limits of all tendons. Shape is (num_instances, num_fixed_tendons, 2).""" - - ## - # Joint commands -- Set into simulation. - ## - - joint_pos_target: torch.Tensor = None - """Joint position targets commanded by the user. Shape is (num_instances, num_joints). - - For an implicit actuator model, the targets are directly set into the simulation. - For an explicit actuator model, the targets are used to compute the joint torques (see :attr:`applied_torque`), - which are then set into the simulation. - """ - - joint_vel_target: torch.Tensor = None - """Joint velocity targets commanded by the user. Shape is (num_instances, num_joints). - - For an implicit actuator model, the targets are directly set into the simulation. - For an explicit actuator model, the targets are used to compute the joint torques (see :attr:`applied_torque`), - which are then set into the simulation. - """ - - joint_effort_target: torch.Tensor = None - """Joint effort targets commanded by the user. Shape is (num_instances, num_joints). - - For an implicit actuator model, the targets are directly set into the simulation. - For an explicit actuator model, the targets are used to compute the joint torques (see :attr:`applied_torque`), - which are then set into the simulation. - """ - - ## - # Joint commands -- Explicit actuators. - ## - - computed_torque: torch.Tensor = None - """Joint torques computed from the actuator model (before clipping). Shape is (num_instances, num_joints). - - This quantity is the raw torque output from the actuator mode, before any clipping is applied. - It is exposed for users who want to inspect the computations inside the actuator model. - For instance, to penalize the learning agent for a difference between the computed and applied torques. - - Note: The torques are zero for implicit actuator models. - """ - - applied_torque: torch.Tensor = None - """Joint torques applied from the actuator model (after clipping). Shape is (num_instances, num_joints). - - These torques are set into the simulation, after clipping the :attr:`computed_torque` based on the - actuator model. - - Note: The torques are zero for implicit actuator models. - """ - - ## - # Joint properties. - ## - - joint_stiffness: torch.Tensor = None - """Joint stiffness provided to simulation. Shape is (num_instances, num_joints).""" - - joint_damping: torch.Tensor = None - """Joint damping provided to simulation. Shape is (num_instances, num_joints).""" - - joint_armature: torch.Tensor = None - """Joint armature provided to simulation. Shape is (num_instances, num_joints).""" - - joint_friction: torch.Tensor = None - """Joint friction provided to simulation. Shape is (num_instances, num_joints).""" - - joint_limits: torch.Tensor = None - """Joint limits provided to simulation. Shape is (num_instances, num_joints, 2).""" - - ## - # Fixed tendon properties. - ## - - fixed_tendon_stiffness: torch.Tensor = None - """Fixed tendon stiffness provided to simulation. Shape is (num_instances, num_fixed_tendons).""" - - fixed_tendon_damping: torch.Tensor = None - """Fixed tendon damping provided to simulation. Shape is (num_instances, num_fixed_tendons).""" - - fixed_tendon_limit_stiffness: torch.Tensor = None - """Fixed tendon limit stiffness provided to simulation. Shape is (num_instances, num_fixed_tendons).""" - - fixed_tendon_rest_length: torch.Tensor = None - """Fixed tendon rest length provided to simulation. Shape is (num_instances, num_fixed_tendons).""" - - fixed_tendon_offset: torch.Tensor = None - """Fixed tendon offset provided to simulation. Shape is (num_instances, num_fixed_tendons).""" - - fixed_tendon_limit: torch.Tensor = None - """Fixed tendon limits provided to simulation. Shape is (num_instances, num_fixed_tendons, 2).""" - - ## - # Other Data. - ## - - soft_joint_pos_limits: torch.Tensor = None - """Joint positions limits for all joints. Shape is (num_instances, num_joints, 2).""" - - soft_joint_vel_limits: torch.Tensor = None - """Joint velocity limits for all joints. Shape is (num_instances, num_joints).""" - - gear_ratio: torch.Tensor = None - """Gear ratio for relating motor torques to applied Joint torques. Shape is (num_instances, num_joints).""" - - ## - # Properties. - ## - - @property - def root_state_w(self): - """Root state ``[pos, quat, lin_vel, ang_vel]`` in simulation world frame. Shape is (num_instances, 13).""" - if self._root_state_w.timestamp < self._sim_timestamp: - # read data from simulation - pose = self._root_physx_view.get_root_transforms().clone() - pose[:, 3:7] = math_utils.convert_quat(pose[:, 3:7], to="wxyz") - velocity = self._root_physx_view.get_root_velocities() - # set the buffer data and timestamp - self._root_state_w.data = torch.cat((pose, velocity), dim=-1) - self._root_state_w.timestamp = self._sim_timestamp - return self._root_state_w.data - - @property - def body_state_w(self): - """State of all bodies `[pos, quat, lin_vel, ang_vel]` in simulation world frame. - Shape is (num_instances, num_bodies, 13).""" - if self._body_state_w.timestamp < self._sim_timestamp: - # read data from simulation - poses = self._root_physx_view.get_link_transforms().clone() - poses[..., 3:7] = math_utils.convert_quat(poses[..., 3:7], to="wxyz") - velocities = self._root_physx_view.get_link_velocities() - # set the buffer data and timestamp - self._body_state_w.data = torch.cat((poses, velocities), dim=-1) - self._body_state_w.timestamp = self._sim_timestamp - return self._body_state_w.data - - @property - def body_acc_w(self): - """Acceleration of all bodies. Shape is (num_instances, num_bodies, 6).""" - if self._body_acc_w.timestamp < self._sim_timestamp: - # read data from simulation and set the buffer data and timestamp - self._body_acc_w.data = self._root_physx_view.get_link_accelerations() - self._body_acc_w.timestamp = self._sim_timestamp - return self._body_acc_w.data - - @property - def body_lin_acc_w(self) -> torch.Tensor: - """Linear acceleration of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" - return self.body_acc_w[..., 0:3] - - @property - def body_ang_acc_w(self) -> torch.Tensor: - """Angular acceleration of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" - return self.body_acc_w[..., 3:6] - - @property - def joint_pos(self): - """Joint positions of all joints. Shape is (num_instances, num_joints).""" - if self._joint_pos.timestamp < self._sim_timestamp: - # read data from simulation and set the buffer data and timestamp - self._joint_pos.data = self._root_physx_view.get_dof_positions() - self._joint_pos.timestamp = self._sim_timestamp - return self._joint_pos.data - - @property - def joint_vel(self): - """Joint velocities of all joints. Shape is (num_instances, num_joints).""" - if self._joint_vel.timestamp < self._sim_timestamp: - # read data from simulation and set the buffer data and timestamp - self._joint_vel.data = self._root_physx_view.get_dof_velocities() - self._joint_vel.timestamp = self._sim_timestamp - return self._joint_vel.data - - @property - def joint_acc(self): - """Joint acceleration of all joints. Shape is (num_instances, num_joints).""" - if self._joint_acc.timestamp < self._sim_timestamp: - # note: we use finite differencing to compute acceleration - self._joint_acc.data = (self.joint_vel - self._previous_joint_vel) / ( - self._sim_timestamp - self._joint_acc.timestamp - ) - self._joint_acc.timestamp = self._sim_timestamp - # update the previous joint velocity - self._previous_joint_vel[:] = self.joint_vel - return self._joint_acc.data
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/asset_base.html b/docs/_build/html/_modules/omni/isaac/lab/assets/asset_base.html deleted file mode 100644 index c105d44e83..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/asset_base.html +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.asset_base — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.asset_base 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import inspect
-import re
-import weakref
-from abc import ABC, abstractmethod
-from collections.abc import Sequence
-from typing import TYPE_CHECKING, Any
-
-import omni.kit.app
-import omni.timeline
-
-import omni.isaac.lab.sim as sim_utils
-
-if TYPE_CHECKING:
-    from .asset_base_cfg import AssetBaseCfg
-
-
-
[文档]class AssetBase(ABC): - """The base interface class for assets. - - An asset corresponds to any physics-enabled object that can be spawned in the simulation. These include - rigid objects, articulated objects, deformable objects etc. The core functionality of an asset is to - provide a set of buffers that can be used to interact with the simulator. The buffers are updated - by the asset class and can be written into the simulator using the their respective ``write`` methods. - This allows a convenient way to perform post-processing operations on the buffers before writing them - into the simulator and obtaining the corresponding simulation results. - - The class handles both the spawning of the asset into the USD stage as well as initialization of necessary - physics handles to interact with the asset. Upon construction of the asset instance, the prim corresponding - to the asset is spawned into the USD stage if the spawn configuration is not None. The spawn configuration - is defined in the :attr:`AssetBaseCfg.spawn` attribute. In case the configured :attr:`AssetBaseCfg.prim_path` - is an expression, then the prim is spawned at all the matching paths. Otherwise, a single prim is spawned - at the configured path. For more information on the spawn configuration, see the - :mod:`omni.isaac.lab.sim.spawners` module. - - Unlike Isaac Sim interface, where one usually needs to call the - :meth:`omni.isaac.core.prims.XFormPrimView.initialize` method to initialize the PhysX handles, the asset - class automatically initializes and invalidates the PhysX handles when the stage is played/stopped. This - is done by registering callbacks for the stage play/stop events. - - Additionally, the class registers a callback for debug visualization of the asset if a debug visualization - is implemented in the asset class. This can be enabled by setting the :attr:`AssetBaseCfg.debug_vis` attribute - to True. The debug visualization is implemented through the :meth:`_set_debug_vis_impl` and - :meth:`_debug_vis_callback` methods. - """ - -
[文档] def __init__(self, cfg: AssetBaseCfg): - """Initialize the asset base. - - Args: - cfg: The configuration class for the asset. - - Raises: - RuntimeError: If no prims found at input prim path or prim path expression. - """ - # store inputs - self.cfg = cfg - # flag for whether the asset is initialized - self._is_initialized = False - - # check if base asset path is valid - # note: currently the spawner does not work if there is a regex pattern in the leaf - # For example, if the prim path is "/World/Robot_[1,2]" since the spawner will not - # know which prim to spawn. This is a limitation of the spawner and not the asset. - asset_path = self.cfg.prim_path.split("/")[-1] - asset_path_is_regex = re.match(r"^[a-zA-Z0-9/_]+$", asset_path) is None - # spawn the asset - if self.cfg.spawn is not None and not asset_path_is_regex: - self.cfg.spawn.func( - self.cfg.prim_path, - self.cfg.spawn, - translation=self.cfg.init_state.pos, - orientation=self.cfg.init_state.rot, - ) - # check that spawn was successful - matching_prims = sim_utils.find_matching_prims(self.cfg.prim_path) - if len(matching_prims) == 0: - raise RuntimeError(f"Could not find prim with path {self.cfg.prim_path}.") - - # note: Use weakref on all callbacks to ensure that this object can be deleted when its destructor is called. - # add callbacks for stage play/stop - # The order is set to 10 which is arbitrary but should be lower priority than the default order of 0 - timeline_event_stream = omni.timeline.get_timeline_interface().get_timeline_event_stream() - self._initialize_handle = timeline_event_stream.create_subscription_to_pop_by_type( - int(omni.timeline.TimelineEventType.PLAY), - lambda event, obj=weakref.proxy(self): obj._initialize_callback(event), - order=10, - ) - self._invalidate_initialize_handle = timeline_event_stream.create_subscription_to_pop_by_type( - int(omni.timeline.TimelineEventType.STOP), - lambda event, obj=weakref.proxy(self): obj._invalidate_initialize_callback(event), - order=10, - ) - # add handle for debug visualization (this is set to a valid handle inside set_debug_vis) - self._debug_vis_handle = None - # set initial state of debug visualization - self.set_debug_vis(self.cfg.debug_vis)
- - def __del__(self): - """Unsubscribe from the callbacks.""" - # clear physics events handles - if self._initialize_handle: - self._initialize_handle.unsubscribe() - self._initialize_handle = None - if self._invalidate_initialize_handle: - self._invalidate_initialize_handle.unsubscribe() - self._invalidate_initialize_handle = None - # clear debug visualization - if self._debug_vis_handle: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - - """ - Properties - """ - - @property - def is_initialized(self) -> bool: - """Whether the asset is initialized. - - Returns True if the asset is initialized, False otherwise. - """ - return self._is_initialized - - @property - @abstractmethod - def num_instances(self) -> int: - """Number of instances of the asset. - - This is equal to the number of asset instances per environment multiplied by the number of environments. - """ - return NotImplementedError - - @property - def device(self) -> str: - """Memory device for computation.""" - return self._device - - @property - @abstractmethod - def data(self) -> Any: - """Data related to the asset.""" - return NotImplementedError - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the asset has a debug visualization implemented.""" - # check if function raises NotImplementedError - source_code = inspect.getsource(self._set_debug_vis_impl) - return "NotImplementedError" not in source_code - - """ - Operations. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Sets whether to visualize the asset data. - - Args: - debug_vis: Whether to visualize the asset data. - - Returns: - Whether the debug visualization was successfully set. False if the asset - does not support debug visualization. - """ - # check if debug visualization is supported - if not self.has_debug_vis_implementation: - return False - # toggle debug visualization objects - self._set_debug_vis_impl(debug_vis) - # toggle debug visualization handles - if debug_vis: - # create a subscriber for the post update event if it doesn't exist - if self._debug_vis_handle is None: - 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) - ) - else: - # remove the subscriber if it exists - if self._debug_vis_handle is not None: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - # return success - return True
- -
[文档] @abstractmethod - def reset(self, env_ids: Sequence[int] | None = None): - """Resets all internal buffers of selected environments. - - Args: - env_ids: The indices of the object to reset. Defaults to None (all instances). - """ - raise NotImplementedError
- -
[文档] @abstractmethod - def write_data_to_sim(self): - """Writes data to the simulator.""" - raise NotImplementedError
- -
[文档] @abstractmethod - def update(self, dt: float): - """Update the internal buffers. - - The time step ``dt`` is used to compute numerical derivatives of quantities such as joint - accelerations which are not provided by the simulator. - - Args: - dt: The amount of time passed from last ``update`` call. - """ - raise NotImplementedError
- - """ - Implementation specific. - """ - - @abstractmethod - def _initialize_impl(self): - """Initializes the PhysX handles and internal buffers.""" - raise NotImplementedError - - def _set_debug_vis_impl(self, debug_vis: bool): - """Set debug visualization into visualization objects. - - This function is responsible for creating the visualization objects if they don't exist - and input ``debug_vis`` is True. If the visualization objects exist, the function should - set their visibility into the stage. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") - - def _debug_vis_callback(self, event): - """Callback for debug visualization. - - This function calls the visualization objects and sets the data to visualize into them. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") - - """ - Internal simulation callbacks. - """ - - def _initialize_callback(self, event): - """Initializes the scene elements. - - Note: - PhysX handles are only enabled once the simulator starts playing. Hence, this function needs to be - called whenever the simulator "plays" from a "stop" state. - """ - if not self._is_initialized: - # obtain simulation related information - sim = sim_utils.SimulationContext.instance() - if sim is None: - raise RuntimeError("SimulationContext is not initialized! Please initialize SimulationContext first.") - self._backend = sim.backend - self._device = sim.device - # initialize the asset - self._initialize_impl() - # set flag - self._is_initialized = True - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - self._is_initialized = False
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/asset_base_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/assets/asset_base_cfg.html deleted file mode 100644 index fe3b49c1b9..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/asset_base_cfg.html +++ /dev/null @@ -1,639 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.asset_base_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.asset_base_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.sim import SpawnerCfg
-from omni.isaac.lab.utils import configclass
-
-from .asset_base import AssetBase
-
-
-
[文档]@configclass -class AssetBaseCfg: - """The base configuration class for an asset's parameters. - - Please see the :class:`AssetBase` class for more information on the asset class. - """ - -
[文档] @configclass - class InitialStateCfg: - """Initial state of the asset. - - This defines the default initial state of the asset when it is spawned into the simulation, as - well as the default state when the simulation is reset. - - After parsing the initial state, the asset class stores this information in the :attr:`data` - attribute of the asset class. This can then be accessed by the user to modify the state of the asset - during the simulation, for example, at resets. - """ - - # root position - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Position of the root in simulation world frame. Defaults to (0.0, 0.0, 0.0).""" - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation (w, x, y, z) of the root in simulation world frame. - Defaults to (1.0, 0.0, 0.0, 0.0). - """
- - class_type: type[AssetBase] = MISSING - """The associated asset class. - - The class should inherit from :class:`omni.isaac.lab.assets.asset_base.AssetBase`. - """ - - prim_path: str = MISSING - """Prim path (or expression) to the asset. - - .. note:: - The expression can contain the environment namespace regex ``{ENV_REGEX_NS}`` which - will be replaced with the environment namespace. - - Example: ``{ENV_REGEX_NS}/Robot`` will be replaced with ``/World/envs/env_.*/Robot``. - """ - - spawn: SpawnerCfg | None = None - """Spawn configuration for the asset. Defaults to None. - - If None, then no prims are spawned by the asset class. Instead, it is assumed that the - asset is already present in the scene. - """ - - init_state: InitialStateCfg = InitialStateCfg() - """Initial state of the rigid object. Defaults to identity pose.""" - - collision_group: Literal[0, -1] = 0 - """Collision group of the asset. Defaults to ``0``. - - * ``-1``: global collision group (collides with all assets in the scene). - * ``0``: local collision group (collides with other assets in the same environment). - """ - - debug_vis: bool = False - """Whether to enable debug visualization for the asset. Defaults to ``False``."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object.html b/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object.html deleted file mode 100644 index 810643bc85..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object.html +++ /dev/null @@ -1,934 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.rigid_object.rigid_object — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.rigid_object.rigid_object 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-import warnings
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-import carb
-import omni.physics.tensors.impl.api as physx
-from pxr import UsdPhysics
-
-import omni.isaac.lab.sim as sim_utils
-import omni.isaac.lab.utils.math as math_utils
-import omni.isaac.lab.utils.string as string_utils
-
-from ..asset_base import AssetBase
-from .rigid_object_data import RigidObjectData
-
-if TYPE_CHECKING:
-    from .rigid_object_cfg import RigidObjectCfg
-
-
-
[文档]class RigidObject(AssetBase): - """A rigid object asset class. - - Rigid objects are assets comprising of rigid bodies. They can be used to represent dynamic objects - such as boxes, spheres, etc. A rigid body is described by its pose, velocity and mass distribution. - - For an asset to be considered a rigid object, the root prim of the asset must have the `USD RigidBodyAPI`_ - applied to it. This API is used to define the simulation properties of the rigid body. On playing the - simulation, the physics engine will automatically register the rigid body and create a corresponding - rigid body handle. This handle can be accessed using the :attr:`root_physx_view` attribute. - - .. note:: - - For users familiar with Isaac Sim, the PhysX view class API is not the exactly same as Isaac Sim view - class API. Similar to Isaac Lab, Isaac Sim wraps around the PhysX view API. However, as of now (2023.1 release), - we see a large difference in initializing the view classes in Isaac Sim. This is because the view classes - in Isaac Sim perform additional USD-related operations which are slow and also not required. - - .. _`USD RigidBodyAPI`: https://openusd.org/dev/api/class_usd_physics_rigid_body_a_p_i.html - """ - - cfg: RigidObjectCfg - """Configuration instance for the rigid object.""" - -
[文档] def __init__(self, cfg: RigidObjectCfg): - """Initialize the rigid object. - - Args: - cfg: A configuration instance. - """ - super().__init__(cfg)
- - """ - Properties - """ - - @property - def data(self) -> RigidObjectData: - return self._data - - @property - def num_instances(self) -> int: - return self.root_physx_view.count - - @property - def num_bodies(self) -> int: - """Number of bodies in the asset.""" - return 1 - - @property - def body_names(self) -> list[str]: - """Ordered names of bodies in the rigid object.""" - prim_paths = self.root_physx_view.prim_paths[: self.num_bodies] - return [path.split("/")[-1] for path in prim_paths] - - @property - def root_physx_view(self) -> physx.RigidBodyView: - """Rigid body view for the asset (PhysX). - - Note: - Use this view with caution. It requires handling of tensors in a specific way. - """ - return self._root_physx_view - - @property - def body_physx_view(self) -> physx.RigidBodyView: - """Rigid body view for the asset (PhysX). - - .. deprecated:: v0.3.0 - - The attribute 'body_physx_view' will be removed in v0.4.0. Please use :attr:`root_physx_view` instead. - - """ - dep_msg = "The attribute 'body_physx_view' will be removed in v0.4.0. Please use 'root_physx_view' instead." - warnings.warn(dep_msg, DeprecationWarning) - carb.log_error(dep_msg) - return self.root_physx_view - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - # resolve all indices - if env_ids is None: - env_ids = slice(None) - # reset external wrench - self._external_force_b[env_ids] = 0.0 - self._external_torque_b[env_ids] = 0.0
- -
[文档] def write_data_to_sim(self): - """Write external wrench to the simulation. - - Note: - We write external wrench to the simulation here since this function is called before the simulation step. - This ensures that the external wrench is applied at every simulation step. - """ - # write external wrench - if self.has_external_wrench: - self.root_physx_view.apply_forces_and_torques_at_position( - force_data=self._external_force_b.view(-1, 3), - torque_data=self._external_torque_b.view(-1, 3), - position_data=None, - indices=self._ALL_BODY_INDICES, - is_global=False, - )
- -
[文档] def update(self, dt: float): - self._data.update(dt)
- -
[文档] def find_bodies(self, name_keys: str | Sequence[str], preserve_order: bool = False) -> tuple[list[int], list[str]]: - """Find bodies in the articulation based on the name keys. - - Please check the :meth:`omni.isaac.lab.utils.string_utils.resolve_matching_names` function for more - information on the name matching. - - Args: - name_keys: A regular expression or a list of regular expressions to match the body names. - preserve_order: Whether to preserve the order of the name keys in the output. Defaults to False. - - Returns: - A tuple of lists containing the body indices and names. - """ - return string_utils.resolve_matching_names(name_keys, self.body_names, preserve_order)
- - """ - Operations - Write to simulation. - """ - -
[文档] def write_root_state_to_sim(self, root_state: torch.Tensor, env_ids: Sequence[int] | None = None): - """Set the root state over selected environment indices into the simulation. - - The root state comprises of the cartesian position, quaternion orientation in (w, x, y, z), and linear - and angular velocity. All the quantities are in the simulation frame. - - Args: - root_state: Root state in simulation frame. Shape is (len(env_ids), 13). - env_ids: Environment indices. If None, then all indices are used. - """ - # set into simulation - self.write_root_pose_to_sim(root_state[:, :7], env_ids=env_ids) - self.write_root_velocity_to_sim(root_state[:, 7:], env_ids=env_ids)
- -
[文档] def write_root_pose_to_sim(self, root_pose: torch.Tensor, env_ids: Sequence[int] | None = None): - """Set the root pose over selected environment indices into the simulation. - - The root pose comprises of the cartesian position and quaternion orientation in (w, x, y, z). - - Args: - root_pose: Root poses in simulation frame. Shape is (len(env_ids), 7). - env_ids: Environment indices. If None, then all indices are used. - """ - # resolve all indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - # note: we need to do this here since tensors are not set into simulation until step. - # set into internal buffers - self._data.root_state_w[env_ids, :7] = root_pose.clone() - # convert root quaternion from wxyz to xyzw - root_poses_xyzw = self._data.root_state_w[:, :7].clone() - root_poses_xyzw[:, 3:] = math_utils.convert_quat(root_poses_xyzw[:, 3:], to="xyzw") - # set into simulation - self.root_physx_view.set_transforms(root_poses_xyzw, indices=physx_env_ids)
- -
[文档] def write_root_velocity_to_sim(self, root_velocity: torch.Tensor, env_ids: Sequence[int] | None = None): - """Set the root velocity over selected environment indices into the simulation. - - Args: - root_velocity: Root velocities in simulation frame. Shape is (len(env_ids), 6). - env_ids: Environment indices. If None, then all indices are used. - """ - # resolve all indices - physx_env_ids = env_ids - if env_ids is None: - env_ids = slice(None) - physx_env_ids = self._ALL_INDICES - # note: we need to do this here since tensors are not set into simulation until step. - # set into internal buffers - self._data.root_state_w[env_ids, 7:] = root_velocity.clone() - self._data._previous_body_vel_w[env_ids, 0] = root_velocity.clone() - self._data.body_acc_w[env_ids] = 0.0 - # set into simulation - self.root_physx_view.set_velocities(self._data.root_state_w[:, 7:], indices=physx_env_ids)
- - """ - Operations - Setters. - """ - -
[文档] def set_external_force_and_torque( - self, - forces: torch.Tensor, - torques: torch.Tensor, - body_ids: Sequence[int] | slice | None = None, - env_ids: Sequence[int] | None = None, - ): - """Set external force and torque to apply on the asset's bodies in their local frame. - - For many applications, we want to keep the applied external force on rigid bodies constant over a period of - time (for instance, during the policy control). This function allows us to store the external force and torque - into buffers which are then applied to the simulation at every step. - - .. caution:: - If the function is called with empty forces and torques, then this function disables the application - of external wrench to the simulation. - - .. code-block:: python - - # example of disabling external wrench - asset.set_external_force_and_torque(forces=torch.zeros(0, 3), torques=torch.zeros(0, 3)) - - .. note:: - This function does not apply the external wrench to the simulation. It only fills the buffers with - the desired values. To apply the external wrench, call the :meth:`write_data_to_sim` function - right before the simulation step. - - Args: - forces: External forces in bodies' local frame. Shape is (len(env_ids), len(body_ids), 3). - torques: External torques in bodies' local frame. Shape is (len(env_ids), len(body_ids), 3). - body_ids: Body indices to apply external wrench to. Defaults to None (all bodies). - env_ids: Environment indices to apply external wrench to. Defaults to None (all instances). - """ - if forces.any() or torques.any(): - self.has_external_wrench = True - # resolve all indices - # -- env_ids - if env_ids is None: - env_ids = self._ALL_INDICES - elif not isinstance(env_ids, torch.Tensor): - env_ids = torch.tensor(env_ids, dtype=torch.long, device=self.device) - # -- body_ids - if body_ids is None: - body_ids = torch.arange(self.num_bodies, dtype=torch.long, device=self.device) - elif isinstance(body_ids, slice): - body_ids = torch.arange(self.num_bodies, dtype=torch.long, device=self.device)[body_ids] - elif not isinstance(body_ids, torch.Tensor): - body_ids = torch.tensor(body_ids, dtype=torch.long, device=self.device) - - # note: we need to do this complicated indexing since torch doesn't support multi-indexing - # create global body indices from env_ids and env_body_ids - # (env_id * total_bodies_per_env) + body_id - indices = body_ids.repeat(len(env_ids), 1) + env_ids.unsqueeze(1) * self.num_bodies - indices = indices.view(-1) - # set into internal buffers - # note: these are applied in the write_to_sim function - self._external_force_b.flatten(0, 1)[indices] = forces.flatten(0, 1) - self._external_torque_b.flatten(0, 1)[indices] = torques.flatten(0, 1) - else: - self.has_external_wrench = False
- - """ - Internal helper. - """ - - def _initialize_impl(self): - # create simulation view - self._physics_sim_view = physx.create_simulation_view(self._backend) - self._physics_sim_view.set_subspace_roots("/") - # obtain the first prim in the regex expression (all others are assumed to be a copy of this) - template_prim = sim_utils.find_first_matching_prim(self.cfg.prim_path) - if template_prim is None: - raise RuntimeError(f"Failed to find prim for expression: '{self.cfg.prim_path}'.") - template_prim_path = template_prim.GetPath().pathString - - # find rigid root prims - root_prims = sim_utils.get_all_matching_child_prims( - template_prim_path, predicate=lambda prim: prim.HasAPI(UsdPhysics.RigidBodyAPI) - ) - if len(root_prims) == 0: - raise RuntimeError( - f"Failed to find a rigid body when resolving '{self.cfg.prim_path}'." - " Please ensure that the prim has 'USD RigidBodyAPI' applied." - ) - if len(root_prims) > 1: - raise RuntimeError( - f"Failed to find a single rigid body when resolving '{self.cfg.prim_path}'." - f" Found multiple '{root_prims}' under '{template_prim_path}'." - " Please ensure that there is only one rigid body in the prim path tree." - ) - - # resolve root prim back into regex expression - root_prim_path = root_prims[0].GetPath().pathString - root_prim_path_expr = self.cfg.prim_path + root_prim_path[len(template_prim_path) :] - # -- object view - self._root_physx_view = self._physics_sim_view.create_rigid_body_view(root_prim_path_expr.replace(".*", "*")) - - # log information about the articulation - carb.log_info(f"Rigid body initialized at: {self.cfg.prim_path} with root '{root_prim_path_expr}'.") - carb.log_info(f"Number of instances: {self.num_instances}") - carb.log_info(f"Number of bodies: {self.num_bodies}") - carb.log_info(f"Body names: {self.body_names}") - - # container for data access - self._data = RigidObjectData(self.root_physx_view, self.device) - - # create buffers - self._create_buffers() - # process configuration - self._process_cfg() - # update the rigid body data - self.update(0.0) - - def _create_buffers(self): - """Create buffers for storing data.""" - # constants - self._ALL_INDICES = torch.arange(self.num_instances, dtype=torch.long, device=self.device) - self._ALL_BODY_INDICES = torch.arange( - self.root_physx_view.count * self.num_bodies, dtype=torch.long, device=self.device - ) - # external forces and torques - self.has_external_wrench = False - self._external_force_b = torch.zeros((self.num_instances, self.num_bodies, 3), device=self.device) - self._external_torque_b = torch.zeros_like(self._external_force_b) - - # set information about rigid body into data - self._data.body_names = self.body_names - self._data.default_mass = self.root_physx_view.get_masses().clone() - - def _process_cfg(self): - """Post processing of configuration parameters.""" - # default state - # -- root state - # note: we cast to tuple to avoid torch/numpy type mismatch. - default_root_state = ( - tuple(self.cfg.init_state.pos) - + tuple(self.cfg.init_state.rot) - + tuple(self.cfg.init_state.lin_vel) - + tuple(self.cfg.init_state.ang_vel) - ) - default_root_state = torch.tensor(default_root_state, dtype=torch.float, device=self.device) - self._data.default_root_state = default_root_state.repeat(self.num_instances, 1) - - """ - Internal simulation callbacks. - """ - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - # call parent - super()._invalidate_initialize_callback(event) - # set all existing views to None to invalidate them - self._physics_sim_view = None - self._root_physx_view = None
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object_cfg.html deleted file mode 100644 index 7d21f62d1c..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object_cfg.html +++ /dev/null @@ -1,595 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.rigid_object.rigid_object_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.rigid_object.rigid_object_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from omni.isaac.lab.utils import configclass
-
-from ..asset_base_cfg import AssetBaseCfg
-from .rigid_object import RigidObject
-
-
-
[文档]@configclass -class RigidObjectCfg(AssetBaseCfg): - """Configuration parameters for a rigid object.""" - -
[文档] @configclass - class InitialStateCfg(AssetBaseCfg.InitialStateCfg): - """Initial state of the rigid body.""" - - lin_vel: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Linear velocity of the root in simulation world frame. Defaults to (0.0, 0.0, 0.0).""" - ang_vel: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Angular velocity of the root in simulation world frame. Defaults to (0.0, 0.0, 0.0)."""
- - ## - # Initialize configurations. - ## - - class_type: type = RigidObject - - init_state: InitialStateCfg = InitialStateCfg() - """Initial state of the rigid object. Defaults to identity pose with zero velocity."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object_data.html b/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object_data.html deleted file mode 100644 index e53d9a44f3..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/assets/rigid_object/rigid_object_data.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.assets.rigid_object.rigid_object_data — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.assets.rigid_object.rigid_object_data 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-import weakref
-
-import omni.physics.tensors.impl.api as physx
-
-import omni.isaac.lab.utils.math as math_utils
-from omni.isaac.lab.utils.buffers import TimestampedBuffer
-
-
-
[文档]class RigidObjectData: - """Data container for a rigid object. - - This class contains the data for a rigid object in the simulation. The data includes the state of - the root rigid body and the state of all the bodies in the object. The data is stored in the simulation - world frame unless otherwise specified. - - The data is lazily updated, meaning that the data is only updated when it is accessed. This is useful - when the data is expensive to compute or retrieve. The data is updated when the timestamp of the buffer - is older than the current simulation timestamp. The timestamp is updated whenever the data is updated. - """ - - _root_physx_view: physx.RigidBodyView - """The root rigid body view of the object. - - Note: - Internally, this is stored as a weak reference to avoid circular references between the asset class - and the data container. This is important to avoid memory leaks. - """ - - def __init__(self, root_physx_view: physx.RigidBodyView, device: str): - """Initializes the rigid object data. - - Args: - root_physx_view: The root rigid body view of the object. - device: The device used for processing. - """ - # Set the parameters - self.device = device - self._root_physx_view = weakref.proxy(root_physx_view) # weak reference to avoid circular references - # Set initial time stamp - self._sim_timestamp = 0.0 - - # Obtain global physics sim view - physics_sim_view = physx.create_simulation_view("torch") - physics_sim_view.set_subspace_roots("/") - gravity = physics_sim_view.get_gravity() - # Convert to direction vector - gravity_dir = torch.tensor((gravity[0], gravity[1], gravity[2]), device=self.device) - gravity_dir = math_utils.normalize(gravity_dir.unsqueeze(0)).squeeze(0) - - # Initialize constants - self.GRAVITY_VEC_W = gravity_dir.repeat(self._root_physx_view.count, 1) - self.FORWARD_VEC_B = torch.tensor((1.0, 0.0, 0.0), device=self.device).repeat(self._root_physx_view.count, 1) - - # Initialize buffers for finite differencing - self._previous_body_vel_w = torch.zeros((self._root_physx_view.count, 1, 6), device=self.device) - - # Initialize the lazy buffers. - self._root_state_w = TimestampedBuffer() - self._body_acc_w = TimestampedBuffer() - -
[文档] def update(self, dt: float): - """Updates the data for the rigid object. - - Args: - dt: The time step for the update. This must be a positive value. - """ - self._sim_timestamp += dt - # Trigger an update of the body acceleration buffer at a higher frequency - # since we do finite differencing. - self.body_acc_w
- - ## - # Names. - ## - - body_names: list[str] = None - """Body names in the order parsed by the simulation view.""" - - ## - # Defaults. - ## - - default_root_state: torch.Tensor = None - """Default root state ``[pos, quat, lin_vel, ang_vel]`` in local environment frame. Shape is (num_instances, 13).""" - - default_mass: torch.Tensor = None - """ Default mass provided by simulation. Shape is (num_instances, num_bodies).""" - - ## - # Properties. - ## - - @property - def root_state_w(self): - """Root state ``[pos, quat, lin_vel, ang_vel]`` in simulation world frame. Shape is (num_instances, 13).""" - if self._root_state_w.timestamp < self._sim_timestamp: - # read data from simulation - pose = self._root_physx_view.get_transforms().clone() - pose[:, 3:7] = math_utils.convert_quat(pose[:, 3:7], to="wxyz") - velocity = self._root_physx_view.get_velocities() - # set the buffer data and timestamp - self._root_state_w.data = torch.cat((pose, velocity), dim=-1) - self._root_state_w.timestamp = self._sim_timestamp - return self._root_state_w.data - - @property - def body_state_w(self): - """State of all bodies `[pos, quat, lin_vel, ang_vel]` in simulation world frame. Shape is (num_instances, 1, 13).""" - return self.root_state_w.view(-1, 1, 13) - - @property - def body_acc_w(self): - """Acceleration of all bodies. Shape is (num_instances, 1, 6).""" - if self._body_acc_w.timestamp < self._sim_timestamp: - # note: we use finite differencing to compute acceleration - self._body_acc_w.data = (self.body_vel_w - self._previous_body_vel_w) / ( - self._sim_timestamp - self._body_acc_w.timestamp - ) - self._body_acc_w.timestamp = self._sim_timestamp - # update the previous velocity - self._previous_body_vel_w[:] = self.body_vel_w - return self._body_acc_w.data - - @property - def projected_gravity_b(self): - """Projection of the gravity direction on base frame. Shape is (num_instances, 3).""" - return math_utils.quat_rotate_inverse(self.root_quat_w, self.GRAVITY_VEC_W) - - @property - def heading_w(self): - """Yaw heading of the base frame (in radians). Shape is (num_instances,). - - Note: - This quantity is computed by assuming that the forward-direction of the base - frame is along x-direction, i.e. :math:`(1, 0, 0)`. - """ - forward_w = math_utils.quat_apply(self.root_quat_w, self.FORWARD_VEC_B) - return torch.atan2(forward_w[:, 1], forward_w[:, 0]) - - @property - def root_pos_w(self) -> torch.Tensor: - """Root position in simulation world frame. Shape is (num_instances, 3).""" - return self.root_state_w[:, :3] - - @property - def root_quat_w(self) -> torch.Tensor: - """Root orientation (w, x, y, z) in simulation world frame. Shape is (num_instances, 4).""" - return self.root_state_w[:, 3:7] - - @property - def root_vel_w(self) -> torch.Tensor: - """Root velocity in simulation world frame. Shape is (num_instances, 6).""" - return self.root_state_w[:, 7:13] - - @property - def root_lin_vel_w(self) -> torch.Tensor: - """Root linear velocity in simulation world frame. Shape is (num_instances, 3).""" - return self.root_state_w[:, 7:10] - - @property - def root_ang_vel_w(self) -> torch.Tensor: - """Root angular velocity in simulation world frame. Shape is (num_instances, 3).""" - return self.root_state_w[:, 10:13] - - @property - def root_lin_vel_b(self) -> torch.Tensor: - """Root linear velocity in base frame. Shape is (num_instances, 3).""" - return math_utils.quat_rotate_inverse(self.root_quat_w, self.root_lin_vel_w) - - @property - def root_ang_vel_b(self) -> torch.Tensor: - """Root angular velocity in base world frame. Shape is (num_instances, 3).""" - return math_utils.quat_rotate_inverse(self.root_quat_w, self.root_ang_vel_w) - - @property - def body_pos_w(self) -> torch.Tensor: - """Positions of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" - return self.body_state_w[..., :3] - - @property - def body_quat_w(self) -> torch.Tensor: - """Orientation (w, x, y, z) of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 4).""" - return self.body_state_w[..., 3:7] - - @property - def body_vel_w(self) -> torch.Tensor: - """Velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 6).""" - return self.body_state_w[..., 7:13] - - @property - def body_lin_vel_w(self) -> torch.Tensor: - """Linear velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" - return self.body_state_w[..., 7:10] - - @property - def body_ang_vel_w(self) -> torch.Tensor: - """Angular velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" - return self.body_state_w[..., 10:13]
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/controllers/differential_ik.html b/docs/_build/html/_modules/omni/isaac/lab/controllers/differential_ik.html deleted file mode 100644 index 88de19fe11..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/controllers/differential_ik.html +++ /dev/null @@ -1,797 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.controllers.differential_ik — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.controllers.differential_ik 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from typing import TYPE_CHECKING
-
-from omni.isaac.lab.utils.math import apply_delta_pose, compute_pose_error
-
-if TYPE_CHECKING:
-    from .differential_ik_cfg import DifferentialIKControllerCfg
-
-
-
[文档]class DifferentialIKController: - r"""Differential inverse kinematics (IK) controller. - - This controller is based on the concept of differential inverse kinematics [1, 2] which is a method for computing - the change in joint positions that yields the desired change in pose. - - .. math:: - - \Delta \mathbf{q} = \mathbf{J}^{\dagger} \Delta \mathbf{x} - \mathbf{q}_{\text{desired}} = \mathbf{q}_{\text{current}} + \Delta \mathbf{q} - - where :math:`\mathbf{J}^{\dagger}` is the pseudo-inverse of the Jacobian matrix :math:`\mathbf{J}`, - :math:`\Delta \mathbf{x}` is the desired change in pose, and :math:`\mathbf{q}_{\text{current}}` - is the current joint positions. - - To deal with singularity in Jacobian, the following methods are supported for computing inverse of the Jacobian: - - "pinv": Moore-Penrose pseudo-inverse - - "svd": Adaptive singular-value decomposition (SVD) - - "trans": Transpose of matrix - - "dls": Damped version of Moore-Penrose pseudo-inverse (also called Levenberg-Marquardt) - - .. caution:: - The controller does not assume anything about the frames of the current and desired end-effector pose, - or the joint-space velocities. It is up to the user to ensure that these quantities are given - in the correct format. - - Reference: - [1] https://ethz.ch/content/dam/ethz/special-interest/mavt/robotics-n-intelligent-systems/rsl-dam/documents/RobotDynamics2017/RD_HS2017script.pdf - [2] https://www.cs.cmu.edu/~15464-s13/lectures/lecture6/iksurvey.pdf - """ - -
[文档] def __init__(self, cfg: DifferentialIKControllerCfg, num_envs: int, device: str): - """Initialize the controller. - - Args: - cfg: The configuration for the controller. - num_envs: The number of environments. - device: The device to use for computations. - """ - # store inputs - self.cfg = cfg - self.num_envs = num_envs - self._device = device - # create buffers - self.ee_pos_des = torch.zeros(self.num_envs, 3, device=self._device) - self.ee_quat_des = torch.zeros(self.num_envs, 4, device=self._device) - # -- input command - self._command = torch.zeros(self.num_envs, self.action_dim, device=self._device)
- - """ - Properties. - """ - - @property - def action_dim(self) -> int: - """Dimension of the controller's input command.""" - if self.cfg.command_type == "position": - return 3 # (x, y, z) - elif self.cfg.command_type == "pose" and self.cfg.use_relative_mode: - return 6 # (dx, dy, dz, droll, dpitch, dyaw) - else: - return 7 # (x, y, z, qw, qx, qy, qz) - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: torch.Tensor = None): - """Reset the internals. - - Args: - env_ids: The environment indices to reset. If None, then all environments are reset. - """ - pass
- -
[文档] def set_command( - self, command: torch.Tensor, ee_pos: torch.Tensor | None = None, ee_quat: torch.Tensor | None = None - ): - """Set target end-effector pose command. - - Based on the configured command type and relative mode, the method computes the desired end-effector pose. - It is up to the user to ensure that the command is given in the correct frame. The method only - applies the relative mode if the command type is ``position_rel`` or ``pose_rel``. - - Args: - command: The input command in shape (N, 3) or (N, 6) or (N, 7). - ee_pos: The current end-effector position in shape (N, 3). - This is only needed if the command type is ``position_rel`` or ``pose_rel``. - ee_quat: The current end-effector orientation (w, x, y, z) in shape (N, 4). - This is only needed if the command type is ``position_*`` or ``pose_rel``. - - Raises: - ValueError: If the command type is ``position_*`` and :attr:`ee_quat` is None. - ValueError: If the command type is ``position_rel`` and :attr:`ee_pos` is None. - ValueError: If the command type is ``pose_rel`` and either :attr:`ee_pos` or :attr:`ee_quat` is None. - """ - # store command - self._command[:] = command - # compute the desired end-effector pose - if self.cfg.command_type == "position": - # we need end-effector orientation even though we are in position mode - # this is only needed for display purposes - if ee_quat is None: - raise ValueError("End-effector orientation can not be None for `position_*` command type!") - # compute targets - if self.cfg.use_relative_mode: - if ee_pos is None: - raise ValueError("End-effector position can not be None for `position_rel` command type!") - self.ee_pos_des[:] = ee_pos + self._command - self.ee_quat_des[:] = ee_quat - else: - self.ee_pos_des[:] = self._command - self.ee_quat_des[:] = ee_quat - else: - # compute targets - if self.cfg.use_relative_mode: - if ee_pos is None or ee_quat is None: - raise ValueError( - "Neither end-effector position nor orientation can be None for `pose_rel` command type!" - ) - self.ee_pos_des, self.ee_quat_des = apply_delta_pose(ee_pos, ee_quat, self._command) - else: - self.ee_pos_des = self._command[:, 0:3] - self.ee_quat_des = self._command[:, 3:7]
- -
[文档] def compute( - self, ee_pos: torch.Tensor, ee_quat: torch.Tensor, jacobian: torch.Tensor, joint_pos: torch.Tensor - ) -> torch.Tensor: - """Computes the target joint positions that will yield the desired end effector pose. - - Args: - ee_pos: The current end-effector position in shape (N, 3). - ee_quat: The current end-effector orientation in shape (N, 4). - jacobian: The geometric jacobian matrix in shape (N, 6, num_joints). - joint_pos: The current joint positions in shape (N, num_joints). - - Returns: - The target joint positions commands in shape (N, num_joints). - """ - # compute the delta in joint-space - if "position" in self.cfg.command_type: - position_error = self.ee_pos_des - ee_pos - jacobian_pos = jacobian[:, 0:3] - delta_joint_pos = self._compute_delta_joint_pos(delta_pose=position_error, jacobian=jacobian_pos) - else: - position_error, axis_angle_error = compute_pose_error( - ee_pos, ee_quat, self.ee_pos_des, self.ee_quat_des, rot_error_type="axis_angle" - ) - pose_error = torch.cat((position_error, axis_angle_error), dim=1) - delta_joint_pos = self._compute_delta_joint_pos(delta_pose=pose_error, jacobian=jacobian) - # return the desired joint positions - return joint_pos + delta_joint_pos
- - """ - Helper functions. - """ - - def _compute_delta_joint_pos(self, delta_pose: torch.Tensor, jacobian: torch.Tensor) -> torch.Tensor: - """Computes the change in joint position that yields the desired change in pose. - - The method uses the Jacobian mapping from joint-space velocities to end-effector velocities - to compute the delta-change in the joint-space that moves the robot closer to a desired - end-effector position. - - Args: - delta_pose: The desired delta pose in shape (N, 3) or (N, 6). - jacobian: The geometric jacobian matrix in shape (N, 3, num_joints) or (N, 6, num_joints). - - Returns: - The desired delta in joint space. Shape is (N, num-jointsß). - """ - if self.cfg.ik_params is None: - raise RuntimeError(f"Inverse-kinematics parameters for method '{self.cfg.ik_method}' is not defined!") - # compute the delta in joint-space - if self.cfg.ik_method == "pinv": # Jacobian pseudo-inverse - # parameters - k_val = self.cfg.ik_params["k_val"] - # computation - jacobian_pinv = torch.linalg.pinv(jacobian) - delta_joint_pos = k_val * jacobian_pinv @ delta_pose.unsqueeze(-1) - delta_joint_pos = delta_joint_pos.squeeze(-1) - elif self.cfg.ik_method == "svd": # adaptive SVD - # parameters - k_val = self.cfg.ik_params["k_val"] - min_singular_value = self.cfg.ik_params["min_singular_value"] - # computation - # U: 6xd, S: dxd, V: d x num-joint - U, S, Vh = torch.linalg.svd(jacobian) - S_inv = 1.0 / S - S_inv = torch.where(S > min_singular_value, S_inv, torch.zeros_like(S_inv)) - jacobian_pinv = ( - torch.transpose(Vh, dim0=1, dim1=2)[:, :, :6] - @ torch.diag_embed(S_inv) - @ torch.transpose(U, dim0=1, dim1=2) - ) - delta_joint_pos = k_val * jacobian_pinv @ delta_pose.unsqueeze(-1) - delta_joint_pos = delta_joint_pos.squeeze(-1) - elif self.cfg.ik_method == "trans": # Jacobian transpose - # parameters - k_val = self.cfg.ik_params["k_val"] - # computation - jacobian_T = torch.transpose(jacobian, dim0=1, dim1=2) - delta_joint_pos = k_val * jacobian_T @ delta_pose.unsqueeze(-1) - delta_joint_pos = delta_joint_pos.squeeze(-1) - elif self.cfg.ik_method == "dls": # damped least squares - # parameters - lambda_val = self.cfg.ik_params["lambda_val"] - # computation - jacobian_T = torch.transpose(jacobian, dim0=1, dim1=2) - lambda_matrix = (lambda_val**2) * torch.eye(n=jacobian.shape[1], device=self._device) - delta_joint_pos = ( - jacobian_T @ torch.inverse(jacobian @ jacobian_T + lambda_matrix) @ delta_pose.unsqueeze(-1) - ) - delta_joint_pos = delta_joint_pos.squeeze(-1) - else: - raise ValueError(f"Unsupported inverse-kinematics method: {self.cfg.ik_method}") - - return delta_joint_pos
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/controllers/differential_ik_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/controllers/differential_ik_cfg.html deleted file mode 100644 index 94c77b7a4e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/controllers/differential_ik_cfg.html +++ /dev/null @@ -1,633 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.controllers.differential_ik_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.controllers.differential_ik_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-from .differential_ik import DifferentialIKController
-
-
-
[文档]@configclass -class DifferentialIKControllerCfg: - """Configuration for differential inverse kinematics controller.""" - - class_type: type = DifferentialIKController - """The associated controller class.""" - - command_type: Literal["position", "pose"] = MISSING - """Type of task-space command to control the articulation's body. - - If "position", then the controller only controls the position of the articulation's body. - Otherwise, the controller controls the pose of the articulation's body. - """ - - use_relative_mode: bool = False - """Whether to use relative mode for the controller. Defaults to False. - - If True, then the controller treats the input command as a delta change in the position/pose. - Otherwise, the controller treats the input command as the absolute position/pose. - """ - - ik_method: Literal["pinv", "svd", "trans", "dls"] = MISSING - """Method for computing inverse of Jacobian.""" - - ik_params: dict[str, float] | None = None - """Parameters for the inverse-kinematics method. Defaults to None, in which case the default - parameters for the method are used. - - - Moore-Penrose pseudo-inverse ("pinv"): - - "k_val": Scaling of computed delta-joint positions (default: 1.0). - - Adaptive Singular Value Decomposition ("svd"): - - "k_val": Scaling of computed delta-joint positions (default: 1.0). - - "min_singular_value": Single values less than this are suppressed to zero (default: 1e-5). - - Jacobian transpose ("trans"): - - "k_val": Scaling of computed delta-joint positions (default: 1.0). - - Damped Moore-Penrose pseudo-inverse ("dls"): - - "lambda_val": Damping coefficient (default: 0.01). - """ - - def __post_init__(self): - # check valid input - if self.command_type not in ["position", "pose"]: - raise ValueError(f"Unsupported inverse-kinematics command: {self.command_type}.") - if self.ik_method not in ["pinv", "svd", "trans", "dls"]: - raise ValueError(f"Unsupported inverse-kinematics method: {self.ik_method}.") - # default parameters for different inverse kinematics approaches. - default_ik_params = { - "pinv": {"k_val": 1.0}, - "svd": {"k_val": 1.0, "min_singular_value": 1e-5}, - "trans": {"k_val": 1.0}, - "dls": {"lambda_val": 0.01}, - } - # update parameters for IK-method if not provided - ik_params = default_ik_params[self.ik_method].copy() - if self.ik_params is not None: - ik_params.update(self.ik_params) - self.ik_params = ik_params
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/device_base.html b/docs/_build/html/_modules/omni/isaac/lab/devices/device_base.html deleted file mode 100644 index 40a887189d..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/device_base.html +++ /dev/null @@ -1,614 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.device_base — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.device_base 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Base class for teleoperation interface."""
-
-from abc import ABC, abstractmethod
-from collections.abc import Callable
-from typing import Any
-
-
-
[文档]class DeviceBase(ABC): - """An interface class for teleoperation devices.""" - -
[文档] def __init__(self): - """Initialize the teleoperation interface.""" - pass
- - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - return f"{self.__class__.__name__}" - - """ - Operations - """ - -
[文档] @abstractmethod - def reset(self): - """Reset the internals.""" - raise NotImplementedError
- -
[文档] @abstractmethod - def add_callback(self, key: Any, func: Callable): - """Add additional functions to bind keyboard. - - Args: - key: The button to check against. - func: The function to call when key is pressed. The callback function should not - take any arguments. - """ - raise NotImplementedError
- -
[文档] @abstractmethod - def advance(self) -> Any: - """Provides the joystick event state. - - Returns: - The processed output form the joystick. - """ - raise NotImplementedError
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/gamepad/se2_gamepad.html b/docs/_build/html/_modules/omni/isaac/lab/devices/gamepad/se2_gamepad.html deleted file mode 100644 index 208626f155..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/gamepad/se2_gamepad.html +++ /dev/null @@ -1,762 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.gamepad.se2_gamepad — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.gamepad.se2_gamepad 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Gamepad controller for SE(2) control."""
-
-import numpy as np
-import weakref
-from collections.abc import Callable
-
-import carb
-import omni
-
-from ..device_base import DeviceBase
-
-
-
[文档]class Se2Gamepad(DeviceBase): - r"""A gamepad controller for sending SE(2) commands as velocity commands. - - This class is designed to provide a gamepad controller for mobile base (such as quadrupeds). - It uses the Omniverse gamepad interface to listen to gamepad events and map them to robot's - task-space commands. - - The command comprises of the base linear and angular velocity: :math:`(v_x, v_y, \omega_z)`. - - Key bindings: - ====================== ========================= ======================== - Command Key (+ve axis) Key (-ve axis) - ====================== ========================= ======================== - Move along x-axis left stick up left stick down - Move along y-axis left stick right left stick left - Rotate along z-axis right stick right right stick left - ====================== ========================= ======================== - - .. seealso:: - - The official documentation for the gamepad interface: `Carb Gamepad Interface <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/gamepad.html>`__. - - """ - -
[文档] def __init__( - self, - v_x_sensitivity: float = 1.0, - v_y_sensitivity: float = 1.0, - omega_z_sensitivity: float = 1.0, - dead_zone: float = 0.01, - ): - """Initialize the gamepad layer. - - Args: - v_x_sensitivity: Magnitude of linear velocity along x-direction scaling. Defaults to 1.0. - v_y_sensitivity: Magnitude of linear velocity along y-direction scaling. Defaults to 1.0. - omega_z_sensitivity: Magnitude of angular velocity along z-direction scaling. Defaults to 1.0. - dead_zone: Magnitude of dead zone for gamepad. An event value from the gamepad less than - this value will be ignored. Defaults to 0.01. - """ - # turn off simulator gamepad control - carb_settings_iface = carb.settings.get_settings() - carb_settings_iface.set_bool("/persistent/app/omniverse/gamepadCameraControl", False) - # store inputs - self.v_x_sensitivity = v_x_sensitivity - self.v_y_sensitivity = v_y_sensitivity - self.omega_z_sensitivity = omega_z_sensitivity - self.dead_zone = dead_zone - # acquire omniverse interfaces - self._appwindow = omni.appwindow.get_default_app_window() - self._input = carb.input.acquire_input_interface() - self._gamepad = self._appwindow.get_gamepad(0) - # note: Use weakref on callbacks to ensure that this object can be deleted when its destructor is called - self._gamepad_sub = self._input.subscribe_to_gamepad_events( - self._gamepad, - lambda event, *args, obj=weakref.proxy(self): obj._on_gamepad_event(event, *args), - ) - # bindings for gamepad to command - self._create_key_bindings() - # command buffers - # When using the gamepad, two values are provided for each axis. - # For example: when the left stick is moved down, there are two evens: `left_stick_down = 0.8` - # and `left_stick_up = 0.0`. If only the value of left_stick_up is used, the value will be 0.0, - # which is not the desired behavior. Therefore, we save both the values into the buffer and use - # the maximum value. - # (positive, negative), (x, y, yaw) - self._base_command_raw = np.zeros([2, 3]) - # dictionary for additional callbacks - self._additional_callbacks = dict()
- - def __del__(self): - """Unsubscribe from gamepad events.""" - self._input.unsubscribe_from_gamepad_events(self._gamepad, self._gamepad_sub) - self._gamepad_sub = None - - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - msg = f"Gamepad Controller for SE(2): {self.__class__.__name__}\n" - msg += f"\tDevice name: {self._input.get_gamepad_name(self._gamepad)}\n" - msg += "\t----------------------------------------------\n" - msg += "\tMove in X-Y plane: left stick\n" - msg += "\tRotate in Z-axis: right stick\n" - return msg - - """ - Operations - """ - -
[文档] def reset(self): - # default flags - self._base_command_raw.fill(0.0)
- -
[文档] def add_callback(self, key: carb.input.GamepadInput, func: Callable): - """Add additional functions to bind gamepad. - - A list of available gamepad keys are present in the - `carb documentation <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/gamepad.html>`__. - - Args: - key: The gamepad button to check against. - func: The function to call when key is pressed. The callback function should not - take any arguments. - """ - self._additional_callbacks[key] = func
- -
[文档] def advance(self) -> np.ndarray: - """Provides the result from gamepad event state. - - Returns: - A 3D array containing the linear (x,y) and angular velocity (z). - """ - return self._resolve_command_buffer(self._base_command_raw)
- - """ - Internal helpers. - """ - - def _on_gamepad_event(self, event: carb.input.GamepadEvent, *args, **kwargs): - """Subscriber callback to when kit is updated. - - Reference: - https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/gamepad.html - """ - - # check if the event is a button press - cur_val = event.value - if abs(cur_val) < self.dead_zone: - cur_val = 0 - # -- left and right stick - if event.input in self._INPUT_STICK_VALUE_MAPPING: - direction, axis, value = self._INPUT_STICK_VALUE_MAPPING[event.input] - # change the value only if the stick is moved (soft press) - self._base_command_raw[direction, axis] = value * cur_val - - # additional callbacks - if event.input in self._additional_callbacks: - self._additional_callbacks[event.input]() - - # since no error, we are fine :) - return True - - def _create_key_bindings(self): - """Creates default key binding.""" - self._INPUT_STICK_VALUE_MAPPING = { - # forward command - carb.input.GamepadInput.LEFT_STICK_UP: (0, 0, self.v_x_sensitivity), - # backward command - carb.input.GamepadInput.LEFT_STICK_DOWN: (1, 0, self.v_x_sensitivity), - # right command - carb.input.GamepadInput.LEFT_STICK_RIGHT: (0, 1, self.v_y_sensitivity), - # left command - carb.input.GamepadInput.LEFT_STICK_LEFT: (1, 1, self.v_y_sensitivity), - # yaw command (positive) - carb.input.GamepadInput.RIGHT_STICK_RIGHT: (0, 2, self.omega_z_sensitivity), - # yaw command (negative) - carb.input.GamepadInput.RIGHT_STICK_LEFT: (1, 2, self.omega_z_sensitivity), - } - - def _resolve_command_buffer(self, raw_command: np.ndarray) -> np.ndarray: - """Resolves the command buffer. - - Args: - raw_command: The raw command from the gamepad. Shape is (2, 3) - This is a 2D array since gamepad dpad/stick returns two values corresponding to - the positive and negative direction. The first index is the direction (0: positive, 1: negative) - and the second index is value (absolute) of the command. - - Returns: - Resolved command. Shape is (3,) - """ - # compare the positive and negative value decide the sign of the value - # if the positive value is larger, the sign is positive (i.e. False, 0) - # if the negative value is larger, the sign is positive (i.e. True, 1) - command_sign = raw_command[1, :] > raw_command[0, :] - # extract the command value - command = raw_command.max(axis=0) - # apply the sign - # if the sign is positive, the value is already positive. - # if the sign is negative, the value is negative after applying the sign. - command[command_sign] *= -1 - - return command
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/gamepad/se3_gamepad.html b/docs/_build/html/_modules/omni/isaac/lab/devices/gamepad/se3_gamepad.html deleted file mode 100644 index 0b8c83eb69..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/gamepad/se3_gamepad.html +++ /dev/null @@ -1,807 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.gamepad.se3_gamepad — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.gamepad.se3_gamepad 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Gamepad controller for SE(3) control."""
-
-import numpy as np
-import weakref
-from collections.abc import Callable
-from scipy.spatial.transform.rotation import Rotation
-
-import carb
-import omni
-
-from ..device_base import DeviceBase
-
-
-
[文档]class Se3Gamepad(DeviceBase): - """A gamepad controller for sending SE(3) commands as delta poses and binary command (open/close). - - This class is designed to provide a gamepad controller for a robotic arm with a gripper. - It uses the gamepad interface to listen to gamepad events and map them to the robot's - task-space commands. - - The command comprises of two parts: - - * delta pose: a 6D vector of (x, y, z, roll, pitch, yaw) in meters and radians. - * gripper: a binary command to open or close the gripper. - - Stick and Button bindings: - ============================ ========================= ========================= - Description Stick/Button (+ve axis) Stick/Button (-ve axis) - ============================ ========================= ========================= - Toggle gripper(open/close) X Button X Button - Move along x-axis Left Stick Up Left Stick Down - Move along y-axis Left Stick Left Left Stick Right - Move along z-axis Right Stick Up Right Stick Down - Rotate along x-axis D-Pad Left D-Pad Right - Rotate along y-axis D-Pad Down D-Pad Up - Rotate along z-axis Right Stick Left Right Stick Right - ============================ ========================= ========================= - - .. seealso:: - - The official documentation for the gamepad interface: `Carb Gamepad Interface <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/gamepad.html>`__. - - """ - -
[文档] def __init__(self, pos_sensitivity: float = 1.0, rot_sensitivity: float = 1.6, dead_zone: float = 0.01): - """Initialize the gamepad layer. - - Args: - pos_sensitivity: Magnitude of input position command scaling. Defaults to 1.0. - rot_sensitivity: Magnitude of scale input rotation commands scaling. Defaults to 1.6. - dead_zone: Magnitude of dead zone for gamepad. An event value from the gamepad less than - this value will be ignored. Defaults to 0.01. - """ - # turn off simulator gamepad control - carb_settings_iface = carb.settings.get_settings() - carb_settings_iface.set_bool("/persistent/app/omniverse/gamepadCameraControl", False) - # store inputs - self.pos_sensitivity = pos_sensitivity - self.rot_sensitivity = rot_sensitivity - self.dead_zone = dead_zone - # acquire omniverse interfaces - self._appwindow = omni.appwindow.get_default_app_window() - self._input = carb.input.acquire_input_interface() - self._gamepad = self._appwindow.get_gamepad(0) - # note: Use weakref on callbacks to ensure that this object can be deleted when its destructor is called - self._gamepad_sub = self._input.subscribe_to_gamepad_events( - self._gamepad, - lambda event, *args, obj=weakref.proxy(self): obj._on_gamepad_event(event, *args), - ) - # bindings for gamepad to command - self._create_key_bindings() - # command buffers - self._close_gripper = False - # When using the gamepad, two values are provided for each axis. - # For example: when the left stick is moved down, there are two evens: `left_stick_down = 0.8` - # and `left_stick_up = 0.0`. If only the value of left_stick_up is used, the value will be 0.0, - # which is not the desired behavior. Therefore, we save both the values into the buffer and use - # the maximum value. - # (positive, negative), (x, y, z, roll, pitch, yaw) - self._delta_pose_raw = np.zeros([2, 6]) - # dictionary for additional callbacks - self._additional_callbacks = dict()
- - def __del__(self): - """Unsubscribe from gamepad events.""" - self._input.unsubscribe_from_gamepad_events(self._gamepad, self._gamepad_sub) - self._gamepad_sub = None - - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - msg = f"Gamepad Controller for SE(3): {self.__class__.__name__}\n" - msg += f"\tDevice name: {self._input.get_gamepad_name(self._gamepad)}\n" - msg += "\t----------------------------------------------\n" - msg += "\tToggle gripper (open/close): X\n" - msg += "\tMove arm along x-axis: Left Stick Up/Down\n" - msg += "\tMove arm along y-axis: Left Stick Left/Right\n" - msg += "\tMove arm along z-axis: Right Stick Up/Down\n" - msg += "\tRotate arm along x-axis: D-Pad Right/Left\n" - msg += "\tRotate arm along y-axis: D-Pad Down/Up\n" - msg += "\tRotate arm along z-axis: Right Stick Left/Right\n" - return msg - - """ - Operations - """ - -
[文档] def reset(self): - # default flags - self._close_gripper = False - self._delta_pose_raw.fill(0.0)
- -
[文档] def add_callback(self, key: carb.input.GamepadInput, func: Callable): - """Add additional functions to bind gamepad. - - A list of available gamepad keys are present in the - `carb documentation <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/gamepad.html>`__. - - Args: - key: The gamepad button to check against. - func: The function to call when key is pressed. The callback function should not - take any arguments. - """ - self._additional_callbacks[key] = func
- -
[文档] def advance(self) -> tuple[np.ndarray, bool]: - """Provides the result from gamepad event state. - - Returns: - A tuple containing the delta pose command and gripper commands. - """ - # -- resolve position command - delta_pos = self._resolve_command_buffer(self._delta_pose_raw[:, :3]) - # -- resolve rotation command - delta_rot = self._resolve_command_buffer(self._delta_pose_raw[:, 3:]) - # -- convert to rotation vector - rot_vec = Rotation.from_euler("XYZ", delta_rot).as_rotvec() - # return the command and gripper state - return np.concatenate([delta_pos, rot_vec]), self._close_gripper
- - """ - Internal helpers. - """ - - def _on_gamepad_event(self, event, *args, **kwargs): - """Subscriber callback to when kit is updated. - - Reference: - https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/gamepad.html - """ - # check if the event is a button press - cur_val = event.value - if abs(cur_val) < self.dead_zone: - cur_val = 0 - # -- button - if event.input == carb.input.GamepadInput.X: - # toggle gripper based on the button pressed - if cur_val > 0.5: - self._close_gripper = not self._close_gripper - # -- left and right stick - if event.input in self._INPUT_STICK_VALUE_MAPPING: - direction, axis, value = self._INPUT_STICK_VALUE_MAPPING[event.input] - # change the value only if the stick is moved (soft press) - self._delta_pose_raw[direction, axis] = value * cur_val - # -- dpad (4 arrow buttons on the console) - if event.input in self._INPUT_DPAD_VALUE_MAPPING: - direction, axis, value = self._INPUT_DPAD_VALUE_MAPPING[event.input] - # change the value only if button is pressed on the DPAD - if cur_val > 0.5: - self._delta_pose_raw[direction, axis] = value - self._delta_pose_raw[1 - direction, axis] = 0 - else: - self._delta_pose_raw[:, axis] = 0 - # additional callbacks - if event.input in self._additional_callbacks: - self._additional_callbacks[event.input]() - - # since no error, we are fine :) - return True - - def _create_key_bindings(self): - """Creates default key binding.""" - # map gamepad input to the element in self._delta_pose_raw - # the first index is the direction (0: positive, 1: negative) - # the second index is the axis (0: x, 1: y, 2: z, 3: roll, 4: pitch, 5: yaw) - # the third index is the sensitivity of the command - self._INPUT_STICK_VALUE_MAPPING = { - # forward command - carb.input.GamepadInput.LEFT_STICK_UP: (0, 0, self.pos_sensitivity), - # backward command - carb.input.GamepadInput.LEFT_STICK_DOWN: (1, 0, self.pos_sensitivity), - # right command - carb.input.GamepadInput.LEFT_STICK_RIGHT: (0, 1, self.pos_sensitivity), - # left command - carb.input.GamepadInput.LEFT_STICK_LEFT: (1, 1, self.pos_sensitivity), - # upward command - carb.input.GamepadInput.RIGHT_STICK_UP: (0, 2, self.pos_sensitivity), - # downward command - carb.input.GamepadInput.RIGHT_STICK_DOWN: (1, 2, self.pos_sensitivity), - # yaw command (positive) - carb.input.GamepadInput.RIGHT_STICK_RIGHT: (0, 5, self.rot_sensitivity), - # yaw command (negative) - carb.input.GamepadInput.RIGHT_STICK_LEFT: (1, 5, self.rot_sensitivity), - } - - self._INPUT_DPAD_VALUE_MAPPING = { - # pitch command (positive) - carb.input.GamepadInput.DPAD_UP: (1, 4, self.rot_sensitivity * 0.8), - # pitch command (negative) - carb.input.GamepadInput.DPAD_DOWN: (0, 4, self.rot_sensitivity * 0.8), - # roll command (positive) - carb.input.GamepadInput.DPAD_RIGHT: (1, 3, self.rot_sensitivity * 0.8), - # roll command (negative) - carb.input.GamepadInput.DPAD_LEFT: (0, 3, self.rot_sensitivity * 0.8), - } - - def _resolve_command_buffer(self, raw_command: np.ndarray) -> np.ndarray: - """Resolves the command buffer. - - Args: - raw_command: The raw command from the gamepad. Shape is (2, 3) - This is a 2D array since gamepad dpad/stick returns two values corresponding to - the positive and negative direction. The first index is the direction (0: positive, 1: negative) - and the second index is value (absolute) of the command. - - Returns: - Resolved command. Shape is (3,) - """ - # compare the positive and negative value decide the sign of the value - # if the positive value is larger, the sign is positive (i.e. False, 0) - # if the negative value is larger, the sign is positive (i.e. True, 1) - delta_command_sign = raw_command[1, :] > raw_command[0, :] - # extract the command value - delta_command = raw_command.max(axis=0) - # apply the sign - # if the sign is positive, the value is already positive. - # if the sign is negative, the value is negative after applying the sign. - delta_command[delta_command_sign] *= -1 - - return delta_command
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/keyboard/se2_keyboard.html b/docs/_build/html/_modules/omni/isaac/lab/devices/keyboard/se2_keyboard.html deleted file mode 100644 index 88a4cfb32a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/keyboard/se2_keyboard.html +++ /dev/null @@ -1,730 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.keyboard.se2_keyboard — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.keyboard.se2_keyboard 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Keyboard controller for SE(2) control."""
-
-import numpy as np
-import weakref
-from collections.abc import Callable
-
-import carb
-import omni
-
-from ..device_base import DeviceBase
-
-
-
[文档]class Se2Keyboard(DeviceBase): - r"""A keyboard controller for sending SE(2) commands as velocity commands. - - This class is designed to provide a keyboard controller for mobile base (such as quadrupeds). - It uses the Omniverse keyboard interface to listen to keyboard events and map them to robot's - task-space commands. - - The command comprises of the base linear and angular velocity: :math:`(v_x, v_y, \omega_z)`. - - Key bindings: - ====================== ========================= ======================== - Command Key (+ve axis) Key (-ve axis) - ====================== ========================= ======================== - Move along x-axis Numpad 8 / Arrow Up Numpad 2 / Arrow Down - Move along y-axis Numpad 4 / Arrow Right Numpad 6 / Arrow Left - Rotate along z-axis Numpad 7 / X Numpad 9 / Y - ====================== ========================= ======================== - - .. seealso:: - - The official documentation for the keyboard interface: `Carb Keyboard Interface <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html>`__. - - """ - -
[文档] def __init__(self, v_x_sensitivity: float = 0.8, v_y_sensitivity: float = 0.4, omega_z_sensitivity: float = 1.0): - """Initialize the keyboard layer. - - Args: - v_x_sensitivity: Magnitude of linear velocity along x-direction scaling. Defaults to 0.8. - v_y_sensitivity: Magnitude of linear velocity along y-direction scaling. Defaults to 0.4. - omega_z_sensitivity: Magnitude of angular velocity along z-direction scaling. Defaults to 1.0. - """ - # store inputs - self.v_x_sensitivity = v_x_sensitivity - self.v_y_sensitivity = v_y_sensitivity - self.omega_z_sensitivity = omega_z_sensitivity - # acquire omniverse interfaces - self._appwindow = omni.appwindow.get_default_app_window() - self._input = carb.input.acquire_input_interface() - self._keyboard = self._appwindow.get_keyboard() - # note: Use weakref on callbacks to ensure that this object can be deleted when its destructor is called - self._keyboard_sub = self._input.subscribe_to_keyboard_events( - self._keyboard, - lambda event, *args, obj=weakref.proxy(self): obj._on_keyboard_event(event, *args), - ) - # bindings for keyboard to command - self._create_key_bindings() - # command buffers - self._base_command = np.zeros(3) - # dictionary for additional callbacks - self._additional_callbacks = dict()
- - def __del__(self): - """Release the keyboard interface.""" - self._input.unsubscribe_from_keyboard_events(self._keyboard, self._keyboard_sub) - self._keyboard_sub = None - - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - msg = f"Keyboard Controller for SE(2): {self.__class__.__name__}\n" - msg += f"\tKeyboard name: {self._input.get_keyboard_name(self._keyboard)}\n" - msg += "\t----------------------------------------------\n" - msg += "\tReset all commands: L\n" - msg += "\tMove forward (along x-axis): Numpad 8 / Arrow Up\n" - msg += "\tMove backward (along x-axis): Numpad 2 / Arrow Down\n" - msg += "\tMove right (along y-axis): Numpad 4 / Arrow Right\n" - msg += "\tMove left (along y-axis): Numpad 6 / Arrow Left\n" - msg += "\tYaw positively (along z-axis): Numpad 7 / X\n" - msg += "\tYaw negatively (along z-axis): Numpad 9 / Y" - return msg - - """ - Operations - """ - -
[文档] def reset(self): - # default flags - self._base_command.fill(0.0)
- -
[文档] def add_callback(self, key: str, func: Callable): - """Add additional functions to bind keyboard. - - A list of available keys are present in the - `carb documentation <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html>`__. - - Args: - key: The keyboard button to check against. - func: The function to call when key is pressed. The callback function should not - take any arguments. - """ - self._additional_callbacks[key] = func
- -
[文档] def advance(self) -> np.ndarray: - """Provides the result from keyboard event state. - - Returns: - 3D array containing the linear (x,y) and angular velocity (z). - """ - return self._base_command
- - """ - Internal helpers. - """ - - def _on_keyboard_event(self, event, *args, **kwargs): - """Subscriber callback to when kit is updated. - - Reference: - https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html - """ - # apply the command when pressed - if event.type == carb.input.KeyboardEventType.KEY_PRESS: - if event.input.name == "L": - self.reset() - elif event.input.name in self._INPUT_KEY_MAPPING: - self._base_command += self._INPUT_KEY_MAPPING[event.input.name] - # remove the command when un-pressed - if event.type == carb.input.KeyboardEventType.KEY_RELEASE: - if event.input.name in self._INPUT_KEY_MAPPING: - self._base_command -= self._INPUT_KEY_MAPPING[event.input.name] - # additional callbacks - if event.type == carb.input.KeyboardEventType.KEY_PRESS: - if event.input.name in self._additional_callbacks: - self._additional_callbacks[event.input.name]() - - # since no error, we are fine :) - return True - - def _create_key_bindings(self): - """Creates default key binding.""" - self._INPUT_KEY_MAPPING = { - # forward command - "NUMPAD_8": np.asarray([1.0, 0.0, 0.0]) * self.v_x_sensitivity, - "UP": np.asarray([1.0, 0.0, 0.0]) * self.v_x_sensitivity, - # back command - "NUMPAD_2": np.asarray([-1.0, 0.0, 0.0]) * self.v_x_sensitivity, - "DOWN": np.asarray([-1.0, 0.0, 0.0]) * self.v_x_sensitivity, - # right command - "NUMPAD_4": np.asarray([0.0, 1.0, 0.0]) * self.v_y_sensitivity, - "LEFT": np.asarray([0.0, 1.0, 0.0]) * self.v_y_sensitivity, - # left command - "NUMPAD_6": np.asarray([0.0, -1.0, 0.0]) * self.v_y_sensitivity, - "RIGHT": np.asarray([0.0, -1.0, 0.0]) * self.v_y_sensitivity, - # yaw command (positive) - "NUMPAD_7": np.asarray([0.0, 0.0, 1.0]) * self.omega_z_sensitivity, - "X": np.asarray([0.0, 0.0, 1.0]) * self.omega_z_sensitivity, - # yaw command (negative) - "NUMPAD_9": np.asarray([0.0, 0.0, -1.0]) * self.omega_z_sensitivity, - "Z": np.asarray([0.0, 0.0, -1.0]) * self.omega_z_sensitivity, - }
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/keyboard/se3_keyboard.html b/docs/_build/html/_modules/omni/isaac/lab/devices/keyboard/se3_keyboard.html deleted file mode 100644 index c0b8b8b2a6..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/keyboard/se3_keyboard.html +++ /dev/null @@ -1,751 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.keyboard.se3_keyboard — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.keyboard.se3_keyboard 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Keyboard controller for SE(3) control."""
-
-import numpy as np
-import weakref
-from collections.abc import Callable
-from scipy.spatial.transform.rotation import Rotation
-
-import carb
-import omni
-
-from ..device_base import DeviceBase
-
-
-
[文档]class Se3Keyboard(DeviceBase): - """A keyboard controller for sending SE(3) commands as delta poses and binary command (open/close). - - This class is designed to provide a keyboard controller for a robotic arm with a gripper. - It uses the Omniverse keyboard interface to listen to keyboard events and map them to robot's - task-space commands. - - The command comprises of two parts: - - * delta pose: a 6D vector of (x, y, z, roll, pitch, yaw) in meters and radians. - * gripper: a binary command to open or close the gripper. - - Key bindings: - ============================== ================= ================= - Description Key (+ve axis) Key (-ve axis) - ============================== ================= ================= - Toggle gripper (open/close) K - Move along x-axis W S - Move along y-axis A D - Move along z-axis Q E - Rotate along x-axis Z X - Rotate along y-axis T G - Rotate along z-axis C V - ============================== ================= ================= - - .. seealso:: - - The official documentation for the keyboard interface: `Carb Keyboard Interface <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html>`__. - - """ - -
[文档] def __init__(self, pos_sensitivity: float = 0.4, rot_sensitivity: float = 0.8): - """Initialize the keyboard layer. - - Args: - pos_sensitivity: Magnitude of input position command scaling. Defaults to 0.05. - rot_sensitivity: Magnitude of scale input rotation commands scaling. Defaults to 0.5. - """ - # store inputs - self.pos_sensitivity = pos_sensitivity - self.rot_sensitivity = rot_sensitivity - # acquire omniverse interfaces - self._appwindow = omni.appwindow.get_default_app_window() - self._input = carb.input.acquire_input_interface() - self._keyboard = self._appwindow.get_keyboard() - # note: Use weakref on callbacks to ensure that this object can be deleted when its destructor is called. - self._keyboard_sub = self._input.subscribe_to_keyboard_events( - self._keyboard, - lambda event, *args, obj=weakref.proxy(self): obj._on_keyboard_event(event, *args), - ) - # bindings for keyboard to command - self._create_key_bindings() - # command buffers - self._close_gripper = False - self._delta_pos = np.zeros(3) # (x, y, z) - self._delta_rot = np.zeros(3) # (roll, pitch, yaw) - # dictionary for additional callbacks - self._additional_callbacks = dict()
- - def __del__(self): - """Release the keyboard interface.""" - self._input.unsubscribe_from_keyboard_events(self._keyboard, self._keyboard_sub) - self._keyboard_sub = None - - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - msg = f"Keyboard Controller for SE(3): {self.__class__.__name__}\n" - msg += f"\tKeyboard name: {self._input.get_keyboard_name(self._keyboard)}\n" - msg += "\t----------------------------------------------\n" - msg += "\tToggle gripper (open/close): K\n" - msg += "\tMove arm along x-axis: W/S\n" - msg += "\tMove arm along y-axis: A/D\n" - msg += "\tMove arm along z-axis: Q/E\n" - msg += "\tRotate arm along x-axis: Z/X\n" - msg += "\tRotate arm along y-axis: T/G\n" - msg += "\tRotate arm along z-axis: C/V" - return msg - - """ - Operations - """ - -
[文档] def reset(self): - # default flags - self._close_gripper = False - self._delta_pos = np.zeros(3) # (x, y, z) - self._delta_rot = np.zeros(3) # (roll, pitch, yaw)
- -
[文档] def add_callback(self, key: str, func: Callable): - """Add additional functions to bind keyboard. - - A list of available keys are present in the - `carb documentation <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html>`__. - - Args: - key: The keyboard button to check against. - func: The function to call when key is pressed. The callback function should not - take any arguments. - """ - self._additional_callbacks[key] = func
- -
[文档] def advance(self) -> tuple[np.ndarray, bool]: - """Provides the result from keyboard event state. - - Returns: - A tuple containing the delta pose command and gripper commands. - """ - # convert to rotation vector - rot_vec = Rotation.from_euler("XYZ", self._delta_rot).as_rotvec() - # return the command and gripper state - return np.concatenate([self._delta_pos, rot_vec]), self._close_gripper
- - """ - Internal helpers. - """ - - def _on_keyboard_event(self, event, *args, **kwargs): - """Subscriber callback to when kit is updated. - - Reference: - https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html - """ - # apply the command when pressed - if event.type == carb.input.KeyboardEventType.KEY_PRESS: - if event.input.name == "L": - self.reset() - if event.input.name == "K": - self._close_gripper = not self._close_gripper - elif event.input.name in ["W", "S", "A", "D", "Q", "E"]: - self._delta_pos += self._INPUT_KEY_MAPPING[event.input.name] - elif event.input.name in ["Z", "X", "T", "G", "C", "V"]: - self._delta_rot += self._INPUT_KEY_MAPPING[event.input.name] - # remove the command when un-pressed - if event.type == carb.input.KeyboardEventType.KEY_RELEASE: - if event.input.name in ["W", "S", "A", "D", "Q", "E"]: - self._delta_pos -= self._INPUT_KEY_MAPPING[event.input.name] - elif event.input.name in ["Z", "X", "T", "G", "C", "V"]: - self._delta_rot -= self._INPUT_KEY_MAPPING[event.input.name] - # additional callbacks - if event.type == carb.input.KeyboardEventType.KEY_PRESS: - if event.input.name in self._additional_callbacks: - self._additional_callbacks[event.input.name]() - - # since no error, we are fine :) - return True - - def _create_key_bindings(self): - """Creates default key binding.""" - self._INPUT_KEY_MAPPING = { - # toggle: gripper command - "K": True, - # x-axis (forward) - "W": np.asarray([1.0, 0.0, 0.0]) * self.pos_sensitivity, - "S": np.asarray([-1.0, 0.0, 0.0]) * self.pos_sensitivity, - # y-axis (right-left) - "D": np.asarray([0.0, 1.0, 0.0]) * self.pos_sensitivity, - "A": np.asarray([0.0, -1.0, 0.0]) * self.pos_sensitivity, - # z-axis (up-down) - "Q": np.asarray([0.0, 0.0, 1.0]) * self.pos_sensitivity, - "E": np.asarray([0.0, 0.0, -1.0]) * self.pos_sensitivity, - # roll (around x-axis) - "Z": np.asarray([1.0, 0.0, 0.0]) * self.rot_sensitivity, - "X": np.asarray([-1.0, 0.0, 0.0]) * self.rot_sensitivity, - # pitch (around y-axis) - "T": np.asarray([0.0, 1.0, 0.0]) * self.rot_sensitivity, - "G": np.asarray([0.0, -1.0, 0.0]) * self.rot_sensitivity, - # yaw (around z-axis) - "C": np.asarray([0.0, 0.0, 1.0]) * self.rot_sensitivity, - "V": np.asarray([0.0, 0.0, -1.0]) * self.rot_sensitivity, - }
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/spacemouse/se2_spacemouse.html b/docs/_build/html/_modules/omni/isaac/lab/devices/spacemouse/se2_spacemouse.html deleted file mode 100644 index 9d029d2f0a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/spacemouse/se2_spacemouse.html +++ /dev/null @@ -1,717 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.spacemouse.se2_spacemouse — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.spacemouse.se2_spacemouse 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Spacemouse controller for SE(2) control."""
-
-import hid
-import numpy as np
-import threading
-import time
-from collections.abc import Callable
-
-from ..device_base import DeviceBase
-from .utils import convert_buffer
-
-
-
[文档]class Se2SpaceMouse(DeviceBase): - r"""A space-mouse controller for sending SE(2) commands as delta poses. - - This class implements a space-mouse controller to provide commands to mobile base. - It uses the `HID-API`_ which interfaces with USD and Bluetooth HID-class devices across multiple platforms. - - The command comprises of the base linear and angular velocity: :math:`(v_x, v_y, \omega_z)`. - - Note: - The interface finds and uses the first supported device connected to the computer. - - Currently tested for following devices: - - - SpaceMouse Compact: https://3dconnexion.com/de/product/spacemouse-compact/ - - .. _HID-API: https://github.com/libusb/hidapi - - """ - -
[文档] def __init__(self, v_x_sensitivity: float = 0.8, v_y_sensitivity: float = 0.4, omega_z_sensitivity: float = 1.0): - """Initialize the spacemouse layer. - - Args: - v_x_sensitivity: Magnitude of linear velocity along x-direction scaling. Defaults to 0.8. - v_y_sensitivity: Magnitude of linear velocity along y-direction scaling. Defaults to 0.4. - omega_z_sensitivity: Magnitude of angular velocity along z-direction scaling. Defaults to 1.0. - """ - # store inputs - self.v_x_sensitivity = v_x_sensitivity - self.v_y_sensitivity = v_y_sensitivity - self.omega_z_sensitivity = omega_z_sensitivity - # acquire device interface - self._device = hid.device() - self._find_device() - # command buffers - self._base_command = np.zeros(3) - # dictionary for additional callbacks - self._additional_callbacks = dict() - # run a thread for listening to device updates - self._thread = threading.Thread(target=self._run_device) - self._thread.daemon = True - self._thread.start()
- - def __del__(self): - """Destructor for the class.""" - self._thread.join() - - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - msg = f"Spacemouse Controller for SE(2): {self.__class__.__name__}\n" - msg += f"\tManufacturer: {self._device.get_manufacturer_string()}\n" - msg += f"\tProduct: {self._device.get_product_string()}\n" - msg += "\t----------------------------------------------\n" - msg += "\tRight button: reset command\n" - msg += "\tMove mouse laterally: move base horizontally in x-y plane\n" - msg += "\tTwist mouse about z-axis: yaw base about a corresponding axis" - return msg - - """ - Operations - """ - -
[文档] def reset(self): - # default flags - self._base_command.fill(0.0)
- -
[文档] def add_callback(self, key: str, func: Callable): - # check keys supported by callback - if key not in ["L", "R"]: - raise ValueError(f"Only left (L) and right (R) buttons supported. Provided: {key}.") - # TODO: Improve this to allow multiple buttons on same key. - self._additional_callbacks[key] = func
- -
[文档] def advance(self) -> np.ndarray: - """Provides the result from spacemouse event state. - - Returns: - A 3D array containing the linear (x,y) and angular velocity (z). - """ - return self._base_command
- - """ - Internal helpers. - """ - - def _find_device(self): - """Find the device connected to computer.""" - found = False - # implement a timeout for device search - for _ in range(5): - for device in hid.enumerate(): - if device["product_string"] == "SpaceMouse Compact": - # set found flag - found = True - vendor_id = device["vendor_id"] - product_id = device["product_id"] - # connect to the device - self._device.open(vendor_id, product_id) - # check if device found - if not found: - time.sleep(1.0) - else: - break - # no device found: return false - if not found: - raise OSError("No device found by SpaceMouse. Is the device connected?") - - def _run_device(self): - """Listener thread that keeps pulling new messages.""" - # keep running - while True: - # read the device data - data = self._device.read(13) - if data is not None: - # readings from 6-DoF sensor - if data[0] == 1: - # along y-axis - self._base_command[1] = self.v_y_sensitivity * convert_buffer(data[1], data[2]) - # along x-axis - self._base_command[0] = self.v_x_sensitivity * convert_buffer(data[3], data[4]) - elif data[0] == 2: - # along z-axis - self._base_command[2] = self.omega_z_sensitivity * convert_buffer(data[3], data[4]) - # readings from the side buttons - elif data[0] == 3: - # press left button - if data[1] == 1: - # additional callbacks - if "L" in self._additional_callbacks: - self._additional_callbacks["L"] - # right button is for reset - if data[1] == 2: - # reset layer - self.reset() - # additional callbacks - if "R" in self._additional_callbacks: - self._additional_callbacks["R"]
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/devices/spacemouse/se3_spacemouse.html b/docs/_build/html/_modules/omni/isaac/lab/devices/spacemouse/se3_spacemouse.html deleted file mode 100644 index ac6f8b51f1..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/devices/spacemouse/se3_spacemouse.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.devices.spacemouse.se3_spacemouse — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.devices.spacemouse.se3_spacemouse 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Spacemouse controller for SE(3) control."""
-
-import hid
-import numpy as np
-import threading
-import time
-from collections.abc import Callable
-from scipy.spatial.transform.rotation import Rotation
-
-from ..device_base import DeviceBase
-from .utils import convert_buffer
-
-
-
[文档]class Se3SpaceMouse(DeviceBase): - """A space-mouse controller for sending SE(3) commands as delta poses. - - This class implements a space-mouse controller to provide commands to a robotic arm with a gripper. - It uses the `HID-API`_ which interfaces with USD and Bluetooth HID-class devices across multiple platforms [1]. - - The command comprises of two parts: - - * delta pose: a 6D vector of (x, y, z, roll, pitch, yaw) in meters and radians. - * gripper: a binary command to open or close the gripper. - - Note: - The interface finds and uses the first supported device connected to the computer. - - Currently tested for following devices: - - - SpaceMouse Compact: https://3dconnexion.com/de/product/spacemouse-compact/ - - .. _HID-API: https://github.com/libusb/hidapi - - """ - -
[文档] def __init__(self, pos_sensitivity: float = 0.4, rot_sensitivity: float = 0.8): - """Initialize the space-mouse layer. - - Args: - pos_sensitivity: Magnitude of input position command scaling. Defaults to 0.4. - rot_sensitivity: Magnitude of scale input rotation commands scaling. Defaults to 0.8. - """ - # store inputs - self.pos_sensitivity = pos_sensitivity - self.rot_sensitivity = rot_sensitivity - # acquire device interface - self._device = hid.device() - self._find_device() - # read rotations - self._read_rotation = False - - # command buffers - self._close_gripper = False - self._delta_pos = np.zeros(3) # (x, y, z) - self._delta_rot = np.zeros(3) # (roll, pitch, yaw) - # dictionary for additional callbacks - self._additional_callbacks = dict() - # run a thread for listening to device updates - self._thread = threading.Thread(target=self._run_device) - self._thread.daemon = True - self._thread.start()
- - def __del__(self): - """Destructor for the class.""" - self._thread.join() - - def __str__(self) -> str: - """Returns: A string containing the information of joystick.""" - msg = f"Spacemouse Controller for SE(3): {self.__class__.__name__}\n" - msg += f"\tManufacturer: {self._device.get_manufacturer_string()}\n" - msg += f"\tProduct: {self._device.get_product_string()}\n" - msg += "\t----------------------------------------------\n" - msg += "\tRight button: reset command\n" - msg += "\tLeft button: toggle gripper command (open/close)\n" - msg += "\tMove mouse laterally: move arm horizontally in x-y plane\n" - msg += "\tMove mouse vertically: move arm vertically\n" - msg += "\tTwist mouse about an axis: rotate arm about a corresponding axis" - return msg - - """ - Operations - """ - -
[文档] def reset(self): - # default flags - self._close_gripper = False - self._delta_pos = np.zeros(3) # (x, y, z) - self._delta_rot = np.zeros(3) # (roll, pitch, yaw)
- -
[文档] def add_callback(self, key: str, func: Callable): - # check keys supported by callback - if key not in ["L", "R"]: - raise ValueError(f"Only left (L) and right (R) buttons supported. Provided: {key}.") - # TODO: Improve this to allow multiple buttons on same key. - self._additional_callbacks[key] = func
- -
[文档] def advance(self) -> tuple[np.ndarray, bool]: - """Provides the result from spacemouse event state. - - Returns: - A tuple containing the delta pose command and gripper commands. - """ - rot_vec = Rotation.from_euler("XYZ", self._delta_rot).as_rotvec() - # if new command received, reset event flag to False until keyboard updated. - return np.concatenate([self._delta_pos, rot_vec]), self._close_gripper
- - """ - Internal helpers. - """ - - def _find_device(self): - """Find the device connected to computer.""" - found = False - # implement a timeout for device search - for _ in range(5): - for device in hid.enumerate(): - if device["product_string"] == "SpaceMouse Compact": - # set found flag - found = True - vendor_id = device["vendor_id"] - product_id = device["product_id"] - # connect to the device - self._device.open(vendor_id, product_id) - # check if device found - if not found: - time.sleep(1.0) - else: - break - # no device found: return false - if not found: - raise OSError("No device found by SpaceMouse. Is the device connected?") - - def _run_device(self): - """Listener thread that keeps pulling new messages.""" - # keep running - while True: - # read the device data - data = self._device.read(7) - if data is not None: - # readings from 6-DoF sensor - if data[0] == 1: - self._delta_pos[1] = self.pos_sensitivity * convert_buffer(data[1], data[2]) - self._delta_pos[0] = self.pos_sensitivity * convert_buffer(data[3], data[4]) - self._delta_pos[2] = self.pos_sensitivity * convert_buffer(data[5], data[6]) * -1.0 - elif data[0] == 2 and not self._read_rotation: - self._delta_rot[1] = self.rot_sensitivity * convert_buffer(data[1], data[2]) - self._delta_rot[0] = self.rot_sensitivity * convert_buffer(data[3], data[4]) - self._delta_rot[2] = self.rot_sensitivity * convert_buffer(data[5], data[6]) - # readings from the side buttons - elif data[0] == 3: - # press left button - if data[1] == 1: - # close gripper - self._close_gripper = not self._close_gripper - # additional callbacks - if "L" in self._additional_callbacks: - self._additional_callbacks["L"] - # right button is for reset - if data[1] == 2: - # reset layer - self.reset() - # additional callbacks - if "R" in self._additional_callbacks: - self._additional_callbacks["R"] - if data[1] == 3: - self._read_rotation = not self._read_rotation
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/common.html b/docs/_build/html/_modules/omni/isaac/lab/envs/common.html deleted file mode 100644 index 49f4076cc6..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/common.html +++ /dev/null @@ -1,661 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.common — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.common 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from typing import Dict, Literal
-
-from omni.isaac.lab.utils import configclass
-
-##
-# Configuration.
-##
-
-
-
[文档]@configclass -class ViewerCfg: - """Configuration of the scene viewport camera.""" - - eye: tuple[float, float, float] = (7.5, 7.5, 7.5) - """Initial camera position (in m). Default is (7.5, 7.5, 7.5).""" - - lookat: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Initial camera target position (in m). Default is (0.0, 0.0, 0.0).""" - - cam_prim_path: str = "/OmniverseKit_Persp" - """The camera prim path to record images from. Default is "/OmniverseKit_Persp", - which is the default camera in the viewport. - """ - - resolution: tuple[int, int] = (1280, 720) - """The resolution (width, height) of the camera specified using :attr:`cam_prim_path`. - Default is (1280, 720). - """ - - origin_type: Literal["world", "env", "asset_root"] = "world" - """The frame in which the camera position (eye) and target (lookat) are defined in. Default is "world". - - Available options are: - - * ``"world"``: The origin of the world. - * ``"env"``: The origin of the environment defined by :attr:`env_index`. - * ``"asset_root"``: The center of the asset defined by :attr:`asset_name` in environment :attr:`env_index`. - """ - - env_index: int = 0 - """The environment index for frame origin. Default is 0. - - This quantity is only effective if :attr:`origin` is set to "env" or "asset_root". - """ - - asset_name: str | None = None - """The asset name in the interactive scene for the frame origin. Default is None. - - This quantity is only effective if :attr:`origin` is set to "asset_root". - """
- - -## -# Types. -## - -VecEnvObs = Dict[str, torch.Tensor | Dict[str, torch.Tensor]] -"""Observation returned by the environment. - -The observations are stored in a dictionary. The keys are the group to which the observations belong. -This is useful for various setups such as reinforcement learning with asymmetric actor-critic or -multi-agent learning. For non-learning paradigms, this may include observations for different components -of a system. - -Within each group, the observations can be stored either as a dictionary with keys as the names of each -observation term in the group, or a single tensor obtained from concatenating all the observation terms. -For example, for asymmetric actor-critic, the observation for the actor and the critic can be accessed -using the keys ``"policy"`` and ``"critic"`` respectively. - -Note: - By default, most learning frameworks deal with default and privileged observations in different ways. - This handling must be taken care of by the wrapper around the :class:`ManagerBasedRLEnv` instance. - - For included frameworks (RSL-RL, RL-Games, skrl), the observations must have the key "policy". In case, - the key "critic" is also present, then the critic observations are taken from the "critic" group. - Otherwise, they are the same as the "policy" group. - -""" - -VecEnvStepReturn = tuple[VecEnvObs, torch.Tensor, torch.Tensor, torch.Tensor, dict] -"""The environment signals processed at the end of each step. - -The tuple contains batched information for each sub-environment. The information is stored in the following order: - -1. **Observations**: The observations from the environment. -2. **Rewards**: The rewards from the environment. -3. **Terminated Dones**: Whether the environment reached a terminal state, such as task success or robot falling etc. -4. **Timeout Dones**: Whether the environment reached a timeout state, such as end of max episode length. -5. **Extras**: A dictionary containing additional information from the environment. -""" -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/direct_rl_env.html b/docs/_build/html/_modules/omni/isaac/lab/envs/direct_rl_env.html deleted file mode 100644 index d1498b95e3..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/direct_rl_env.html +++ /dev/null @@ -1,1179 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.direct_rl_env — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.direct_rl_env 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import builtins
-import gymnasium as gym
-import inspect
-import math
-import numpy as np
-import torch
-import weakref
-from abc import abstractmethod
-from collections.abc import Sequence
-from typing import Any, ClassVar
-
-import carb
-import omni.isaac.core.utils.torch as torch_utils
-import omni.kit.app
-from omni.isaac.version import get_version
-
-from omni.isaac.lab.managers import EventManager
-from omni.isaac.lab.scene import InteractiveScene
-from omni.isaac.lab.sim import SimulationContext
-from omni.isaac.lab.utils.noise import NoiseModel
-from omni.isaac.lab.utils.timer import Timer
-
-from .common import VecEnvObs, VecEnvStepReturn
-from .direct_rl_env_cfg import DirectRLEnvCfg
-from .ui import ViewportCameraController
-
-
-
[文档]class DirectRLEnv(gym.Env): - """The superclass for the direct workflow to design environments. - - This class implements the core functionality for reinforcement learning (RL) - environments. It is designed to be used with any RL library. The class is designed - to be used with vectorized environments, i.e., the environment is expected to be run - in parallel with multiple sub-environments. - - While the environment itself is implemented as a vectorized environment, we do not - inherit from :class:`gym.vector.VectorEnv`. This is mainly because the class adds - various methods (for wait and asynchronous updates) which are not required. - Additionally, each RL library typically has its own definition for a vectorized - environment. Thus, to reduce complexity, we directly use the :class:`gym.Env` over - here and leave it up to library-defined wrappers to take care of wrapping this - environment for their agents. - - Note: - For vectorized environments, it is recommended to **only** call the :meth:`reset` - method once before the first call to :meth:`step`, i.e. after the environment is created. - After that, the :meth:`step` function handles the reset of terminated sub-environments. - This is because the simulator does not support resetting individual sub-environments - in a vectorized environment. - - """ - - is_vector_env: ClassVar[bool] = True - """Whether the environment is a vectorized environment.""" - metadata: ClassVar[dict[str, Any]] = { - "render_modes": [None, "human", "rgb_array"], - "isaac_sim_version": get_version(), - } - """Metadata for the environment.""" - -
[文档] def __init__(self, cfg: DirectRLEnvCfg, render_mode: str | None = None, **kwargs): - """Initialize the environment. - - Args: - cfg: The configuration object for the environment. - render_mode: The render mode for the environment. Defaults to None, which - is similar to ``"human"``. - - Raises: - RuntimeError: If a simulation context already exists. The environment must always create one - since it configures the simulation context and controls the simulation. - """ - # store inputs to class - self.cfg = cfg - # store the render mode - self.render_mode = render_mode - # initialize internal variables - self._is_closed = False - - # create a simulation context to control the simulator - if SimulationContext.instance() is None: - self.sim: SimulationContext = SimulationContext(self.cfg.sim) - else: - raise RuntimeError("Simulation context already exists. Cannot create a new one.") - - # print useful information - print("[INFO]: Base environment:") - print(f"\tEnvironment device : {self.device}") - print(f"\tPhysics step-size : {self.physics_dt}") - print(f"\tRendering step-size : {self.physics_dt * self.cfg.sim.render_interval}") - print(f"\tEnvironment step-size : {self.step_dt}") - print(f"\tPhysics GPU pipeline : {self.cfg.sim.use_gpu_pipeline}") - print(f"\tPhysics GPU simulation: {self.cfg.sim.physx.use_gpu}") - - if self.cfg.sim.render_interval < self.cfg.decimation: - msg = ( - f"The render interval ({self.cfg.sim.render_interval}) is smaller than the decimation " - f"({self.cfg.decimation}). Multiple multiple render calls will happen for each environment step." - "If this is not intended, set the render interval to be equal to the decimation." - ) - carb.log_warn(msg) - - # generate scene - with Timer("[INFO]: Time taken for scene creation"): - self.scene = InteractiveScene(self.cfg.scene) - self._setup_scene() - print("[INFO]: Scene manager: ", self.scene) - - # set up camera viewport controller - # viewport is not available in other rendering modes so the function will throw a warning - # FIXME: This needs to be fixed in the future when we unify the UI functionalities even for - # non-rendering modes. - if self.sim.render_mode >= self.sim.RenderMode.PARTIAL_RENDERING: - self.viewport_camera_controller = ViewportCameraController(self, self.cfg.viewer) - else: - self.viewport_camera_controller = None - - # play the simulator to activate physics handles - # note: this activates the physics simulation view that exposes TensorAPIs - # note: when started in extension mode, first call sim.reset_async() and then initialize the managers - if builtins.ISAAC_LAUNCHED_FROM_TERMINAL is False: - print("[INFO]: Starting the simulation. This may take a few seconds. Please wait...") - with Timer("[INFO]: Time taken for simulation start"): - self.sim.reset() - - # -- event manager used for randomization - if self.cfg.events: - self.event_manager = EventManager(self.cfg.events, self) - print("[INFO] Event Manager: ", self.event_manager) - - # make sure torch is running on the correct device - if "cuda" in self.device: - torch.cuda.set_device(self.device) - - # check if debug visualization is has been implemented by the environment - source_code = inspect.getsource(self._set_debug_vis_impl) - self.has_debug_vis_implementation = "NotImplementedError" not in source_code - self._debug_vis_handle = None - - # extend UI elements - # we need to do this here after all the managers are initialized - # this is because they dictate the sensors and commands right now - if self.sim.has_gui() and self.cfg.ui_window_class_type is not None: - self._window = self.cfg.ui_window_class_type(self, window_name="IsaacLab") - else: - # if no window, then we don't need to store the window - self._window = None - - # allocate dictionary to store metrics - self.extras = {} - - # initialize data and constants - # -- counter for simulation steps - self._sim_step_counter = 0 - # -- counter for curriculum - self.common_step_counter = 0 - # -- init buffers - self.episode_length_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.long) - self.reset_terminated = torch.zeros(self.num_envs, device=self.device, dtype=torch.bool) - self.reset_time_outs = torch.zeros_like(self.reset_terminated) - self.reset_buf = torch.zeros(self.num_envs, dtype=torch.bool, device=self.sim.device) - self.actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.sim.device) - - # setup the action and observation spaces for Gym - self._configure_gym_env_spaces() - - # setup noise cfg for adding action and observation noise - if self.cfg.action_noise_model: - self._action_noise_model: NoiseModel = self.cfg.action_noise_model.class_type( - self.num_envs, self.cfg.action_noise_model, self.device - ) - if self.cfg.observation_noise_model: - self._observation_noise_model: NoiseModel = self.cfg.observation_noise_model.class_type( - self.num_envs, self.cfg.observation_noise_model, self.device - ) - - # perform events at the start of the simulation - if self.cfg.events: - if "startup" in self.event_manager.available_modes: - self.event_manager.apply(mode="startup") - - # print the environment information - print("[INFO]: Completed setting up the environment...")
- - def __del__(self): - """Cleanup for the environment.""" - self.close() - - """ - Properties. - """ - - @property - def num_envs(self) -> int: - """The number of instances of the environment that are running.""" - return self.scene.num_envs - - @property - def physics_dt(self) -> float: - """The physics time-step (in s). - - This is the lowest time-decimation at which the simulation is happening. - """ - return self.cfg.sim.dt - - @property - def step_dt(self) -> float: - """The environment stepping time-step (in s). - - This is the time-step at which the environment steps forward. - """ - return self.cfg.sim.dt * self.cfg.decimation - - @property - def device(self): - """The device on which the environment is running.""" - return self.sim.device - - @property - def max_episode_length_s(self) -> float: - """Maximum episode length in seconds.""" - return self.cfg.episode_length_s - - @property - def max_episode_length(self): - """The maximum episode length in steps adjusted from s.""" - return math.ceil(self.max_episode_length_s / (self.cfg.sim.dt * self.cfg.decimation)) - - """ - Operations. - """ - -
[文档] def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[VecEnvObs, dict]: - """Resets all the environments and returns observations. - - Args: - seed: The seed to use for randomization. Defaults to None, in which case the seed is not set. - options: Additional information to specify how the environment is reset. Defaults to None. - - Note: - This argument is used for compatibility with Gymnasium environment definition. - - Returns: - A tuple containing the observations and extras. - """ - # set the seed - if seed is not None: - self.seed(seed) - # reset state of scene - indices = torch.arange(self.num_envs, dtype=torch.int64, device=self.device) - self._reset_idx(indices) - - obs = self._get_observations() - # return observations - return obs, self.extras
- -
[文档] def step(self, action: torch.Tensor) -> VecEnvStepReturn: - """Execute one time-step of the environment's dynamics. - - The environment steps forward at a fixed time-step, while the physics simulation is decimated at a - lower time-step. This is to ensure that the simulation is stable. These two time-steps can be configured - independently using the :attr:`DirectRLEnvCfg.decimation` (number of simulation steps per environment step) - and the :attr:`DirectRLEnvCfg.sim.physics_dt` (physics time-step). Based on these parameters, the environment - time-step is computed as the product of the two. - - This function performs the following steps: - - 1. Pre-process the actions before stepping through the physics. - 2. Apply the actions to the simulator and step through the physics in a decimated manner. - 3. Compute the reward and done signals. - 4. Reset environments that have terminated or reached the maximum episode length. - 5. Apply interval events if they are enabled. - 6. Compute observations. - - Args: - action: The actions to apply on the environment. Shape is (num_envs, action_dim). - - Returns: - A tuple containing the observations, rewards, resets (terminated and truncated) and extras. - """ - # add action noise - if self.cfg.action_noise_model: - action = self._action_noise_model.apply(action.clone()) - # process actions - self._pre_physics_step(action) - - # check if we need to do rendering within the physics loop - # note: checked here once to avoid multiple checks within the loop - is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors() - - # perform physics stepping - for _ in range(self.cfg.decimation): - self._sim_step_counter += 1 - # set actions into buffers - self._apply_action() - # set actions into simulator - self.scene.write_data_to_sim() - # simulate - self.sim.step(render=False) - # render between steps only if the GUI or an RTX sensor needs it - # note: we assume the render interval to be the shortest accepted rendering interval. - # If a camera needs rendering at a faster frequency, this will lead to unexpected behavior. - if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering: - self.sim.render() - # update buffers at sim dt - self.scene.update(dt=self.physics_dt) - - # post-step: - # -- update env counters (used for curriculum generation) - self.episode_length_buf += 1 # step in current episode (per env) - self.common_step_counter += 1 # total step (common for all envs) - - self.reset_terminated[:], self.reset_time_outs[:] = self._get_dones() - self.reset_buf = self.reset_terminated | self.reset_time_outs - self.reward_buf = self._get_rewards() - - # -- reset envs that terminated/timed-out and log the episode information - reset_env_ids = self.reset_buf.nonzero(as_tuple=False).squeeze(-1) - if len(reset_env_ids) > 0: - self._reset_idx(reset_env_ids) - - # post-step: step interval event - if self.cfg.events: - if "interval" in self.event_manager.available_modes: - self.event_manager.apply(mode="interval", dt=self.step_dt) - - # update observations - self.obs_buf = self._get_observations() - - # add observation noise - # note: we apply no noise to the state space (since it is used for critic networks) - if self.cfg.observation_noise_model: - self.obs_buf["policy"] = self._observation_noise_model.apply(self.obs_buf["policy"]) - - # return observations, rewards, resets and extras - return self.obs_buf, self.reward_buf, self.reset_terminated, self.reset_time_outs, self.extras
- -
[文档] @staticmethod - def seed(seed: int = -1) -> int: - """Set the seed for the environment. - - Args: - seed: The seed for random generator. Defaults to -1. - - Returns: - The seed used for random generator. - """ - # set seed for replicator - try: - import omni.replicator.core as rep - - rep.set_global_seed(seed) - except ModuleNotFoundError: - pass - # set seed for torch and other libraries - return torch_utils.set_seed(seed)
- -
[文档] def render(self, recompute: bool = False) -> np.ndarray | None: - """Run rendering without stepping through the physics. - - By convention, if mode is: - - - **human**: Render to the current display and return nothing. Usually for human consumption. - - **rgb_array**: Return an numpy.ndarray with shape (x, y, 3), representing RGB values for an - x-by-y pixel image, suitable for turning into a video. - - Args: - recompute: Whether to force a render even if the simulator has already rendered the scene. - Defaults to False. - - Returns: - The rendered image as a numpy array if mode is "rgb_array". Otherwise, returns None. - - Raises: - RuntimeError: If mode is set to "rgb_data" and simulation render mode does not support it. - In this case, the simulation render mode must be set to ``RenderMode.PARTIAL_RENDERING`` - or ``RenderMode.FULL_RENDERING``. - NotImplementedError: If an unsupported rendering mode is specified. - """ - # run a rendering step of the simulator - # if we have rtx sensors, we do not need to render again sin - if not self.sim.has_rtx_sensors() and not recompute: - self.sim.render() - # decide the rendering mode - if self.render_mode == "human" or self.render_mode is None: - return None - elif self.render_mode == "rgb_array": - # check that if any render could have happened - if self.sim.render_mode.value < self.sim.RenderMode.PARTIAL_RENDERING.value: - raise RuntimeError( - f"Cannot render '{self.render_mode}' when the simulation render mode is" - f" '{self.sim.render_mode.name}'. Please set the simulation render mode to:" - f"'{self.sim.RenderMode.PARTIAL_RENDERING.name}' or '{self.sim.RenderMode.FULL_RENDERING.name}'." - " If running headless, make sure --enable_cameras is set." - ) - # create the annotator if it does not exist - if not hasattr(self, "_rgb_annotator"): - import omni.replicator.core as rep - - # create render product - self._render_product = rep.create.render_product( - self.cfg.viewer.cam_prim_path, self.cfg.viewer.resolution - ) - # create rgb annotator -- used to read data from the render product - self._rgb_annotator = rep.AnnotatorRegistry.get_annotator("rgb", device="cpu") - self._rgb_annotator.attach([self._render_product]) - # obtain the rgb data - rgb_data = self._rgb_annotator.get_data() - # convert to numpy array - rgb_data = np.frombuffer(rgb_data, dtype=np.uint8).reshape(*rgb_data.shape) - # return the rgb data - # note: initially the renerer is warming up and returns empty data - if rgb_data.size == 0: - return np.zeros((self.cfg.viewer.resolution[1], self.cfg.viewer.resolution[0], 3), dtype=np.uint8) - else: - return rgb_data[:, :, :3] - else: - raise NotImplementedError( - f"Render mode '{self.render_mode}' is not supported. Please use: {self.metadata['render_modes']}." - )
- -
[文档] def close(self): - """Cleanup for the environment.""" - if not self._is_closed: - # close entities related to the environment - # note: this is order-sensitive to avoid any dangling references - if self.cfg.events: - del self.event_manager - del self.scene - if self.viewport_camera_controller is not None: - del self.viewport_camera_controller - # clear callbacks and instance - self.sim.clear_all_callbacks() - self.sim.clear_instance() - # destroy the window - if self._window is not None: - self._window = None - # update closing status - self._is_closed = True
- - """ - Operations - Debug Visualization. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Toggles the environment debug visualization. - - Args: - debug_vis: Whether to visualize the environment debug visualization. - - Returns: - Whether the debug visualization was successfully set. False if the environment - does not support debug visualization. - """ - # check if debug visualization is supported - if not self.has_debug_vis_implementation: - return False - # toggle debug visualization objects - self._set_debug_vis_impl(debug_vis) - # toggle debug visualization handles - if debug_vis: - # create a subscriber for the post update event if it doesn't exist - if self._debug_vis_handle is None: - 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) - ) - else: - # remove the subscriber if it exists - if self._debug_vis_handle is not None: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - # return success - return True
- - """ - Helper functions. - """ - - def _configure_gym_env_spaces(self): - """Configure the action and observation spaces for the Gym environment.""" - # observation space (unbounded since we don't impose any limits) - self.num_actions = self.cfg.num_actions - self.num_observations = self.cfg.num_observations - self.num_states = self.cfg.num_states - - # set up spaces - self.single_observation_space = gym.spaces.Dict() - self.single_observation_space["policy"] = gym.spaces.Box( - low=-np.inf, high=np.inf, shape=(self.num_observations,) - ) - self.single_action_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.num_actions,)) - - # batch the spaces for vectorized environments - self.observation_space = gym.vector.utils.batch_space(self.single_observation_space["policy"], self.num_envs) - self.action_space = gym.vector.utils.batch_space(self.single_action_space, self.num_envs) - - # optional state space for asymmetric actor-critic architectures - if self.num_states > 0: - self.single_observation_space["critic"] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.num_states,)) - self.state_space = gym.vector.utils.batch_space(self.single_observation_space["critic"], self.num_envs) - - def _reset_idx(self, env_ids: Sequence[int]): - """Reset environments based on specified indices. - - Args: - env_ids: List of environment ids which must be reset - """ - self.scene.reset(env_ids) - - # apply events such as randomization for environments that need a reset - if self.cfg.events: - if "reset" in self.event_manager.available_modes: - self.event_manager.apply(env_ids=env_ids, mode="reset") - - # reset noise models - if self.cfg.action_noise_model: - self._action_noise_model.reset(env_ids) - if self.cfg.observation_noise_model: - self._observation_noise_model.reset(env_ids) - - # reset the episode length buffer - self.episode_length_buf[env_ids] = 0 - - """ - Implementation-specific functions. - """ - - def _setup_scene(self): - """Setup the scene for the environment. - - This function is responsible for creating the scene objects and setting up the scene for the environment. - The scene creation can happen through :class:`omni.isaac.lab.scene.InteractiveSceneCfg` or through - directly creating the scene objects and registering them with the scene manager. - - We leave the implementation of this function to the derived classes. If the environment does not require - any explicit scene setup, the function can be left empty. - """ - pass - - @abstractmethod - def _pre_physics_step(self, actions: torch.Tensor): - """Pre-process actions before stepping through the physics. - - This function is responsible for pre-processing the actions before stepping through the physics. - It is called before the physics stepping (which is decimated). - - Args: - actions: The actions to apply on the environment. Shape is (num_envs, action_dim). - """ - raise NotImplementedError(f"Please implement the '_pre_physics_step' method for {self.__class__.__name__}.") - - @abstractmethod - def _apply_action(self): - """Apply actions to the simulator. - - This function is responsible for applying the actions to the simulator. It is called at each - physics time-step. - """ - raise NotImplementedError(f"Please implement the '_apply_action' method for {self.__class__.__name__}.") - - @abstractmethod - def _get_observations(self) -> VecEnvObs: - """Compute and return the observations for the environment. - - Returns: - The observations for the environment. - """ - raise NotImplementedError(f"Please implement the '_get_observations' method for {self.__class__.__name__}.") - - def _get_states(self) -> VecEnvObs | None: - """Compute and return the states for the environment. - - The state-space is used for asymmetric actor-critic architectures. It is configured - using the :attr:`DirectRLEnvCfg.num_states` parameter. - - Returns: - The states for the environment. If the environment does not have a state-space, the function - returns a None. - """ - return None # noqa: R501 - - @abstractmethod - def _get_rewards(self) -> torch.Tensor: - """Compute and return the rewards for the environment. - - Returns: - The rewards for the environment. Shape is (num_envs,). - """ - raise NotImplementedError(f"Please implement the '_get_rewards' method for {self.__class__.__name__}.") - - @abstractmethod - def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: - """Compute and return the done flags for the environment. - - Returns: - A tuple containing the done flags for termination and time-out. - Shape of individual tensors is (num_envs,). - """ - raise NotImplementedError(f"Please implement the '_get_dones' method for {self.__class__.__name__}.") - - def _set_debug_vis_impl(self, debug_vis: bool): - """Set debug visualization into visualization objects. - - This function is responsible for creating the visualization objects if they don't exist - and input ``debug_vis`` is True. If the visualization objects exist, the function should - set their visibility into the stage. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.")
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/direct_rl_env_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/envs/direct_rl_env_cfg.html deleted file mode 100644 index fbaaa4485f..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/direct_rl_env_cfg.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.direct_rl_env_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.direct_rl_env_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.scene import InteractiveSceneCfg
-from omni.isaac.lab.sim import SimulationCfg
-from omni.isaac.lab.utils import configclass
-from omni.isaac.lab.utils.noise import NoiseModelCfg
-
-from .common import ViewerCfg
-from .ui import BaseEnvWindow
-
-
-
[文档]@configclass -class DirectRLEnvCfg: - """Configuration for an RL environment defined with the direct workflow. - - Please refer to the :class:`omni.isaac.lab.envs.direct_rl_env.DirectRLEnv` class for more details. - """ - - # simulation settings - viewer: ViewerCfg = ViewerCfg() - """Viewer configuration. Default is ViewerCfg().""" - - sim: SimulationCfg = SimulationCfg() - """Physics simulation configuration. Default is SimulationCfg().""" - - # ui settings - ui_window_class_type: type | None = BaseEnvWindow - """The class type of the UI window. Default is None. - - If None, then no UI window is created. - - Note: - If you want to make your own UI window, you can create a class that inherits from - from :class:`omni.isaac.lab.envs.ui.base_env_window.BaseEnvWindow`. Then, you can set - this attribute to your class type. - """ - - # general settings - decimation: int = MISSING - """Number of control action updates @ sim dt per policy dt. - - For instance, if the simulation dt is 0.01s and the policy dt is 0.1s, then the decimation is 10. - This means that the control action is updated every 10 simulation steps. - """ - - is_finite_horizon: bool = False - """Whether the learning task is treated as a finite or infinite horizon problem for the agent. - Defaults to False, which means the task is treated as an infinite horizon problem. - - This flag handles the subtleties of finite and infinite horizon tasks: - - * **Finite horizon**: no penalty or bootstrapping value is required by the the agent for - running out of time. However, the environment still needs to terminate the episode after the - time limit is reached. - * **Infinite horizon**: the agent needs to bootstrap the value of the state at the end of the episode. - This is done by sending a time-limit (or truncated) done signal to the agent, which triggers this - bootstrapping calculation. - - If True, then the environment is treated as a finite horizon problem and no time-out (or truncated) done signal - is sent to the agent. If False, then the environment is treated as an infinite horizon problem and a time-out - (or truncated) done signal is sent to the agent. - - Note: - The base :class:`ManagerBasedRLEnv` class does not use this flag directly. It is used by the environment - wrappers to determine what type of done signal to send to the corresponding learning agent. - """ - - episode_length_s: float = MISSING - """Duration of an episode (in seconds). - - Based on the decimation rate and physics time step, the episode length is calculated as: - - .. code-block:: python - - episode_length_steps = ceil(episode_length_s / (decimation_rate * physics_time_step)) - - For example, if the decimation rate is 10, the physics time step is 0.01, and the episode length is 10 seconds, - then the episode length in steps is 100. - """ - - # environment settings - scene: InteractiveSceneCfg = MISSING - """Scene settings. - - Please refer to the :class:`omni.isaac.lab.scene.InteractiveSceneCfg` class for more details. - """ - - events: object = None - """Event settings. Defaults to None, in which case no events are applied through the event manager. - - Please refer to the :class:`omni.isaac.lab.managers.EventManager` class for more details. - """ - - num_observations: int = MISSING - """The dimension of the observation space from each environment instance.""" - - num_states: int = 0 - """The dimension of the state-space from each environment instance. Default is 0, which means no state-space is defined. - - This is useful for asymmetric actor-critic and defines the observation space for the critic. - """ - - observation_noise_model: NoiseModelCfg | None = None - """The noise model to apply to the computed observations from the environment. Default is None, which means no noise is added. - - Please refer to the :class:`omni.isaac.lab.utils.noise.NoiseModel` class for more details. - """ - - num_actions: int = MISSING - """The dimension of the action space for each environment.""" - - action_noise_model: NoiseModelCfg | None = None - """The noise model applied to the actions provided to the environment. Default is None, which means no noise is added. - - Please refer to the :class:`omni.isaac.lab.utils.noise.NoiseModel` class for more details. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_env.html b/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_env.html deleted file mode 100644 index a2daf0da70..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_env.html +++ /dev/null @@ -1,927 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.manager_based_env — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.manager_based_env 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import builtins
-import torch
-import warnings
-from collections.abc import Sequence
-from typing import Any
-
-import carb
-import omni.isaac.core.utils.torch as torch_utils
-
-from omni.isaac.lab.managers import ActionManager, EventManager, ObservationManager
-from omni.isaac.lab.scene import InteractiveScene
-from omni.isaac.lab.sim import SimulationContext
-from omni.isaac.lab.utils.timer import Timer
-
-from .common import VecEnvObs
-from .manager_based_env_cfg import ManagerBasedEnvCfg
-from .ui import ViewportCameraController
-
-
-
[文档]class ManagerBasedEnv: - """The base environment encapsulates the simulation scene and the environment managers for the manager-based workflow. - - While a simulation scene or world comprises of different components such as the robots, objects, - and sensors (cameras, lidars, etc.), the environment is a higher level abstraction - that provides an interface for interacting with the simulation. The environment is comprised of - the following components: - - * **Scene**: The scene manager that creates and manages the virtual world in which the robot operates. - This includes defining the robot, static and dynamic objects, sensors, etc. - * **Observation Manager**: The observation manager that generates observations from the current simulation - state and the data gathered from the sensors. These observations may include privileged information - that is not available to the robot in the real world. Additionally, user-defined terms can be added - to process the observations and generate custom observations. For example, using a network to embed - high-dimensional observations into a lower-dimensional space. - * **Action Manager**: The action manager that processes the raw actions sent to the environment and - converts them to low-level commands that are sent to the simulation. It can be configured to accept - raw actions at different levels of abstraction. For example, in case of a robotic arm, the raw actions - can be joint torques, joint positions, or end-effector poses. Similarly for a mobile base, it can be - the joint torques, or the desired velocity of the floating base. - * **Event Manager**: The event manager orchestrates operations triggered based on simulation events. - This includes resetting the scene to a default state, applying random pushes to the robot at different intervals - of time, or randomizing properties such as mass and friction coefficients. This is useful for training - and evaluating the robot in a variety of scenarios. - - The environment provides a unified interface for interacting with the simulation. However, it does not - include task-specific quantities such as the reward function, or the termination conditions. These - quantities are often specific to defining Markov Decision Processes (MDPs) while the base environment - is agnostic to the MDP definition. - - The environment steps forward in time at a fixed time-step. The physics simulation is decimated at a - lower time-step. This is to ensure that the simulation is stable. These two time-steps can be configured - independently using the :attr:`ManagerBasedEnvCfg.decimation` (number of simulation steps per environment step) - and the :attr:`ManagerBasedEnvCfg.sim.dt` (physics time-step) parameters. Based on these parameters, the - environment time-step is computed as the product of the two. The two time-steps can be obtained by - querying the :attr:`physics_dt` and the :attr:`step_dt` properties respectively. - """ - -
[文档] def __init__(self, cfg: ManagerBasedEnvCfg): - """Initialize the environment. - - Args: - cfg: The configuration object for the environment. - - Raises: - RuntimeError: If a simulation context already exists. The environment must always create one - since it configures the simulation context and controls the simulation. - """ - # store inputs to class - self.cfg = cfg - # initialize internal variables - self._is_closed = False - - # create a simulation context to control the simulator - if SimulationContext.instance() is None: - # the type-annotation is required to avoid a type-checking error - # since it gets confused with Isaac Sim's SimulationContext class - self.sim: SimulationContext = SimulationContext(self.cfg.sim) - else: - # simulation context should only be created before the environment - # when in extension mode - if not builtins.ISAAC_LAUNCHED_FROM_TERMINAL: - raise RuntimeError("Simulation context already exists. Cannot create a new one.") - self.sim: SimulationContext = SimulationContext.instance() - - # print useful information - print("[INFO]: Base environment:") - print(f"\tEnvironment device : {self.device}") - print(f"\tPhysics step-size : {self.physics_dt}") - print(f"\tRendering step-size : {self.physics_dt * self.cfg.sim.render_interval}") - print(f"\tEnvironment step-size : {self.step_dt}") - print(f"\tPhysics GPU pipeline : {self.cfg.sim.use_gpu_pipeline}") - print(f"\tPhysics GPU simulation: {self.cfg.sim.physx.use_gpu}") - - if self.cfg.sim.render_interval < self.cfg.decimation: - msg = ( - f"The render interval ({self.cfg.sim.render_interval}) is smaller than the decimation " - f"({self.cfg.decimation}). Multiple multiple render calls will happen for each environment step. " - "If this is not intended, set the render interval to be equal to the decimation." - ) - carb.log_warn(msg) - - # counter for simulation steps - self._sim_step_counter = 0 - - # generate scene - with Timer("[INFO]: Time taken for scene creation"): - self.scene = InteractiveScene(self.cfg.scene) - print("[INFO]: Scene manager: ", self.scene) - - # set up camera viewport controller - # viewport is not available in other rendering modes so the function will throw a warning - # FIXME: This needs to be fixed in the future when we unify the UI functionalities even for - # non-rendering modes. - if self.sim.render_mode >= self.sim.RenderMode.PARTIAL_RENDERING: - self.viewport_camera_controller = ViewportCameraController(self, self.cfg.viewer) - else: - self.viewport_camera_controller = None - - # play the simulator to activate physics handles - # note: this activates the physics simulation view that exposes TensorAPIs - # note: when started in extension mode, first call sim.reset_async() and then initialize the managers - if builtins.ISAAC_LAUNCHED_FROM_TERMINAL is False: - print("[INFO]: Starting the simulation. This may take a few seconds. Please wait...") - with Timer("[INFO]: Time taken for simulation start"): - self.sim.reset() - # add timeline event to load managers - self.load_managers() - - # make sure torch is running on the correct device - if "cuda" in self.device: - torch.cuda.set_device(self.device) - - # extend UI elements - # we need to do this here after all the managers are initialized - # this is because they dictate the sensors and commands right now - if self.sim.has_gui() and self.cfg.ui_window_class_type is not None: - self._window = self.cfg.ui_window_class_type(self, window_name="IsaacLab") - else: - # if no window, then we don't need to store the window - self._window = None - - # allocate dictionary to store metrics - self.extras = {}
- - def __del__(self): - """Cleanup for the environment.""" - self.close() - - """ - Properties. - """ - - @property - def num_envs(self) -> int: - """The number of instances of the environment that are running.""" - return self.scene.num_envs - - @property - def physics_dt(self) -> float: - """The physics time-step (in s). - - This is the lowest time-decimation at which the simulation is happening. - """ - return self.cfg.sim.dt - - @property - def step_dt(self) -> float: - """The environment stepping time-step (in s). - - This is the time-step at which the environment steps forward. - """ - return self.cfg.sim.dt * self.cfg.decimation - - @property - def device(self): - """The device on which the environment is running.""" - return self.sim.device - - """ - Operations - Setup. - """ - -
[文档] def load_managers(self): - """Load the managers for the environment. - - This function is responsible for creating the various managers (action, observation, - events, etc.) for the environment. Since the managers require access to physics handles, - they can only be created after the simulator is reset (i.e. played for the first time). - - .. note:: - In case of standalone application (when running simulator from Python), the function is called - automatically when the class is initialized. - - However, in case of extension mode, the user must call this function manually after the simulator - is reset. This is because the simulator is only reset when the user calls - :meth:`SimulationContext.reset_async` and it isn't possible to call async functions in the constructor. - - """ - # check the configs - if self.cfg.randomization is not None: - msg = ( - "The 'randomization' attribute is deprecated and will be removed in a future release. " - "Please use the 'events' attribute to configure the randomization settings." - ) - warnings.warn(msg, category=DeprecationWarning) - carb.log_warn(msg) - # set the randomization as events (for backward compatibility) - self.cfg.events = self.cfg.randomization - - # prepare the managers - # -- action manager - self.action_manager = ActionManager(self.cfg.actions, self) - print("[INFO] Action Manager: ", self.action_manager) - # -- observation manager - self.observation_manager = ObservationManager(self.cfg.observations, self) - print("[INFO] Observation Manager:", self.observation_manager) - # -- event manager - self.event_manager = EventManager(self.cfg.events, self) - print("[INFO] Event Manager: ", self.event_manager)
- - """ - Operations - MDP. - """ - -
[文档] def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[VecEnvObs, dict]: - """Resets all the environments and returns observations. - - Args: - seed: The seed to use for randomization. Defaults to None, in which case the seed is not set. - options: Additional information to specify how the environment is reset. Defaults to None. - - Note: - This argument is used for compatibility with Gymnasium environment definition. - - Returns: - A tuple containing the observations and extras. - """ - # set the seed - if seed is not None: - self.seed(seed) - # reset state of scene - indices = torch.arange(self.num_envs, dtype=torch.int64, device=self.device) - self._reset_idx(indices) - # return observations - return self.observation_manager.compute(), self.extras
- -
[文档] def step(self, action: torch.Tensor) -> tuple[VecEnvObs, dict]: - """Execute one time-step of the environment's dynamics. - - The environment steps forward at a fixed time-step, while the physics simulation is - decimated at a lower time-step. This is to ensure that the simulation is stable. These two - time-steps can be configured independently using the :attr:`ManagerBasedEnvCfg.decimation` (number of - simulation steps per environment step) and the :attr:`ManagerBasedEnvCfg.sim.dt` (physics time-step). - Based on these parameters, the environment time-step is computed as the product of the two. - - Args: - action: The actions to apply on the environment. Shape is (num_envs, action_dim). - - Returns: - A tuple containing the observations and extras. - """ - # process actions - self.action_manager.process_action(action) - - # check if we need to do rendering within the physics loop - # note: checked here once to avoid multiple checks within the loop - is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors() - - # perform physics stepping - for _ in range(self.cfg.decimation): - self._sim_step_counter += 1 - # set actions into buffers - self.action_manager.apply_action() - # set actions into simulator - self.scene.write_data_to_sim() - # simulate - self.sim.step(render=False) - # render between steps only if the GUI or an RTX sensor needs it - # note: we assume the render interval to be the shortest accepted rendering interval. - # If a camera needs rendering at a faster frequency, this will lead to unexpected behavior. - if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering: - self.sim.render() - # update buffers at sim dt - self.scene.update(dt=self.physics_dt) - - # post-step: step interval event - if "interval" in self.event_manager.available_modes: - self.event_manager.apply(mode="interval", dt=self.step_dt) - - # return observations and extras - return self.observation_manager.compute(), self.extras
- -
[文档] @staticmethod - def seed(seed: int = -1) -> int: - """Set the seed for the environment. - - Args: - seed: The seed for random generator. Defaults to -1. - - Returns: - The seed used for random generator. - """ - # set seed for replicator - try: - import omni.replicator.core as rep - - rep.set_global_seed(seed) - except ModuleNotFoundError: - pass - # set seed for torch and other libraries - return torch_utils.set_seed(seed)
- -
[文档] def close(self): - """Cleanup for the environment.""" - if not self._is_closed: - # destructor is order-sensitive - del self.viewport_camera_controller - del self.action_manager - del self.observation_manager - del self.event_manager - del self.scene - # clear callbacks and instance - self.sim.clear_all_callbacks() - self.sim.clear_instance() - # destroy the window - if self._window is not None: - self._window = None - # update closing status - self._is_closed = True
- - """ - Helper functions. - """ - - def _reset_idx(self, env_ids: Sequence[int]): - """Reset environments based on specified indices. - - Args: - env_ids: List of environment ids which must be reset - """ - # reset the internal buffers of the scene elements - self.scene.reset(env_ids) - # apply events such as randomizations for environments that need a reset - if "reset" in self.event_manager.available_modes: - self.event_manager.apply(env_ids=env_ids, mode="reset") - - # iterate over all managers and reset them - # this returns a dictionary of information which is stored in the extras - # note: This is order-sensitive! Certain things need be reset before others. - self.extras["log"] = dict() - # -- observation manager - info = self.observation_manager.reset(env_ids) - self.extras["log"].update(info) - # -- action manager - info = self.action_manager.reset(env_ids) - self.extras["log"].update(info) - # -- event manager - info = self.event_manager.reset(env_ids) - self.extras["log"].update(info)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_env_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_env_cfg.html deleted file mode 100644 index a91a29aa5b..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_env_cfg.html +++ /dev/null @@ -1,661 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.manager_based_env_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.manager_based_env_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Base configuration of the environment.
-
-This module defines the general configuration of the environment. It includes parameters for
-configuring the environment instances, viewer settings, and simulation parameters.
-"""
-
-from dataclasses import MISSING
-
-import omni.isaac.lab.envs.mdp as mdp
-from omni.isaac.lab.managers import EventTermCfg as EventTerm
-from omni.isaac.lab.scene import InteractiveSceneCfg
-from omni.isaac.lab.sim import SimulationCfg
-from omni.isaac.lab.utils import configclass
-
-from .common import ViewerCfg
-from .ui import BaseEnvWindow
-
-
-@configclass
-class DefaultEventManagerCfg:
-    """Configuration of the default event manager.
-
-    This manager is used to reset the scene to a default state. The default state is specified
-    by the scene configuration.
-    """
-
-    reset_scene_to_default = EventTerm(func=mdp.reset_scene_to_default, mode="reset")
-
-
-
[文档]@configclass -class ManagerBasedEnvCfg: - """Base configuration of the environment.""" - - # simulation settings - viewer: ViewerCfg = ViewerCfg() - """Viewer configuration. Default is ViewerCfg().""" - - sim: SimulationCfg = SimulationCfg() - """Physics simulation configuration. Default is SimulationCfg().""" - - # ui settings - ui_window_class_type: type | None = BaseEnvWindow - """The class type of the UI window. Default is None. - - If None, then no UI window is created. - - Note: - If you want to make your own UI window, you can create a class that inherits from - from :class:`omni.isaac.lab.envs.ui.base_env_window.BaseEnvWindow`. Then, you can set - this attribute to your class type. - """ - - # general settings - decimation: int = MISSING - """Number of control action updates @ sim dt per policy dt. - - For instance, if the simulation dt is 0.01s and the policy dt is 0.1s, then the decimation is 10. - This means that the control action is updated every 10 simulation steps. - """ - - # environment settings - scene: InteractiveSceneCfg = MISSING - """Scene settings. - - Please refer to the :class:`omni.isaac.lab.scene.InteractiveSceneCfg` class for more details. - """ - - observations: object = MISSING - """Observation space settings. - - Please refer to the :class:`omni.isaac.lab.managers.ObservationManager` class for more details. - """ - - actions: object = MISSING - """Action space settings. - - Please refer to the :class:`omni.isaac.lab.managers.ActionManager` class for more details. - """ - - events: object = DefaultEventManagerCfg() - """Event settings. Defaults to the basic configuration that resets the scene to its default state. - - Please refer to the :class:`omni.isaac.lab.managers.EventManager` class for more details. - """ - - randomization: object | None = None - """Randomization settings. Default is None. - - .. deprecated:: 0.3.0 - This attribute is deprecated and will be removed in v0.4.0. Please use the :attr:`events` - attribute to configure the randomization settings. - - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_rl_env.html b/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_rl_env.html deleted file mode 100644 index e01fbc56ac..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_rl_env.html +++ /dev/null @@ -1,910 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.manager_based_rl_env — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.manager_based_rl_env 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# needed to import for allowing type-hinting: np.ndarray | None
-from __future__ import annotations
-
-import gymnasium as gym
-import math
-import numpy as np
-import torch
-from collections.abc import Sequence
-from typing import Any, ClassVar
-
-from omni.isaac.version import get_version
-
-from omni.isaac.lab.managers import CommandManager, CurriculumManager, RewardManager, TerminationManager
-
-from .common import VecEnvStepReturn
-from .manager_based_env import ManagerBasedEnv
-from .manager_based_rl_env_cfg import ManagerBasedRLEnvCfg
-
-
-
[文档]class ManagerBasedRLEnv(ManagerBasedEnv, gym.Env): - """The superclass for the manager-based workflow reinforcement learning-based environments. - - This class inherits from :class:`ManagerBasedEnv` and implements the core functionality for - reinforcement learning-based environments. It is designed to be used with any RL - library. The class is designed to be used with vectorized environments, i.e., the - environment is expected to be run in parallel with multiple sub-environments. The - number of sub-environments is specified using the ``num_envs``. - - Each observation from the environment is a batch of observations for each sub- - environments. The method :meth:`step` is also expected to receive a batch of actions - for each sub-environment. - - While the environment itself is implemented as a vectorized environment, we do not - inherit from :class:`gym.vector.VectorEnv`. This is mainly because the class adds - various methods (for wait and asynchronous updates) which are not required. - Additionally, each RL library typically has its own definition for a vectorized - environment. Thus, to reduce complexity, we directly use the :class:`gym.Env` over - here and leave it up to library-defined wrappers to take care of wrapping this - environment for their agents. - - Note: - For vectorized environments, it is recommended to **only** call the :meth:`reset` - method once before the first call to :meth:`step`, i.e. after the environment is created. - After that, the :meth:`step` function handles the reset of terminated sub-environments. - This is because the simulator does not support resetting individual sub-environments - in a vectorized environment. - - """ - - is_vector_env: ClassVar[bool] = True - """Whether the environment is a vectorized environment.""" - metadata: ClassVar[dict[str, Any]] = { - "render_modes": [None, "human", "rgb_array"], - "isaac_sim_version": get_version(), - } - """Metadata for the environment.""" - - cfg: ManagerBasedRLEnvCfg - """Configuration for the environment.""" - -
[文档] def __init__(self, cfg: ManagerBasedRLEnvCfg, render_mode: str | None = None, **kwargs): - """Initialize the environment. - - Args: - cfg: The configuration for the environment. - render_mode: The render mode for the environment. Defaults to None, which - is similar to ``"human"``. - """ - # initialize the base class to setup the scene. - super().__init__(cfg=cfg) - # store the render mode - self.render_mode = render_mode - - # initialize data and constants - # -- counter for curriculum - self.common_step_counter = 0 - # -- init buffers - self.episode_length_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.long) - print("[INFO]: Completed setting up the environment...")
- - """ - Properties. - """ - - @property - def max_episode_length_s(self) -> float: - """Maximum episode length in seconds.""" - return self.cfg.episode_length_s - - @property - def max_episode_length(self) -> int: - """Maximum episode length in environment steps.""" - return math.ceil(self.max_episode_length_s / self.step_dt) - - """ - Operations - Setup. - """ - -
[文档] def load_managers(self): - # note: this order is important since observation manager needs to know the command and action managers - # and the reward manager needs to know the termination manager - # -- command manager - self.command_manager: CommandManager = CommandManager(self.cfg.commands, self) - print("[INFO] Command Manager: ", self.command_manager) - # call the parent class to load the managers for observations and actions. - super().load_managers() - # prepare the managers - # -- termination manager - self.termination_manager = TerminationManager(self.cfg.terminations, self) - print("[INFO] Termination Manager: ", self.termination_manager) - # -- reward manager - self.reward_manager = RewardManager(self.cfg.rewards, self) - print("[INFO] Reward Manager: ", self.reward_manager) - # -- curriculum manager - self.curriculum_manager = CurriculumManager(self.cfg.curriculum, self) - print("[INFO] Curriculum Manager: ", self.curriculum_manager) - # setup the action and observation spaces for Gym - self._configure_gym_env_spaces() - # perform events at the start of the simulation - if "startup" in self.event_manager.available_modes: - self.event_manager.apply(mode="startup")
- - """ - Operations - MDP - """ - -
[文档] def step(self, action: torch.Tensor) -> VecEnvStepReturn: - """Execute one time-step of the environment's dynamics and reset terminated environments. - - Unlike the :class:`ManagerBasedEnv.step` class, the function performs the following operations: - - 1. Process the actions. - 2. Perform physics stepping. - 3. Perform rendering if gui is enabled. - 4. Update the environment counters and compute the rewards and terminations. - 5. Reset the environments that terminated. - 6. Compute the observations. - 7. Return the observations, rewards, resets and extras. - - Args: - action: The actions to apply on the environment. Shape is (num_envs, action_dim). - - Returns: - A tuple containing the observations, rewards, resets (terminated and truncated) and extras. - """ - # process actions - self.action_manager.process_action(action) - - # check if we need to do rendering within the physics loop - # note: checked here once to avoid multiple checks within the loop - is_rendering = self.sim.has_gui() or self.sim.has_rtx_sensors() - - # perform physics stepping - for _ in range(self.cfg.decimation): - self._sim_step_counter += 1 - # set actions into buffers - self.action_manager.apply_action() - # set actions into simulator - self.scene.write_data_to_sim() - # simulate - self.sim.step(render=False) - # render between steps only if the GUI or an RTX sensor needs it - # note: we assume the render interval to be the shortest accepted rendering interval. - # If a camera needs rendering at a faster frequency, this will lead to unexpected behavior. - if self._sim_step_counter % self.cfg.sim.render_interval == 0 and is_rendering: - self.sim.render() - # update buffers at sim dt - self.scene.update(dt=self.physics_dt) - - # post-step: - # -- update env counters (used for curriculum generation) - self.episode_length_buf += 1 # step in current episode (per env) - self.common_step_counter += 1 # total step (common for all envs) - # -- check terminations - self.reset_buf = self.termination_manager.compute() - self.reset_terminated = self.termination_manager.terminated - self.reset_time_outs = self.termination_manager.time_outs - # -- reward computation - self.reward_buf = self.reward_manager.compute(dt=self.step_dt) - - # -- reset envs that terminated/timed-out and log the episode information - reset_env_ids = self.reset_buf.nonzero(as_tuple=False).squeeze(-1) - if len(reset_env_ids) > 0: - self._reset_idx(reset_env_ids) - # -- update command - self.command_manager.compute(dt=self.step_dt) - # -- step interval events - if "interval" in self.event_manager.available_modes: - self.event_manager.apply(mode="interval", dt=self.step_dt) - # -- compute observations - # note: done after reset to get the correct observations for reset envs - self.obs_buf = self.observation_manager.compute() - - # return observations, rewards, resets and extras - return self.obs_buf, self.reward_buf, self.reset_terminated, self.reset_time_outs, self.extras
- -
[文档] def render(self, recompute: bool = False) -> np.ndarray | None: - """Run rendering without stepping through the physics. - - By convention, if mode is: - - - **human**: Render to the current display and return nothing. Usually for human consumption. - - **rgb_array**: Return an numpy.ndarray with shape (x, y, 3), representing RGB values for an - x-by-y pixel image, suitable for turning into a video. - - Args: - recompute: Whether to force a render even if the simulator has already rendered the scene. - Defaults to False. - - Returns: - The rendered image as a numpy array if mode is "rgb_array". Otherwise, returns None. - - Raises: - RuntimeError: If mode is set to "rgb_data" and simulation render mode does not support it. - In this case, the simulation render mode must be set to ``RenderMode.PARTIAL_RENDERING`` - or ``RenderMode.FULL_RENDERING``. - NotImplementedError: If an unsupported rendering mode is specified. - """ - # run a rendering step of the simulator - # if we have rtx sensors, we do not need to render again sin - if not self.sim.has_rtx_sensors() and not recompute: - self.sim.render() - # decide the rendering mode - if self.render_mode == "human" or self.render_mode is None: - return None - elif self.render_mode == "rgb_array": - # check that if any render could have happened - if self.sim.render_mode.value < self.sim.RenderMode.PARTIAL_RENDERING.value: - raise RuntimeError( - f"Cannot render '{self.render_mode}' when the simulation render mode is" - f" '{self.sim.render_mode.name}'. Please set the simulation render mode to:" - f"'{self.sim.RenderMode.PARTIAL_RENDERING.name}' or '{self.sim.RenderMode.FULL_RENDERING.name}'." - " If running headless, make sure --enable_cameras is set." - ) - # create the annotator if it does not exist - if not hasattr(self, "_rgb_annotator"): - import omni.replicator.core as rep - - # create render product - self._render_product = rep.create.render_product( - self.cfg.viewer.cam_prim_path, self.cfg.viewer.resolution - ) - # create rgb annotator -- used to read data from the render product - self._rgb_annotator = rep.AnnotatorRegistry.get_annotator("rgb", device="cpu") - self._rgb_annotator.attach([self._render_product]) - # obtain the rgb data - rgb_data = self._rgb_annotator.get_data() - # convert to numpy array - rgb_data = np.frombuffer(rgb_data, dtype=np.uint8).reshape(*rgb_data.shape) - # return the rgb data - # note: initially the renerer is warming up and returns empty data - if rgb_data.size == 0: - return np.zeros((self.cfg.viewer.resolution[1], self.cfg.viewer.resolution[0], 3), dtype=np.uint8) - else: - return rgb_data[:, :, :3] - else: - raise NotImplementedError( - f"Render mode '{self.render_mode}' is not supported. Please use: {self.metadata['render_modes']}." - )
- -
[文档] def close(self): - if not self._is_closed: - # destructor is order-sensitive - del self.command_manager - del self.reward_manager - del self.termination_manager - del self.curriculum_manager - # call the parent class to close the environment - super().close()
- - """ - Helper functions. - """ - - def _configure_gym_env_spaces(self): - """Configure the action and observation spaces for the Gym environment.""" - # observation space (unbounded since we don't impose any limits) - self.single_observation_space = gym.spaces.Dict() - for group_name, group_term_names in self.observation_manager.active_terms.items(): - # extract quantities about the group - has_concatenated_obs = self.observation_manager.group_obs_concatenate[group_name] - group_dim = self.observation_manager.group_obs_dim[group_name] - group_term_dim = self.observation_manager.group_obs_term_dim[group_name] - # check if group is concatenated or not - # if not concatenated, then we need to add each term separately as a dictionary - if has_concatenated_obs: - self.single_observation_space[group_name] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=group_dim) - else: - self.single_observation_space[group_name] = gym.spaces.Dict({ - term_name: gym.spaces.Box(low=-np.inf, high=np.inf, shape=term_dim) - for term_name, term_dim in zip(group_term_names, group_term_dim) - }) - # action space (unbounded since we don't impose any limits) - action_dim = sum(self.action_manager.action_term_dim) - self.single_action_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(action_dim,)) - - # batch the spaces for vectorized environments - self.observation_space = gym.vector.utils.batch_space(self.single_observation_space, self.num_envs) - self.action_space = gym.vector.utils.batch_space(self.single_action_space, self.num_envs) - - def _reset_idx(self, env_ids: Sequence[int]): - """Reset environments based on specified indices. - - Args: - env_ids: List of environment ids which must be reset - """ - # update the curriculum for environments that need a reset - self.curriculum_manager.compute(env_ids=env_ids) - # reset the internal buffers of the scene elements - self.scene.reset(env_ids) - # apply events such as randomizations for environments that need a reset - if "reset" in self.event_manager.available_modes: - self.event_manager.apply(env_ids=env_ids, mode="reset") - - # iterate over all managers and reset them - # this returns a dictionary of information which is stored in the extras - # note: This is order-sensitive! Certain things need be reset before others. - self.extras["log"] = dict() - # -- observation manager - info = self.observation_manager.reset(env_ids) - self.extras["log"].update(info) - # -- action manager - info = self.action_manager.reset(env_ids) - self.extras["log"].update(info) - # -- rewards manager - info = self.reward_manager.reset(env_ids) - self.extras["log"].update(info) - # -- curriculum manager - info = self.curriculum_manager.reset(env_ids) - self.extras["log"].update(info) - # -- command manager - info = self.command_manager.reset(env_ids) - self.extras["log"].update(info) - # -- event manager - info = self.event_manager.reset(env_ids) - self.extras["log"].update(info) - # -- termination manager - info = self.termination_manager.reset(env_ids) - self.extras["log"].update(info) - - # reset the episode length buffer - self.episode_length_buf[env_ids] = 0
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_rl_env_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_rl_env_cfg.html deleted file mode 100644 index a0476780d4..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/manager_based_rl_env_cfg.html +++ /dev/null @@ -1,643 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.manager_based_rl_env_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.manager_based_rl_env_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.utils import configclass
-
-from .manager_based_env_cfg import ManagerBasedEnvCfg
-from .ui import ManagerBasedRLEnvWindow
-
-
-
[文档]@configclass -class ManagerBasedRLEnvCfg(ManagerBasedEnvCfg): - """Configuration for a reinforcement learning environment with the manager-based workflow.""" - - # ui settings - ui_window_class_type: type | None = ManagerBasedRLEnvWindow - - # general settings - is_finite_horizon: bool = False - """Whether the learning task is treated as a finite or infinite horizon problem for the agent. - Defaults to False, which means the task is treated as an infinite horizon problem. - - This flag handles the subtleties of finite and infinite horizon tasks: - - * **Finite horizon**: no penalty or bootstrapping value is required by the the agent for - running out of time. However, the environment still needs to terminate the episode after the - time limit is reached. - * **Infinite horizon**: the agent needs to bootstrap the value of the state at the end of the episode. - This is done by sending a time-limit (or truncated) done signal to the agent, which triggers this - bootstrapping calculation. - - If True, then the environment is treated as a finite horizon problem and no time-out (or truncated) done signal - is sent to the agent. If False, then the environment is treated as an infinite horizon problem and a time-out - (or truncated) done signal is sent to the agent. - - Note: - The base :class:`ManagerBasedRLEnv` class does not use this flag directly. It is used by the environment - wrappers to determine what type of done signal to send to the corresponding learning agent. - """ - - episode_length_s: float = MISSING - """Duration of an episode (in seconds). - - Based on the decimation rate and physics time step, the episode length is calculated as: - - .. code-block:: python - - episode_length_steps = ceil(episode_length_s / (decimation_rate * physics_time_step)) - - For example, if the decimation rate is 10, the physics time step is 0.01, and the episode length is 10 seconds, - then the episode length in steps is 100. - """ - - # environment settings - rewards: object = MISSING - """Reward settings. - - Please refer to the :class:`omni.isaac.lab.managers.RewardManager` class for more details. - """ - - terminations: object = MISSING - """Termination settings. - - Please refer to the :class:`omni.isaac.lab.managers.TerminationManager` class for more details. - """ - - curriculum: object = MISSING - """Curriculum settings. - - Please refer to the :class:`omni.isaac.lab.managers.CurriculumManager` class for more details. - """ - - commands: object = MISSING - """Command settings. - - Please refer to the :class:`omni.isaac.lab.managers.CommandManager` class for more details. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/actions/actions_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/actions/actions_cfg.html deleted file mode 100644 index 1ef1b78197..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/actions/actions_cfg.html +++ /dev/null @@ -1,811 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.actions.actions_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.actions.actions_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.controllers import DifferentialIKControllerCfg
-from omni.isaac.lab.managers.action_manager import ActionTerm, ActionTermCfg
-from omni.isaac.lab.utils import configclass
-
-from . import binary_joint_actions, joint_actions, joint_actions_to_limits, non_holonomic_actions, task_space_actions
-
-##
-# Joint actions.
-##
-
-
-
[文档]@configclass -class JointActionCfg(ActionTermCfg): - """Configuration for the base joint action term. - - See :class:`JointAction` for more details. - """ - - joint_names: list[str] = MISSING - """List of joint names or regex expressions that the action will be mapped to.""" - scale: float | dict[str, float] = 1.0 - """Scale factor for the action (float or dict of regex expressions). Defaults to 1.0.""" - offset: float | dict[str, float] = 0.0 - """Offset factor for the action (float or dict of regex expressions). Defaults to 0.0."""
- - -
[文档]@configclass -class JointPositionActionCfg(JointActionCfg): - """Configuration for the joint position action term. - - See :class:`JointPositionAction` for more details. - """ - - class_type: type[ActionTerm] = joint_actions.JointPositionAction - - use_default_offset: bool = True - """Whether to use default joint positions configured in the articulation asset as offset. - Defaults to True. - - If True, this flag results in overwriting the values of :attr:`offset` to the default joint positions - from the articulation asset. - """
- - -
[文档]@configclass -class RelativeJointPositionActionCfg(JointActionCfg): - """Configuration for the relative joint position action term. - - See :class:`RelativeJointPositionAction` for more details. - """ - - class_type: type[ActionTerm] = joint_actions.RelativeJointPositionAction - - use_zero_offset: bool = True - """Whether to ignore the offset defined in articulation asset. Defaults to True. - - If True, this flag results in overwriting the values of :attr:`offset` to zero. - """
- - -
[文档]@configclass -class JointVelocityActionCfg(JointActionCfg): - """Configuration for the joint velocity action term. - - See :class:`JointVelocityAction` for more details. - """ - - class_type: type[ActionTerm] = joint_actions.JointVelocityAction - - use_default_offset: bool = True - """Whether to use default joint velocities configured in the articulation asset as offset. - Defaults to True. - - This overrides the settings from :attr:`offset` if set to True. - """
- - -
[文档]@configclass -class JointEffortActionCfg(JointActionCfg): - """Configuration for the joint effort action term. - - See :class:`JointEffortAction` for more details. - """ - - class_type: type[ActionTerm] = joint_actions.JointEffortAction
- - -## -# Joint actions rescaled to limits. -## - - -
[文档]@configclass -class JointPositionToLimitsActionCfg(ActionTermCfg): - """Configuration for the bounded joint position action term. - - See :class:`JointPositionWithinLimitsAction` for more details. - """ - - class_type: type[ActionTerm] = joint_actions_to_limits.JointPositionToLimitsAction - - joint_names: list[str] = MISSING - """List of joint names or regex expressions that the action will be mapped to.""" - - scale: float | dict[str, float] = 1.0 - """Scale factor for the action (float or dict of regex expressions). Defaults to 1.0.""" - - rescale_to_limits: bool = True - """Whether to rescale the action to the joint limits. Defaults to True. - - If True, the input actions are rescaled to the joint limits, i.e., the action value in - the range [-1, 1] corresponds to the joint lower and upper limits respectively. - - Note: - This operation is performed after applying the scale factor. - """
- - -
[文档]@configclass -class EMAJointPositionToLimitsActionCfg(JointPositionToLimitsActionCfg): - """Configuration for the exponential moving average (EMA) joint position action term. - - See :class:`EMAJointPositionToLimitsAction` for more details. - """ - - class_type: type[ActionTerm] = joint_actions_to_limits.EMAJointPositionToLimitsAction - - alpha: float | dict[str, float] = 1.0 - """The weight for the moving average (float or dict of regex expressions). Defaults to 1.0. - - If set to 1.0, the processed action is applied directly without any moving average window. - """
- - -## -# Gripper actions. -## - - -
[文档]@configclass -class BinaryJointActionCfg(ActionTermCfg): - """Configuration for the base binary joint action term. - - See :class:`BinaryJointAction` for more details. - """ - - joint_names: list[str] = MISSING - """List of joint names or regex expressions that the action will be mapped to.""" - open_command_expr: dict[str, float] = MISSING - """The joint command to move to *open* configuration.""" - close_command_expr: dict[str, float] = MISSING - """The joint command to move to *close* configuration."""
- - -
[文档]@configclass -class BinaryJointPositionActionCfg(BinaryJointActionCfg): - """Configuration for the binary joint position action term. - - See :class:`BinaryJointPositionAction` for more details. - """ - - class_type: type[ActionTerm] = binary_joint_actions.BinaryJointPositionAction
- - -
[文档]@configclass -class BinaryJointVelocityActionCfg(BinaryJointActionCfg): - """Configuration for the binary joint velocity action term. - - See :class:`BinaryJointVelocityAction` for more details. - """ - - class_type: type[ActionTerm] = binary_joint_actions.BinaryJointVelocityAction
- - -## -# Non-holonomic actions. -## - - -
[文档]@configclass -class NonHolonomicActionCfg(ActionTermCfg): - """Configuration for the non-holonomic action term with dummy joints at the base. - - See :class:`NonHolonomicAction` for more details. - """ - - class_type: type[ActionTerm] = non_holonomic_actions.NonHolonomicAction - - body_name: str = MISSING - """Name of the body which has the dummy mechanism connected to.""" - x_joint_name: str = MISSING - """The dummy joint name in the x direction.""" - y_joint_name: str = MISSING - """The dummy joint name in the y direction.""" - yaw_joint_name: str = MISSING - """The dummy joint name in the yaw direction.""" - scale: tuple[float, float] = (1.0, 1.0) - """Scale factor for the action. Defaults to (1.0, 1.0).""" - offset: tuple[float, float] = (0.0, 0.0) - """Offset factor for the action. Defaults to (0.0, 0.0)."""
- - -## -# Task-space Actions. -## - - -
[文档]@configclass -class DifferentialInverseKinematicsActionCfg(ActionTermCfg): - """Configuration for inverse differential kinematics action term. - - See :class:`DifferentialInverseKinematicsAction` for more details. - """ - -
[文档] @configclass - class OffsetCfg: - """The offset pose from parent frame to child frame. - - On many robots, end-effector frames are fictitious frames that do not have a corresponding - rigid body. In such cases, it is easier to define this transform w.r.t. their parent rigid body. - For instance, for the Franka Emika arm, the end-effector is defined at an offset to the the - "panda_hand" frame. - """ - - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation ``(w, x, y, z)`` w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0)."""
- - class_type: type[ActionTerm] = task_space_actions.DifferentialInverseKinematicsAction - - joint_names: list[str] = MISSING - """List of joint names or regex expressions that the action will be mapped to.""" - body_name: str = MISSING - """Name of the body or frame for which IK is performed.""" - body_offset: OffsetCfg | None = None - """Offset of target frame w.r.t. to the body frame. Defaults to None, in which case no offset is applied.""" - scale: float | tuple[float, ...] = 1.0 - """Scale factor for the action. Defaults to 1.0.""" - controller: DifferentialIKControllerCfg = MISSING - """The configuration for the differential IK controller."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/commands/commands_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/commands/commands_cfg.html deleted file mode 100644 index 1252c92d14..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/commands/commands_cfg.html +++ /dev/null @@ -1,742 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.commands.commands_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.commands.commands_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import math
-from dataclasses import MISSING
-
-from omni.isaac.lab.managers import CommandTermCfg
-from omni.isaac.lab.utils import configclass
-
-from .null_command import NullCommand
-from .pose_2d_command import TerrainBasedPose2dCommand, UniformPose2dCommand
-from .pose_command import UniformPoseCommand
-from .velocity_command import NormalVelocityCommand, UniformVelocityCommand
-
-
-
[文档]@configclass -class NullCommandCfg(CommandTermCfg): - """Configuration for the null command generator.""" - - class_type: type = NullCommand - - def __post_init__(self): - """Post initialization.""" - # set the resampling time range to infinity to avoid resampling - self.resampling_time_range = (math.inf, math.inf)
- - -
[文档]@configclass -class UniformVelocityCommandCfg(CommandTermCfg): - """Configuration for the uniform velocity command generator.""" - - class_type: type = UniformVelocityCommand - - asset_name: str = MISSING - """Name of the asset in the environment for which the commands are generated.""" - heading_command: bool = MISSING - """Whether to use heading command or angular velocity command. - - If True, the angular velocity command is computed from the heading error, where the - target heading is sampled uniformly from provided range. Otherwise, the angular velocity - command is sampled uniformly from provided range. - """ - heading_control_stiffness: float = MISSING - """Scale factor to convert the heading error to angular velocity command.""" - rel_standing_envs: float = MISSING - """Probability threshold for environments where the robots that are standing still.""" - rel_heading_envs: float = MISSING - """Probability threshold for environments where the robots follow the heading-based angular velocity command - (the others follow the sampled angular velocity command).""" - -
[文档] @configclass - class Ranges: - """Uniform distribution ranges for the velocity commands.""" - - lin_vel_x: tuple[float, float] = MISSING # min max [m/s] - lin_vel_y: tuple[float, float] = MISSING # min max [m/s] - ang_vel_z: tuple[float, float] = MISSING # min max [rad/s] - heading: tuple[float, float] = MISSING # min max [rad]
- - ranges: Ranges = MISSING - """Distribution ranges for the velocity commands."""
- - -
[文档]@configclass -class NormalVelocityCommandCfg(UniformVelocityCommandCfg): - """Configuration for the normal velocity command generator.""" - - class_type: type = NormalVelocityCommand - heading_command: bool = False # --> we don't use heading command for normal velocity command. - -
[文档] @configclass - class Ranges: - """Normal distribution ranges for the velocity commands.""" - - mean_vel: tuple[float, float, float] = MISSING - """Mean velocity for the normal distribution. - - The tuple contains the mean linear-x, linear-y, and angular-z velocity. - """ - std_vel: tuple[float, float, float] = MISSING - """Standard deviation for the normal distribution. - - The tuple contains the standard deviation linear-x, linear-y, and angular-z velocity. - """ - zero_prob: tuple[float, float, float] = MISSING - """Probability of zero velocity for the normal distribution. - - The tuple contains the probability of zero linear-x, linear-y, and angular-z velocity. - """
- - ranges: Ranges = MISSING - """Distribution ranges for the velocity commands."""
- - -
[文档]@configclass -class UniformPoseCommandCfg(CommandTermCfg): - """Configuration for uniform pose command generator.""" - - class_type: type = UniformPoseCommand - - asset_name: str = MISSING - """Name of the asset in the environment for which the commands are generated.""" - body_name: str = MISSING - """Name of the body in the asset for which the commands are generated.""" - - make_quat_unique: bool = False - """Whether to make the quaternion unique or not. Defaults to False. - - If True, the quaternion is made unique by ensuring the real part is positive. - """ - -
[文档] @configclass - class Ranges: - """Uniform distribution ranges for the pose commands.""" - - pos_x: tuple[float, float] = MISSING # min max [m] - pos_y: tuple[float, float] = MISSING # min max [m] - pos_z: tuple[float, float] = MISSING # min max [m] - roll: tuple[float, float] = MISSING # min max [rad] - pitch: tuple[float, float] = MISSING # min max [rad] - yaw: tuple[float, float] = MISSING # min max [rad]
- - ranges: Ranges = MISSING - """Ranges for the commands."""
- - -
[文档]@configclass -class UniformPose2dCommandCfg(CommandTermCfg): - """Configuration for the uniform 2D-pose command generator.""" - - class_type: type = UniformPose2dCommand - - asset_name: str = MISSING - """Name of the asset in the environment for which the commands are generated.""" - - simple_heading: bool = MISSING - """Whether to use simple heading or not. - - If True, the heading is in the direction of the target position. - """ - -
[文档] @configclass - class Ranges: - """Uniform distribution ranges for the position commands.""" - - pos_x: tuple[float, float] = MISSING - """Range for the x position (in m).""" - pos_y: tuple[float, float] = MISSING - """Range for the y position (in m).""" - heading: tuple[float, float] = MISSING - """Heading range for the position commands (in rad). - - Used only if :attr:`simple_heading` is False. - """
- - ranges: Ranges = MISSING - """Distribution ranges for the position commands."""
- - -
[文档]@configclass -class TerrainBasedPose2dCommandCfg(UniformPose2dCommandCfg): - """Configuration for the terrain-based position command generator.""" - - class_type = TerrainBasedPose2dCommand - -
[文档] @configclass - class Ranges: - """Uniform distribution ranges for the position commands.""" - - heading: tuple[float, float] = MISSING - """Heading range for the position commands (in rad). - - Used only if :attr:`simple_heading` is False. - """
- - ranges: Ranges = MISSING - """Distribution ranges for the sampled commands."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/curriculums.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/curriculums.html deleted file mode 100644 index da07913e05..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/curriculums.html +++ /dev/null @@ -1,599 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.curriculums — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.curriculums 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Common functions that can be used to create curriculum for the learning environment.
-
-The functions can be passed to the :class:`omni.isaac.lab.managers.CurriculumTermCfg` object to enable
-the curriculum introduced by the function.
-"""
-
-from __future__ import annotations
-
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-
-
[文档]def modify_reward_weight(env: ManagerBasedRLEnv, env_ids: Sequence[int], term_name: str, weight: float, num_steps: int): - """Curriculum that modifies a reward weight a given number of steps. - - Args: - env: The learning environment. - env_ids: Not used since all environments are affected. - term_name: The name of the reward term. - weight: The weight of the reward term. - num_steps: The number of steps after which the change should be applied. - """ - if env.common_step_counter > num_steps: - # obtain term settings - term_cfg = env.reward_manager.get_term_cfg(term_name) - # update term settings - term_cfg.weight = weight - env.reward_manager.set_term_cfg(term_name, term_cfg)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/events.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/events.html deleted file mode 100644 index d652089397..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/events.html +++ /dev/null @@ -1,1476 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.events — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.events 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Common functions that can be used to enable different events.
-
-Events include anything related to altering the simulation state. This includes changing the physics
-materials, applying external forces, and resetting the state of the asset.
-
-The functions can be passed to the :class:`omni.isaac.lab.managers.EventTermCfg` object to enable
-the event introduced by the function.
-"""
-
-from __future__ import annotations
-
-import torch
-import warnings
-from typing import TYPE_CHECKING, Literal
-
-import carb
-import omni.physics.tensors.impl.api as physx
-
-import omni.isaac.lab.sim as sim_utils
-import omni.isaac.lab.utils.math as math_utils
-from omni.isaac.lab.actuators import ImplicitActuator
-from omni.isaac.lab.assets import Articulation, RigidObject
-from omni.isaac.lab.managers import SceneEntityCfg
-from omni.isaac.lab.terrains import TerrainImporter
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedEnv
-
-
-
[文档]def randomize_rigid_body_material( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - static_friction_range: tuple[float, float], - dynamic_friction_range: tuple[float, float], - restitution_range: tuple[float, float], - num_buckets: int, - asset_cfg: SceneEntityCfg, -): - """Randomize the physics materials on all geometries of the asset. - - This function creates a set of physics materials with random static friction, dynamic friction, and restitution - values. The number of materials is specified by ``num_buckets``. The materials are generated by sampling - uniform random values from the given ranges. - - The material properties are then assigned to the geometries of the asset. The assignment is done by - creating a random integer tensor of shape (num_instances, max_num_shapes) where ``num_instances`` - is the number of assets spawned and ``max_num_shapes`` is the maximum number of shapes in the asset (over - all bodies). The integer values are used as indices to select the material properties from the - material buckets. - - .. attention:: - This function uses CPU tensors to assign the material properties. It is recommended to use this function - only during the initialization of the environment. Otherwise, it may lead to a significant performance - overhead. - - .. note:: - PhysX only allows 64000 unique physics materials in the scene. If the number of materials exceeds this - limit, the simulation will crash. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - - if not isinstance(asset, (RigidObject, Articulation)): - raise ValueError( - f"Randomization term 'randomize_rigid_body_material' not supported for asset: '{asset_cfg.name}'" - f" with type: '{type(asset)}'." - ) - - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device="cpu") - else: - env_ids = env_ids.cpu() - - # retrieve material buffer - materials = asset.root_physx_view.get_material_properties() - - # sample material properties from the given ranges - material_samples = torch.zeros(materials[env_ids].shape) - material_samples[..., 0].uniform_(*static_friction_range) - material_samples[..., 1].uniform_(*dynamic_friction_range) - material_samples[..., 2].uniform_(*restitution_range) - - # create uniform range tensor for bucketing - lo = torch.tensor([static_friction_range[0], dynamic_friction_range[0], restitution_range[0]], device="cpu") - hi = torch.tensor([static_friction_range[1], dynamic_friction_range[1], restitution_range[1]], device="cpu") - - # to avoid 64k material limit in physx, we bucket materials by binning randomized material properties - # into buckets based on the number of buckets specified - for d in range(3): - buckets = torch.tensor([(hi[d] - lo[d]) * i / num_buckets + lo[d] for i in range(num_buckets)], device="cpu") - material_samples[..., d] = buckets[torch.searchsorted(buckets, material_samples[..., d].contiguous()) - 1] - - # update material buffer with new samples - if isinstance(asset, Articulation) and asset_cfg.body_ids != slice(None): - # obtain number of shapes per body (needed for indexing the material properties correctly) - # note: this is a workaround since the Articulation does not provide a direct way to obtain the number of shapes - # per body. We use the physics simulation view to obtain the number of shapes per body. - num_shapes_per_body = [] - for link_path in asset.root_physx_view.link_paths[0]: - link_physx_view = asset._physics_sim_view.create_rigid_body_view(link_path) # type: ignore - num_shapes_per_body.append(link_physx_view.max_shapes) - - # sample material properties from the given ranges - for body_id in asset_cfg.body_ids: - # start index of shape - start_idx = sum(num_shapes_per_body[:body_id]) - # end index of shape - end_idx = start_idx + num_shapes_per_body[body_id] - # assign the new materials - # material ids are of shape: num_env_ids x num_shapes - # material_buckets are of shape: num_buckets x 3 - materials[env_ids, start_idx:end_idx] = material_samples[:, start_idx:end_idx] - else: - materials[env_ids] = material_samples - - # apply to simulation - asset.root_physx_view.set_material_properties(materials, env_ids)
- - -
[文档]def add_body_mass( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - mass_distribution_params: tuple[float, float], - asset_cfg: SceneEntityCfg, -): - """Randomize the mass of the bodies by adding a random value sampled from the given range. - - .. tip:: - This function uses CPU tensors to assign the body masses. It is recommended to use this function - only during the initialization of the environment. - - .. deprecated:: v0.4 - This function is deprecated. Please use :func:`randomize_rigid_body_mass` with ``operation="add"`` instead. - - """ - msg = "Event term 'add_body_mass' is deprecated. Please use 'randomize_rigid_body_mass' with operation='add'." - warnings.warn(msg, DeprecationWarning) - carb.log_warn(msg) - - # call the new function - randomize_rigid_body_mass( - env, env_ids, asset_cfg, mass_distribution_params, operation="add", distribution="uniform" - )
- - -
[文档]def randomize_rigid_body_mass( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg, - mass_distribution_params: tuple[float, float], - operation: Literal["add", "scale", "abs"], - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", -): - """Randomize the mass of the bodies by adding, scaling, or setting random values. - - This function allows randomizing the mass of the bodies of the asset. The function samples random values from the - given distribution parameters and adds, scales, or sets the values into the physics simulation based on the operation. - - .. tip:: - This function uses CPU tensors to assign the body masses. It is recommended to use this function - only during the initialization of the environment. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device="cpu") - else: - env_ids = env_ids.cpu() - - # resolve body indices - if asset_cfg.body_ids == slice(None): - body_ids = torch.arange(asset.num_bodies, dtype=torch.int, device="cpu") - else: - body_ids = torch.tensor(asset_cfg.body_ids, dtype=torch.int, device="cpu") - - # get the current masses of the bodies (num_assets, num_bodies) - masses = asset.root_physx_view.get_masses() - # apply randomization on default values - masses[env_ids[:, None], body_ids] = asset.data.default_mass[env_ids[:, None], body_ids].clone() - - # sample from the given range - # note: we modify the masses in-place for all environments - # however, the setter takes care that only the masses of the specified environments are modified - masses = _randomize_prop_by_op( - masses, mass_distribution_params, env_ids, body_ids, operation=operation, distribution=distribution - ) - - # set the mass into the physics simulation - asset.root_physx_view.set_masses(masses, env_ids)
- - -
[文档]def randomize_physics_scene_gravity( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - gravity_distribution_params: tuple[list[float], list[float]], - operation: Literal["add", "scale", "abs"], - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", -): - """Randomize gravity by adding, scaling, or setting random values. - - This function allows randomizing gravity of the physics scene. The function samples random values from the - given distribution parameters and adds, scales, or sets the values into the physics simulation based on the - operation. - - The distribution parameters are lists of two elements each, representing the lower and upper bounds of the - distribution for the x, y, and z components of the gravity vector. The function samples random values for each - component independently. - - .. attention:: - This function applied the same gravity for all the environments. - - .. tip:: - This function uses CPU tensors to assign gravity. - """ - # get the current gravity - gravity = torch.tensor(env.sim.cfg.gravity, device="cpu").unsqueeze(0) - dist_param_0 = torch.tensor(gravity_distribution_params[0], device="cpu") - dist_param_1 = torch.tensor(gravity_distribution_params[1], device="cpu") - gravity = _randomize_prop_by_op( - gravity, - (dist_param_0, dist_param_1), - None, - slice(None), - operation=operation, - distribution=distribution, - ) - # unbatch the gravity tensor into a list - gravity = gravity[0].tolist() - - # set the gravity into the physics simulation - physics_sim_view: physx.SimulationView = sim_utils.SimulationContext.instance().physics_sim_view - physics_sim_view.set_gravity(carb.Float3(*gravity))
- - -
[文档]def randomize_actuator_gains( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg, - stiffness_distribution_params: tuple[float, float] | None = None, - damping_distribution_params: tuple[float, float] | None = None, - operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", -): - """Randomize the actuator gains in an articulation by adding, scaling, or setting random values. - - This function allows randomizing the actuator stiffness and damping gains. - - The function samples random values from the given distribution parameters and applies the operation to the joint properties. - It then sets the values into the actuator models. If the distribution parameters are not provided for a particular property, - the function does not modify the property. - - .. tip:: - For implicit actuators, this function uses CPU tensors to assign the actuator gains into the simulation. - In such cases, it is recommended to use this function only during the initialization of the environment. - - Raises: - NotImplementedError: If the joint indices are in explicit motor mode. This operation is currently - not supported for explicit actuator models. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device=asset.device) - - # resolve joint indices - if asset_cfg.joint_ids == slice(None): - joint_ids_list = range(asset.num_joints) - joint_ids = slice(None) # for optimization purposes - else: - joint_ids_list = asset_cfg.joint_ids - joint_ids = torch.tensor(asset_cfg.joint_ids, dtype=torch.int, device=asset.device) - - # check if none of the joint indices are in explicit motor mode - for joint_index in joint_ids_list: - for act_name, actuator in asset.actuators.items(): - # if joint indices are a slice (i.e., all joints are captured) or the joint index is in the actuator - if actuator.joint_indices == slice(None) or joint_index in actuator.joint_indices: - if not isinstance(actuator, ImplicitActuator): - raise NotImplementedError( - "Event term 'randomize_actuator_stiffness_and_damping' is performed on asset" - f" '{asset_cfg.name}' on the joint '{asset.joint_names[joint_index]}' ('{joint_index}') which" - f" uses an explicit actuator model '{act_name}<{actuator.__class__.__name__}>'. This operation" - " is currently not supported for explicit actuator models." - ) - - # sample joint properties from the given ranges and set into the physics simulation - # -- stiffness - if stiffness_distribution_params is not None: - stiffness = asset.data.default_joint_stiffness.to(asset.device).clone() - stiffness = _randomize_prop_by_op( - stiffness, stiffness_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution - )[env_ids][:, joint_ids] - asset.write_joint_stiffness_to_sim(stiffness, joint_ids=joint_ids, env_ids=env_ids) - # -- damping - if damping_distribution_params is not None: - damping = asset.data.default_joint_damping.to(asset.device).clone() - damping = _randomize_prop_by_op( - damping, damping_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution - )[env_ids][:, joint_ids] - asset.write_joint_damping_to_sim(damping, joint_ids=joint_ids, env_ids=env_ids)
- - -
[文档]def randomize_joint_parameters( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg, - friction_distribution_params: tuple[float, float] | None = None, - armature_distribution_params: tuple[float, float] | None = None, - lower_limit_distribution_params: tuple[float, float] | None = None, - upper_limit_distribution_params: tuple[float, float] | None = None, - operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", -): - """Randomize the joint parameters of an articulation by adding, scaling, or setting random values. - - This function allows randomizing the joint parameters of the asset. - These correspond to the physics engine joint properties that affect the joint behavior. - - The function samples random values from the given distribution parameters and applies the operation to the joint properties. - It then sets the values into the physics simulation. If the distribution parameters are not provided for a - particular property, the function does not modify the property. - - .. tip:: - This function uses CPU tensors to assign the joint properties. It is recommended to use this function - only during the initialization of the environment. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device=asset.device) - - # resolve joint indices - if asset_cfg.joint_ids == slice(None): - joint_ids = slice(None) # for optimization purposes - else: - joint_ids = torch.tensor(asset_cfg.joint_ids, dtype=torch.int, device=asset.device) - - # sample joint properties from the given ranges and set into the physics simulation - # -- friction - if friction_distribution_params is not None: - friction = asset.data.default_joint_friction.to(asset.device).clone() - friction = _randomize_prop_by_op( - friction, friction_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution - )[env_ids][:, joint_ids] - asset.write_joint_friction_to_sim(friction, joint_ids=joint_ids, env_ids=env_ids) - # -- armature - if armature_distribution_params is not None: - armature = asset.data.default_joint_armature.to(asset.device).clone() - armature = _randomize_prop_by_op( - armature, armature_distribution_params, env_ids, joint_ids, operation=operation, distribution=distribution - )[env_ids][:, joint_ids] - asset.write_joint_armature_to_sim(armature, joint_ids=joint_ids, env_ids=env_ids) - # -- dof limits - if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: - dof_limits = asset.data.default_joint_limits.to(asset.device).clone() - if lower_limit_distribution_params is not None: - lower_limits = dof_limits[..., 0] - lower_limits = _randomize_prop_by_op( - lower_limits, - lower_limit_distribution_params, - env_ids, - joint_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, joint_ids] - dof_limits[env_ids[:, None], joint_ids, 0] = lower_limits - if upper_limit_distribution_params is not None: - upper_limits = dof_limits[..., 1] - upper_limits = _randomize_prop_by_op( - upper_limits, - upper_limit_distribution_params, - env_ids, - joint_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, joint_ids] - dof_limits[env_ids[:, None], joint_ids, 1] = upper_limits - if (dof_limits[env_ids[:, None], joint_ids, 0] > dof_limits[env_ids[:, None], joint_ids, 1]).any(): - raise ValueError( - "Randomization term 'randomize_joint_parameters' is setting lower joint limits that are greater than" - " upper joint limits." - ) - - asset.write_joint_limits_to_sim(dof_limits[env_ids][:, joint_ids], joint_ids=joint_ids, env_ids=env_ids)
- - -
[文档]def randomize_fixed_tendon_parameters( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg, - stiffness_distribution_params: tuple[float, float] | None = None, - damping_distribution_params: tuple[float, float] | None = None, - limit_stiffness_distribution_params: tuple[float, float] | None = None, - lower_limit_distribution_params: tuple[float, float] | None = None, - upper_limit_distribution_params: tuple[float, float] | None = None, - rest_length_distribution_params: tuple[float, float] | None = None, - offset_distribution_params: tuple[float, float] | None = None, - operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", -): - """Randomize the fixed tendon parameters of an articulation by adding, scaling, or setting random values. - - This function allows randomizing the fixed tendon parameters of the asset. - These correspond to the physics engine tendon properties that affect the joint behavior. - - The function samples random values from the given distribution parameters and applies the operation to the tendon properties. - It then sets the values into the physics simulation. If the distribution parameters are not provided for a - particular property, the function does not modify the property. - - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device=asset.device) - - # resolve joint indices - if asset_cfg.fixed_tendon_ids == slice(None): - fixed_tendon_ids = slice(None) # for optimization purposes - else: - fixed_tendon_ids = torch.tensor(asset_cfg.fixed_tendon_ids, dtype=torch.int, device=asset.device) - - # sample tendon properties from the given ranges and set into the physics simulation - # -- stiffness - if stiffness_distribution_params is not None: - stiffness = asset.data.default_fixed_tendon_stiffness.clone() - stiffness = _randomize_prop_by_op( - stiffness, - stiffness_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - asset.set_fixed_tendon_stiffness(stiffness, fixed_tendon_ids, env_ids) - # -- damping - if damping_distribution_params is not None: - damping = asset.data.default_fixed_tendon_damping.clone() - damping = _randomize_prop_by_op( - damping, - damping_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - asset.set_fixed_tendon_damping(damping, fixed_tendon_ids, env_ids) - # -- limit stiffness - if limit_stiffness_distribution_params is not None: - limit_stiffness = asset.data.default_fixed_tendon_limit_stiffness.clone() - limit_stiffness = _randomize_prop_by_op( - limit_stiffness, - limit_stiffness_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - asset.set_fixed_tendon_limit_stiffness(limit_stiffness, fixed_tendon_ids, env_ids) - # -- limits - if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: - limit = asset.data.default_fixed_tendon_limit.clone() - # -- lower limit - if lower_limit_distribution_params is not None: - lower_limit = limit[..., 0] - lower_limit = _randomize_prop_by_op( - lower_limit, - lower_limit_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - limit[env_ids[:, None], fixed_tendon_ids, 0] = lower_limit - # -- upper limit - if upper_limit_distribution_params is not None: - upper_limit = limit[..., 1] - upper_limit = _randomize_prop_by_op( - upper_limit, - upper_limit_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - limit[env_ids[:, None], fixed_tendon_ids, 1] = upper_limit - if (limit[env_ids[:, None], fixed_tendon_ids, 0] > limit[env_ids[:, None], fixed_tendon_ids, 1]).any(): - raise ValueError( - "Randomization term 'randomize_fixed_tendon_parameters' is setting lower tendon limits that are greater" - " than upper tendon limits." - ) - asset.set_fixed_tendon_limit(limit, fixed_tendon_ids, env_ids) - # -- rest length - if rest_length_distribution_params is not None: - rest_length = asset.data.default_fixed_tendon_rest_length.clone() - rest_length = _randomize_prop_by_op( - rest_length, - rest_length_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - asset.set_fixed_tendon_rest_length(rest_length, fixed_tendon_ids, env_ids) - # -- offset - if offset_distribution_params is not None: - offset = asset.data.default_fixed_tendon_offset.clone() - offset = _randomize_prop_by_op( - offset, - offset_distribution_params, - env_ids, - fixed_tendon_ids, - operation=operation, - distribution=distribution, - )[env_ids][:, fixed_tendon_ids] - asset.set_fixed_tendon_offset(offset, fixed_tendon_ids, env_ids) - - asset.write_fixed_tendon_properties_to_sim(fixed_tendon_ids, env_ids)
- - -
[文档]def apply_external_force_torque( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - force_range: tuple[float, float], - torque_range: tuple[float, float], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Randomize the external forces and torques applied to the bodies. - - This function creates a set of random forces and torques sampled from the given ranges. The number of forces - and torques is equal to the number of bodies times the number of environments. The forces and torques are - applied to the bodies by calling ``asset.set_external_force_and_torque``. The forces and torques are only - applied when ``asset.write_data_to_sim()`` is called in the environment. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device=asset.device) - # resolve number of bodies - num_bodies = len(asset_cfg.body_ids) if isinstance(asset_cfg.body_ids, list) else asset.num_bodies - - # sample random forces and torques - size = (len(env_ids), num_bodies, 3) - forces = math_utils.sample_uniform(*force_range, size, asset.device) - torques = math_utils.sample_uniform(*torque_range, size, asset.device) - # set the forces and torques into the buffers - # note: these are only applied when you call: `asset.write_data_to_sim()` - asset.set_external_force_and_torque(forces, torques, env_ids=env_ids, body_ids=asset_cfg.body_ids)
- - -
[文档]def push_by_setting_velocity( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - velocity_range: dict[str, tuple[float, float]], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Push the asset by setting the root velocity to a random value within the given ranges. - - This creates an effect similar to pushing the asset with a random impulse that changes the asset's velocity. - It samples the root velocity from the given ranges and sets the velocity into the physics simulation. - - The function takes a dictionary of velocity ranges for each axis and rotation. The keys of the dictionary - are ``x``, ``y``, ``z``, ``roll``, ``pitch``, and ``yaw``. The values are tuples of the form ``(min, max)``. - If the dictionary does not contain a key, the velocity is set to zero for that axis. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - - # velocities - vel_w = asset.data.root_vel_w[env_ids] - # sample random velocities - range_list = [velocity_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]] - ranges = torch.tensor(range_list, device=asset.device) - vel_w[:] = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], vel_w.shape, device=asset.device) - # set the velocities into the physics simulation - asset.write_root_velocity_to_sim(vel_w, env_ids=env_ids)
- - -
[文档]def reset_root_state_uniform( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - pose_range: dict[str, tuple[float, float]], - velocity_range: dict[str, tuple[float, float]], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Reset the asset root state to a random position and velocity uniformly within the given ranges. - - This function randomizes the root position and velocity of the asset. - - * It samples the root position from the given ranges and adds them to the default root position, before setting - them into the physics simulation. - * It samples the root orientation from the given ranges and sets them into the physics simulation. - * It samples the root velocity from the given ranges and sets them into the physics simulation. - - The function takes a dictionary of pose and velocity ranges for each axis and rotation. The keys of the - dictionary are ``x``, ``y``, ``z``, ``roll``, ``pitch``, and ``yaw``. The values are tuples of the form - ``(min, max)``. If the dictionary does not contain a key, the position or velocity is set to zero for that axis. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - # get default root state - root_states = asset.data.default_root_state[env_ids].clone() - - # poses - range_list = [pose_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]] - ranges = torch.tensor(range_list, device=asset.device) - rand_samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 6), device=asset.device) - - positions = root_states[:, 0:3] + env.scene.env_origins[env_ids] + rand_samples[:, 0:3] - orientations_delta = math_utils.quat_from_euler_xyz(rand_samples[:, 3], rand_samples[:, 4], rand_samples[:, 5]) - orientations = math_utils.quat_mul(root_states[:, 3:7], orientations_delta) - # velocities - range_list = [velocity_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]] - ranges = torch.tensor(range_list, device=asset.device) - rand_samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 6), device=asset.device) - - velocities = root_states[:, 7:13] + rand_samples - - # set into the physics simulation - asset.write_root_pose_to_sim(torch.cat([positions, orientations], dim=-1), env_ids=env_ids) - asset.write_root_velocity_to_sim(velocities, env_ids=env_ids)
- - -
[文档]def reset_root_state_with_random_orientation( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - pose_range: dict[str, tuple[float, float]], - velocity_range: dict[str, tuple[float, float]], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Reset the asset root position and velocities sampled randomly within the given ranges - and the asset root orientation sampled randomly from the SO(3). - - This function randomizes the root position and velocity of the asset. - - * It samples the root position from the given ranges and adds them to the default root position, before setting - them into the physics simulation. - * It samples the root orientation uniformly from the SO(3) and sets them into the physics simulation. - * It samples the root velocity from the given ranges and sets them into the physics simulation. - - The function takes a dictionary of position and velocity ranges for each axis and rotation: - - * :attr:`pose_range` - a dictionary of position ranges for each axis. The keys of the dictionary are ``x``, - ``y``, and ``z``. The orientation is sampled uniformly from the SO(3). - * :attr:`velocity_range` - a dictionary of velocity ranges for each axis and rotation. The keys of the dictionary - are ``x``, ``y``, ``z``, ``roll``, ``pitch``, and ``yaw``. - - The values are tuples of the form ``(min, max)``. If the dictionary does not contain a particular key, - the position is set to zero for that axis. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - # get default root state - root_states = asset.data.default_root_state[env_ids].clone() - - # poses - range_list = [pose_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z"]] - ranges = torch.tensor(range_list, device=asset.device) - rand_samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 3), device=asset.device) - - positions = root_states[:, 0:3] + env.scene.env_origins[env_ids] + rand_samples - orientations = math_utils.random_orientation(len(env_ids), device=asset.device) - - # velocities - range_list = [velocity_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]] - ranges = torch.tensor(range_list, device=asset.device) - rand_samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 6), device=asset.device) - - velocities = root_states[:, 7:13] + rand_samples - - # set into the physics simulation - asset.write_root_pose_to_sim(torch.cat([positions, orientations], dim=-1), env_ids=env_ids) - asset.write_root_velocity_to_sim(velocities, env_ids=env_ids)
- - -
[文档]def reset_root_state_from_terrain( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - pose_range: dict[str, tuple[float, float]], - velocity_range: dict[str, tuple[float, float]], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Reset the asset root state by sampling a random valid pose from the terrain. - - This function samples a random valid pose(based on flat patches) from the terrain and sets the root state - of the asset to this position. The function also samples random velocities from the given ranges and sets them - into the physics simulation. - - The function takes a dictionary of position and velocity ranges for each axis and rotation: - - * :attr:`pose_range` - a dictionary of pose ranges for each axis. The keys of the dictionary are ``roll``, - ``pitch``, and ``yaw``. The position is sampled from the flat patches of the terrain. - * :attr:`velocity_range` - a dictionary of velocity ranges for each axis and rotation. The keys of the dictionary - are ``x``, ``y``, ``z``, ``roll``, ``pitch``, and ``yaw``. - - The values are tuples of the form ``(min, max)``. If the dictionary does not contain a particular key, - the position is set to zero for that axis. - - Note: - The function expects the terrain to have valid flat patches under the key "init_pos". The flat patches - are used to sample the random pose for the robot. - - Raises: - ValueError: If the terrain does not have valid flat patches under the key "init_pos". - """ - # access the used quantities (to enable type-hinting) - asset: RigidObject | Articulation = env.scene[asset_cfg.name] - terrain: TerrainImporter = env.scene.terrain - - # obtain all flat patches corresponding to the valid poses - valid_positions: torch.Tensor = terrain.flat_patches.get("init_pos") - if valid_positions is None: - raise ValueError( - "The event term 'reset_root_state_from_terrain' requires valid flat patches under 'init_pos'." - f" Found: {list(terrain.flat_patches.keys())}" - ) - - # sample random valid poses - ids = torch.randint(0, valid_positions.shape[2], size=(len(env_ids),), device=env.device) - positions = valid_positions[terrain.terrain_levels[env_ids], terrain.terrain_types[env_ids], ids] - positions += asset.data.default_root_state[env_ids, :3] - - # sample random orientations - range_list = [pose_range.get(key, (0.0, 0.0)) for key in ["roll", "pitch", "yaw"]] - ranges = torch.tensor(range_list, device=asset.device) - rand_samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 3), device=asset.device) - - # convert to quaternions - orientations = math_utils.quat_from_euler_xyz(rand_samples[:, 0], rand_samples[:, 1], rand_samples[:, 2]) - - # sample random velocities - range_list = [velocity_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]] - ranges = torch.tensor(range_list, device=asset.device) - rand_samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 6), device=asset.device) - - velocities = asset.data.default_root_state[:, 7:13] + rand_samples - - # set into the physics simulation - asset.write_root_pose_to_sim(torch.cat([positions, orientations], dim=-1), env_ids=env_ids) - asset.write_root_velocity_to_sim(velocities, env_ids=env_ids)
- - -
[文档]def reset_joints_by_scale( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - position_range: tuple[float, float], - velocity_range: tuple[float, float], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Reset the robot joints by scaling the default position and velocity by the given ranges. - - This function samples random values from the given ranges and scales the default joint positions and velocities - by these values. The scaled values are then set into the physics simulation. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # get default joint state - joint_pos = asset.data.default_joint_pos[env_ids].clone() - joint_vel = asset.data.default_joint_vel[env_ids].clone() - - # scale these values randomly - joint_pos *= math_utils.sample_uniform(*position_range, joint_pos.shape, joint_pos.device) - joint_vel *= math_utils.sample_uniform(*velocity_range, joint_vel.shape, joint_vel.device) - - # clamp joint pos to limits - joint_pos_limits = asset.data.soft_joint_pos_limits[env_ids] - joint_pos = joint_pos.clamp_(joint_pos_limits[..., 0], joint_pos_limits[..., 1]) - # clamp joint vel to limits - joint_vel_limits = asset.data.soft_joint_vel_limits[env_ids] - joint_vel = joint_vel.clamp_(-joint_vel_limits, joint_vel_limits) - - # set into the physics simulation - asset.write_joint_state_to_sim(joint_pos, joint_vel, env_ids=env_ids)
- - -
[文档]def reset_joints_by_offset( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - position_range: tuple[float, float], - velocity_range: tuple[float, float], - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -): - """Reset the robot joints with offsets around the default position and velocity by the given ranges. - - This function samples random values from the given ranges and biases the default joint positions and velocities - by these values. The biased values are then set into the physics simulation. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - - # get default joint state - joint_pos = asset.data.default_joint_pos[env_ids].clone() - joint_vel = asset.data.default_joint_vel[env_ids].clone() - - # bias these values randomly - joint_pos += math_utils.sample_uniform(*position_range, joint_pos.shape, joint_pos.device) - joint_vel += math_utils.sample_uniform(*velocity_range, joint_vel.shape, joint_vel.device) - - # clamp joint pos to limits - joint_pos_limits = asset.data.soft_joint_pos_limits[env_ids] - joint_pos = joint_pos.clamp_(joint_pos_limits[..., 0], joint_pos_limits[..., 1]) - # clamp joint vel to limits - joint_vel_limits = asset.data.soft_joint_vel_limits[env_ids] - joint_vel = joint_vel.clamp_(-joint_vel_limits, joint_vel_limits) - - # set into the physics simulation - asset.write_joint_state_to_sim(joint_pos, joint_vel, env_ids=env_ids)
- - -
[文档]def reset_scene_to_default(env: ManagerBasedEnv, env_ids: torch.Tensor): - """Reset the scene to the default state specified in the scene configuration.""" - # rigid bodies - for rigid_object in env.scene.rigid_objects.values(): - # obtain default and deal with the offset for env origins - default_root_state = rigid_object.data.default_root_state[env_ids].clone() - default_root_state[:, 0:3] += env.scene.env_origins[env_ids] - # set into the physics simulation - rigid_object.write_root_state_to_sim(default_root_state, env_ids=env_ids) - # articulations - for articulation_asset in env.scene.articulations.values(): - # obtain default and deal with the offset for env origins - default_root_state = articulation_asset.data.default_root_state[env_ids].clone() - default_root_state[:, 0:3] += env.scene.env_origins[env_ids] - # set into the physics simulation - articulation_asset.write_root_state_to_sim(default_root_state, env_ids=env_ids) - # obtain default joint positions - default_joint_pos = articulation_asset.data.default_joint_pos[env_ids].clone() - default_joint_vel = articulation_asset.data.default_joint_vel[env_ids].clone() - # set into the physics simulation - articulation_asset.write_joint_state_to_sim(default_joint_pos, default_joint_vel, env_ids=env_ids)
- - -""" -Internal helper functions. -""" - - -def _randomize_prop_by_op( - data: torch.Tensor, - distribution_parameters: tuple[float | torch.Tensor, float | torch.Tensor], - dim_0_ids: torch.Tensor | None, - dim_1_ids: torch.Tensor | slice, - operation: Literal["add", "scale", "abs"], - distribution: Literal["uniform", "log_uniform", "gaussian"], -) -> torch.Tensor: - """Perform data randomization based on the given operation and distribution. - - Args: - data: The data tensor to be randomized. Shape is (dim_0, dim_1). - distribution_parameters: The parameters for the distribution to sample values from. - dim_0_ids: The indices of the first dimension to randomize. - dim_1_ids: The indices of the second dimension to randomize. - operation: The operation to perform on the data. Options: 'add', 'scale', 'abs'. - distribution: The distribution to sample the random values from. Options: 'uniform', 'log_uniform'. - - Returns: - The data tensor after randomization. Shape is (dim_0, dim_1). - - Raises: - NotImplementedError: If the operation or distribution is not supported. - """ - # resolve shape - # -- dim 0 - if dim_0_ids is None: - n_dim_0 = data.shape[0] - dim_0_ids = slice(None) - else: - n_dim_0 = len(dim_0_ids) - dim_0_ids = dim_0_ids[:, None] - # -- dim 1 - if isinstance(dim_1_ids, slice): - n_dim_1 = data.shape[1] - else: - n_dim_1 = len(dim_1_ids) - - # resolve the distribution - if distribution == "uniform": - dist_fn = math_utils.sample_uniform - elif distribution == "log_uniform": - dist_fn = math_utils.sample_log_uniform - elif distribution == "gaussian": - dist_fn = math_utils.sample_gaussian - else: - raise NotImplementedError( - f"Unknown distribution: '{distribution}' for joint properties randomization." - " Please use 'uniform', 'log_uniform', 'gaussian'." - ) - # perform the operation - if operation == "add": - data[dim_0_ids, dim_1_ids] += dist_fn(*distribution_parameters, (n_dim_0, n_dim_1), device=data.device) - elif operation == "scale": - data[dim_0_ids, dim_1_ids] *= dist_fn(*distribution_parameters, (n_dim_0, n_dim_1), device=data.device) - elif operation == "abs": - data[dim_0_ids, dim_1_ids] = dist_fn(*distribution_parameters, (n_dim_0, n_dim_1), device=data.device) - else: - raise NotImplementedError( - f"Unknown operation: '{operation}' for property randomization. Please use 'add', 'scale', or 'abs'." - ) - return data -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/observations.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/observations.html deleted file mode 100644 index fb3e752856..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/observations.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.observations — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.observations 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Common functions that can be used to create observation terms.
-
-The functions can be passed to the :class:`omni.isaac.lab.managers.ObservationTermCfg` object to enable
-the observation introduced by the function.
-"""
-
-from __future__ import annotations
-
-import torch
-from typing import TYPE_CHECKING
-
-import omni.isaac.lab.utils.math as math_utils
-from omni.isaac.lab.assets import Articulation, RigidObject
-from omni.isaac.lab.managers import SceneEntityCfg
-from omni.isaac.lab.sensors import RayCaster
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedEnv, ManagerBasedRLEnv
-
-"""
-Root state.
-"""
-
-
-
[文档]def base_pos_z(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Root height in the simulation world frame.""" - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return asset.data.root_pos_w[:, 2].unsqueeze(-1)
- - -
[文档]def base_lin_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Root linear velocity in the asset's root frame.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.root_lin_vel_b
- - -
[文档]def base_ang_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Root angular velocity in the asset's root frame.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.root_ang_vel_b
- - -
[文档]def projected_gravity(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Gravity projection on the asset's root frame.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.projected_gravity_b
- - -
[文档]def root_pos_w(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Asset root position in the environment frame.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.root_pos_w - env.scene.env_origins
- - -
[文档]def root_quat_w( - env: ManagerBasedEnv, make_quat_unique: bool = False, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Asset root orientation (w, x, y, z) in the environment frame. - - If :attr:`make_quat_unique` is True, then returned quaternion is made unique by ensuring - the quaternion has non-negative real component. This is because both ``q`` and ``-q`` represent - the same orientation. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - - quat = asset.data.root_quat_w - # make the quaternion real-part positive if configured - return math_utils.quat_unique(quat) if make_quat_unique else quat
- - -
[文档]def root_lin_vel_w(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Asset root linear velocity in the environment frame.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.root_lin_vel_w
- - -
[文档]def root_ang_vel_w(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Asset root angular velocity in the environment frame.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.root_ang_vel_w
- - -""" -Joint state. -""" - - -
[文档]def joint_pos(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """The joint positions of the asset. - - Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their positions returned. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return asset.data.joint_pos[:, asset_cfg.joint_ids]
- - -
[文档]def joint_pos_rel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """The joint positions of the asset w.r.t. the default joint positions. - - Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their positions returned. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return asset.data.joint_pos[:, asset_cfg.joint_ids] - asset.data.default_joint_pos[:, asset_cfg.joint_ids]
- - -
[文档]def joint_pos_limit_normalized( - env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """The joint positions of the asset normalized with the asset's joint limits. - - Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their normalized positions returned. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return math_utils.scale_transform( - asset.data.joint_pos[:, asset_cfg.joint_ids], - asset.data.soft_joint_pos_limits[:, asset_cfg.joint_ids, 0], - asset.data.soft_joint_pos_limits[:, asset_cfg.joint_ids, 1], - )
- - -
[文档]def joint_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")): - """The joint velocities of the asset. - - Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their velocities returned. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return asset.data.joint_vel[:, asset_cfg.joint_ids]
- - -
[文档]def joint_vel_rel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")): - """The joint velocities of the asset w.r.t. the default joint velocities. - - Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their velocities returned. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return asset.data.joint_vel[:, asset_cfg.joint_ids] - asset.data.default_joint_vel[:, asset_cfg.joint_ids]
- - -""" -Sensors. -""" - - -
[文档]def height_scan(env: ManagerBasedEnv, sensor_cfg: SceneEntityCfg, offset: float = 0.5) -> torch.Tensor: - """Height scan from the given sensor w.r.t. the sensor's frame. - - The provided offset (Defaults to 0.5) is subtracted from the returned values. - """ - # extract the used quantities (to enable type-hinting) - sensor: RayCaster = env.scene.sensors[sensor_cfg.name] - # height scan: height = sensor_height - hit_point_z - offset - return sensor.data.pos_w[:, 2].unsqueeze(1) - sensor.data.ray_hits_w[..., 2] - offset
- - -
[文档]def body_incoming_wrench(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: - """Incoming spatial wrench on bodies of an articulation in the simulation world frame. - - This is the 6-D wrench (force and torque) applied to the body link by the incoming joint force. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # obtain the link incoming forces in world frame - link_incoming_forces = asset.root_physx_view.get_link_incoming_joint_force()[:, asset_cfg.body_ids] - return link_incoming_forces.view(env.num_envs, -1)
- - -""" -Actions. -""" - - -
[文档]def last_action(env: ManagerBasedEnv, action_name: str | None = None) -> torch.Tensor: - """The last input action to the environment. - - The name of the action term for which the action is required. If None, the - entire action tensor is returned. - """ - if action_name is None: - return env.action_manager.action - else: - return env.action_manager.get_term(action_name).raw_actions
- - -""" -Commands. -""" - - -
[文档]def generated_commands(env: ManagerBasedRLEnv, command_name: str) -> torch.Tensor: - """The generated command from command term in the command manager with the given name.""" - return env.command_manager.get_command(command_name)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/rewards.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/rewards.html deleted file mode 100644 index 53e08e2be3..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/rewards.html +++ /dev/null @@ -1,861 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.rewards — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.rewards 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Common functions that can be used to enable reward functions.
-
-The functions can be passed to the :class:`omni.isaac.lab.managers.RewardTermCfg` object to include
-the reward introduced by the function.
-"""
-
-from __future__ import annotations
-
-import torch
-from typing import TYPE_CHECKING
-
-from omni.isaac.lab.assets import Articulation, RigidObject
-from omni.isaac.lab.managers import SceneEntityCfg
-from omni.isaac.lab.managers.manager_base import ManagerTermBase
-from omni.isaac.lab.managers.manager_term_cfg import RewardTermCfg
-from omni.isaac.lab.sensors import ContactSensor
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-"""
-General.
-"""
-
-
-
[文档]def is_alive(env: ManagerBasedRLEnv) -> torch.Tensor: - """Reward for being alive.""" - return (~env.termination_manager.terminated).float()
- - -
[文档]def is_terminated(env: ManagerBasedRLEnv) -> torch.Tensor: - """Penalize terminated episodes that don't correspond to episodic timeouts.""" - return env.termination_manager.terminated.float()
- - -
[文档]class is_terminated_term(ManagerTermBase): - """Penalize termination for specific terms that don't correspond to episodic timeouts. - - The parameters are as follows: - - * attr:`term_keys`: The termination terms to penalize. This can be a string, a list of strings - or regular expressions. Default is ".*" which penalizes all terminations. - - The reward is computed as the sum of the termination terms that are not episodic timeouts. - This means that the reward is 0 if the episode is terminated due to an episodic timeout. Otherwise, - if two termination terms are active, the reward is 2. - """ - -
[文档] def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): - # initialize the base class - super().__init__(cfg, env) - # find and store the termination terms - term_keys = cfg.params.get("term_keys", ".*") - self._term_names = env.termination_manager.find_terms(term_keys)
- - def __call__(self, env: ManagerBasedRLEnv, term_keys: str | list[str] = ".*") -> torch.Tensor: - # Return the unweighted reward for the termination terms - reset_buf = torch.zeros(env.num_envs, device=env.device) - for term in self._term_names: - # Sums over terminations term values to account for multiple terminations in the same step - reset_buf += env.termination_manager.get_term(term) - - return (reset_buf * (~env.termination_manager.time_outs)).float()
- - -""" -Root penalties. -""" - - -
[文档]def lin_vel_z_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize z-axis base linear velocity using L2-kernel.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return torch.square(asset.data.root_lin_vel_b[:, 2])
- - -
[文档]def ang_vel_xy_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize xy-axis base angular velocity using L2-kernel.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return torch.sum(torch.square(asset.data.root_ang_vel_b[:, :2]), dim=1)
- - -
[文档]def flat_orientation_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize non-flat base orientation using L2-kernel. - - This is computed by penalizing the xy-components of the projected gravity vector. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return torch.sum(torch.square(asset.data.projected_gravity_b[:, :2]), dim=1)
- - -
[文档]def base_height_l2( - env: ManagerBasedRLEnv, target_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Penalize asset height from its target using L2-kernel. - - Note: - Currently, it assumes a flat terrain, i.e. the target height is in the world frame. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - # TODO: Fix this for rough-terrain. - return torch.square(asset.data.root_pos_w[:, 2] - target_height)
- - -
[文档]def body_lin_acc_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize the linear acceleration of bodies using L2-kernel.""" - asset: Articulation = env.scene[asset_cfg.name] - return torch.sum(torch.norm(asset.data.body_lin_acc_w[:, asset_cfg.body_ids, :], dim=-1), dim=1)
- - -""" -Joint penalties. -""" - - -
[文档]def joint_torques_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize joint torques applied on the articulation using L2-kernel. - - NOTE: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their joint torques contribute to the L2 norm. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return torch.sum(torch.square(asset.data.applied_torque[:, asset_cfg.joint_ids]), dim=1)
- - -
[文档]def joint_vel_l1(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: - """Penalize joint velocities on the articulation using an L1-kernel.""" - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return torch.sum(torch.abs(asset.data.joint_vel[:, asset_cfg.joint_ids]), dim=1)
- - -
[文档]def joint_vel_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize joint velocities on the articulation using L1-kernel. - - NOTE: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their joint velocities contribute to the L1 norm. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return torch.sum(torch.square(asset.data.joint_vel[:, asset_cfg.joint_ids]), dim=1)
- - -
[文档]def joint_acc_l2(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize joint accelerations on the articulation using L2-kernel. - - NOTE: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their joint accelerations contribute to the L2 norm. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - return torch.sum(torch.square(asset.data.joint_acc[:, asset_cfg.joint_ids]), dim=1)
- - -
[文档]def joint_deviation_l1(env, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize joint positions that deviate from the default one.""" - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute out of limits constraints - angle = asset.data.joint_pos[:, asset_cfg.joint_ids] - asset.data.default_joint_pos[:, asset_cfg.joint_ids] - return torch.sum(torch.abs(angle), dim=1)
- - -
[文档]def joint_pos_limits(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize joint positions if they cross the soft limits. - - This is computed as a sum of the absolute value of the difference between the joint position and the soft limits. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute out of limits constraints - out_of_limits = -( - asset.data.joint_pos[:, asset_cfg.joint_ids] - asset.data.soft_joint_pos_limits[:, asset_cfg.joint_ids, 0] - ).clip(max=0.0) - out_of_limits += ( - asset.data.joint_pos[:, asset_cfg.joint_ids] - asset.data.soft_joint_pos_limits[:, asset_cfg.joint_ids, 1] - ).clip(min=0.0) - return torch.sum(out_of_limits, dim=1)
- - -
[文档]def joint_vel_limits( - env: ManagerBasedRLEnv, soft_ratio: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Penalize joint velocities if they cross the soft limits. - - This is computed as a sum of the absolute value of the difference between the joint velocity and the soft limits. - - Args: - soft_ratio: The ratio of the soft limits to be used. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute out of limits constraints - out_of_limits = ( - torch.abs(asset.data.joint_vel[:, asset_cfg.joint_ids]) - - asset.data.soft_joint_vel_limits[:, asset_cfg.joint_ids] * soft_ratio - ) - # clip to max error = 1 rad/s per joint to avoid huge penalties - out_of_limits = out_of_limits.clip_(min=0.0, max=1.0) - return torch.sum(out_of_limits, dim=1)
- - -""" -Action penalties. -""" - - -
[文档]def applied_torque_limits(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Penalize applied torques if they cross the limits. - - This is computed as a sum of the absolute value of the difference between the applied torques and the limits. - - .. caution:: - Currently, this only works for explicit actuators since we manually compute the applied torques. - For implicit actuators, we currently cannot retrieve the applied torques from the physics engine. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute out of limits constraints - # TODO: We need to fix this to support implicit joints. - out_of_limits = torch.abs( - asset.data.applied_torque[:, asset_cfg.joint_ids] - asset.data.computed_torque[:, asset_cfg.joint_ids] - ) - return torch.sum(out_of_limits, dim=1)
- - -
[文档]def action_rate_l2(env: ManagerBasedRLEnv) -> torch.Tensor: - """Penalize the rate of change of the actions using L2-kernel.""" - return torch.sum(torch.square(env.action_manager.action - env.action_manager.prev_action), dim=1)
- - -
[文档]def action_l2(env: ManagerBasedRLEnv) -> torch.Tensor: - """Penalize the actions using L2-kernel.""" - return torch.sum(torch.square(env.action_manager.action), dim=1)
- - -""" -Contact sensor. -""" - - -
[文档]def undesired_contacts(env: ManagerBasedRLEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: - """Penalize undesired contacts as the number of violations that are above a threshold.""" - # extract the used quantities (to enable type-hinting) - contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] - # check if contact force is above threshold - net_contact_forces = contact_sensor.data.net_forces_w_history - is_contact = torch.max(torch.norm(net_contact_forces[:, :, sensor_cfg.body_ids], dim=-1), dim=1)[0] > threshold - # sum over contacts for each environment - return torch.sum(is_contact, dim=1)
- - -
[文档]def contact_forces(env: ManagerBasedRLEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: - """Penalize contact forces as the amount of violations of the net contact force.""" - # extract the used quantities (to enable type-hinting) - contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] - net_contact_forces = contact_sensor.data.net_forces_w_history - # compute the violation - violation = torch.max(torch.norm(net_contact_forces[:, :, sensor_cfg.body_ids], dim=-1), dim=1)[0] - threshold - # compute the penalty - return torch.sum(violation.clip(min=0.0), dim=1)
- - -""" -Velocity-tracking rewards. -""" - - -
[文档]def track_lin_vel_xy_exp( - env: ManagerBasedRLEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Reward tracking of linear velocity commands (xy axes) using exponential kernel.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - # compute the error - lin_vel_error = torch.sum( - torch.square(env.command_manager.get_command(command_name)[:, :2] - asset.data.root_lin_vel_b[:, :2]), - dim=1, - ) - return torch.exp(-lin_vel_error / std**2)
- - -
[文档]def track_ang_vel_z_exp( - env: ManagerBasedRLEnv, std: float, command_name: str, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Reward tracking of angular velocity commands (yaw) using exponential kernel.""" - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - # compute the error - ang_vel_error = torch.square(env.command_manager.get_command(command_name)[:, 2] - asset.data.root_ang_vel_b[:, 2]) - return torch.exp(-ang_vel_error / std**2)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/terminations.html b/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/terminations.html deleted file mode 100644 index ab1f15c1b8..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/mdp/terminations.html +++ /dev/null @@ -1,721 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.mdp.terminations — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.mdp.terminations 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Common functions that can be used to activate certain terminations.
-
-The functions can be passed to the :class:`omni.isaac.lab.managers.TerminationTermCfg` object to enable
-the termination introduced by the function.
-"""
-
-from __future__ import annotations
-
-import torch
-from typing import TYPE_CHECKING
-
-from omni.isaac.lab.assets import Articulation, RigidObject
-from omni.isaac.lab.managers import SceneEntityCfg
-from omni.isaac.lab.sensors import ContactSensor
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-    from omni.isaac.lab.managers.command_manager import CommandTerm
-
-"""
-MDP terminations.
-"""
-
-
-
[文档]def time_out(env: ManagerBasedRLEnv) -> torch.Tensor: - """Terminate the episode when the episode length exceeds the maximum episode length.""" - return env.episode_length_buf >= env.max_episode_length
- - -
[文档]def command_resample(env: ManagerBasedRLEnv, command_name: str, num_resamples: int = 1) -> torch.Tensor: - """Terminate the episode based on the total number of times commands have been re-sampled. - - This makes the maximum episode length fluid in nature as it depends on how the commands are - sampled. It is useful in situations where delayed rewards are used :cite:`rudin2022advanced`. - """ - command: CommandTerm = env.command_manager.get_term(command_name) - return torch.logical_and((command.time_left <= env.step_dt), (command.command_counter == num_resamples))
- - -""" -Root terminations. -""" - - -
[文档]def bad_orientation( - env: ManagerBasedRLEnv, limit_angle: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Terminate when the asset's orientation is too far from the desired orientation limits. - - This is computed by checking the angle between the projected gravity vector and the z-axis. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return torch.acos(-asset.data.projected_gravity_b[:, 2]).abs() > limit_angle
- - -
[文档]def root_height_below_minimum( - env: ManagerBasedRLEnv, minimum_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Terminate when the asset's root height is below the minimum height. - - Note: - This is currently only supported for flat terrains, i.e. the minimum height is in the world frame. - """ - # extract the used quantities (to enable type-hinting) - asset: RigidObject = env.scene[asset_cfg.name] - return asset.data.root_pos_w[:, 2] < minimum_height
- - -""" -Joint terminations. -""" - - -
[文档]def joint_pos_out_of_limit(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Terminate when the asset's joint positions are outside of the soft joint limits.""" - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute any violations - out_of_upper_limits = torch.any(asset.data.joint_pos > asset.data.soft_joint_pos_limits[..., 1], dim=1) - out_of_lower_limits = torch.any(asset.data.joint_pos < asset.data.soft_joint_pos_limits[..., 0], dim=1) - return torch.logical_or(out_of_upper_limits[:, asset_cfg.joint_ids], out_of_lower_limits[:, asset_cfg.joint_ids])
- - -
[文档]def joint_pos_out_of_manual_limit( - env: ManagerBasedRLEnv, bounds: tuple[float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Terminate when the asset's joint positions are outside of the configured bounds. - - Note: - This function is similar to :func:`joint_pos_out_of_limit` but allows the user to specify the bounds manually. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - if asset_cfg.joint_ids is None: - asset_cfg.joint_ids = slice(None) - # compute any violations - out_of_upper_limits = torch.any(asset.data.joint_pos[:, asset_cfg.joint_ids] > bounds[1], dim=1) - out_of_lower_limits = torch.any(asset.data.joint_pos[:, asset_cfg.joint_ids] < bounds[0], dim=1) - return torch.logical_or(out_of_upper_limits, out_of_lower_limits)
- - -
[文档]def joint_vel_out_of_limit(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: - """Terminate when the asset's joint velocities are outside of the soft joint limits.""" - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute any violations - limits = asset.data.soft_joint_vel_limits - return torch.any(torch.abs(asset.data.joint_vel[:, asset_cfg.joint_ids]) > limits[:, asset_cfg.joint_ids], dim=1)
- - -
[文档]def joint_vel_out_of_manual_limit( - env: ManagerBasedRLEnv, max_velocity: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Terminate when the asset's joint velocities are outside the provided limits.""" - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # compute any violations - return torch.any(torch.abs(asset.data.joint_vel[:, asset_cfg.joint_ids]) > max_velocity, dim=1)
- - -
[文档]def joint_effort_out_of_limit( - env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Terminate when effort applied on the asset's joints are outside of the soft joint limits. - - In the actuators, the applied torque are the efforts applied on the joints. These are computed by clipping - the computed torques to the joint limits. Hence, we check if the computed torques are equal to the applied - torques. - """ - # extract the used quantities (to enable type-hinting) - asset: Articulation = env.scene[asset_cfg.name] - # check if any joint effort is out of limit - out_of_limits = torch.isclose( - asset.data.computed_torque[:, asset_cfg.joint_ids], asset.data.applied_torque[:, asset_cfg.joint_ids] - ) - return torch.any(out_of_limits, dim=1)
- - -""" -Contact sensor. -""" - - -
[文档]def illegal_contact(env: ManagerBasedRLEnv, threshold: float, sensor_cfg: SceneEntityCfg) -> torch.Tensor: - """Terminate when the contact force on the sensor exceeds the force threshold.""" - # extract the used quantities (to enable type-hinting) - contact_sensor: ContactSensor = env.scene.sensors[sensor_cfg.name] - net_contact_forces = contact_sensor.data.net_forces_w_history - # check if any contact force exceeds the threshold - return torch.any( - torch.max(torch.norm(net_contact_forces[:, :, sensor_cfg.body_ids], dim=-1), dim=1)[0] > threshold, dim=1 - )
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/ui/base_env_window.html b/docs/_build/html/_modules/omni/isaac/lab/envs/ui/base_env_window.html deleted file mode 100644 index d7fec056a8..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/ui/base_env_window.html +++ /dev/null @@ -1,966 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.ui.base_env_window — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.ui.base_env_window 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import asyncio
-import os
-import weakref
-from datetime import datetime
-from typing import TYPE_CHECKING
-
-import omni.kit.app
-import omni.kit.commands
-import omni.usd
-from pxr import PhysxSchema, Sdf, Usd, UsdGeom, UsdPhysics
-
-if TYPE_CHECKING:
-    import omni.ui
-
-    from ..manager_based_env import ManagerBasedEnv
-
-
-
[文档]class BaseEnvWindow: - """Window manager for the basic environment. - - This class creates a window that is used to control the environment. The window - contains controls for rendering, debug visualization, and other environment-specific - UI elements. - - Users can add their own UI elements to the window by using the `with` context manager. - This can be done either be inheriting the class or by using the `env.window` object - directly from the standalone execution script. - - Example for adding a UI element from the standalone execution script: - >>> with env.window.ui_window_elements["main_vstack"]: - >>> ui.Label("My UI element") - - """ - -
[文档] def __init__(self, env: ManagerBasedEnv, window_name: str = "IsaacLab"): - """Initialize the window. - - Args: - env: The environment object. - window_name: The name of the window. Defaults to "IsaacLab". - """ - # store inputs - self.env = env - # prepare the list of assets that can be followed by the viewport camera - # note that the first two options are "World" and "Env" which are special cases - self._viewer_assets_options = [ - "World", - "Env", - *self.env.scene.rigid_objects.keys(), - *self.env.scene.articulations.keys(), - ] - - print("Creating window for environment.") - # create window for UI - self.ui_window = omni.ui.Window( - window_name, width=400, height=500, visible=True, dock_preference=omni.ui.DockPreference.RIGHT_TOP - ) - # dock next to properties window - asyncio.ensure_future(self._dock_window(window_title=self.ui_window.title)) - - # keep a dictionary of stacks so that child environments can add their own UI elements - # this can be done by using the `with` context manager - self.ui_window_elements = dict() - # create main frame - self.ui_window_elements["main_frame"] = self.ui_window.frame - with self.ui_window_elements["main_frame"]: - # create main stack - self.ui_window_elements["main_vstack"] = omni.ui.VStack(spacing=5, height=0) - with self.ui_window_elements["main_vstack"]: - # create collapsable frame for simulation - self._build_sim_frame() - # create collapsable frame for viewer - self._build_viewer_frame() - # create collapsable frame for debug visualization - self._build_debug_vis_frame()
- - def __del__(self): - """Destructor for the window.""" - # destroy the window - if self.ui_window is not None: - self.ui_window.visible = False - self.ui_window.destroy() - self.ui_window = None - - """ - Build sub-sections of the UI. - """ - - def _build_sim_frame(self): - """Builds the sim-related controls frame for the UI.""" - # create collapsable frame for controls - self.ui_window_elements["sim_frame"] = omni.ui.CollapsableFrame( - title="Simulation Settings", - width=omni.ui.Fraction(1), - height=0, - collapsed=False, - style=omni.isaac.ui.ui_utils.get_style(), - horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED, - vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON, - ) - with self.ui_window_elements["sim_frame"]: - # create stack for controls - self.ui_window_elements["sim_vstack"] = omni.ui.VStack(spacing=5, height=0) - with self.ui_window_elements["sim_vstack"]: - # create rendering mode dropdown - render_mode_cfg = { - "label": "Rendering Mode", - "type": "dropdown", - "default_val": self.env.sim.render_mode.value, - "items": [member.name for member in self.env.sim.RenderMode if member.value >= 0], - "tooltip": "Select a rendering mode\n" + self.env.sim.RenderMode.__doc__, - "on_clicked_fn": lambda value: self.env.sim.set_render_mode(self.env.sim.RenderMode[value]), - } - self.ui_window_elements["render_dropdown"] = omni.isaac.ui.ui_utils.dropdown_builder(**render_mode_cfg) - - # create animation recording box - record_animate_cfg = { - "label": "Record Animation", - "type": "state_button", - "a_text": "START", - "b_text": "STOP", - "tooltip": "Record the animation of the scene. Only effective if fabric is disabled.", - "on_clicked_fn": lambda value: self._toggle_recording_animation_fn(value), - } - self.ui_window_elements["record_animation"] = omni.isaac.ui.ui_utils.state_btn_builder( - **record_animate_cfg - ) - # disable the button if fabric is not enabled - self.ui_window_elements["record_animation"].enabled = not self.env.sim.is_fabric_enabled() - - def _build_viewer_frame(self): - """Build the viewer-related control frame for the UI.""" - # create collapsable frame for viewer - self.ui_window_elements["viewer_frame"] = omni.ui.CollapsableFrame( - title="Viewer Settings", - width=omni.ui.Fraction(1), - height=0, - collapsed=False, - style=omni.isaac.ui.ui_utils.get_style(), - horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED, - vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON, - ) - with self.ui_window_elements["viewer_frame"]: - # create stack for controls - self.ui_window_elements["viewer_vstack"] = omni.ui.VStack(spacing=5, height=0) - with self.ui_window_elements["viewer_vstack"]: - # create a number slider to move to environment origin - # NOTE: slider is 1-indexed, whereas the env index is 0-indexed - viewport_origin_cfg = { - "label": "Environment Index", - "type": "button", - "default_val": self.env.cfg.viewer.env_index + 1, - "min": 1, - "max": self.env.num_envs, - "tooltip": "The environment index to follow. Only effective if follow mode is not 'World'.", - } - self.ui_window_elements["viewer_env_index"] = omni.isaac.ui.ui_utils.int_builder(**viewport_origin_cfg) - # create a number slider to move to environment origin - self.ui_window_elements["viewer_env_index"].add_value_changed_fn(self._set_viewer_env_index_fn) - - # create a tracker for the camera location - viewer_follow_cfg = { - "label": "Follow Mode", - "type": "dropdown", - "default_val": 0, - "items": [name.replace("_", " ").title() for name in self._viewer_assets_options], - "tooltip": "Select the viewport camera following mode.", - "on_clicked_fn": self._set_viewer_origin_type_fn, - } - self.ui_window_elements["viewer_follow"] = omni.isaac.ui.ui_utils.dropdown_builder(**viewer_follow_cfg) - - # add viewer default eye and lookat locations - self.ui_window_elements["viewer_eye"] = omni.isaac.ui.ui_utils.xyz_builder( - label="Camera Eye", - tooltip="Modify the XYZ location of the viewer eye.", - default_val=self.env.cfg.viewer.eye, - step=0.1, - on_value_changed_fn=[self._set_viewer_location_fn] * 3, - ) - self.ui_window_elements["viewer_lookat"] = omni.isaac.ui.ui_utils.xyz_builder( - label="Camera Target", - tooltip="Modify the XYZ location of the viewer target.", - default_val=self.env.cfg.viewer.lookat, - step=0.1, - on_value_changed_fn=[self._set_viewer_location_fn] * 3, - ) - - def _build_debug_vis_frame(self): - """Builds the debug visualization frame for various scene elements. - - This function inquires the scene for all elements that have a debug visualization - implemented and creates a checkbox to toggle the debug visualization for each element - that has it implemented. If the element does not have a debug visualization implemented, - a label is created instead. - """ - # import omni.isaac.ui.ui_utils as ui_utils - # import omni.ui - - # create collapsable frame for debug visualization - self.ui_window_elements["debug_frame"] = omni.ui.CollapsableFrame( - title="Scene Debug Visualization", - width=omni.ui.Fraction(1), - height=0, - collapsed=False, - style=omni.isaac.ui.ui_utils.get_style(), - horizontal_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED, - vertical_scrollbar_policy=omni.ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON, - ) - with self.ui_window_elements["debug_frame"]: - # create stack for debug visualization - self.ui_window_elements["debug_vstack"] = omni.ui.VStack(spacing=5, height=0) - with self.ui_window_elements["debug_vstack"]: - elements = [ - self.env.scene.terrain, - *self.env.scene.rigid_objects.values(), - *self.env.scene.articulations.values(), - *self.env.scene.sensors.values(), - ] - names = [ - "terrain", - *self.env.scene.rigid_objects.keys(), - *self.env.scene.articulations.keys(), - *self.env.scene.sensors.keys(), - ] - # create one for the terrain - for elem, name in zip(elements, names): - if elem is not None: - self._create_debug_vis_ui_element(name, elem) - - """ - Custom callbacks for UI elements. - """ - - def _toggle_recording_animation_fn(self, value: bool): - """Toggles the animation recording.""" - if value: - # log directory to save the recording - if not hasattr(self, "animation_log_dir"): - # create a new log directory - log_dir = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - self.animation_log_dir = os.path.join(os.getcwd(), "recordings", log_dir) - # start the recording - _ = omni.kit.commands.execute( - "StartRecording", - target_paths=[("/World", True)], - live_mode=True, - use_frame_range=False, - start_frame=0, - end_frame=0, - use_preroll=False, - preroll_frame=0, - record_to="FILE", - fps=0, - apply_root_anim=False, - increment_name=True, - record_folder=self.animation_log_dir, - take_name="TimeSample", - ) - else: - # stop the recording - _ = omni.kit.commands.execute("StopRecording") - # save the current stage - stage = omni.usd.get_context().get_stage() - source_layer = stage.GetRootLayer() - # output the stage to a file - stage_usd_path = os.path.join(self.animation_log_dir, "Stage.usd") - source_prim_path = "/" - # creates empty anon layer - temp_layer = Sdf.Find(stage_usd_path) - if temp_layer is None: - temp_layer = Sdf.Layer.CreateNew(stage_usd_path) - temp_stage = Usd.Stage.Open(temp_layer) - # update stage data - UsdGeom.SetStageUpAxis(temp_stage, UsdGeom.GetStageUpAxis(stage)) - UsdGeom.SetStageMetersPerUnit(temp_stage, UsdGeom.GetStageMetersPerUnit(stage)) - # copy the prim - Sdf.CreatePrimInLayer(temp_layer, source_prim_path) - Sdf.CopySpec(source_layer, source_prim_path, temp_layer, source_prim_path) - # set the default prim - temp_layer.defaultPrim = Sdf.Path(source_prim_path).name - # remove all physics from the stage - for prim in temp_stage.TraverseAll(): - # skip if the prim is an instance - if prim.IsInstanceable(): - continue - # if prim has articulation then disable it - if prim.HasAPI(UsdPhysics.ArticulationRootAPI): - prim.RemoveAPI(UsdPhysics.ArticulationRootAPI) - prim.RemoveAPI(PhysxSchema.PhysxArticulationAPI) - # if prim has rigid body then disable it - if prim.HasAPI(UsdPhysics.RigidBodyAPI): - prim.RemoveAPI(UsdPhysics.RigidBodyAPI) - prim.RemoveAPI(PhysxSchema.PhysxRigidBodyAPI) - # if prim is a joint type then disable it - if prim.IsA(UsdPhysics.Joint): - prim.GetAttribute("physics:jointEnabled").Set(False) - # resolve all paths relative to layer path - omni.usd.resolve_paths(source_layer.identifier, temp_layer.identifier) - # save the stage - temp_layer.Save() - # print the path to the saved stage - print("Recording completed.") - print(f"\tSaved recorded stage to : {stage_usd_path}") - print(f"\tSaved recorded animation to: {os.path.join(self.animation_log_dir, 'TimeSample_tk001.usd')}") - print("\nTo play the animation, check the instructions in the following link:") - print( - "\thttps://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html#using-the-captured-timesamples" - ) - print("\n") - # reset the log directory - self.animation_log_dir = None - - def _set_viewer_origin_type_fn(self, value: str): - """Sets the origin of the viewport's camera. This is based on the drop-down menu in the UI.""" - # Extract the viewport camera controller from environment - vcc = self.env.viewport_camera_controller - if vcc is None: - raise ValueError("Viewport camera controller is not initialized! Please check the rendering mode.") - - # Based on origin type, update the camera view - if value == "World": - vcc.update_view_to_world() - elif value == "Env": - vcc.update_view_to_env() - else: - # find which index the asset is - fancy_names = [name.replace("_", " ").title() for name in self._viewer_assets_options] - # store the desired env index - viewer_asset_name = self._viewer_assets_options[fancy_names.index(value)] - # update the camera view - vcc.update_view_to_asset_root(viewer_asset_name) - - def _set_viewer_location_fn(self, model: omni.ui.SimpleFloatModel): - """Sets the viewport camera location based on the UI.""" - # access the viewport camera controller (for brevity) - vcc = self.env.viewport_camera_controller - if vcc is None: - raise ValueError("Viewport camera controller is not initialized! Please check the rendering mode.") - # obtain the camera locations and set them in the viewpoint camera controller - eye = [self.ui_window_elements["viewer_eye"][i].get_value_as_float() for i in range(3)] - lookat = [self.ui_window_elements["viewer_lookat"][i].get_value_as_float() for i in range(3)] - # update the camera view - vcc.update_view_location(eye, lookat) - - def _set_viewer_env_index_fn(self, model: omni.ui.SimpleIntModel): - """Sets the environment index and updates the camera if in 'env' origin mode.""" - # access the viewport camera controller (for brevity) - vcc = self.env.viewport_camera_controller - if vcc is None: - raise ValueError("Viewport camera controller is not initialized! Please check the rendering mode.") - # store the desired env index, UI is 1-indexed - vcc.set_view_env_index(model.as_int - 1) - - """ - Helper functions - UI building. - """ - - def _create_debug_vis_ui_element(self, name: str, elem: object): - """Create a checkbox for toggling debug visualization for the given element.""" - from omni.kit.window.extensions import SimpleCheckBox - - with omni.ui.HStack(): - # create the UI element - text = ( - "Toggle debug visualization." - if elem.has_debug_vis_implementation - else "Debug visualization not implemented." - ) - omni.ui.Label( - name.replace("_", " ").title(), - width=omni.isaac.ui.ui_utils.LABEL_WIDTH - 12, - alignment=omni.ui.Alignment.LEFT_CENTER, - tooltip=text, - ) - self.ui_window_elements[f"{name}_cb"] = SimpleCheckBox( - model=omni.ui.SimpleBoolModel(), - enabled=elem.has_debug_vis_implementation, - checked=elem.cfg.debug_vis, - on_checked_fn=lambda value, e=weakref.proxy(elem): e.set_debug_vis(value), - ) - omni.isaac.ui.ui_utils.add_line_rect_flourish() - - async def _dock_window(self, window_title: str): - """Docks the custom UI window to the property window.""" - # wait for the window to be created - for _ in range(5): - if omni.ui.Workspace.get_window(window_title): - break - await self.env.sim.app.next_update_async() - - # dock next to properties window - custom_window = omni.ui.Workspace.get_window(window_title) - property_window = omni.ui.Workspace.get_window("Property") - if custom_window and property_window: - custom_window.dock_in(property_window, omni.ui.DockPosition.SAME, 1.0) - custom_window.focus()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/ui/manager_based_rl_env_window.html b/docs/_build/html/_modules/omni/isaac/lab/envs/ui/manager_based_rl_env_window.html deleted file mode 100644 index 6869b45934..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/ui/manager_based_rl_env_window.html +++ /dev/null @@ -1,601 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.ui.manager_based_rl_env_window — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.ui.manager_based_rl_env_window 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from .base_env_window import BaseEnvWindow
-
-if TYPE_CHECKING:
-    from ..manager_based_rl_env import ManagerBasedRLEnv
-
-
-
[文档]class ManagerBasedRLEnvWindow(BaseEnvWindow): - """Window manager for the RL environment. - - On top of the basic environment window, this class adds controls for the RL environment. - This includes visualization of the command manager. - """ - -
[文档] def __init__(self, env: ManagerBasedRLEnv, window_name: str = "IsaacLab"): - """Initialize the window. - - Args: - env: The environment object. - window_name: The name of the window. Defaults to "IsaacLab". - """ - # initialize base window - super().__init__(env, window_name) - - # add custom UI elements - with self.ui_window_elements["main_vstack"]: - with self.ui_window_elements["debug_frame"]: - with self.ui_window_elements["debug_vstack"]: - self._create_debug_vis_ui_element("commands", self.env.command_manager) - self._create_debug_vis_ui_element("actions", self.env.action_manager)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/envs/ui/viewport_camera_controller.html b/docs/_build/html/_modules/omni/isaac/lab/envs/ui/viewport_camera_controller.html deleted file mode 100644 index ff25f009fe..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/envs/ui/viewport_camera_controller.html +++ /dev/null @@ -1,755 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.envs.ui.viewport_camera_controller — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.envs.ui.viewport_camera_controller 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import copy
-import numpy as np
-import torch
-import weakref
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-import omni.kit.app
-import omni.timeline
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedEnv, ViewerCfg
-
-
-
[文档]class ViewportCameraController: - """This class handles controlling the camera associated with a viewport in the simulator. - - It can be used to set the viewpoint camera to track different origin types: - - - **world**: the center of the world (static) - - **env**: the center of an environment (static) - - **asset_root**: the root of an asset in the scene (e.g. tracking a robot moving in the scene) - - On creation, the camera is set to track the origin type specified in the configuration. - - For the :attr:`asset_root` origin type, the camera is updated at each rendering step to track the asset's - root position. For this, it registers a callback to the post update event stream from the simulation app. - """ - -
[文档] def __init__(self, env: ManagerBasedEnv | DirectRLEnv, cfg: ViewerCfg): - """Initialize the ViewportCameraController. - - Args: - env: The environment. - cfg: The configuration for the viewport camera controller. - - Raises: - ValueError: If origin type is configured to be "env" but :attr:`cfg.env_index` is out of bounds. - ValueError: If origin type is configured to be "asset_root" but :attr:`cfg.asset_name` is unset. - - """ - # store inputs - self._env = env - self._cfg = copy.deepcopy(cfg) - # cast viewer eye and look-at to numpy arrays - self.default_cam_eye = np.array(self._cfg.eye) - self.default_cam_lookat = np.array(self._cfg.lookat) - - # set the camera origins - if self.cfg.origin_type == "env": - # check that the env_index is within bounds - self.set_view_env_index(self.cfg.env_index) - # set the camera origin to the center of the environment - self.update_view_to_env() - elif self.cfg.origin_type == "asset_root": - # note: we do not yet update camera for tracking an asset origin, as the asset may not yet be - # in the scene when this is called. Instead, we subscribe to the post update event to update the camera - # at each rendering step. - if self.cfg.asset_name is None: - raise ValueError(f"No asset name provided for viewer with origin type: '{self.cfg.origin_type}'.") - else: - # set the camera origin to the center of the world - self.update_view_to_world() - - # subscribe to post update event so that camera view can be updated at each rendering step - app_interface = omni.kit.app.get_app_interface() - app_event_stream = app_interface.get_post_update_event_stream() - self._viewport_camera_update_handle = app_event_stream.create_subscription_to_pop( - lambda event, obj=weakref.proxy(self): obj._update_tracking_callback(event) - )
- - def __del__(self): - """Unsubscribe from the callback.""" - # use hasattr to handle case where __init__ has not completed before __del__ is called - if hasattr(self, "_viewport_camera_update_handle") and self._viewport_camera_update_handle is not None: - self._viewport_camera_update_handle.unsubscribe() - self._viewport_camera_update_handle = None - - """ - Properties - """ - - @property - def cfg(self) -> ViewerCfg: - """The configuration for the viewer.""" - return self._cfg - - """ - Public Functions - """ - -
[文档] def set_view_env_index(self, env_index: int): - """Sets the environment index for the camera view. - - Args: - env_index: The index of the environment to set the camera view to. - - Raises: - ValueError: If the environment index is out of bounds. It should be between 0 and num_envs - 1. - """ - # check that the env_index is within bounds - if env_index < 0 or env_index >= self._env.num_envs: - raise ValueError( - f"Out of range value for attribute 'env_index': {env_index}." - f" Expected a value between 0 and {self._env.num_envs - 1} for the current environment." - ) - # update the environment index - self.cfg.env_index = env_index - # update the camera view if the origin is set to env type (since, the camera view is static) - # note: for assets, the camera view is updated at each rendering step - if self.cfg.origin_type == "env": - self.update_view_to_env()
- -
[文档] def update_view_to_world(self): - """Updates the viewer's origin to the origin of the world which is (0, 0, 0).""" - # set origin type to world - self.cfg.origin_type = "world" - # update the camera origins - self.viewer_origin = torch.zeros(3) - # update the camera view - self.update_view_location()
- -
[文档] def update_view_to_env(self): - """Updates the viewer's origin to the origin of the selected environment.""" - # set origin type to world - self.cfg.origin_type = "env" - # update the camera origins - self.viewer_origin = self._env.scene.env_origins[self.cfg.env_index] - # update the camera view - self.update_view_location()
- -
[文档] def update_view_to_asset_root(self, asset_name: str): - """Updates the viewer's origin based upon the root of an asset in the scene. - - Args: - asset_name: The name of the asset in the scene. The name should match the name of the - asset in the scene. - - Raises: - ValueError: If the asset is not in the scene. - """ - # check if the asset is in the scene - if self.cfg.asset_name != asset_name: - asset_entities = [*self._env.scene.rigid_objects.keys(), *self._env.scene.articulations.keys()] - if asset_name not in asset_entities: - raise ValueError(f"Asset '{asset_name}' is not in the scene. Available entities: {asset_entities}.") - # update the asset name - self.cfg.asset_name = asset_name - # set origin type to asset_root - self.cfg.origin_type = "asset_root" - # update the camera origins - self.viewer_origin = self._env.scene[self.cfg.asset_name].data.root_pos_w[self.cfg.env_index] - # update the camera view - self.update_view_location()
- -
[文档] def update_view_location(self, eye: Sequence[float] | None = None, lookat: Sequence[float] | None = None): - """Updates the camera view pose based on the current viewer origin and the eye and lookat positions. - - Args: - eye: The eye position of the camera. If None, the current eye position is used. - lookat: The lookat position of the camera. If None, the current lookat position is used. - """ - # store the camera view pose for later use - if eye is not None: - self.default_cam_eye = np.asarray(eye) - if lookat is not None: - self.default_cam_lookat = np.asarray(lookat) - # set the camera locations - viewer_origin = self.viewer_origin.detach().cpu().numpy() - cam_eye = viewer_origin + self.default_cam_eye - cam_target = viewer_origin + self.default_cam_lookat - - # set the camera view - self._env.sim.set_camera_view(eye=cam_eye, target=cam_target)
- - """ - Private Functions - """ - - def _update_tracking_callback(self, event): - """Updates the camera view at each rendering step.""" - # update the camera view if the origin is set to asset_root - # in other cases, the camera view is static and does not need to be updated continuously - if self.cfg.origin_type == "asset_root" and self.cfg.asset_name is not None: - self.update_view_to_asset_root(self.cfg.asset_name)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/action_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/action_manager.html deleted file mode 100644 index 3717323ef2..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/action_manager.html +++ /dev/null @@ -1,927 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.action_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.action_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Action manager for processing actions sent to the environment."""
-
-from __future__ import annotations
-
-import inspect
-import torch
-import weakref
-from abc import abstractmethod
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-import omni.kit.app
-
-from omni.isaac.lab.assets import AssetBase
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import ActionTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedEnv
-
-
-
[文档]class ActionTerm(ManagerTermBase): - """Base class for action terms. - - The action term is responsible for processing the raw actions sent to the environment - and applying them to the asset managed by the term. The action term is comprised of two - operations: - - * Processing of actions: This operation is performed once per **environment step** and - is responsible for pre-processing the raw actions sent to the environment. - * Applying actions: This operation is performed once per **simulation step** and is - responsible for applying the processed actions to the asset managed by the term. - """ - -
[文档] def __init__(self, cfg: ActionTermCfg, env: ManagerBasedEnv): - """Initialize the action term. - - Args: - cfg: The configuration object. - env: The environment instance. - """ - # call the base class constructor - super().__init__(cfg, env) - # parse config to obtain asset to which the term is applied - self._asset: AssetBase = self._env.scene[self.cfg.asset_name] - - # add handle for debug visualization (this is set to a valid handle inside set_debug_vis) - self._debug_vis_handle = None - # set initial state of debug visualization - self.set_debug_vis(self.cfg.debug_vis)
- - def __del__(self): - """Unsubscribe from the callbacks.""" - if self._debug_vis_handle: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - - """ - Properties. - """ - - @property - @abstractmethod - def action_dim(self) -> int: - """Dimension of the action term.""" - raise NotImplementedError - - @property - @abstractmethod - def raw_actions(self) -> torch.Tensor: - """The input/raw actions sent to the term.""" - raise NotImplementedError - - @property - @abstractmethod - def processed_actions(self) -> torch.Tensor: - """The actions computed by the term after applying any processing.""" - raise NotImplementedError - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the action term has a debug visualization implemented.""" - # check if function raises NotImplementedError - source_code = inspect.getsource(self._set_debug_vis_impl) - return "NotImplementedError" not in source_code - - """ - Operations. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Sets whether to visualize the action term data. - Args: - debug_vis: Whether to visualize the action term data. - Returns: - Whether the debug visualization was successfully set. False if the action term does - not support debug visualization. - """ - # check if debug visualization is supported - if not self.has_debug_vis_implementation: - return False - # toggle debug visualization objects - self._set_debug_vis_impl(debug_vis) - # toggle debug visualization handles - if debug_vis: - # create a subscriber for the post update event if it doesn't exist - if self._debug_vis_handle is None: - 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) - ) - else: - # remove the subscriber if it exists - if self._debug_vis_handle is not None: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - # return success - return True
- -
[文档] @abstractmethod - def process_actions(self, actions: torch.Tensor): - """Processes the actions sent to the environment. - - Note: - This function is called once per environment step by the manager. - - Args: - actions: The actions to process. - """ - raise NotImplementedError
- -
[文档] @abstractmethod - def apply_actions(self): - """Applies the actions to the asset managed by the term. - - Note: - This is called at every simulation step by the manager. - """ - raise NotImplementedError
- - def _set_debug_vis_impl(self, debug_vis: bool): - """Set debug visualization into visualization objects. - This function is responsible for creating the visualization objects if they don't exist - and input ``debug_vis`` is True. If the visualization objects exist, the function should - set their visibility into the stage. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") - - def _debug_vis_callback(self, event): - """Callback for debug visualization. - This function calls the visualization objects and sets the data to visualize into them. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.")
- - -
[文档]class ActionManager(ManagerBase): - """Manager for processing and applying actions for a given world. - - The action manager handles the interpretation and application of user-defined - actions on a given world. It is comprised of different action terms that decide - the dimension of the expected actions. - - The action manager performs operations at two stages: - - * processing of actions: It splits the input actions to each term and performs any - pre-processing needed. This should be called once at every environment step. - * apply actions: This operation typically sets the processed actions into the assets in the - scene (such as robots). It should be called before every simulation step. - """ - -
[文档] def __init__(self, cfg: object, env: ManagerBasedEnv): - """Initialize the action manager. - - Args: - cfg: The configuration object or dictionary (``dict[str, ActionTermCfg]``). - env: The environment instance. - """ - super().__init__(cfg, env) - # create buffers to store actions - self._action = torch.zeros((self.num_envs, self.total_action_dim), device=self.device) - self._prev_action = torch.zeros_like(self._action) - - self.cfg.debug_vis = False - for term in self._terms.values(): - self.cfg.debug_vis |= term.cfg.debug_vis
- - def __str__(self) -> str: - """Returns: A string representation for action manager.""" - msg = f"<ActionManager> contains {len(self._term_names)} active terms.\n" - - # create table for term information - table = PrettyTable() - table.title = f"Active Action Terms (shape: {self.total_action_dim})" - table.field_names = ["Index", "Name", "Dimension"] - # set alignment of table columns - table.align["Name"] = "l" - table.align["Dimension"] = "r" - # add info on each term - for index, (name, term) in enumerate(self._terms.items()): - table.add_row([index, name, term.action_dim]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def total_action_dim(self) -> int: - """Total dimension of actions.""" - return sum(self.action_term_dim) - - @property - def active_terms(self) -> list[str]: - """Name of active action terms.""" - return self._term_names - - @property - def action_term_dim(self) -> list[int]: - """Shape of each action term.""" - return [term.action_dim for term in self._terms.values()] - - @property - def action(self) -> torch.Tensor: - """The actions sent to the environment. Shape is (num_envs, total_action_dim).""" - return self._action - - @property - def prev_action(self) -> torch.Tensor: - """The previous actions sent to the environment. Shape is (num_envs, total_action_dim).""" - return self._prev_action - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the command terms have debug visualization implemented.""" - # check if function raises NotImplementedError - has_debug_vis = False - for term in self._terms.values(): - has_debug_vis |= term.has_debug_vis_implementation - return has_debug_vis - - """ - Operations. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Sets whether to visualize the action data. - Args: - debug_vis: Whether to visualize the action data. - Returns: - Whether the debug visualization was successfully set. False if the action - does not support debug visualization. - """ - for term in self._terms.values(): - term.set_debug_vis(debug_vis)
- -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: - """Resets the action history. - - Args: - env_ids: The environment ids. Defaults to None, in which case - all environments are considered. - - Returns: - An empty dictionary. - """ - # resolve environment ids - if env_ids is None: - env_ids = slice(None) - # reset the action history - self._prev_action[env_ids] = 0.0 - self._action[env_ids] = 0.0 - # reset all action terms - for term in self._terms.values(): - term.reset(env_ids=env_ids) - # nothing to log here - return {}
- -
[文档] def process_action(self, action: torch.Tensor): - """Processes the actions sent to the environment. - - Note: - This function should be called once per environment step. - - Args: - action: The actions to process. - """ - # check if action dimension is valid - if self.total_action_dim != action.shape[1]: - raise ValueError(f"Invalid action shape, expected: {self.total_action_dim}, received: {action.shape[1]}.") - # store the input actions - self._prev_action[:] = self._action - self._action[:] = action.to(self.device) - - # split the actions and apply to each tensor - idx = 0 - for term in self._terms.values(): - term_actions = action[:, idx : idx + term.action_dim] - term.process_actions(term_actions) - idx += term.action_dim
- -
[文档] def apply_action(self) -> None: - """Applies the actions to the environment/simulation. - - Note: - This should be called at every simulation step. - """ - for term in self._terms.values(): - term.apply_actions()
- -
[文档] def get_term(self, name: str) -> ActionTerm: - """Returns the action term with the specified name. - - Args: - name: The name of the action term. - - Returns: - The action term with the specified name. - """ - return self._terms[name]
- - """ - Helper functions. - """ - - def _prepare_terms(self): - """Prepares a list of action terms.""" - # parse action terms from the config - self._term_names: list[str] = list() - self._terms: dict[str, ActionTerm] = dict() - - # check if config is dict already - if isinstance(self.cfg, dict): - cfg_items = self.cfg.items() - else: - cfg_items = self.cfg.__dict__.items() - for term_name, term_cfg in cfg_items: - # check if term config is None - if term_cfg is None: - continue - # check valid type - if not isinstance(term_cfg, ActionTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type ActionTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # create the action term - term = term_cfg.class_type(term_cfg, self._env) - # sanity check if term is valid type - if not isinstance(term, ActionTerm): - raise TypeError(f"Returned object for the term '{term_name}' is not of type ActionType.") - # add term name and parameters - self._term_names.append(term_name) - self._terms[term_name] = term
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/command_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/command_manager.html deleted file mode 100644 index 322c3f0f21..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/command_manager.html +++ /dev/null @@ -1,960 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.command_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.command_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Command manager for generating and updating commands."""
-
-from __future__ import annotations
-
-import inspect
-import torch
-import weakref
-from abc import abstractmethod
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-import omni.kit.app
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import CommandTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-
-
[文档]class CommandTerm(ManagerTermBase): - """The base class for implementing a command term. - - A command term is used to generate commands for goal-conditioned tasks. For example, - in the case of a goal-conditioned navigation task, the command term can be used to - generate a target position for the robot to navigate to. - - It implements a resampling mechanism that allows the command to be resampled at a fixed - frequency. The resampling frequency can be specified in the configuration object. - Additionally, it is possible to assign a visualization function to the command term - that can be used to visualize the command in the simulator. - """ - - def __init__(self, cfg: CommandTermCfg, env: ManagerBasedRLEnv): - """Initialize the command generator class. - - Args: - cfg: The configuration parameters for the command generator. - env: The environment object. - """ - super().__init__(cfg, env) - - # create buffers to store the command - # -- metrics that can be used for logging - self.metrics = dict() - # -- time left before resampling - self.time_left = torch.zeros(self.num_envs, device=self.device) - # -- counter for the number of times the command has been resampled within the current episode - self.command_counter = torch.zeros(self.num_envs, device=self.device, dtype=torch.long) - - # add handle for debug visualization (this is set to a valid handle inside set_debug_vis) - self._debug_vis_handle = None - # set initial state of debug visualization - self.set_debug_vis(self.cfg.debug_vis) - - def __del__(self): - """Unsubscribe from the callbacks.""" - if self._debug_vis_handle: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - - """ - Properties - """ - - @property - @abstractmethod - def command(self) -> torch.Tensor: - """The command tensor. Shape is (num_envs, command_dim).""" - raise NotImplementedError - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the command generator has a debug visualization implemented.""" - # check if function raises NotImplementedError - source_code = inspect.getsource(self._set_debug_vis_impl) - return "NotImplementedError" not in source_code - - """ - Operations. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Sets whether to visualize the command data. - - Args: - debug_vis: Whether to visualize the command data. - - Returns: - Whether the debug visualization was successfully set. False if the command - generator does not support debug visualization. - """ - # check if debug visualization is supported - if not self.has_debug_vis_implementation: - return False - # toggle debug visualization objects - self._set_debug_vis_impl(debug_vis) - # toggle debug visualization handles - if debug_vis: - # create a subscriber for the post update event if it doesn't exist - if self._debug_vis_handle is None: - 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) - ) - else: - # remove the subscriber if it exists - if self._debug_vis_handle is not None: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - # return success - return True
- -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: - """Reset the command generator and log metrics. - - This function resets the command counter and resamples the command. It should be called - at the beginning of each episode. - - Args: - env_ids: The list of environment IDs to reset. Defaults to None. - - Returns: - A dictionary containing the information to log under the "{name}" key. - """ - # resolve the environment IDs - if env_ids is None: - env_ids = slice(None) - # set the command counter to zero - self.command_counter[env_ids] = 0 - # resample the command - self._resample(env_ids) - # add logging metrics - extras = {} - for metric_name, metric_value in self.metrics.items(): - # compute the mean metric value - extras[metric_name] = torch.mean(metric_value[env_ids]).item() - # reset the metric value - metric_value[env_ids] = 0.0 - return extras
- -
[文档] def compute(self, dt: float): - """Compute the command. - - Args: - dt: The time step passed since the last call to compute. - """ - # update the metrics based on current state - self._update_metrics() - # reduce the time left before resampling - self.time_left -= dt - # resample the command if necessary - resample_env_ids = (self.time_left <= 0.0).nonzero().flatten() - if len(resample_env_ids) > 0: - self._resample(resample_env_ids) - # update the command - self._update_command()
- - """ - Helper functions. - """ - - def _resample(self, env_ids: Sequence[int]): - """Resample the command. - - This function resamples the command and time for which the command is applied for the - specified environment indices. - - Args: - env_ids: The list of environment IDs to resample. - """ - # resample the time left before resampling - if len(env_ids) != 0: - self.time_left[env_ids] = self.time_left[env_ids].uniform_(*self.cfg.resampling_time_range) - # increment the command counter - self.command_counter[env_ids] += 1 - # resample the command - self._resample_command(env_ids) - - """ - Implementation specific functions. - """ - - @abstractmethod - def _update_metrics(self): - """Update the metrics based on the current state.""" - raise NotImplementedError - - @abstractmethod - def _resample_command(self, env_ids: Sequence[int]): - """Resample the command for the specified environments.""" - raise NotImplementedError - - @abstractmethod - def _update_command(self): - """Update the command based on the current state.""" - raise NotImplementedError - - def _set_debug_vis_impl(self, debug_vis: bool): - """Set debug visualization into visualization objects. - - This function is responsible for creating the visualization objects if they don't exist - and input ``debug_vis`` is True. If the visualization objects exist, the function should - set their visibility into the stage. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") - - def _debug_vis_callback(self, event): - """Callback for debug visualization. - - This function calls the visualization objects and sets the data to visualize into them. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.")
- - -
[文档]class CommandManager(ManagerBase): - """Manager for generating commands. - - The command manager is used to generate commands for an agent to execute. It makes it convenient to switch - between different command generation strategies within the same environment. For instance, in an environment - consisting of a quadrupedal robot, the command to it could be a velocity command or position command. - By keeping the command generation logic separate from the environment, it is easy to switch between different - command generation strategies. - - The command terms are implemented as classes that inherit from the :class:`CommandTerm` class. - Each command generator term should also have a corresponding configuration class that inherits from the - :class:`CommandTermCfg` class. - """ - - _env: ManagerBasedRLEnv - """The environment instance.""" - -
[文档] def __init__(self, cfg: object, env: ManagerBasedRLEnv): - """Initialize the command manager. - - Args: - cfg: The configuration object or dictionary (``dict[str, CommandTermCfg]``). - env: The environment instance. - """ - super().__init__(cfg, env) - # store the commands - self._commands = dict() - self.cfg.debug_vis = False - for term in self._terms.values(): - self.cfg.debug_vis |= term.cfg.debug_vis
- - def __str__(self) -> str: - """Returns: A string representation for the command manager.""" - msg = f"<CommandManager> contains {len(self._terms.values())} active terms.\n" - - # create table for term information - table = PrettyTable() - table.title = "Active Command Terms" - table.field_names = ["Index", "Name", "Type"] - # set alignment of table columns - table.align["Name"] = "l" - # add info on each term - for index, (name, term) in enumerate(self._terms.items()): - table.add_row([index, name, term.__class__.__name__]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def active_terms(self) -> list[str]: - """Name of active command terms.""" - return list(self._terms.keys()) - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the command terms have debug visualization implemented.""" - # check if function raises NotImplementedError - has_debug_vis = False - for term in self._terms.values(): - has_debug_vis |= term.has_debug_vis_implementation - return has_debug_vis - - """ - Operations. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Sets whether to visualize the command data. - - Args: - debug_vis: Whether to visualize the command data. - - Returns: - Whether the debug visualization was successfully set. False if the command - generator does not support debug visualization. - """ - for term in self._terms.values(): - term.set_debug_vis(debug_vis)
- -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: - """Reset the command terms and log their metrics. - - This function resets the command counter and resamples the command for each term. It should be called - at the beginning of each episode. - - Args: - env_ids: The list of environment IDs to reset. Defaults to None. - - Returns: - A dictionary containing the information to log under the "Metrics/{term_name}/{metric_name}" key. - """ - # resolve environment ids - if env_ids is None: - env_ids = slice(None) - # store information - extras = {} - for name, term in self._terms.items(): - # reset the command term - metrics = term.reset(env_ids=env_ids) - # compute the mean metric value - for metric_name, metric_value in metrics.items(): - extras[f"Metrics/{name}/{metric_name}"] = metric_value - # return logged information - return extras
- -
[文档] def compute(self, dt: float): - """Updates the commands. - - This function calls each command term managed by the class. - - Args: - dt: The time-step interval of the environment. - - """ - # iterate over all the command terms - for term in self._terms.values(): - # compute term's value - term.compute(dt)
- -
[文档] def get_command(self, name: str) -> torch.Tensor: - """Returns the command for the specified command term. - - Args: - name: The name of the command term. - - Returns: - The command tensor of the specified command term. - """ - return self._terms[name].command
- -
[文档] def get_term(self, name: str) -> CommandTerm: - """Returns the command term with the specified name. - - Args: - name: The name of the command term. - - Returns: - The command term with the specified name. - """ - return self._terms[name]
- - """ - Helper functions. - """ - - def _prepare_terms(self): - """Prepares a list of command terms.""" - # parse command terms from the config - self._terms: dict[str, CommandTerm] = dict() - - # check if config is dict already - if isinstance(self.cfg, dict): - cfg_items = self.cfg.items() - else: - cfg_items = self.cfg.__dict__.items() - # iterate over all the terms - for term_name, term_cfg in cfg_items: - # check for non config - if term_cfg is None: - continue - # check for valid config type - if not isinstance(term_cfg, CommandTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type CommandTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # create the action term - term = term_cfg.class_type(term_cfg, self._env) - # add class to dict - self._terms[term_name] = term
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/curriculum_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/curriculum_manager.html deleted file mode 100644 index 0a3a1983ca..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/curriculum_manager.html +++ /dev/null @@ -1,730 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.curriculum_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.curriculum_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Curriculum manager for updating environment quantities subject to a training curriculum."""
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import CurriculumTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-
-
[文档]class CurriculumManager(ManagerBase): - """Manager to implement and execute specific curricula. - - The curriculum manager updates various quantities of the environment subject to a training curriculum by - calling a list of terms. These help stabilize learning by progressively making the learning tasks harder - as the agent improves. - - The curriculum terms are parsed from a config class containing the manager's settings and each term's - parameters. Each curriculum term should instantiate the :class:`CurriculumTermCfg` class. - """ - - _env: ManagerBasedRLEnv - """The environment instance.""" - -
[文档] def __init__(self, cfg: object, env: ManagerBasedRLEnv): - """Initialize the manager. - - Args: - cfg: The configuration object or dictionary (``dict[str, CurriculumTermCfg]``) - env: An environment object. - - Raises: - TypeError: If curriculum term is not of type :class:`CurriculumTermCfg`. - ValueError: If curriculum term configuration does not satisfy its function signature. - """ - super().__init__(cfg, env) - # prepare logging - self._curriculum_state = dict() - for term_name in self._term_names: - self._curriculum_state[term_name] = None
- - def __str__(self) -> str: - """Returns: A string representation for curriculum manager.""" - msg = f"<CurriculumManager> contains {len(self._term_names)} active terms.\n" - - # create table for term information - table = PrettyTable() - table.title = "Active Curriculum Terms" - table.field_names = ["Index", "Name"] - # set alignment of table columns - table.align["Name"] = "l" - # add info on each term - for index, name in enumerate(self._term_names): - table.add_row([index, name]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def active_terms(self) -> list[str]: - """Name of active curriculum terms.""" - return self._term_names - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: - """Returns the current state of individual curriculum terms. - - Note: - This function does not use the environment indices :attr:`env_ids` - and logs the state of all the terms. The argument is only present - to maintain consistency with other classes. - - Returns: - Dictionary of curriculum terms and their states. - """ - extras = {} - for term_name, term_state in self._curriculum_state.items(): - if term_state is not None: - # deal with dict - if isinstance(term_state, dict): - # each key is a separate state to log - for key, value in term_state.items(): - if isinstance(value, torch.Tensor): - value = value.item() - extras[f"Curriculum/{term_name}/{key}"] = value - else: - # log directly if not a dict - if isinstance(term_state, torch.Tensor): - term_state = term_state.item() - extras[f"Curriculum/{term_name}"] = term_state - # reset all the curriculum terms - for term_cfg in self._class_term_cfgs: - term_cfg.func.reset(env_ids=env_ids) - # return logged information - return extras
- -
[文档] def compute(self, env_ids: Sequence[int] | None = None): - """Update the curriculum terms. - - This function calls each curriculum term managed by the class. - - Args: - env_ids: The list of environment IDs to update. - If None, all the environments are updated. Defaults to None. - """ - # resolve environment indices - if env_ids is None: - env_ids = slice(None) - # iterate over all the curriculum terms - for name, term_cfg in zip(self._term_names, self._term_cfgs): - state = term_cfg.func(self._env, env_ids, **term_cfg.params) - self._curriculum_state[name] = state
- - """ - Helper functions. - """ - - def _prepare_terms(self): - # parse remaining curriculum terms and decimate their information - self._term_names: list[str] = list() - self._term_cfgs: list[CurriculumTermCfg] = list() - self._class_term_cfgs: list[CurriculumTermCfg] = list() - - # check if config is dict already - if isinstance(self.cfg, dict): - cfg_items = self.cfg.items() - else: - cfg_items = self.cfg.__dict__.items() - # iterate over all the terms - for term_name, term_cfg in cfg_items: - # check for non config - if term_cfg is None: - continue - # check if the term is a valid term config - if not isinstance(term_cfg, CurriculumTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type CurriculumTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # resolve common parameters - self._resolve_common_term_cfg(term_name, term_cfg, min_argc=2) - # add name and config to list - self._term_names.append(term_name) - self._term_cfgs.append(term_cfg) - # check if the term is a class - if isinstance(term_cfg.func, ManagerTermBase): - self._class_term_cfgs.append(term_cfg)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/event_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/event_manager.html deleted file mode 100644 index 58a1fe25e4..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/event_manager.html +++ /dev/null @@ -1,888 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.event_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.event_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Event manager for orchestrating operations based on different simulation events."""
-
-from __future__ import annotations
-
-import torch
-import warnings
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-import carb
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import EventTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-
-
[文档]class EventManager(ManagerBase): - """Manager for orchestrating operations based on different simulation events. - - The event manager applies operations to the environment based on different simulation events. For example, - changing the masses of objects or their friction coefficients during initialization/ reset, or applying random - pushes to the robot at a fixed interval of steps. The user can specify several modes of events to fine-tune the - behavior based on when to apply the event. - - The event terms are parsed from a config class containing the manager's settings and each term's - parameters. Each event term should instantiate the :class:`EventTermCfg` class. - - Event terms can be grouped by their mode. The mode is a user-defined string that specifies when - the event term should be applied. This provides the user complete control over when event - terms should be applied. - - For a typical training process, you may want to apply events in the following modes: - - - "startup": Event is applied once at the beginning of the training. - - "reset": Event is applied at every reset. - - "interval": Event is applied at pre-specified intervals of time. - - However, you can also define your own modes and use them in the training process as you see fit. - For this you will need to add the triggering of that mode in the environment implementation as well. - - .. note:: - - The triggering of operations corresponding to the mode ``"interval"`` are the only mode that are - directly handled by the manager itself. The other modes are handled by the environment implementation. - - """ - - _env: ManagerBasedRLEnv - """The environment instance.""" - -
[文档] def __init__(self, cfg: object, env: ManagerBasedRLEnv): - """Initialize the event manager. - - Args: - cfg: A configuration object or dictionary (``dict[str, EventTermCfg]``). - env: An environment object. - """ - super().__init__(cfg, env)
- - def __str__(self) -> str: - """Returns: A string representation for event manager.""" - msg = f"<EventManager> contains {len(self._mode_term_names)} active terms.\n" - - # add info on each mode - for mode in self._mode_term_names: - # create table for term information - table = PrettyTable() - table.title = f"Active Event Terms in Mode: '{mode}'" - # add table headers based on mode - if mode == "interval": - table.field_names = ["Index", "Name", "Interval time range (s)"] - table.align["Name"] = "l" - for index, (name, cfg) in enumerate(zip(self._mode_term_names[mode], self._mode_term_cfgs[mode])): - table.add_row([index, name, cfg.interval_range_s]) - else: - table.field_names = ["Index", "Name"] - table.align["Name"] = "l" - for index, name in enumerate(self._mode_term_names[mode]): - table.add_row([index, name]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def active_terms(self) -> dict[str, list[str]]: - """Name of active event terms. - - The keys are the modes of event and the values are the names of the event terms. - """ - return self._mode_term_names - - @property - def available_modes(self) -> list[str]: - """Modes of events.""" - return list(self._mode_term_names.keys()) - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: - # call all terms that are classes - for mode_cfg in self._mode_class_term_cfgs.values(): - for term_cfg in mode_cfg: - term_cfg.func.reset(env_ids=env_ids) - # nothing to log here - return {}
- -
[文档] def apply(self, mode: str, env_ids: Sequence[int] | None = None, dt: float | None = None): - """Calls each event term in the specified mode. - - Note: - For interval mode, the time step of the environment is used to determine if the event - should be applied. - - Args: - mode: The mode of event. - env_ids: The indices of the environments to apply the event to. - Defaults to None, in which case the event is applied to all environments. - dt: The time step of the environment. This is only used for the "interval" mode. - Defaults to None to simplify the call for other modes. - - Raises: - ValueError: If the mode is ``"interval"`` and the time step is not provided. - """ - # check if mode is valid - if mode not in self._mode_term_names: - carb.log_warn(f"Event mode '{mode}' is not defined. Skipping event.") - return - # iterate over all the event terms - for index, term_cfg in enumerate(self._mode_term_cfgs[mode]): - # resample interval if needed - if mode == "interval": - if dt is None: - raise ValueError( - f"Event mode '{mode}' requires the time step of the environment" - " to be passed to the event manager." - ) - if term_cfg.is_global_time: - # extract time left for this term - time_left = self._interval_mode_time_global[index] - # update the time left for each environment - time_left -= dt - # check if the interval has passed - if time_left <= 0.0: - lower, upper = term_cfg.interval_range_s - self._interval_mode_time_global[index] = torch.rand(1) * (upper - lower) + lower - else: - # no need to call func to sample - continue - else: - # extract time left for this term - time_left = self._interval_mode_time_left[index] - # update the time left for each environment - time_left -= dt - # check if the interval has passed - env_ids = (time_left <= 0.0).nonzero().flatten() - if len(env_ids) > 0: - lower, upper = term_cfg.interval_range_s - time_left[env_ids] = torch.rand(len(env_ids), device=self.device) * (upper - lower) + lower - # call the event term - term_cfg.func(self._env, env_ids, **term_cfg.params)
- - """ - Operations - Term settings. - """ - -
[文档] def set_term_cfg(self, term_name: str, cfg: EventTermCfg): - """Sets the configuration of the specified term into the manager. - - The method finds the term by name by searching through all the modes. - It then updates the configuration of the term with the first matching name. - - Args: - term_name: The name of the event term. - cfg: The configuration for the event term. - - Raises: - ValueError: If the term name is not found. - """ - term_found = False - for mode, terms in self._mode_term_names.items(): - if term_name in terms: - self._mode_term_cfgs[mode][terms.index(term_name)] = cfg - term_found = True - break - if not term_found: - raise ValueError(f"Event term '{term_name}' not found.")
- -
[文档] def get_term_cfg(self, term_name: str) -> EventTermCfg: - """Gets the configuration for the specified term. - - The method finds the term by name by searching through all the modes. - It then returns the configuration of the term with the first matching name. - - Args: - term_name: The name of the event term. - - Returns: - The configuration of the event term. - - Raises: - ValueError: If the term name is not found. - """ - for mode, terms in self._mode_term_names.items(): - if term_name in terms: - return self._mode_term_cfgs[mode][terms.index(term_name)] - raise ValueError(f"Event term '{term_name}' not found.")
- - """ - Helper functions. - """ - - def _prepare_terms(self): - """Prepares a list of event functions.""" - # parse remaining event terms and decimate their information - self._mode_term_names: dict[str, list[str]] = dict() - self._mode_term_cfgs: dict[str, list[EventTermCfg]] = dict() - self._mode_class_term_cfgs: dict[str, list[EventTermCfg]] = dict() - # buffer to store the time left for each environment for "interval" mode - self._interval_mode_time_left: list[torch.Tensor] = list() - # global timer for "interval" mode for global properties - self._interval_mode_time_global: list[torch.Tensor] = list() - - # check if config is dict already - if isinstance(self.cfg, dict): - cfg_items = self.cfg.items() - else: - cfg_items = self.cfg.__dict__.items() - # iterate over all the terms - for term_name, term_cfg in cfg_items: - # check for non config - if term_cfg is None: - continue - # check for valid config type - if not isinstance(term_cfg, EventTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type EventTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # resolve common parameters - self._resolve_common_term_cfg(term_name, term_cfg, min_argc=2) - # check if mode is a new mode - if term_cfg.mode not in self._mode_term_names: - # add new mode - self._mode_term_names[term_cfg.mode] = list() - self._mode_term_cfgs[term_cfg.mode] = list() - self._mode_class_term_cfgs[term_cfg.mode] = list() - # add term name and parameters - self._mode_term_names[term_cfg.mode].append(term_name) - self._mode_term_cfgs[term_cfg.mode].append(term_cfg) - - # check if the term is a class - if isinstance(term_cfg.func, ManagerTermBase): - self._mode_class_term_cfgs[term_cfg.mode].append(term_cfg) - - # resolve the mode of the events - if term_cfg.mode == "interval": - if term_cfg.interval_range_s is None: - raise ValueError( - f"Event term '{term_name}' has mode 'interval' but 'interval_range_s' is not specified." - ) - - # sample the time left for global - if term_cfg.is_global_time: - lower, upper = term_cfg.interval_range_s - time_left = torch.rand(1) * (upper - lower) + lower - self._interval_mode_time_global.append(time_left) - else: - # sample the time left for each environment - lower, upper = term_cfg.interval_range_s - time_left = torch.rand(self.num_envs, device=self.device) * (upper - lower) + lower - self._interval_mode_time_left.append(time_left)
- - -
[文档]class RandomizationManager(EventManager): - """Manager for applying event specific operations to different elements in the scene. - - .. deprecated:: v0.4.0 - As the RandomizationManager also handles events such as resetting the environment, the class has been - renamed to EventManager as it is more general purpose. The RandomizationManager will be removed in v0.4.0. - """ - -
[文档] def __init__(self, cfg: object, env: ManagerBasedRLEnv): - """Initialize the randomization manager. - - Args: - cfg: A configuration object or dictionary (``dict[str, EventTermCfg]``). - env: An environment object. - """ - dep_msg = "The class 'RandomizationManager' will be removed in v0.4.0. Please use 'EventManager' instead." - warnings.warn(dep_msg, DeprecationWarning) - carb.log_error(dep_msg) - - super().__init__(cfg, env)
- -
[文档] def randomize(self, mode: str, env_ids: Sequence[int] | None = None, dt: float | None = None): - """Randomize the environment. - - .. deprecated:: v0.4.0 - This method will be removed in v0.4.0. Please use the method :meth:`EventManager.apply` - instead. - """ - dep_msg = ( - "The class 'RandomizationManager' including its method 'randomize' will be removed in v0.4.0. Please use " - "the class 'EventManager' with the method 'apply' instead." - ) - warnings.warn(dep_msg, DeprecationWarning) - carb.log_error(dep_msg) - - self.apply(mode, env_ids, dt)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/manager_base.html b/docs/_build/html/_modules/omni/isaac/lab/managers/manager_base.html deleted file mode 100644 index 53d3c7ad34..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/manager_base.html +++ /dev/null @@ -1,848 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.manager_base — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.manager_base 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import copy
-import inspect
-from abc import ABC, abstractmethod
-from collections.abc import Sequence
-from typing import TYPE_CHECKING, Any
-
-import carb
-
-import omni.isaac.lab.utils.string as string_utils
-from omni.isaac.lab.utils import string_to_callable
-
-from .manager_term_cfg import ManagerTermBaseCfg
-from .scene_entity_cfg import SceneEntityCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedEnv
-
-
-
[文档]class ManagerTermBase(ABC): - """Base class for manager terms. - - Manager term implementations can be functions or classes. If the term is a class, it should - inherit from this base class and implement the required methods. - - Each manager is implemented as a class that inherits from the :class:`ManagerBase` class. Each manager - class should also have a corresponding configuration class that defines the configuration terms for the - manager. Each term should the :class:`ManagerTermBaseCfg` class or its subclass. - - Example pseudo-code for creating a manager: - - .. code-block:: python - - from omni.isaac.lab.utils import configclass - from omni.isaac.lab.utils.mdp import ManagerBase, ManagerTermBaseCfg - - @configclass - class MyManagerCfg: - - my_term_1: ManagerTermBaseCfg = ManagerTermBaseCfg(...) - my_term_2: ManagerTermBaseCfg = ManagerTermBaseCfg(...) - my_term_3: ManagerTermBaseCfg = ManagerTermBaseCfg(...) - - # define manager instance - my_manager = ManagerBase(cfg=ManagerCfg(), env=env) - - """ - -
[文档] def __init__(self, cfg: ManagerTermBaseCfg, env: ManagerBasedEnv): - """Initialize the manager term. - - Args: - cfg: The configuration object. - env: The environment instance. - """ - # store the inputs - self.cfg = cfg - self._env = env
- - """ - Properties. - """ - - @property - def num_envs(self) -> int: - """Number of environments.""" - return self._env.num_envs - - @property - def device(self) -> str: - """Device on which to perform computations.""" - return self._env.device - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> None: - """Resets the manager term. - - Args: - env_ids: The environment ids. Defaults to None, in which case - all environments are considered. - """ - pass
- - def __call__(self, *args) -> Any: - """Returns the value of the term required by the manager. - - In case of a class implementation, this function is called by the manager - to get the value of the term. The arguments passed to this function are - the ones specified in the term configuration (see :attr:`ManagerTermBaseCfg.params`). - - .. attention:: - To be consistent with memory-less implementation of terms with functions, it is - recommended to ensure that the returned mutable quantities are cloned before - returning them. For instance, if the term returns a tensor, it is recommended - to ensure that the returned tensor is a clone of the original tensor. This prevents - the manager from storing references to the tensors and altering the original tensors. - - Args: - *args: Variable length argument list. - - Returns: - The value of the term. - """ - raise NotImplementedError
- - -
[文档]class ManagerBase(ABC): - """Base class for all managers.""" - -
[文档] def __init__(self, cfg: object, env: ManagerBasedEnv): - """Initialize the manager. - - Args: - cfg: The configuration object. - env: The environment instance. - """ - # store the inputs - self.cfg = copy.deepcopy(cfg) - self._env = env - # parse config to create terms information - self._prepare_terms()
- - """ - Properties. - """ - - @property - def num_envs(self) -> int: - """Number of environments.""" - return self._env.num_envs - - @property - def device(self) -> str: - """Device on which to perform computations.""" - return self._env.device - - @property - @abstractmethod - def active_terms(self) -> list[str] | dict[str, list[str]]: - """Name of active terms.""" - raise NotImplementedError - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: - """Resets the manager and returns logging information for the current time-step. - - Args: - env_ids: The environment ids for which to log data. - Defaults None, which logs data for all environments. - - Returns: - Dictionary containing the logging information. - """ - return {}
- -
[文档] def find_terms(self, name_keys: str | Sequence[str]) -> list[str]: - """Find terms in the manager based on the names. - - This function searches the manager for terms based on the names. The names can be - specified as regular expressions or a list of regular expressions. The search is - performed on the active terms in the manager. - - Please check the :meth:`omni.isaac.lab.utils.string_utils.resolve_matching_names` function for more - information on the name matching. - - Args: - name_keys: A regular expression or a list of regular expressions to match the term names. - - Returns: - A list of term names that match the input keys. - """ - # resolve search keys - if isinstance(self.active_terms, dict): - list_of_strings = [] - for names in self.active_terms.values(): - list_of_strings.extend(names) - else: - list_of_strings = self.active_terms - - # return the matching names - return string_utils.resolve_matching_names(name_keys, list_of_strings)[1]
- - """ - Implementation specific. - """ - - @abstractmethod - def _prepare_terms(self): - """Prepare terms information from the configuration object.""" - raise NotImplementedError - - """ - Helper functions. - """ - - def _resolve_common_term_cfg(self, term_name: str, term_cfg: ManagerTermBaseCfg, min_argc: int = 1): - """Resolve common term configuration. - - Usually, called by the :meth:`_prepare_terms` method to resolve common term configuration. - - Note: - By default, all term functions are expected to have at least one argument, which is the - environment object. Some other managers may expect functions to take more arguments, for - instance, the environment indices as the second argument. In such cases, the - ``min_argc`` argument can be used to specify the minimum number of arguments - required by the term function to be called correctly by the manager. - - Args: - term_name: The name of the term. - term_cfg: The term configuration. - min_argc: The minimum number of arguments required by the term function to be called correctly - by the manager. - - Raises: - TypeError: If the term configuration is not of type :class:`ManagerTermBaseCfg`. - ValueError: If the scene entity defined in the term configuration does not exist. - AttributeError: If the term function is not callable. - ValueError: If the term function's arguments are not matched by the parameters. - """ - # check if the term is a valid term config - if not isinstance(term_cfg, ManagerTermBaseCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type ManagerTermBaseCfg." - f" Received: '{type(term_cfg)}'." - ) - # iterate over all the entities and parse the joint and body names - for key, value in term_cfg.params.items(): - # deal with string - if isinstance(value, SceneEntityCfg): - # load the entity - try: - value.resolve(self._env.scene) - except ValueError as e: - raise ValueError(f"Error while parsing '{term_name}:{key}'. {e}") - # log the entity for checking later - msg = f"[{term_cfg.__class__.__name__}:{term_name}] Found entity '{value.name}'." - if value.joint_ids is not None: - msg += f"\n\tJoint names: {value.joint_names} [{value.joint_ids}]" - if value.body_ids is not None: - msg += f"\n\tBody names: {value.body_names} [{value.body_ids}]" - # print the information - carb.log_info(msg) - # store the entity - term_cfg.params[key] = value - - # get the corresponding function or functional class - if isinstance(term_cfg.func, str): - term_cfg.func = string_to_callable(term_cfg.func) - # initialize the term if it is a class - if inspect.isclass(term_cfg.func): - if not issubclass(term_cfg.func, ManagerTermBase): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type ManagerTermBase." - f" Received: '{type(term_cfg.func)}'." - ) - term_cfg.func = term_cfg.func(cfg=term_cfg, env=self._env) - # check if function is callable - if not callable(term_cfg.func): - raise AttributeError(f"The term '{term_name}' is not callable. Received: {term_cfg.func}") - # check if term's arguments are matched by params - term_params = list(term_cfg.params.keys()) - args = inspect.signature(term_cfg.func).parameters - args_with_defaults = [arg for arg in args if args[arg].default is not inspect.Parameter.empty] - args_without_defaults = [arg for arg in args if args[arg].default is inspect.Parameter.empty] - args = args_without_defaults + args_with_defaults - # ignore first two arguments for env and env_ids - # Think: Check for cases when kwargs are set inside the function? - if len(args) > min_argc: - if set(args[min_argc:]) != set(term_params + args_with_defaults): - raise ValueError( - f"The term '{term_name}' expects mandatory parameters: {args_without_defaults[min_argc:]}" - f" and optional parameters: {args_with_defaults}, but received: {term_params}." - )
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/manager_term_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/managers/manager_term_cfg.html deleted file mode 100644 index 7c059ef9e9..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/manager_term_cfg.html +++ /dev/null @@ -1,846 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.manager_term_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.manager_term_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Configuration terms for different managers."""
-
-from __future__ import annotations
-
-import torch
-import warnings
-from collections.abc import Callable
-from dataclasses import MISSING
-from typing import TYPE_CHECKING, Any
-
-from omni.isaac.lab.utils import configclass
-from omni.isaac.lab.utils.noise import NoiseCfg
-
-from .scene_entity_cfg import SceneEntityCfg
-
-if TYPE_CHECKING:
-    from .action_manager import ActionTerm
-    from .command_manager import CommandTerm
-    from .manager_base import ManagerTermBase
-
-
-
[文档]@configclass -class ManagerTermBaseCfg: - """Configuration for a manager term.""" - - func: Callable | ManagerTermBase = MISSING - """The function or class to be called for the term. - - The function must take the environment object as the first argument. - The remaining arguments are specified in the :attr:`params` attribute. - - It also supports `callable classes`_, i.e. classes that implement the :meth:`__call__` - method. In this case, the class should inherit from the :class:`ManagerTermBase` class - and implement the required methods. - - .. _`callable classes`: https://docs.python.org/3/reference/datamodel.html#object.__call__ - """ - - params: dict[str, Any | SceneEntityCfg] = dict() - """The parameters to be passed to the function as keyword arguments. Defaults to an empty dict. - - .. note:: - If the value is a :class:`SceneEntityCfg` object, the manager will query the scene entity - from the :class:`InteractiveScene` and process the entity's joints and bodies as specified - in the :class:`SceneEntityCfg` object. - """
- - -## -# Action manager. -## - - -
[文档]@configclass -class ActionTermCfg: - """Configuration for an action term.""" - - class_type: type[ActionTerm] = MISSING - """The associated action term class. - - The class should inherit from :class:`omni.isaac.lab.managers.action_manager.ActionTerm`. - """ - - asset_name: str = MISSING - """The name of the scene entity. - - This is the name defined in the scene configuration file. See the :class:`InteractiveSceneCfg` - class for more details. - """ - - debug_vis: bool = False - """Whether to visualize debug information. Defaults to False."""
- - -## -# Command manager. -## - - -
[文档]@configclass -class CommandTermCfg: - """Configuration for a command generator term.""" - - class_type: type[CommandTerm] = MISSING - """The associated command term class to use. - - The class should inherit from :class:`omni.isaac.lab.managers.command_manager.CommandTerm`. - """ - - resampling_time_range: tuple[float, float] = MISSING - """Time before commands are changed [s].""" - debug_vis: bool = False - """Whether to visualize debug information. Defaults to False."""
- - -## -# Curriculum manager. -## - - -
[文档]@configclass -class CurriculumTermCfg(ManagerTermBaseCfg): - """Configuration for a curriculum term.""" - - func: Callable[..., float | dict[str, float] | None] = MISSING - """The name of the function to be called. - - This function should take the environment object, environment indices - and any other parameters as input and return the curriculum state for - logging purposes. If the function returns None, the curriculum state - is not logged. - """
- - -## -# Observation manager. -## - - -
[文档]@configclass -class ObservationTermCfg(ManagerTermBaseCfg): - """Configuration for an observation term.""" - - func: Callable[..., torch.Tensor] = MISSING - """The name of the function to be called. - - This function should take the environment object and any other parameters - as input and return the observation signal as torch float tensors of - shape (num_envs, obs_term_dim). - """ - - noise: NoiseCfg | None = None - """The noise to add to the observation. Defaults to None, in which case no noise is added.""" - - clip: tuple[float, float] | None = None - """The clipping range for the observation after adding noise. Defaults to None, - in which case no clipping is applied.""" - - scale: float | None = None - """The scale to apply to the observation after clipping. Defaults to None, - in which case no scaling is applied (same as setting scale to :obj:`1`)."""
- - -
[文档]@configclass -class ObservationGroupCfg: - """Configuration for an observation group.""" - - concatenate_terms: bool = True - """Whether to concatenate the observation terms in the group. Defaults to True. - - If true, the observation terms in the group are concatenated along the last dimension. - Otherwise, they are kept separate and returned as a dictionary. - """ - - enable_corruption: bool = False - """Whether to enable corruption for the observation group. Defaults to False. - - If true, the observation terms in the group are corrupted by adding noise (if specified). - Otherwise, no corruption is applied. - """
- - -## -# Event manager -## - - -
[文档]@configclass -class EventTermCfg(ManagerTermBaseCfg): - """Configuration for a event term.""" - - func: Callable[..., None] = MISSING - """The name of the function to be called. - - This function should take the environment object, environment indices - and any other parameters as input. - """ - - mode: str = MISSING - """The mode in which the event term is applied. - - Note: - The mode name ``"interval"`` is a special mode that is handled by the - manager Hence, its name is reserved and cannot be used for other modes. - """ - - interval_range_s: tuple[float, float] | None = None - """The range of time in seconds at which the term is applied. - - Based on this, the interval is sampled uniformly between the specified - range for each environment instance. The term is applied on the environment - instances where the current time hits the interval time. - - Note: - This is only used if the mode is ``"interval"``. - """ - - is_global_time: bool = False - """ Whether randomization should be tracked on a per-environment basis. - - If True, the same time for the interval is tracked for all the environments instead of - tracking the time per-environment. - - Note: - This is only used if the mode is ``"interval"``. - """
- - -
[文档]@configclass -class RandomizationTermCfg(EventTermCfg): - """Configuration for a randomization term. - - .. deprecated:: v0.3.0 - - This class is deprecated and will be removed in v0.4.0. Please use :class:`EventTermCfg` instead. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Deprecation warning. - warnings.warn( - "The RandomizationTermCfg has been renamed to EventTermCfg and will be removed in v0.4.0. Please use" - " EventTermCfg instead.", - DeprecationWarning, - )
- - -## -# Reward manager. -## - - -
[文档]@configclass -class RewardTermCfg(ManagerTermBaseCfg): - """Configuration for a reward term.""" - - func: Callable[..., torch.Tensor] = MISSING - """The name of the function to be called. - - This function should take the environment object and any other parameters - as input and return the reward signals as torch float tensors of - shape (num_envs,). - """ - - weight: float = MISSING - """The weight of the reward term. - - This is multiplied with the reward term's value to compute the final - reward. - - Note: - If the weight is zero, the reward term is ignored. - """
- - -## -# Termination manager. -## - - -
[文档]@configclass -class TerminationTermCfg(ManagerTermBaseCfg): - """Configuration for a termination term.""" - - func: Callable[..., torch.Tensor] = MISSING - """The name of the function to be called. - - This function should take the environment object and any other parameters - as input and return the termination signals as torch boolean tensors of - shape (num_envs,). - """ - - time_out: bool = False - """Whether the termination term contributes towards episodic timeouts. Defaults to False. - - Note: - These usually correspond to tasks that have a fixed time limit. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/observation_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/observation_manager.html deleted file mode 100644 index bce75eef55..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/observation_manager.html +++ /dev/null @@ -1,823 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.observation_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.observation_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Observation manager for computing observation signals for a given world."""
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import ObservationGroupCfg, ObservationTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedEnv
-
-
-
[文档]class ObservationManager(ManagerBase): - """Manager for computing observation signals for a given world. - - Observations are organized into groups based on their intended usage. This allows having different observation - groups for different types of learning such as asymmetric actor-critic and student-teacher training. Each - group contains observation terms which contain information about the observation function to call, the noise - corruption model to use, and the sensor to retrieve data from. - - Each observation group should inherit from the :class:`ObservationGroupCfg` class. Within each group, each - observation term should instantiate the :class:`ObservationTermCfg` class. - """ - -
[文档] def __init__(self, cfg: object, env: ManagerBasedEnv): - """Initialize observation manager. - - Args: - cfg: The configuration object or dictionary (``dict[str, ObservationGroupCfg]``). - env: The environment instance. - """ - super().__init__(cfg, env) - # compute combined vector for obs group - self._group_obs_dim: dict[str, tuple[int, ...]] = dict() - for group_name, group_term_dims in self._group_obs_term_dim.items(): - term_dims = [torch.tensor(dims, device="cpu") for dims in group_term_dims] - self._group_obs_dim[group_name] = tuple(torch.sum(torch.stack(term_dims, dim=0), dim=0).tolist())
- - def __str__(self) -> str: - """Returns: A string representation for the observation manager.""" - msg = f"<ObservationManager> contains {len(self._group_obs_term_names)} groups.\n" - - # add info for each group - for group_name, group_dim in self._group_obs_dim.items(): - # create table for term information - table = PrettyTable() - table.title = f"Active Observation Terms in Group: '{group_name}' (shape: {group_dim})" - table.field_names = ["Index", "Name", "Shape"] - # set alignment of table columns - table.align["Name"] = "l" - # add info for each term - obs_terms = zip( - self._group_obs_term_names[group_name], - self._group_obs_term_dim[group_name], - ) - for index, (name, dims) in enumerate(obs_terms): - # resolve inputs to simplify prints - tab_dims = tuple(dims) - # add row - table.add_row([index, name, tab_dims]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def active_terms(self) -> dict[str, list[str]]: - """Name of active observation terms in each group.""" - return self._group_obs_term_names - - @property - def group_obs_dim(self) -> dict[str, tuple[int, ...]]: - """Shape of observation tensor in each group.""" - return self._group_obs_dim - - @property - def group_obs_term_dim(self) -> dict[str, list[tuple[int, ...]]]: - """Shape of observation tensor for each term in each group.""" - return self._group_obs_term_dim - - @property - def group_obs_concatenate(self) -> dict[str, bool]: - """Whether the observation terms are concatenated in each group.""" - return self._group_obs_concatenate - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: - # call all terms that are classes - for group_cfg in self._group_obs_class_term_cfgs.values(): - for term_cfg in group_cfg: - term_cfg.func.reset(env_ids=env_ids) - # nothing to log here - return {}
- -
[文档] def compute(self) -> dict[str, torch.Tensor | dict[str, torch.Tensor]]: - """Compute the observations per group for all groups. - - The method computes the observations for all the groups handled by the observation manager. - Please check the :meth:`compute_group` on the processing of observations per group. - - Returns: - A dictionary with keys as the group names and values as the computed observations. - """ - # create a buffer for storing obs from all the groups - obs_buffer = dict() - # iterate over all the terms in each group - for group_name in self._group_obs_term_names: - obs_buffer[group_name] = self.compute_group(group_name) - # otherwise return a dict with observations of all groups - return obs_buffer
- -
[文档] def compute_group(self, group_name: str) -> torch.Tensor | dict[str, torch.Tensor]: - """Computes the observations for a given group. - - The observations for a given group are computed by calling the registered functions for each - term in the group. The functions are called in the order of the terms in the group. The functions - are expected to return a tensor with shape (num_envs, ...). - - If a corruption/noise model is registered for a term, the function is called to corrupt - the observation. The corruption function is expected to return a tensor with the same - shape as the observation. The observations are clipped and scaled as per the configuration - settings. - - The operations are performed in the order: compute, add corruption/noise, clip, scale. - By default, no scaling or clipping is applied. - - Args: - group_name: The name of the group for which to compute the observations. Defaults to None, - in which case observations for all the groups are computed and returned. - - Returns: - Depending on the group's configuration, the tensors for individual observation terms are - concatenated along the last dimension into a single tensor. Otherwise, they are returned as - a dictionary with keys corresponding to the term's name. - - Raises: - ValueError: If input ``group_name`` is not a valid group handled by the manager. - """ - # check ig group name is valid - if group_name not in self._group_obs_term_names: - raise ValueError( - f"Unable to find the group '{group_name}' in the observation manager." - f" Available groups are: {list(self._group_obs_term_names.keys())}" - ) - # iterate over all the terms in each group - group_term_names = self._group_obs_term_names[group_name] - # buffer to store obs per group - group_obs = dict.fromkeys(group_term_names, None) - # read attributes for each term - obs_terms = zip(group_term_names, self._group_obs_term_cfgs[group_name]) - # evaluate terms: compute, add noise, clip, scale. - for name, term_cfg in obs_terms: - # compute term's value - obs: torch.Tensor = term_cfg.func(self._env, **term_cfg.params).clone() - # apply post-processing - if term_cfg.noise: - obs = term_cfg.noise.func(obs, term_cfg.noise) - if term_cfg.clip: - obs = obs.clip_(min=term_cfg.clip[0], max=term_cfg.clip[1]) - if term_cfg.scale: - obs = obs.mul_(term_cfg.scale) - # TODO: Introduce delay and filtering models. - # Ref: https://robosuite.ai/docs/modules/sensors.html#observables - # add value to list - group_obs[name] = obs - # concatenate all observations in the group together - if self._group_obs_concatenate[group_name]: - return torch.cat(list(group_obs.values()), dim=-1) - else: - return group_obs
- - """ - Helper functions. - """ - - def _prepare_terms(self): - """Prepares a list of observation terms functions.""" - # create buffers to store information for each observation group - # TODO: Make this more convenient by using data structures. - self._group_obs_term_names: dict[str, list[str]] = dict() - self._group_obs_term_dim: dict[str, list[int]] = dict() - self._group_obs_term_cfgs: dict[str, list[ObservationTermCfg]] = dict() - self._group_obs_class_term_cfgs: dict[str, list[ObservationTermCfg]] = dict() - self._group_obs_concatenate: dict[str, bool] = dict() - - # check if config is dict already - if isinstance(self.cfg, dict): - group_cfg_items = self.cfg.items() - else: - group_cfg_items = self.cfg.__dict__.items() - # iterate over all the groups - for group_name, group_cfg in group_cfg_items: - # check for non config - if group_cfg is None: - continue - # check if the term is a curriculum term - if not isinstance(group_cfg, ObservationGroupCfg): - raise TypeError( - f"Observation group '{group_name}' is not of type 'ObservationGroupCfg'." - f" Received: '{type(group_cfg)}'." - ) - # initialize list for the group settings - self._group_obs_term_names[group_name] = list() - self._group_obs_term_dim[group_name] = list() - self._group_obs_term_cfgs[group_name] = list() - self._group_obs_class_term_cfgs[group_name] = list() - # read common config for the group - self._group_obs_concatenate[group_name] = group_cfg.concatenate_terms - - # check if config is dict already - if isinstance(group_cfg, dict): - group_cfg_items = group_cfg.items() - else: - group_cfg_items = group_cfg.__dict__.items() - # iterate over all the terms in each group - for term_name, term_cfg in group_cfg.__dict__.items(): - # skip non-obs settings - if term_name in ["enable_corruption", "concatenate_terms"]: - continue - # check for non config - if term_cfg is None: - continue - if not isinstance(term_cfg, ObservationTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type ObservationTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # resolve common terms in the config - self._resolve_common_term_cfg(f"{group_name}/{term_name}", term_cfg, min_argc=1) - # check noise settings - if not group_cfg.enable_corruption: - term_cfg.noise = None - # add term config to list to list - self._group_obs_term_names[group_name].append(term_name) - self._group_obs_term_cfgs[group_name].append(term_cfg) - # call function the first time to fill up dimensions - obs_dims = tuple(term_cfg.func(self._env, **term_cfg.params).shape[1:]) - self._group_obs_term_dim[group_name].append(obs_dims) - # add term in a separate list if term is a class - if isinstance(term_cfg.func, ManagerTermBase): - self._group_obs_class_term_cfgs[group_name].append(term_cfg) - # call reset (in-case above call to get obs dims changed the state) - term_cfg.func.reset()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/reward_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/reward_manager.html deleted file mode 100644 index 727f643886..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/reward_manager.html +++ /dev/null @@ -1,786 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.reward_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.reward_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Reward manager for computing reward signals for a given world."""
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import RewardTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-
-
[文档]class RewardManager(ManagerBase): - """Manager for computing reward signals for a given world. - - The reward manager computes the total reward as a sum of the weighted reward terms. The reward - terms are parsed from a nested config class containing the reward manger's settings and reward - terms configuration. - - The reward terms are parsed from a config class containing the manager's settings and each term's - parameters. Each reward term should instantiate the :class:`RewardTermCfg` class. - - .. note:: - - The reward manager multiplies the reward term's ``weight`` with the time-step interval ``dt`` - of the environment. This is done to ensure that the computed reward terms are balanced with - respect to the chosen time-step interval in the environment. - - """ - - _env: ManagerBasedRLEnv - """The environment instance.""" - -
[文档] def __init__(self, cfg: object, env: ManagerBasedRLEnv): - """Initialize the reward manager. - - Args: - cfg: The configuration object or dictionary (``dict[str, RewardTermCfg]``). - env: The environment instance. - """ - super().__init__(cfg, env) - # prepare extra info to store individual reward term information - self._episode_sums = dict() - for term_name in self._term_names: - self._episode_sums[term_name] = torch.zeros(self.num_envs, dtype=torch.float, device=self.device) - # create buffer for managing reward per environment - self._reward_buf = torch.zeros(self.num_envs, dtype=torch.float, device=self.device)
- - def __str__(self) -> str: - """Returns: A string representation for reward manager.""" - msg = f"<RewardManager> contains {len(self._term_names)} active terms.\n" - - # create table for term information - table = PrettyTable() - table.title = "Active Reward Terms" - table.field_names = ["Index", "Name", "Weight"] - # set alignment of table columns - table.align["Name"] = "l" - table.align["Weight"] = "r" - # add info on each term - for index, (name, term_cfg) in enumerate(zip(self._term_names, self._term_cfgs)): - table.add_row([index, name, term_cfg.weight]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def active_terms(self) -> list[str]: - """Name of active reward terms.""" - return self._term_names - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: - """Returns the episodic sum of individual reward terms. - - Args: - env_ids: The environment ids for which the episodic sum of - individual reward terms is to be returned. Defaults to all the environment ids. - - Returns: - Dictionary of episodic sum of individual reward terms. - """ - # resolve environment ids - if env_ids is None: - env_ids = slice(None) - # store information - extras = {} - for key in self._episode_sums.keys(): - # store information - # r_1 + r_2 + ... + r_n - episodic_sum_avg = torch.mean(self._episode_sums[key][env_ids]) - extras["Episode Reward/" + key] = episodic_sum_avg / self._env.max_episode_length_s - # reset episodic sum - self._episode_sums[key][env_ids] = 0.0 - # reset all the reward terms - for term_cfg in self._class_term_cfgs: - term_cfg.func.reset(env_ids=env_ids) - # return logged information - return extras
- -
[文档] def compute(self, dt: float) -> torch.Tensor: - """Computes the reward signal as a weighted sum of individual terms. - - This function calls each reward term managed by the class and adds them to compute the net - reward signal. It also updates the episodic sums corresponding to individual reward terms. - - Args: - dt: The time-step interval of the environment. - - Returns: - The net reward signal of shape (num_envs,). - """ - # reset computation - self._reward_buf[:] = 0.0 - # iterate over all the reward terms - for name, term_cfg in zip(self._term_names, self._term_cfgs): - # skip if weight is zero (kind of a micro-optimization) - if term_cfg.weight == 0.0: - continue - # compute term's value - value = term_cfg.func(self._env, **term_cfg.params) * term_cfg.weight * dt - # update total reward - self._reward_buf += value - # update episodic sum - self._episode_sums[name] += value - - return self._reward_buf
- - """ - Operations - Term settings. - """ - -
[文档] def set_term_cfg(self, term_name: str, cfg: RewardTermCfg): - """Sets the configuration of the specified term into the manager. - - Args: - term_name: The name of the reward term. - cfg: The configuration for the reward term. - - Raises: - ValueError: If the term name is not found. - """ - if term_name not in self._term_names: - raise ValueError(f"Reward term '{term_name}' not found.") - # set the configuration - self._term_cfgs[self._term_names.index(term_name)] = cfg
- -
[文档] def get_term_cfg(self, term_name: str) -> RewardTermCfg: - """Gets the configuration for the specified term. - - Args: - term_name: The name of the reward term. - - Returns: - The configuration of the reward term. - - Raises: - ValueError: If the term name is not found. - """ - if term_name not in self._term_names: - raise ValueError(f"Reward term '{term_name}' not found.") - # return the configuration - return self._term_cfgs[self._term_names.index(term_name)]
- - """ - Helper functions. - """ - - def _prepare_terms(self): - """Prepares a list of reward functions.""" - # parse remaining reward terms and decimate their information - self._term_names: list[str] = list() - self._term_cfgs: list[RewardTermCfg] = list() - self._class_term_cfgs: list[RewardTermCfg] = list() - - # check if config is dict already - if isinstance(self.cfg, dict): - cfg_items = self.cfg.items() - else: - cfg_items = self.cfg.__dict__.items() - # iterate over all the terms - for term_name, term_cfg in cfg_items: - # check for non config - if term_cfg is None: - continue - # check for valid config type - if not isinstance(term_cfg, RewardTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type RewardTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # check for valid weight type - if not isinstance(term_cfg.weight, (float, int)): - raise TypeError( - f"Weight for the term '{term_name}' is not of type float or int." - f" Received: '{type(term_cfg.weight)}'." - ) - # resolve common parameters - self._resolve_common_term_cfg(term_name, term_cfg, min_argc=1) - # add function to list - self._term_names.append(term_name) - self._term_cfgs.append(term_cfg) - # check if the term is a class - if isinstance(term_cfg.func, ManagerTermBase): - self._class_term_cfgs.append(term_cfg)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/scene_entity_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/managers/scene_entity_cfg.html deleted file mode 100644 index 2a27e4c154..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/scene_entity_cfg.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.scene_entity_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.scene_entity_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Configuration terms for different managers."""
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.assets import Articulation, RigidObject
-from omni.isaac.lab.scene import InteractiveScene
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class SceneEntityCfg: - """Configuration for a scene entity that is used by the manager's term. - - This class is used to specify the name of the scene entity that is queried from the - :class:`InteractiveScene` and passed to the manager's term function. - """ - - name: str = MISSING - """The name of the scene entity. - - This is the name defined in the scene configuration file. See the :class:`InteractiveSceneCfg` - class for more details. - """ - - joint_names: str | list[str] | None = None - """The names of the joints from the scene entity. Defaults to None. - - The names can be either joint names or a regular expression matching the joint names. - - These are converted to joint indices on initialization of the manager and passed to the term - function as a list of joint indices under :attr:`joint_ids`. - """ - - joint_ids: list[int] | slice = slice(None) - """The indices of the joints from the asset required by the term. Defaults to slice(None), which means - all the joints in the asset (if present). - - If :attr:`joint_names` is specified, this is filled in automatically on initialization of the - manager. - """ - - fixed_tendon_names: str | list[str] | None = None - """The names of the fixed tendons from the scene entity. Defaults to None. - - The names can be either joint names or a regular expression matching the joint names. - - These are converted to fixed tendon indices on initialization of the manager and passed to the term - function as a list of fixed tendon indices under :attr:`fixed_tendon_ids`. - """ - - fixed_tendon_ids: list[int] | slice = slice(None) - """The indices of the fixed tendons from the asset required by the term. Defaults to slice(None), which means - all the fixed tendons in the asset (if present). - - If :attr:`fixed_tendon_names` is specified, this is filled in automatically on initialization of the - manager. - """ - - body_names: str | list[str] | None = None - """The names of the bodies from the asset required by the term. Defaults to None. - - The names can be either body names or a regular expression matching the body names. - - These are converted to body indices on initialization of the manager and passed to the term - function as a list of body indices under :attr:`body_ids`. - """ - - body_ids: list[int] | slice = slice(None) - """The indices of the bodies from the asset required by the term. Defaults to slice(None), which means - all the bodies in the asset. - - If :attr:`body_names` is specified, this is filled in automatically on initialization of the - manager. - """ - - preserve_order: bool = False - """Whether to preserve indices ordering to match with that in the specified joint or body names. Defaults to False. - - If False, the ordering of the indices are sorted in ascending order (i.e. the ordering in the entity's joints - or bodies). Otherwise, the indices are preserved in the order of the specified joint and body names. - - For more details, see the :meth:`omni.isaac.lab.utils.string.resolve_matching_names` function. - - .. note:: - This attribute is only used when :attr:`joint_names` or :attr:`body_names` are specified. - - """ - -
[文档] def resolve(self, scene: InteractiveScene): - """Resolves the scene entity and converts the joint and body names to indices. - - This function examines the scene entity from the :class:`InteractiveScene` and resolves the indices - and names of the joints and bodies. It is an expensive operation as it resolves regular expressions - and should be called only once. - - Args: - scene: The interactive scene instance. - - Raises: - ValueError: If the scene entity is not found. - ValueError: If both ``joint_names`` and ``joint_ids`` are specified and are not consistent. - ValueError: If both ``fixed_tendon_names`` and ``fixed_tendon_ids`` are specified and are not consistent. - ValueError: If both ``body_names`` and ``body_ids`` are specified and are not consistent. - """ - # check if the entity is valid - if self.name not in scene.keys(): - raise ValueError(f"The scene entity '{self.name}' does not exist. Available entities: {scene.keys()}.") - - # convert joint names to indices based on regex - self._resolve_joint_names(scene) - - # convert fixed tendon names to indices based on regex - self._resolve_fixed_tendon_names(scene) - - # convert body names to indices based on regex - self._resolve_body_names(scene)
- - def _resolve_joint_names(self, scene: InteractiveScene): - # convert joint names to indices based on regex - if self.joint_names is not None or self.joint_ids != slice(None): - entity: Articulation = scene[self.name] - # -- if both are not their default values, check if they are valid - if self.joint_names is not None and self.joint_ids != slice(None): - if isinstance(self.joint_names, str): - self.joint_names = [self.joint_names] - if isinstance(self.joint_ids, int): - self.joint_ids = [self.joint_ids] - joint_ids, _ = entity.find_joints(self.joint_names, preserve_order=self.preserve_order) - joint_names = [entity.joint_names[i] for i in self.joint_ids] - if joint_ids != self.joint_ids or joint_names != self.joint_names: - raise ValueError( - "Both 'joint_names' and 'joint_ids' are specified, and are not consistent." - f"\n\tfrom joint names: {self.joint_names} [{joint_ids}]" - f"\n\tfrom joint ids: {joint_names} [{self.joint_ids}]" - "\nHint: Use either 'joint_names' or 'joint_ids' to avoid confusion." - ) - # -- from joint names to joint indices - elif self.joint_names is not None: - if isinstance(self.joint_names, str): - self.joint_names = [self.joint_names] - self.joint_ids, _ = entity.find_joints(self.joint_names, preserve_order=self.preserve_order) - # performance optimization (slice offers faster indexing than list of indices) - # only all joint in the entity order are selected - if len(self.joint_ids) == entity.num_joints and self.joint_names == entity.joint_names: - self.joint_ids = slice(None) - # -- from joint indices to joint names - elif self.joint_ids != slice(None): - if isinstance(self.joint_ids, int): - self.joint_ids = [self.joint_ids] - self.joint_names = [entity.joint_names[i] for i in self.joint_ids] - - def _resolve_fixed_tendon_names(self, scene: InteractiveScene): - # convert tendon names to indices based on regex - if self.fixed_tendon_names is not None or self.fixed_tendon_ids != slice(None): - entity: Articulation = scene[self.name] - # -- if both are not their default values, check if they are valid - if self.fixed_tendon_names is not None and self.fixed_tendon_ids != slice(None): - if isinstance(self.fixed_tendon_names, str): - self.fixed_tendon_names = [self.fixed_tendon_names] - if isinstance(self.fixed_tendon_ids, int): - self.fixed_tendon_ids = [self.fixed_tendon_ids] - fixed_tendon_ids, _ = entity.find_fixed_tendons( - self.fixed_tendon_names, preserve_order=self.preserve_order - ) - fixed_tendon_names = [entity.fixed_tendon_names[i] for i in self.fixed_tendon_ids] - if fixed_tendon_ids != self.fixed_tendon_ids or fixed_tendon_names != self.fixed_tendon_names: - raise ValueError( - "Both 'fixed_tendon_names' and 'fixed_tendon_ids' are specified, and are not consistent." - f"\n\tfrom joint names: {self.fixed_tendon_names} [{fixed_tendon_ids}]" - f"\n\tfrom joint ids: {fixed_tendon_names} [{self.fixed_tendon_ids}]" - "\nHint: Use either 'fixed_tendon_names' or 'fixed_tendon_ids' to avoid confusion." - ) - # -- from fixed tendon names to fixed tendon indices - elif self.fixed_tendon_names is not None: - if isinstance(self.fixed_tendon_names, str): - self.fixed_tendon_names = [self.fixed_tendon_names] - self.fixed_tendon_ids, _ = entity.find_fixed_tendons( - self.fixed_tendon_names, preserve_order=self.preserve_order - ) - # performance optimization (slice offers faster indexing than list of indices) - # only all fixed tendon in the entity order are selected - if ( - len(self.fixed_tendon_ids) == entity.num_fixed_tendons - and self.fixed_tendon_names == entity.fixed_tendon_names - ): - self.fixed_tendon_ids = slice(None) - # -- from fixed tendon indices to fixed tendon names - elif self.fixed_tendon_ids != slice(None): - if isinstance(self.fixed_tendon_ids, int): - self.fixed_tendon_ids = [self.fixed_tendon_ids] - self.fixed_tendon_names = [entity.fixed_tendon_names[i] for i in self.fixed_tendon_ids] - - def _resolve_body_names(self, scene: InteractiveScene): - # convert body names to indices based on regex - if self.body_names is not None or self.body_ids != slice(None): - entity: RigidObject = scene[self.name] - # -- if both are not their default values, check if they are valid - if self.body_names is not None and self.body_ids != slice(None): - if isinstance(self.body_names, str): - self.body_names = [self.body_names] - if isinstance(self.body_ids, int): - self.body_ids = [self.body_ids] - body_ids, _ = entity.find_bodies(self.body_names, preserve_order=self.preserve_order) - body_names = [entity.body_names[i] for i in self.body_ids] - if body_ids != self.body_ids or body_names != self.body_names: - raise ValueError( - "Both 'body_names' and 'body_ids' are specified, and are not consistent." - f"\n\tfrom body names: {self.body_names} [{body_ids}]" - f"\n\tfrom body ids: {body_names} [{self.body_ids}]" - "\nHint: Use either 'body_names' or 'body_ids' to avoid confusion." - ) - # -- from body names to body indices - elif self.body_names is not None: - if isinstance(self.body_names, str): - self.body_names = [self.body_names] - self.body_ids, _ = entity.find_bodies(self.body_names, preserve_order=self.preserve_order) - # performance optimization (slice offers faster indexing than list of indices) - # only all bodies in the entity order are selected - if len(self.body_ids) == entity.num_bodies and self.body_names == entity.body_names: - self.body_ids = slice(None) - # -- from body indices to body names - elif self.body_ids != slice(None): - if isinstance(self.body_ids, int): - self.body_ids = [self.body_ids] - self.body_names = [entity.body_names[i] for i in self.body_ids]
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/managers/termination_manager.html b/docs/_build/html/_modules/omni/isaac/lab/managers/termination_manager.html deleted file mode 100644 index 6aadb5329e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/managers/termination_manager.html +++ /dev/null @@ -1,814 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.managers.termination_manager — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.managers.termination_manager 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Termination manager for computing done signals for a given world."""
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from prettytable import PrettyTable
-from typing import TYPE_CHECKING
-
-from .manager_base import ManagerBase, ManagerTermBase
-from .manager_term_cfg import TerminationTermCfg
-
-if TYPE_CHECKING:
-    from omni.isaac.lab.envs import ManagerBasedRLEnv
-
-
-
[文档]class TerminationManager(ManagerBase): - """Manager for computing done signals for a given world. - - The termination manager computes the termination signal (also called dones) as a combination - of termination terms. Each termination term is a function which takes the environment as an - argument and returns a boolean tensor of shape (num_envs,). The termination manager - computes the termination signal as the union (logical or) of all the termination terms. - - Following the `Gymnasium API <https://gymnasium.farama.org/tutorials/gymnasium_basics/handling_time_limits/>`_, - the termination signal is computed as the logical OR of the following signals: - - * **Time-out**: This signal is set to true if the environment has ended after an externally defined condition - (that is outside the scope of a MDP). For example, the environment may be terminated if the episode has - timed out (i.e. reached max episode length). - * **Terminated**: This signal is set to true if the environment has reached a terminal state defined by the - environment. This state may correspond to task success, task failure, robot falling, etc. - - These signals can be individually accessed using the :attr:`time_outs` and :attr:`terminated` properties. - - The termination terms are parsed from a config class containing the manager's settings and each term's - parameters. Each termination term should instantiate the :class:`TerminationTermCfg` class. The term's - configuration :attr:`TerminationTermCfg.time_out` decides whether the term is a timeout or a termination term. - """ - - _env: ManagerBasedRLEnv - """The environment instance.""" - -
[文档] def __init__(self, cfg: object, env: ManagerBasedRLEnv): - """Initializes the termination manager. - - Args: - cfg: The configuration object or dictionary (``dict[str, TerminationTermCfg]``). - env: An environment object. - """ - super().__init__(cfg, env) - # prepare extra info to store individual termination term information - self._term_dones = dict() - for term_name in self._term_names: - self._term_dones[term_name] = torch.zeros(self.num_envs, device=self.device, dtype=torch.bool) - # create buffer for managing termination per environment - self._truncated_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.bool) - self._terminated_buf = torch.zeros_like(self._truncated_buf)
- - def __str__(self) -> str: - """Returns: A string representation for termination manager.""" - msg = f"<TerminationManager> contains {len(self._term_names)} active terms.\n" - - # create table for term information - table = PrettyTable() - table.title = "Active Termination Terms" - table.field_names = ["Index", "Name", "Time Out"] - # set alignment of table columns - table.align["Name"] = "l" - # add info on each term - for index, (name, term_cfg) in enumerate(zip(self._term_names, self._term_cfgs)): - table.add_row([index, name, term_cfg.time_out]) - # convert table to string - msg += table.get_string() - msg += "\n" - - return msg - - """ - Properties. - """ - - @property - def active_terms(self) -> list[str]: - """Name of active termination terms.""" - return self._term_names - - @property - def dones(self) -> torch.Tensor: - """The net termination signal. Shape is (num_envs,).""" - return self._truncated_buf | self._terminated_buf - - @property - def time_outs(self) -> torch.Tensor: - """The timeout signal (reaching max episode length). Shape is (num_envs,). - - This signal is set to true if the environment has ended after an externally defined condition - (that is outside the scope of a MDP). For example, the environment may be terminated if the episode has - timed out (i.e. reached max episode length). - """ - return self._truncated_buf - - @property - def terminated(self) -> torch.Tensor: - """The terminated signal (reaching a terminal state). Shape is (num_envs,). - - This signal is set to true if the environment has reached a terminal state defined by the environment. - This state may correspond to task success, task failure, robot falling, etc. - """ - return self._terminated_buf - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: - """Returns the episodic counts of individual termination terms. - - Args: - env_ids: The environment ids. Defaults to None, in which case - all environments are considered. - - Returns: - Dictionary of episodic sum of individual reward terms. - """ - # resolve environment ids - if env_ids is None: - env_ids = slice(None) - # add to episode dict - extras = {} - for key in self._term_dones.keys(): - # store information - extras["Episode Termination/" + key] = torch.count_nonzero(self._term_dones[key][env_ids]).item() - # reset all the reward terms - for term_cfg in self._class_term_cfgs: - term_cfg.func.reset(env_ids=env_ids) - # return logged information - return extras
- -
[文档] def compute(self) -> torch.Tensor: - """Computes the termination signal as union of individual terms. - - This function calls each termination term managed by the class and performs a logical OR operation - to compute the net termination signal. - - Returns: - The combined termination signal of shape (num_envs,). - """ - # reset computation - self._truncated_buf[:] = False - self._terminated_buf[:] = False - # iterate over all the termination terms - for name, term_cfg in zip(self._term_names, self._term_cfgs): - value = term_cfg.func(self._env, **term_cfg.params) - # store timeout signal separately - if term_cfg.time_out: - self._truncated_buf |= value - else: - self._terminated_buf |= value - # add to episode dones - self._term_dones[name][:] = value - # return combined termination signal - return self._truncated_buf | self._terminated_buf
- -
[文档] def get_term(self, name: str) -> torch.Tensor: - """Returns the termination term with the specified name. - - Args: - name: The name of the termination term. - - Returns: - The corresponding termination term value. Shape is (num_envs,). - """ - return self._term_dones[name]
- - """ - Operations - Term settings. - """ - -
[文档] def set_term_cfg(self, term_name: str, cfg: TerminationTermCfg): - """Sets the configuration of the specified term into the manager. - - Args: - term_name: The name of the termination term. - cfg: The configuration for the termination term. - - Raises: - ValueError: If the term name is not found. - """ - if term_name not in self._term_names: - raise ValueError(f"Termination term '{term_name}' not found.") - # set the configuration - self._term_cfgs[self._term_names.index(term_name)] = cfg
- -
[文档] def get_term_cfg(self, term_name: str) -> TerminationTermCfg: - """Gets the configuration for the specified term. - - Args: - term_name: The name of the termination term. - - Returns: - The configuration of the termination term. - - Raises: - ValueError: If the term name is not found. - """ - if term_name not in self._term_names: - raise ValueError(f"Termination term '{term_name}' not found.") - # return the configuration - return self._term_cfgs[self._term_names.index(term_name)]
- - """ - Helper functions. - """ - - def _prepare_terms(self): - """Prepares a list of termination functions.""" - # parse remaining termination terms and decimate their information - self._term_names: list[str] = list() - self._term_cfgs: list[TerminationTermCfg] = list() - self._class_term_cfgs: list[TerminationTermCfg] = list() - - # check if config is dict already - if isinstance(self.cfg, dict): - cfg_items = self.cfg.items() - else: - cfg_items = self.cfg.__dict__.items() - # iterate over all the terms - for term_name, term_cfg in cfg_items: - # check for non config - if term_cfg is None: - continue - # check for valid config type - if not isinstance(term_cfg, TerminationTermCfg): - raise TypeError( - f"Configuration for the term '{term_name}' is not of type TerminationTermCfg." - f" Received: '{type(term_cfg)}'." - ) - # resolve common parameters - self._resolve_common_term_cfg(term_name, term_cfg, min_argc=1) - # add function to list - self._term_names.append(term_name) - self._term_cfgs.append(term_cfg) - # check if the term is a class - if isinstance(term_cfg.func, ManagerTermBase): - self._class_term_cfgs.append(term_cfg)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/markers/visualization_markers.html b/docs/_build/html/_modules/omni/isaac/lab/markers/visualization_markers.html deleted file mode 100644 index 1e63443ba8..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/markers/visualization_markers.html +++ /dev/null @@ -1,972 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.markers.visualization_markers — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.markers.visualization_markers 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""A class to coordinate groups of visual markers (such as spheres, frames or arrows)
-using `UsdGeom.PointInstancer`_ class.
-
-The class :class:`VisualizationMarkers` is used to create a group of visual markers and
-visualize them in the viewport. The markers are represented as :class:`UsdGeom.PointInstancer` prims
-in the USD stage. The markers are created as prototypes in the :class:`UsdGeom.PointInstancer` prim
-and are instanced in the :class:`UsdGeom.PointInstancer` prim. The markers can be visualized by
-passing the indices of the marker prototypes and their translations, orientations and scales.
-The marker prototypes can be configured with the :class:`VisualizationMarkersCfg` class.
-
-.. _UsdGeom.PointInstancer: https://graphics.pixar.com/usd/dev/api/class_usd_geom_point_instancer.html
-"""
-
-# needed to import for allowing type-hinting: np.ndarray | torch.Tensor | None
-from __future__ import annotations
-
-import numpy as np
-import torch
-from dataclasses import MISSING
-
-import omni.isaac.core.utils.stage as stage_utils
-import omni.kit.commands
-import omni.physx.scripts.utils as physx_utils
-from pxr import Gf, PhysxSchema, Sdf, Usd, UsdGeom, UsdPhysics, Vt
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.sim.spawners import SpawnerCfg
-from omni.isaac.lab.utils.configclass import configclass
-from omni.isaac.lab.utils.math import convert_quat
-
-
-
[文档]@configclass -class VisualizationMarkersCfg: - """A class to configure a :class:`VisualizationMarkers`.""" - - prim_path: str = MISSING - """The prim path where the :class:`UsdGeom.PointInstancer` will be created.""" - - markers: dict[str, SpawnerCfg] = MISSING - """The dictionary of marker configurations. - - The key is the name of the marker, and the value is the configuration of the marker. - The key is used to identify the marker in the class. - """
- - -
[文档]class VisualizationMarkers: - """A class to coordinate groups of visual markers (loaded from USD). - - This class allows visualization of different UI markers in the scene, such as points and frames. - The class wraps around the `UsdGeom.PointInstancer`_ for efficient handling of objects - in the stage via instancing the created marker prototype prims. - - A marker prototype prim is a reusable template prim used for defining variations of objects - in the scene. For example, a sphere prim can be used as a marker prototype prim to create - multiple sphere prims in the scene at different locations. Thus, prototype prims are useful - for creating multiple instances of the same prim in the scene. - - The class parses the configuration to create different the marker prototypes into the stage. Each marker - prototype prim is created as a child of the :class:`UsdGeom.PointInstancer` prim. The prim path for the - the marker prim is resolved using the key of the marker in the :attr:`VisualizationMarkersCfg.markers` - dictionary. The marker prototypes are created using the :meth:`omni.isaac.core.utils.create_prim` - function, and then then instanced using :class:`UsdGeom.PointInstancer` prim to allow creating multiple - instances of the marker prims. - - Switching between different marker prototypes is possible by calling the :meth:`visualize` method with - the prototype indices corresponding to the marker prototype. The prototype indices are based on the order - in the :attr:`VisualizationMarkersCfg.markers` dictionary. For example, if the dictionary has two markers, - "marker1" and "marker2", then their prototype indices are 0 and 1 respectively. The prototype indices - can be passed as a list or array of integers. - - Usage: - The following snippet shows how to create 24 sphere markers with a radius of 1.0 at random translations - within the range [-1.0, 1.0]. The first 12 markers will be colored red and the rest will be colored green. - - .. code-block:: python - - import omni.isaac.lab.sim as sim_utils - from omni.isaac.lab.markers import VisualizationMarkersCfg, VisualizationMarkers - - # Create the markers configuration - # This creates two marker prototypes, "marker1" and "marker2" which are spheres with a radius of 1.0. - # The color of "marker1" is red and the color of "marker2" is green. - cfg = VisualizationMarkersCfg( - prim_path="/World/Visuals/testMarkers", - markers={ - "marker1": sim_utils.SphereCfg( - radius=1.0, - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)), - ), - "marker2": VisualizationMarkersCfg.SphereCfg( - radius=1.0, - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)), - ), - } - ) - # Create the markers instance - # This will create a UsdGeom.PointInstancer prim at the given path along with the marker prototypes. - marker = VisualizationMarkers(cfg) - - # Set position of the marker - # -- randomly sample translations between -1.0 and 1.0 - marker_translations = np.random.uniform(-1.0, 1.0, (24, 3)) - # -- this will create 24 markers at the given translations - # note: the markers will all be `marker1` since the marker indices are not given - marker.visualize(translations=marker_translations) - - # alter the markers based on their prototypes indices - # first 12 markers will be marker1 and the rest will be marker2 - # 0 -> marker1, 1 -> marker2 - marker_indices = [0] * 12 + [1] * 12 - # this will change the marker prototypes at the given indices - # note: the translations of the markers will not be changed from the previous call - # since the translations are not given. - marker.visualize(marker_indices=marker_indices) - - # alter the markers based on their prototypes indices and translations - marker.visualize(marker_indices=marker_indices, translations=marker_translations) - - .. _UsdGeom.PointInstancer: https://graphics.pixar.com/usd/dev/api/class_usd_geom_point_instancer.html - - """ - -
[文档] def __init__(self, cfg: VisualizationMarkersCfg): - """Initialize the class. - - When the class is initialized, the :class:`UsdGeom.PointInstancer` is created into the stage - and the marker prims are registered into it. - - .. note:: - If a prim already exists at the given path, the function will find the next free path - and create the :class:`UsdGeom.PointInstancer` prim there. - - Args: - cfg: The configuration for the markers. - - Raises: - ValueError: When no markers are provided in the :obj:`cfg`. - """ - # get next free path for the prim - prim_path = stage_utils.get_next_free_path(cfg.prim_path) - # create a new prim - stage = stage_utils.get_current_stage() - self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path) - # store inputs - self.prim_path = prim_path - self.cfg = cfg - # check if any markers is provided - if len(self.cfg.markers) == 0: - raise ValueError(f"The `cfg.markers` cannot be empty. Received: {self.cfg.markers}") - - # create a child prim for the marker - self._add_markers_prototypes(self.cfg.markers) - # Note: We need to do this the first time to initialize the instancer. - # Otherwise, the instancer will not be "created" and the function `GetInstanceIndices()` will fail. - self._instancer_manager.GetProtoIndicesAttr().Set(list(range(self.num_prototypes))) - self._instancer_manager.GetPositionsAttr().Set([Gf.Vec3f(0.0)] * self.num_prototypes) - self._count = self.num_prototypes
- - def __str__(self) -> str: - """Return: A string representation of the class.""" - msg = f"VisualizationMarkers(prim_path={self.prim_path})" - msg += f"\n\tCount: {self.count}" - msg += f"\n\tNumber of prototypes: {self.num_prototypes}" - msg += "\n\tMarkers Prototypes:" - for index, (name, marker) in enumerate(self.cfg.markers.items()): - msg += f"\n\t\t[Index: {index}]: {name}: {marker.to_dict()}" - return msg - - """ - Properties. - """ - - @property - def num_prototypes(self) -> int: - """The number of marker prototypes available.""" - return len(self.cfg.markers) - - @property - def count(self) -> int: - """The total number of marker instances.""" - # TODO: Update this when the USD API is available (Isaac Sim 2023.1) - # return self._instancer_manager.GetInstanceCount() - return self._count - - """ - Operations. - """ - -
[文档] def set_visibility(self, visible: bool): - """Sets the visibility of the markers. - - The method does this through the USD API. - - Args: - visible: flag to set the visibility. - """ - imageable = UsdGeom.Imageable(self._instancer_manager) - if visible: - imageable.MakeVisible() - else: - imageable.MakeInvisible()
- -
[文档] def is_visible(self) -> bool: - """Checks the visibility of the markers. - - Returns: - True if the markers are visible, False otherwise. - """ - return self._instancer_manager.GetVisibilityAttr().Get() != UsdGeom.Tokens.invisible
- -
[文档] def visualize( - self, - translations: np.ndarray | torch.Tensor | None = None, - orientations: np.ndarray | torch.Tensor | None = None, - scales: np.ndarray | torch.Tensor | None = None, - marker_indices: list[int] | np.ndarray | torch.Tensor | None = None, - ): - """Update markers in the viewport. - - .. note:: - If the prim `PointInstancer` is hidden in the stage, the function will simply return - without updating the markers. This helps in unnecessary computation when the markers - are not visible. - - Whenever updating the markers, the input arrays must have the same number of elements - in the first dimension. If the number of elements is different, the `UsdGeom.PointInstancer` - will raise an error complaining about the mismatch. - - Additionally, the function supports dynamic update of the markers. This means that the - number of markers can change between calls. For example, if you have 24 points that you - want to visualize, you can pass 24 translations, orientations, and scales. If you want to - visualize only 12 points, you can pass 12 translations, orientations, and scales. The - function will automatically update the number of markers in the scene. - - The function will also update the marker prototypes based on their prototype indices. For instance, - if you have two marker prototypes, and you pass the following marker indices: [0, 1, 0, 1], the function - will update the first and third markers with the first prototype, and the second and fourth markers - with the second prototype. This is useful when you want to visualize different markers in the same - scene. The list of marker indices must have the same number of elements as the translations, orientations, - or scales. If the number of elements is different, the function will raise an error. - - .. caution:: - This function will update all the markers instanced from the prototypes. That means - if you have 24 markers, you will need to pass 24 translations, orientations, and scales. - - If you want to update only a subset of the markers, you will need to handle the indices - yourself and pass the complete arrays to this function. - - Args: - translations: Translations w.r.t. parent prim frame. Shape is (M, 3). - Defaults to None, which means left unchanged. - orientations: Quaternion orientations (w, x, y, z) w.r.t. parent prim frame. Shape is (M, 4). - Defaults to None, which means left unchanged. - scales: Scale applied before any rotation is applied. Shape is (M, 3). - Defaults to None, which means left unchanged. - marker_indices: Decides which marker prototype to visualize. Shape is (M). - Defaults to None, which means left unchanged provided that the total number of markers - is the same as the previous call. If the number of markers is different, the function - will update the number of markers in the scene. - - Raises: - ValueError: When input arrays do not follow the expected shapes. - ValueError: When the function is called with all None arguments. - """ - # check if it is visible (if not then let's not waste time) - if not self.is_visible(): - return - # check if we have any markers to visualize - num_markers = 0 - # resolve inputs - # -- position - if translations is not None: - if isinstance(translations, torch.Tensor): - translations = translations.detach().cpu().numpy() - # check that shape is correct - if translations.shape[1] != 3 or len(translations.shape) != 2: - raise ValueError(f"Expected `translations` to have shape (M, 3). Received: {translations.shape}.") - # apply translations - self._instancer_manager.GetPositionsAttr().Set(Vt.Vec3fArray.FromNumpy(translations)) - # update number of markers - num_markers = translations.shape[0] - # -- orientation - if orientations is not None: - if isinstance(orientations, torch.Tensor): - orientations = orientations.detach().cpu().numpy() - # check that shape is correct - if orientations.shape[1] != 4 or len(orientations.shape) != 2: - raise ValueError(f"Expected `orientations` to have shape (M, 4). Received: {orientations.shape}.") - # roll orientations from (w, x, y, z) to (x, y, z, w) - # internally USD expects (x, y, z, w) - orientations = convert_quat(orientations, to="xyzw") - # apply orientations - self._instancer_manager.GetOrientationsAttr().Set(Vt.QuathArray.FromNumpy(orientations)) - # update number of markers - num_markers = orientations.shape[0] - # -- scales - if scales is not None: - if isinstance(scales, torch.Tensor): - scales = scales.detach().cpu().numpy() - # check that shape is correct - if scales.shape[1] != 3 or len(scales.shape) != 2: - raise ValueError(f"Expected `scales` to have shape (M, 3). Received: {scales.shape}.") - # apply scales - self._instancer_manager.GetScalesAttr().Set(Vt.Vec3fArray.FromNumpy(scales)) - # update number of markers - num_markers = scales.shape[0] - # -- status - if marker_indices is not None or num_markers != self._count: - # apply marker indices - if marker_indices is not None: - if isinstance(marker_indices, torch.Tensor): - marker_indices = marker_indices.detach().cpu().numpy() - elif isinstance(marker_indices, list): - marker_indices = np.array(marker_indices) - # check that shape is correct - if len(marker_indices.shape) != 1: - raise ValueError(f"Expected `marker_indices` to have shape (M,). Received: {marker_indices.shape}.") - # apply proto indices - self._instancer_manager.GetProtoIndicesAttr().Set(Vt.IntArray.FromNumpy(marker_indices)) - # update number of markers - num_markers = marker_indices.shape[0] - else: - # check that number of markers is not zero - if num_markers == 0: - raise ValueError("Number of markers cannot be zero! Hint: The function was called with no inputs?") - # set all markers to be the first prototype - self._instancer_manager.GetProtoIndicesAttr().Set([0] * num_markers) - # set number of markers - self._count = num_markers
- - """ - Helper functions. - """ - - def _add_markers_prototypes(self, markers_cfg: dict[str, sim_utils.SpawnerCfg]): - """Adds markers prototypes to the scene and sets the markers instancer to use them.""" - # add markers based on config - for name, cfg in markers_cfg.items(): - # resolve prim path - marker_prim_path = f"{self.prim_path}/{name}" - # create a child prim for the marker - prim = cfg.func(prim_path=marker_prim_path, cfg=cfg) - # make the asset uninstanceable (in case it is) - # point instancer defines its own prototypes so if an asset is already instanced, this doesn't work. - self._process_prototype_prim(prim) - # remove any physics on the markers because they are only for visualization! - physx_utils.removeRigidBodySubtree(prim) - # add child reference to point instancer - self._instancer_manager.GetPrototypesRel().AddTarget(marker_prim_path) - # check that we loaded all the prototypes - prototypes = self._instancer_manager.GetPrototypesRel().GetTargets() - if len(prototypes) != len(markers_cfg): - raise RuntimeError( - f"Failed to load all the prototypes. Expected: {len(markers_cfg)}. Received: {len(prototypes)}." - ) - - def _process_prototype_prim(self, prim: Usd.Prim): - """Process a prim and its descendants to make them suitable for defining prototypes. - - Point instancer defines its own prototypes so if an asset is already instanced, this doesn't work. - This function checks if the prim at the specified prim path and its descendants are instanced. - If so, it makes the respective prim uninstanceable by disabling instancing on the prim. - - Additionally, it makes the prim invisible to secondary rays. This is useful when we do not want - to see the marker prims on camera images. - - Args: - prim_path: The prim path to check. - stage: The stage where the prim exists. - Defaults to None, in which case the current stage is used. - """ - # check if prim is valid - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim.GetPrimAtPath()}' is not valid.") - # iterate over all prims under prim-path - all_prims = [prim] - while len(all_prims) > 0: - # get current prim - child_prim = all_prims.pop(0) - # check if it is physics body -> if so, remove it - if child_prim.HasAPI(UsdPhysics.ArticulationRootAPI): - child_prim.RemoveAPI(UsdPhysics.ArticulationRootAPI) - child_prim.RemoveAPI(PhysxSchema.PhysxArticulationAPI) - if child_prim.HasAPI(UsdPhysics.RigidBodyAPI): - child_prim.RemoveAPI(UsdPhysics.RigidBodyAPI) - child_prim.RemoveAPI(PhysxSchema.PhysxRigidBodyAPI) - if child_prim.IsA(UsdPhysics.Joint): - child_prim.GetAttribute("physics:jointEnabled").Set(False) - # check if prim is instanced -> if so, make it uninstanceable - if child_prim.IsInstance(): - child_prim.SetInstanceable(False) - # check if prim is a mesh -> if so, make it invisible to secondary rays - if child_prim.IsA(UsdGeom.Gprim): - # invisible to secondary rays such as depth images - omni.kit.commands.execute( - "ChangePropertyCommand", - prop_path=Sdf.Path(f"{child_prim.GetPrimPath().pathString}.primvars:invisibleToSecondaryRays"), - value=True, - prev=None, - type_to_create_if_not_exist=Sdf.ValueTypeNames.Bool, - ) - # add children to list - all_prims += child_prim.GetChildren()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/scene/interactive_scene.html b/docs/_build/html/_modules/omni/isaac/lab/scene/interactive_scene.html deleted file mode 100644 index b739d3c740..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/scene/interactive_scene.html +++ /dev/null @@ -1,987 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.scene.interactive_scene — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.scene.interactive_scene 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import builtins
-import torch
-from collections.abc import Sequence
-from typing import Any
-
-import carb
-import omni.usd
-from omni.isaac.cloner import GridCloner
-from omni.isaac.core.prims import XFormPrimView
-from omni.isaac.version import get_version
-from pxr import PhysxSchema
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.assets import Articulation, ArticulationCfg, AssetBaseCfg, RigidObject, RigidObjectCfg
-from omni.isaac.lab.sensors import ContactSensorCfg, FrameTransformerCfg, SensorBase, SensorBaseCfg
-from omni.isaac.lab.terrains import TerrainImporter, TerrainImporterCfg
-
-from .interactive_scene_cfg import InteractiveSceneCfg
-
-
-
[文档]class InteractiveScene: - """A scene that contains entities added to the simulation. - - The interactive scene parses the :class:`InteractiveSceneCfg` class to create the scene. - Based on the specified number of environments, it clones the entities and groups them into different - categories (e.g., articulations, sensors, etc.). - - Each entity is registered to scene based on its name in the configuration class. For example, if the user - specifies a robot in the configuration class as follows: - - .. code-block:: python - - from omni.isaac.lab.scene import InteractiveSceneCfg - from omni.isaac.lab.utils import configclass - - from omni.isaac.lab_assets.anymal import ANYMAL_C_CFG - - @configclass - class MySceneCfg(InteractiveSceneCfg): - - robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - - Then the robot can be accessed from the scene as follows: - - .. code-block:: python - - from omni.isaac.lab.scene import InteractiveScene - - # create 128 environments - scene = InteractiveScene(cfg=MySceneCfg(num_envs=128)) - - # access the robot from the scene - robot = scene["robot"] - # access the robot based on its type - robot = scene.articulations["robot"] - - .. note:: - It is important to note that the scene only performs common operations on the entities. For example, - resetting the internal buffers, writing the buffers to the simulation and updating the buffers from the - simulation. The scene does not perform any task specific to the entity. For example, it does not apply - actions to the robot or compute observations from the robot. These tasks are handled by different - modules called "managers" in the framework. Please refer to the :mod:`omni.isaac.lab.managers` sub-package - for more details. - """ - -
[文档] def __init__(self, cfg: InteractiveSceneCfg): - """Initializes the scene. - - Args: - cfg: The configuration class for the scene. - """ - # store inputs - self.cfg = cfg - # initialize scene elements - self._terrain = None - self._articulations = dict() - self._rigid_objects = dict() - self._sensors = dict() - self._extras = dict() - # obtain the current stage - self.stage = omni.usd.get_context().get_stage() - # physics scene path - self._physics_scene_path = None - # prepare cloner for environment replication - self.cloner = GridCloner(spacing=self.cfg.env_spacing) - self.cloner.define_base_env(self.env_ns) - self.env_prim_paths = self.cloner.generate_paths(f"{self.env_ns}/env", self.cfg.num_envs) - # create source prim - self.stage.DefinePrim(self.env_prim_paths[0], "Xform") - # clone the env xform - env_origins = self.cloner.clone( - source_prim_path=self.env_prim_paths[0], - prim_paths=self.env_prim_paths, - replicate_physics=False, - copy_from_source=True, - ) - self._default_env_origins = torch.tensor(env_origins, device=self.device, dtype=torch.float32) - self._global_prim_paths = list() - if self._is_scene_setup_from_cfg(): - # add entities from config - self._add_entities_from_cfg() - # replicate physics if we have more than one environment - # this is done to make scene initialization faster at play time - if self.cfg.replicate_physics and self.cfg.num_envs > 1: - self.cloner.replicate_physics( - source_prim_path=self.env_prim_paths[0], - prim_paths=self.env_prim_paths, - base_env_path=self.env_ns, - root_path=self.env_regex_ns.replace(".*", ""), - ) - - self.filter_collisions(self._global_prim_paths) - - # read isaac sim version (this includes build tag, release tag etc.) - # note: we do it once here because it reads the VERSION file from disk and is not expected to change. - self._isaac_sim_version = int(get_version()[0][0])
- -
[文档] def clone_environments(self, copy_from_source: bool = False): - """Creates clones of the environment ``/World/envs/env_0``. - - Args: - copy_from_source: (bool): If set to False, clones inherit from /World/envs/env_0 and mirror its changes. - If True, clones are independent copies of the source prim and won't reflect its changes (start-up time - may increase). Defaults to False. - """ - self.cloner.clone( - source_prim_path=self.env_prim_paths[0], - prim_paths=self.env_prim_paths, - replicate_physics=self.cfg.replicate_physics, - copy_from_source=copy_from_source, - )
- -
[文档] def filter_collisions(self, global_prim_paths: list[str] = []): - """Filter environments collisions. - - Disables collisions between the environments in ``/World/envs/env_.*`` and enables collisions with the prims - in global prim paths (e.g. ground plane). - - Args: - global_prim_paths: The global prim paths to enable collisions with. - """ - # obtain the current physics scene - physics_scene_prim_path = self.physics_scene_path - - # filter collisions within each environment instance - self.cloner.filter_collisions( - physics_scene_prim_path, - "/World/collisions", - self.env_prim_paths, - global_paths=global_prim_paths, - )
- - def __str__(self) -> str: - """Returns a string representation of the scene.""" - msg = f"<class {self.__class__.__name__}>\n" - msg += f"\tNumber of environments: {self.cfg.num_envs}\n" - msg += f"\tEnvironment spacing : {self.cfg.env_spacing}\n" - msg += f"\tSource prim name : {self.env_prim_paths[0]}\n" - msg += f"\tGlobal prim paths : {self._global_prim_paths}\n" - msg += f"\tReplicate physics : {self.cfg.replicate_physics}" - return msg - - """ - Properties. - """ - - @property - def physics_scene_path(self): - """Search the stage for the physics scene""" - if self._physics_scene_path is None: - for prim in self.stage.Traverse(): - if prim.HasAPI(PhysxSchema.PhysxSceneAPI): - self._physics_scene_path = prim.GetPrimPath() - carb.log_info(f"Physics scene prim path: {self._physics_scene_path}") - break - return self._physics_scene_path - - @property - def physics_dt(self) -> float: - """The physics timestep of the scene.""" - return sim_utils.SimulationContext.instance().get_physics_dt() # pyright: ignore [reportOptionalMemberAccess] - - @property - def device(self) -> str: - """The device on which the scene is created.""" - return sim_utils.SimulationContext.instance().device # pyright: ignore [reportOptionalMemberAccess] - - @property - def env_ns(self) -> str: - """The namespace ``/World/envs`` in which all environments created. - - The environments are present w.r.t. this namespace under "env_{N}" prim, - where N is a natural number. - """ - return "/World/envs" - - @property - def env_regex_ns(self) -> str: - """The namespace ``/World/envs/env_.*`` in which all environments created.""" - return f"{self.env_ns}/env_.*" - - @property - def num_envs(self) -> int: - """The number of environments handled by the scene.""" - return self.cfg.num_envs - - @property - def env_origins(self) -> torch.Tensor: - """The origins of the environments in the scene. Shape is (num_envs, 3).""" - if self._terrain is not None: - return self._terrain.env_origins - else: - return self._default_env_origins - - @property - def terrain(self) -> TerrainImporter | None: - """The terrain in the scene. If None, then the scene has no terrain. - - Note: - We treat terrain separate from :attr:`extras` since terrains define environment origins and are - handled differently from other miscellaneous entities. - """ - return self._terrain - - @property - def articulations(self) -> dict[str, Articulation]: - """A dictionary of articulations in the scene.""" - return self._articulations - - @property - def rigid_objects(self) -> dict[str, RigidObject]: - """A dictionary of rigid objects in the scene.""" - return self._rigid_objects - - @property - def sensors(self) -> dict[str, SensorBase]: - """A dictionary of the sensors in the scene, such as cameras and contact reporters.""" - return self._sensors - - @property - def extras(self) -> dict[str, XFormPrimView]: - """A dictionary of miscellaneous simulation objects that neither inherit from assets nor sensors. - - The keys are the names of the miscellaneous objects, and the values are the `XFormPrimView`_ - of the corresponding prims. - - As an example, lights or other props in the scene that do not have any attributes or properties that you - want to alter at runtime can be added to this dictionary. - - Note: - These are not reset or updated by the scene. They are mainly other prims that are not necessarily - handled by the interactive scene, but are useful to be accessed by the user. - - .. _XFormPrimView: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#omni.isaac.core.prims.XFormPrimView - - """ - return self._extras - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - """Resets the scene entities. - - Args: - env_ids: The indices of the environments to reset. - Defaults to None (all instances). - """ - # -- assets - for articulation in self._articulations.values(): - articulation.reset(env_ids) - for rigid_object in self._rigid_objects.values(): - rigid_object.reset(env_ids) - # -- sensors - for sensor in self._sensors.values(): - sensor.reset(env_ids) - if builtins.ISAAC_LAUNCHED_FROM_TERMINAL and self._isaac_sim_version < 4: - # -- flush physics sim view if called in extension mode and Isaac Sim version is < 4.0 - # this is needed when using PhysX GPU pipeline since the data needs to be sent to the underlying - # PhysX buffers that might live on a separate device - # note: In standalone mode, this method is called in the `step()` method of the simulation context. - # So we only need to flush when running in extension mode. - sim_utils.SimulationContext.instance().physics_sim_view.flush() # pyright: ignore [reportOptionalMemberAccess]
- -
[文档] def write_data_to_sim(self): - """Writes the data of the scene entities to the simulation.""" - # -- assets - for articulation in self._articulations.values(): - articulation.write_data_to_sim() - for rigid_object in self._rigid_objects.values(): - rigid_object.write_data_to_sim() - if builtins.ISAAC_LAUNCHED_FROM_TERMINAL and self._isaac_sim_version < 4: - # -- flush physics sim view if called in extension mode and Isaac Sim version is < 4.0 - # this is needed when using PhysX GPU pipeline since the data needs to be sent to the underlying - # PhysX buffers that might live on a separate device - # note: In standalone mode, this method is called in the `step()` method of the simulation context. - # So we only need to flush when running in extension mode. - sim_utils.SimulationContext.instance().physics_sim_view.flush() # pyright: ignore [reportOptionalMemberAccess]
- -
[文档] def update(self, dt: float) -> None: - """Update the scene entities. - - Args: - dt: The amount of time passed from last :meth:`update` call. - """ - # -- assets - for articulation in self._articulations.values(): - articulation.update(dt) - for rigid_object in self._rigid_objects.values(): - rigid_object.update(dt) - # -- sensors - for sensor in self._sensors.values(): - sensor.update(dt, force_recompute=not self.cfg.lazy_sensor_update)
- - """ - Operations: Iteration. - """ - -
[文档] def keys(self) -> list[str]: - """Returns the keys of the scene entities. - - Returns: - The keys of the scene entities. - """ - all_keys = ["terrain"] - for asset_family in [self._articulations, self._rigid_objects, self._sensors, self._extras]: - all_keys += list(asset_family.keys()) - return all_keys
- - def __getitem__(self, key: str) -> Any: - """Returns the scene entity with the given key. - - Args: - key: The key of the scene entity. - - Returns: - The scene entity. - """ - # check if it is a terrain - if key == "terrain": - return self._terrain - - all_keys = ["terrain"] - # check if it is in other dictionaries - for asset_family in [self._articulations, self._rigid_objects, self._sensors, self._extras]: - out = asset_family.get(key) - # if found, return - if out is not None: - return out - all_keys += list(asset_family.keys()) - # if not found, raise error - raise KeyError(f"Scene entity with key '{key}' not found. Available Entities: '{all_keys}'") - - """ - Internal methods. - """ - - def _is_scene_setup_from_cfg(self): - return any( - not (asset_name in InteractiveSceneCfg.__dataclass_fields__ or asset_cfg is None) - for asset_name, asset_cfg in self.cfg.__dict__.items() - ) - - def _add_entities_from_cfg(self): - """Add scene entities from the config.""" - # store paths that are in global collision filter - self._global_prim_paths = list() - # parse the entire scene config and resolve regex - for asset_name, asset_cfg in self.cfg.__dict__.items(): - # skip keywords - # note: easier than writing a list of keywords: [num_envs, env_spacing, lazy_sensor_update] - if asset_name in InteractiveSceneCfg.__dataclass_fields__ or asset_cfg is None: - continue - # resolve regex - asset_cfg.prim_path = asset_cfg.prim_path.format(ENV_REGEX_NS=self.env_regex_ns) - # create asset - if isinstance(asset_cfg, TerrainImporterCfg): - # terrains are special entities since they define environment origins - asset_cfg.num_envs = self.cfg.num_envs - asset_cfg.env_spacing = self.cfg.env_spacing - self._terrain = asset_cfg.class_type(asset_cfg) - elif isinstance(asset_cfg, ArticulationCfg): - self._articulations[asset_name] = asset_cfg.class_type(asset_cfg) - elif isinstance(asset_cfg, RigidObjectCfg): - self._rigid_objects[asset_name] = asset_cfg.class_type(asset_cfg) - elif isinstance(asset_cfg, SensorBaseCfg): - # Update target frame path(s)' regex name space for FrameTransformer - if isinstance(asset_cfg, FrameTransformerCfg): - updated_target_frames = [] - for target_frame in asset_cfg.target_frames: - target_frame.prim_path = target_frame.prim_path.format(ENV_REGEX_NS=self.env_regex_ns) - updated_target_frames.append(target_frame) - asset_cfg.target_frames = updated_target_frames - elif isinstance(asset_cfg, ContactSensorCfg): - updated_filter_prim_paths_expr = [] - for filter_prim_path in asset_cfg.filter_prim_paths_expr: - updated_filter_prim_paths_expr.append(filter_prim_path.format(ENV_REGEX_NS=self.env_regex_ns)) - asset_cfg.filter_prim_paths_expr = updated_filter_prim_paths_expr - - self._sensors[asset_name] = asset_cfg.class_type(asset_cfg) - elif isinstance(asset_cfg, AssetBaseCfg): - # manually spawn asset - if asset_cfg.spawn is not None: - asset_cfg.spawn.func( - asset_cfg.prim_path, - asset_cfg.spawn, - translation=asset_cfg.init_state.pos, - orientation=asset_cfg.init_state.rot, - ) - # store xform prim view corresponding to this asset - # all prims in the scene are Xform prims (i.e. have a transform component) - self._extras[asset_name] = XFormPrimView(asset_cfg.prim_path, reset_xform_properties=False) - else: - raise ValueError(f"Unknown asset config type for {asset_name}: {asset_cfg}") - # store global collision paths - if hasattr(asset_cfg, "collision_group") and asset_cfg.collision_group == -1: - asset_paths = sim_utils.find_matching_prim_paths(asset_cfg.prim_path) - self._global_prim_paths += asset_paths
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/scene/interactive_scene_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/scene/interactive_scene_cfg.html deleted file mode 100644 index 80c8338c2b..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/scene/interactive_scene_cfg.html +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.scene.interactive_scene_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.scene.interactive_scene_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.utils.configclass import configclass
-
-
-
[文档]@configclass -class InteractiveSceneCfg: - """Configuration for the interactive scene. - - The users can inherit from this class to add entities to their scene. This is then parsed by the - :class:`InteractiveScene` class to create the scene. - - .. note:: - The adding of entities to the scene is sensitive to the order of the attributes in the configuration. - Please make sure to add the entities in the order you want them to be added to the scene. - The recommended order of specification is terrain, physics-related assets (articulations and rigid bodies), - sensors and non-physics-related assets (lights). - - For example, to add a robot to the scene, the user can create a configuration class as follows: - - .. code-block:: python - - import omni.isaac.lab.sim as sim_utils - from omni.isaac.lab.assets import AssetBaseCfg - from omni.isaac.lab.scene import InteractiveSceneCfg - from omni.isaac.lab.sensors.ray_caster import GridPatternCfg, RayCasterCfg - from omni.isaac.lab.utils import configclass - - from omni.isaac.lab_assets.anymal import ANYMAL_C_CFG - - @configclass - class MySceneCfg(InteractiveSceneCfg): - - # terrain - flat terrain plane - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - ) - - # articulation - robot 1 - robot_1 = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot_1") - # articulation - robot 2 - robot_2 = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot_2") - robot_2.init_state.pos = (0.0, 1.0, 0.6) - - # sensor - ray caster attached to the base of robot 1 that scans the ground - height_scanner = RayCasterCfg( - prim_path="{ENV_REGEX_NS}/Robot_1/base", - offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), - attach_yaw_only=True, - pattern_cfg=GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), - debug_vis=True, - mesh_prim_paths=["/World/ground"], - ) - - # extras - light - light = AssetBaseCfg( - prim_path="/World/light", - spawn=sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)), - init_state=AssetBaseCfg.InitialStateCfg(pos=(0.0, 0.0, 500.0)), - ) - - """ - - num_envs: int = MISSING - """Number of environment instances handled by the scene.""" - - env_spacing: float = MISSING - """Spacing between environments. - - This is the default distance between environment origins in the scene. Used only when the - number of environments is greater than one. - """ - - lazy_sensor_update: bool = True - """Whether to update sensors only when they are accessed. Default is True. - - If true, the sensor data is only updated when their attribute ``data`` is accessed. Otherwise, the sensor - data is updated every time sensors are updated. - """ - - replicate_physics: bool = True - """Enable/disable replication of physics schemas when using the Cloner APIs. Default is True."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera.html deleted file mode 100644 index 24836c0706..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera.html +++ /dev/null @@ -1,1210 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.camera.camera — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.camera.camera 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import math
-import numpy as np
-import re
-import torch
-from collections.abc import Sequence
-from tensordict import TensorDict
-from typing import TYPE_CHECKING, Any, Literal
-
-import carb
-import omni.kit.commands
-import omni.usd
-from omni.isaac.core.prims import XFormPrimView
-from pxr import UsdGeom
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.utils import to_camel_case
-from omni.isaac.lab.utils.array import convert_to_torch
-from omni.isaac.lab.utils.math import quat_from_matrix
-
-from ..sensor_base import SensorBase
-from .camera_data import CameraData
-from .utils import convert_orientation_convention, create_rotation_matrix_from_view
-
-if TYPE_CHECKING:
-    from .camera_cfg import CameraCfg
-
-
-
[文档]class Camera(SensorBase): - r"""The camera sensor for acquiring visual data. - - This class wraps over the `UsdGeom Camera`_ for providing a consistent API for acquiring visual data. - It ensures that the camera follows the ROS convention for the coordinate system. - - Summarizing from the `replicator extension`_, the following sensor types are supported: - - - ``"rgb"``: A rendered color image. - - ``"distance_to_camera"``: An image containing the distance to camera optical center. - - ``"distance_to_image_plane"``: An image containing distances of 3D points from camera plane along camera's z-axis. - - ``"normals"``: An image containing the local surface normal vectors at each pixel. - - ``"motion_vectors"``: An image containing the motion vector data at each pixel. - - ``"semantic_segmentation"``: The semantic segmentation data. - - ``"instance_segmentation_fast"``: The instance segmentation data. - - ``"instance_id_segmentation_fast"``: The instance id segmentation data. - - .. note:: - Currently the following sensor types are not supported in a "view" format: - - - ``"instance_segmentation"``: The instance segmentation data. Please use the fast counterparts instead. - - ``"instance_id_segmentation"``: The instance id segmentation data. Please use the fast counterparts instead. - - ``"bounding_box_2d_tight"``: The tight 2D bounding box data (only contains non-occluded regions). - - ``"bounding_box_2d_tight_fast"``: The tight 2D bounding box data (only contains non-occluded regions). - - ``"bounding_box_2d_loose"``: The loose 2D bounding box data (contains occluded regions). - - ``"bounding_box_2d_loose_fast"``: The loose 2D bounding box data (contains occluded regions). - - ``"bounding_box_3d"``: The 3D view space bounding box data. - - ``"bounding_box_3d_fast"``: The 3D view space bounding box data. - - .. _replicator extension: https://docs.omniverse.nvidia.com/extensions/latest/ext_replicator/annotators_details.html#annotator-output - .. _USDGeom Camera: https://graphics.pixar.com/usd/docs/api/class_usd_geom_camera.html - - """ - - cfg: CameraCfg - """The configuration parameters.""" - - UNSUPPORTED_TYPES: set[str] = { - "instance_id_segmentation", - "instance_segmentation", - "bounding_box_2d_tight", - "bounding_box_2d_loose", - "bounding_box_3d", - "bounding_box_2d_tight_fast", - "bounding_box_2d_loose_fast", - "bounding_box_3d_fast", - } - """The set of sensor types that are not supported by the camera class.""" - -
[文档] def __init__(self, cfg: CameraCfg): - """Initializes the camera sensor. - - Args: - cfg: The configuration parameters. - - Raises: - RuntimeError: If no camera prim is found at the given path. - ValueError: If the provided data types are not supported by the camera. - """ - # check if sensor path is valid - # note: currently we do not handle environment indices if there is a regex pattern in the leaf - # For example, if the prim path is "/World/Sensor_[1,2]". - sensor_path = cfg.prim_path.split("/")[-1] - sensor_path_is_regex = re.match(r"^[a-zA-Z0-9/_]+$", sensor_path) is None - if sensor_path_is_regex: - raise RuntimeError( - f"Invalid prim path for the camera sensor: {self.cfg.prim_path}." - "\n\tHint: Please ensure that the prim path does not contain any regex patterns in the leaf." - ) - # perform check on supported data types - self._check_supported_data_types(cfg) - # initialize base class - super().__init__(cfg) - - # toggle rendering of rtx sensors as True - # this flag is read by SimulationContext to determine if rtx sensors should be rendered - carb_settings_iface = carb.settings.get_settings() - carb_settings_iface.set_bool("/isaaclab/render/rtx_sensors", True) - - # spawn the asset - if self.cfg.spawn is not None: - # compute the rotation offset - rot = torch.tensor(self.cfg.offset.rot, dtype=torch.float32).unsqueeze(0) - rot_offset = convert_orientation_convention(rot, origin=self.cfg.offset.convention, target="opengl") - rot_offset = rot_offset.squeeze(0).numpy() - # spawn the asset - self.cfg.spawn.func( - self.cfg.prim_path, self.cfg.spawn, translation=self.cfg.offset.pos, orientation=rot_offset - ) - # check that spawn was successful - matching_prims = sim_utils.find_matching_prims(self.cfg.prim_path) - if len(matching_prims) == 0: - raise RuntimeError(f"Could not find prim with path {self.cfg.prim_path}.") - - # UsdGeom Camera prim for the sensor - self._sensor_prims: list[UsdGeom.Camera] = list() - # Create empty variables for storing output data - self._data = CameraData()
- - def __del__(self): - """Unsubscribes from callbacks and detach from the replicator registry.""" - # unsubscribe callbacks - super().__del__() - # delete from replicator registry - for _, annotators in self._rep_registry.items(): - for annotator, render_product_path in zip(annotators, self._render_product_paths): - annotator.detach([render_product_path]) - annotator = None - - def __str__(self) -> str: - """Returns: A string containing information about the instance.""" - # message for class - return ( - f"Camera @ '{self.cfg.prim_path}': \n" - f"\tdata types : {self.data.output.sorted_keys} \n" - f"\tsemantic filter : {self.cfg.semantic_filter}\n" - f"\tcolorize semantic segm. : {self.cfg.colorize_semantic_segmentation}\n" - f"\tcolorize instance segm. : {self.cfg.colorize_instance_segmentation}\n" - f"\tcolorize instance id segm.: {self.cfg.colorize_instance_id_segmentation}\n" - f"\tupdate period (s): {self.cfg.update_period}\n" - f"\tshape : {self.image_shape}\n" - f"\tnumber of sensors : {self._view.count}" - ) - - """ - Properties - """ - - @property - def num_instances(self) -> int: - return self._view.count - - @property - def data(self) -> CameraData: - # update sensors if needed - self._update_outdated_buffers() - # return the data - return self._data - - @property - def frame(self) -> torch.tensor: - """Frame number when the measurement took place.""" - return self._frame - - @property - def render_product_paths(self) -> list[str]: - """The path of the render products for the cameras. - - This can be used via replicator interfaces to attach to writes or external annotator registry. - """ - return self._render_product_paths - - @property - def image_shape(self) -> tuple[int, int]: - """A tuple containing (height, width) of the camera sensor.""" - return (self.cfg.height, self.cfg.width) - - """ - Configuration - """ - -
[文档] def set_intrinsic_matrices( - self, matrices: torch.Tensor, focal_length: float = 1.0, env_ids: Sequence[int] | None = None - ): - """Set parameters of the USD camera from its intrinsic matrix. - - The intrinsic matrix and focal length are used to set the following parameters to the USD camera: - - - ``focal_length``: The focal length of the camera. - - ``horizontal_aperture``: The horizontal aperture of the camera. - - ``vertical_aperture``: The vertical aperture of the camera. - - ``horizontal_aperture_offset``: The horizontal offset of the camera. - - ``vertical_aperture_offset``: The vertical offset of the camera. - - .. warning:: - - Due to limitations of Omniverse camera, we need to assume that the camera is a spherical lens, - i.e. has square pixels, and the optical center is centered at the camera eye. If this assumption - is not true in the input intrinsic matrix, then the camera will not set up correctly. - - Args: - matrices: The intrinsic matrices for the camera. Shape is (N, 3, 3). - focal_length: Focal length to use when computing aperture values. Defaults to 1.0. - env_ids: A sensor ids to manipulate. Defaults to None, which means all sensor indices. - """ - # resolve env_ids - if env_ids is None: - env_ids = self._ALL_INDICES - # convert matrices to numpy tensors - if isinstance(matrices, torch.Tensor): - matrices = matrices.cpu().numpy() - else: - matrices = np.asarray(matrices, dtype=float) - # iterate over env_ids - for i, intrinsic_matrix in zip(env_ids, matrices): - # extract parameters from matrix - f_x = intrinsic_matrix[0, 0] - c_x = intrinsic_matrix[0, 2] - f_y = intrinsic_matrix[1, 1] - c_y = intrinsic_matrix[1, 2] - # get viewport parameters - height, width = self.image_shape - height, width = float(height), float(width) - # resolve parameters for usd camera - params = { - "focal_length": focal_length, - "horizontal_aperture": width * focal_length / f_x, - "vertical_aperture": height * focal_length / f_y, - "horizontal_aperture_offset": (c_x - width / 2) / f_x, - "vertical_aperture_offset": (c_y - height / 2) / f_y, - } - # change data for corresponding camera index - sensor_prim = self._sensor_prims[i] - # set parameters for camera - for param_name, param_value in params.items(): - # convert to camel case (CC) - param_name = to_camel_case(param_name, to="CC") - # get attribute from the class - param_attr = getattr(sensor_prim, f"Get{param_name}Attr") - # set value - # note: We have to do it this way because the camera might be on a different - # layer (default cameras are on session layer), and this is the simplest - # way to set the property on the right layer. - omni.usd.set_prop_val(param_attr(), param_value) - # update the internal buffers - self._update_intrinsic_matrices(env_ids)
- - """ - Operations - Set pose. - """ - -
[文档] def set_world_poses( - self, - positions: torch.Tensor | None = None, - orientations: torch.Tensor | None = None, - env_ids: Sequence[int] | None = None, - convention: Literal["opengl", "ros", "world"] = "ros", - ): - r"""Set the pose of the camera w.r.t. the world frame using specified convention. - - Since different fields use different conventions for camera orientations, the method allows users to - set the camera poses in the specified convention. Possible conventions are: - - - :obj:`"opengl"` - forward axis: -Z - up axis +Y - Offset is applied in the OpenGL (Usd.Camera) convention - - :obj:`"ros"` - forward axis: +Z - up axis -Y - Offset is applied in the ROS convention - - :obj:`"world"` - forward axis: +X - up axis +Z - Offset is applied in the World Frame convention - - See :meth:`omni.isaac.lab.sensors.camera.utils.convert_orientation_convention` for more details - on the conventions. - - Args: - positions: The cartesian coordinates (in meters). Shape is (N, 3). - Defaults to None, in which case the camera position in not changed. - orientations: The quaternion orientation in (w, x, y, z). Shape is (N, 4). - Defaults to None, in which case the camera orientation in not changed. - env_ids: A sensor ids to manipulate. Defaults to None, which means all sensor indices. - convention: The convention in which the poses are fed. Defaults to "ros". - - Raises: - RuntimeError: If the camera prim is not set. Need to call :meth:`initialize` method first. - """ - # resolve env_ids - if env_ids is None: - env_ids = self._ALL_INDICES - # convert to backend tensor - if positions is not None: - if isinstance(positions, np.ndarray): - positions = torch.from_numpy(positions).to(device=self._device) - elif not isinstance(positions, torch.Tensor): - positions = torch.tensor(positions, device=self._device) - # convert rotation matrix from input convention to OpenGL - if orientations is not None: - if isinstance(orientations, np.ndarray): - orientations = torch.from_numpy(orientations).to(device=self._device) - elif not isinstance(orientations, torch.Tensor): - orientations = torch.tensor(orientations, device=self._device) - orientations = convert_orientation_convention(orientations, origin=convention, target="opengl") - # set the pose - self._view.set_world_poses(positions, orientations, env_ids)
- -
[文档] def set_world_poses_from_view( - self, eyes: torch.Tensor, targets: torch.Tensor, env_ids: Sequence[int] | None = None - ): - """Set the poses of the camera from the eye position and look-at target position. - - Args: - eyes: The positions of the camera's eye. Shape is (N, 3). - targets: The target locations to look at. Shape is (N, 3). - env_ids: A sensor ids to manipulate. Defaults to None, which means all sensor indices. - - Raises: - RuntimeError: If the camera prim is not set. Need to call :meth:`initialize` method first. - NotImplementedError: If the stage up-axis is not "Y" or "Z". - """ - # resolve env_ids - if env_ids is None: - env_ids = self._ALL_INDICES - # set camera poses using the view - orientations = quat_from_matrix(create_rotation_matrix_from_view(eyes, targets, device=self._device)) - self._view.set_world_poses(eyes, orientations, env_ids)
- - """ - Operations - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - if not self._is_initialized: - raise RuntimeError( - "Camera could not be initialized. Please ensure --enable_cameras is used to enable rendering." - ) - # reset the timestamps - super().reset(env_ids) - # resolve None - # note: cannot do smart indexing here since we do a for loop over data. - if env_ids is None: - env_ids = self._ALL_INDICES - # reset the data - # note: this recomputation is useful if one performs events such as randomizations on the camera poses. - self._update_poses(env_ids) - # Reset the frame count - self._frame[env_ids] = 0
- - """ - Implementation. - """ - - def _initialize_impl(self): - """Initializes the sensor handles and internal buffers. - - This function creates handles and registers the provided data types with the replicator registry to - be able to access the data from the sensor. It also initializes the internal buffers to store the data. - - Raises: - RuntimeError: If the number of camera prims in the view does not match the number of environments. - RuntimeError: If replicator was not found. - """ - try: - import omni.replicator.core as rep - except ModuleNotFoundError: - raise RuntimeError( - "Replicator was not found for rendering. Please use --enable_cameras to enable rendering." - ) - from omni.syntheticdata.scripts.SyntheticData import SyntheticData - - # Initialize parent class - super()._initialize_impl() - # Create a view for the sensor - self._view = XFormPrimView(self.cfg.prim_path, reset_xform_properties=False) - self._view.initialize() - # Check that sizes are correct - if self._view.count != self._num_envs: - raise RuntimeError( - f"Number of camera prims in the view ({self._view.count}) does not match" - f" the number of environments ({self._num_envs})." - ) - - # Create all env_ids buffer - self._ALL_INDICES = torch.arange(self._view.count, device=self._device, dtype=torch.long) - # Create frame count buffer - self._frame = torch.zeros(self._view.count, device=self._device, dtype=torch.long) - - # Attach the sensor data types to render node - self._render_product_paths: list[str] = list() - self._rep_registry: dict[str, list[rep.annotators.Annotator]] = {name: list() for name in self.cfg.data_types} - - # Obtain current stage - stage = omni.usd.get_context().get_stage() - # Convert all encapsulated prims to Camera - for cam_prim_path in self._view.prim_paths: - # Get camera prim - cam_prim = stage.GetPrimAtPath(cam_prim_path) - # Check if prim is a camera - if not cam_prim.IsA(UsdGeom.Camera): - raise RuntimeError(f"Prim at path '{cam_prim_path}' is not a Camera.") - # Add to list - sensor_prim = UsdGeom.Camera(cam_prim) - self._sensor_prims.append(sensor_prim) - - # Get render product - # From Isaac Sim 2023.1 onwards, render product is a HydraTexture so we need to extract the path - render_prod_path = rep.create.render_product(cam_prim_path, resolution=(self.cfg.width, self.cfg.height)) - if not isinstance(render_prod_path, str): - render_prod_path = render_prod_path.path - self._render_product_paths.append(render_prod_path) - - # Check if semantic types or semantic filter predicate is provided - if isinstance(self.cfg.semantic_filter, list): - semantic_filter_predicate = ":*; ".join(self.cfg.semantic_filter) + ":*" - elif isinstance(self.cfg.semantic_filter, str): - semantic_filter_predicate = self.cfg.semantic_filter - else: - raise ValueError(f"Semantic types must be a list or a string. Received: {self.cfg.semantic_filter}.") - # set the semantic filter predicate - # copied from rep.scripts.writes_default.basic_writer.py - SyntheticData.Get().set_instance_mapping_semantic_filter(semantic_filter_predicate) - - # Iterate over each data type and create annotator - # TODO: This will move out of the loop once Replicator supports multiple render products within a single - # annotator, i.e.: rep_annotator.attach(self._render_product_paths) - for name in self.cfg.data_types: - # note: we are verbose here to make it easier to understand the code. - # if colorize is true, the data is mapped to colors and a uint8 4 channel image is returned. - # if colorize is false, the data is returned as a uint32 image with ids as values. - if name == "semantic_segmentation": - init_params = {"colorize": self.cfg.colorize_semantic_segmentation} - elif name == "instance_segmentation_fast": - init_params = {"colorize": self.cfg.colorize_instance_segmentation} - elif name == "instance_id_segmentation_fast": - init_params = {"colorize": self.cfg.colorize_instance_id_segmentation} - else: - init_params = None - - # Resolve device name - if "cuda" in self._device: - device_name = self._device.split(":")[0] - else: - device_name = "cpu" - - # create annotator node - rep_annotator = rep.AnnotatorRegistry.get_annotator(name, init_params, device=device_name) - rep_annotator.attach(render_prod_path) - # add to registry - self._rep_registry[name].append(rep_annotator) - - # Create internal buffers - self._create_buffers() - self._update_intrinsic_matrices(self._ALL_INDICES) - - def _update_buffers_impl(self, env_ids: Sequence[int]): - # Increment frame count - self._frame[env_ids] += 1 - # -- pose - self._update_poses(env_ids) - # -- read the data from annotator registry - # check if buffer is called for the first time. If so then, allocate the memory - if len(self._data.output.sorted_keys) == 0: - # this is the first time buffer is called - # it allocates memory for all the sensors - self._create_annotator_data() - else: - # iterate over all the data types - for name, annotators in self._rep_registry.items(): - # iterate over all the annotators - for index in env_ids: - # get the output - output = annotators[index].get_data() - # process the output - data, info = self._process_annotator_output(name, output) - # add data to output - self._data.output[name][index] = data - # add info to output - self._data.info[index][name] = info - - """ - Private Helpers - """ - - def _check_supported_data_types(self, cfg: CameraCfg): - """Checks if the data types are supported by the ray-caster camera.""" - # check if there is any intersection in unsupported types - # reason: these use np structured data types which we can't yet convert to torch tensor - common_elements = set(cfg.data_types) & Camera.UNSUPPORTED_TYPES - if common_elements: - # provide alternative fast counterparts - fast_common_elements = [] - for item in common_elements: - if "instance_segmentation" in item or "instance_id_segmentation" in item: - fast_common_elements.append(item + "_fast") - # raise error - raise ValueError( - f"Camera class does not support the following sensor types: {common_elements}." - "\n\tThis is because these sensor types output numpy structured data types which" - "can't be converted to torch tensors easily." - "\n\tHint: If you need to work with these sensor types, we recommend using their fast counterparts." - f"\n\t\tFast counterparts: {fast_common_elements}" - ) - - def _create_buffers(self): - """Create buffers for storing data.""" - # create the data object - # -- pose of the cameras - self._data.pos_w = torch.zeros((self._view.count, 3), device=self._device) - self._data.quat_w_world = torch.zeros((self._view.count, 4), device=self._device) - # -- intrinsic matrix - self._data.intrinsic_matrices = torch.zeros((self._view.count, 3, 3), device=self._device) - self._data.image_shape = self.image_shape - # -- output data - # lazy allocation of data dictionary - # since the size of the output data is not known in advance, we leave it as None - # the memory will be allocated when the buffer() function is called for the first time. - self._data.output = TensorDict({}, batch_size=self._view.count, device=self.device) - self._data.info = [{name: None for name in self.cfg.data_types} for _ in range(self._view.count)] - - def _update_intrinsic_matrices(self, env_ids: Sequence[int]): - """Compute camera's matrix of intrinsic parameters. - - Also called calibration matrix. This matrix works for linear depth images. We assume square pixels. - - Note: - The calibration matrix projects points in the 3D scene onto an imaginary screen of the camera. - The coordinates of points on the image plane are in the homogeneous representation. - """ - # iterate over all cameras - for i in env_ids: - # Get corresponding sensor prim - sensor_prim = self._sensor_prims[i] - # get camera parameters - focal_length = sensor_prim.GetFocalLengthAttr().Get() - horiz_aperture = sensor_prim.GetHorizontalApertureAttr().Get() - # get viewport parameters - height, width = self.image_shape - # calculate the field of view - fov = 2 * math.atan(horiz_aperture / (2 * focal_length)) - # calculate the focal length in pixels - focal_px = width * 0.5 / math.tan(fov / 2) - # create intrinsic matrix for depth linear - self._data.intrinsic_matrices[i, 0, 0] = focal_px - self._data.intrinsic_matrices[i, 0, 2] = width * 0.5 - self._data.intrinsic_matrices[i, 1, 1] = focal_px - self._data.intrinsic_matrices[i, 1, 2] = height * 0.5 - self._data.intrinsic_matrices[i, 2, 2] = 1 - - def _update_poses(self, env_ids: Sequence[int]): - """Computes the pose of the camera in the world frame with ROS convention. - - This methods uses the ROS convention to resolve the input pose. In this convention, - we assume that the camera front-axis is +Z-axis and up-axis is -Y-axis. - - Returns: - A tuple of the position (in meters) and quaternion (w, x, y, z). - """ - # check camera prim exists - if len(self._sensor_prims) == 0: - raise RuntimeError("Camera prim is None. Please call 'sim.play()' first.") - - # get the poses from the view - poses, quat = self._view.get_world_poses(env_ids) - self._data.pos_w[env_ids] = poses - self._data.quat_w_world[env_ids] = convert_orientation_convention(quat, origin="opengl", target="world") - - def _create_annotator_data(self): - """Create the buffers to store the annotator data. - - We create a buffer for each annotator and store the data in a dictionary. Since the data - shape is not known beforehand, we create a list of buffers and concatenate them later. - - This is an expensive operation and should be called only once. - """ - # add data from the annotators - for name, annotators in self._rep_registry.items(): - # create a list to store the data for each annotator - data_all_cameras = list() - # iterate over all the annotators - for index in self._ALL_INDICES: - # get the output - output = annotators[index].get_data() - # process the output - data, info = self._process_annotator_output(name, output) - # append the data - data_all_cameras.append(data) - # store the info - self._data.info[index][name] = info - # concatenate the data along the batch dimension - self._data.output[name] = torch.stack(data_all_cameras, dim=0) - - def _process_annotator_output(self, name: str, output: Any) -> tuple[torch.tensor, dict | None]: - """Process the annotator output. - - This function is called after the data has been collected from all the cameras. - """ - # extract info and data from the output - if isinstance(output, dict): - data = output["data"] - info = output["info"] - else: - data = output - info = None - # convert data into torch tensor - data = convert_to_torch(data, device=self.device) - - # process data for different segmentation types - # Note: Replicator returns raw buffers of dtype int32 for segmentation types - # so we need to convert them to uint8 4 channel images for colorized types - height, width = self.image_shape - if name == "semantic_segmentation": - if self.cfg.colorize_semantic_segmentation: - data = data.view(torch.uint8).reshape(height, width, -1) - else: - data = data.view(height, width) - elif name == "instance_segmentation_fast": - if self.cfg.colorize_instance_segmentation: - data = data.view(torch.uint8).reshape(height, width, -1) - else: - data = data.view(height, width) - elif name == "instance_id_segmentation_fast": - if self.cfg.colorize_instance_id_segmentation: - data = data.view(torch.uint8).reshape(height, width, -1) - else: - data = data.view(height, width) - - # return the data and info - return data, info - - """ - Internal simulation callbacks. - """ - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - # call parent - super()._invalidate_initialize_callback(event) - # set all existing views to None to invalidate them - self._view = None
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera_cfg.html deleted file mode 100644 index a44e40c5b1..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera_cfg.html +++ /dev/null @@ -1,671 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.camera.camera_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.camera.camera_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.sim import FisheyeCameraCfg, PinholeCameraCfg
-from omni.isaac.lab.utils import configclass
-
-from ..sensor_base_cfg import SensorBaseCfg
-from .camera import Camera
-
-
-
[文档]@configclass -class CameraCfg(SensorBaseCfg): - """Configuration for a camera sensor.""" - -
[文档] @configclass - class OffsetCfg: - """The offset pose of the sensor's frame from the sensor's parent frame.""" - - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" - - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation (w, x, y, z) w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0).""" - - convention: Literal["opengl", "ros", "world"] = "ros" - """The convention in which the frame offset is applied. Defaults to "ros". - - - ``"opengl"`` - forward axis: ``-Z`` - up axis: ``+Y`` - Offset is applied in the OpenGL (Usd.Camera) convention. - - ``"ros"`` - forward axis: ``+Z`` - up axis: ``-Y`` - Offset is applied in the ROS convention. - - ``"world"`` - forward axis: ``+X`` - up axis: ``+Z`` - Offset is applied in the World Frame convention. - - """
- - class_type: type = Camera - - offset: OffsetCfg = OffsetCfg() - """The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity. - - Note: - The parent frame is the frame the sensor attaches to. For example, the parent frame of a - camera at path ``/World/envs/env_0/Robot/Camera`` is ``/World/envs/env_0/Robot``. - """ - - spawn: PinholeCameraCfg | FisheyeCameraCfg | None = MISSING - """Spawn configuration for the asset. - - If None, then the prim is not spawned by the asset. Instead, it is assumed that the - asset is already present in the scene. - """ - - data_types: list[str] = ["rgb"] - """List of sensor names/types to enable for the camera. Defaults to ["rgb"]. - - Please refer to the :class:`Camera` class for a list of available data types. - """ - - width: int = MISSING - """Width of the image in pixels.""" - - height: int = MISSING - """Height of the image in pixels.""" - - semantic_filter: str | list[str] = "*:*" - """A string or a list specifying a semantic filter predicate. Defaults to ``"*:*"``. - - If a string, it should be a disjunctive normal form of (semantic type, labels). For examples: - - * ``"typeA : labelA & !labelB | labelC , typeB: labelA ; typeC: labelE"``: - All prims with semantic type "typeA" and label "labelA" but not "labelB" or with label "labelC". - Also, all prims with semantic type "typeB" and label "labelA", or with semantic type "typeC" and label "labelE". - * ``"typeA : * ; * : labelA"``: All prims with semantic type "typeA" or with label "labelA" - - If a list of strings, each string should be a semantic type. The segmentation for prims with - semantics of the specified types will be retrieved. For example, if the list is ["class"], only - the segmentation for prims with semantics of type "class" will be retrieved. - - .. seealso:: - - For more information on the semantics filter, see the documentation on `Replicator Semantics Schema Editor`_. - - .. _Replicator Semantics Schema Editor: https://docs.omniverse.nvidia.com/extensions/latest/ext_replicator/semantics_schema_editor.html#semantics-filtering - """ - - colorize_semantic_segmentation: bool = True - """Whether to colorize the semantic segmentation images. Defaults to True. - - If True, semantic segmentation is converted to an image where semantic IDs are mapped to colors - and returned as a ``uint8`` 4-channel array. If False, the output is returned as a ``int32`` array. - """ - - colorize_instance_id_segmentation: bool = True - """Whether to colorize the instance ID segmentation images. Defaults to True. - - If True, instance id segmentation is converted to an image where instance IDs are mapped to colors. - and returned as a ``uint8`` 4-channel array. If False, the output is returned as a ``int32`` array. - """ - - colorize_instance_segmentation: bool = True - """Whether to colorize the instance ID segmentation images. Defaults to True. - - If True, instance segmentation is converted to an image where instance IDs are mapped to colors. - and returned as a ``uint8`` 4-channel array. If False, the output is returned as a ``int32`` array. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera_data.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera_data.html deleted file mode 100644 index 129e6a21ed..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/camera_data.html +++ /dev/null @@ -1,655 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.camera.camera_data — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.camera.camera_data 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-from dataclasses import dataclass
-from tensordict import TensorDict
-from typing import Any
-
-from .utils import convert_orientation_convention
-
-
-
[文档]@dataclass -class CameraData: - """Data container for the camera sensor.""" - - ## - # Frame state. - ## - - pos_w: torch.Tensor = None - """Position of the sensor origin in world frame, following ROS convention. - - Shape is (N, 3) where N is the number of sensors. - """ - - quat_w_world: torch.Tensor = None - """Quaternion orientation `(w, x, y, z)` of the sensor origin in world frame, following the world coordinate frame - - .. note:: - World frame convention follows the camera aligned with forward axis +X and up axis +Z. - - Shape is (N, 4) where N is the number of sensors. - """ - - ## - # Camera data - ## - - image_shape: tuple[int, int] = None - """A tuple containing (height, width) of the camera sensor.""" - - intrinsic_matrices: torch.Tensor = None - """The intrinsic matrices for the camera. - - Shape is (N, 3, 3) where N is the number of sensors. - """ - - output: TensorDict = None - """The retrieved sensor data with sensor types as key. - - The format of the data is available in the `Replicator Documentation`_. For semantic-based data, - this corresponds to the ``"data"`` key in the output of the sensor. - - .. _Replicator Documentation: https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_replicator/annotators_details.html#annotator-output - """ - - info: list[dict[str, Any]] = None - """The retrieved sensor info with sensor types as key. - - This contains extra information provided by the sensor such as semantic segmentation label mapping, prim paths. - For semantic-based data, this corresponds to the ``"info"`` key in the output of the sensor. For other sensor - types, the info is empty. - """ - - ## - # Additional Frame orientation conventions - ## - - @property - def quat_w_ros(self) -> torch.Tensor: - """Quaternion orientation `(w, x, y, z)` of the sensor origin in the world frame, following ROS convention. - - .. note:: - ROS convention follows the camera aligned with forward axis +Z and up axis -Y. - - Shape is (N, 4) where N is the number of sensors. - """ - return convert_orientation_convention(self.quat_w_world, origin="world", target="ros") - - @property - def quat_w_opengl(self) -> torch.Tensor: - """Quaternion orientation `(w, x, y, z)` of the sensor origin in the world frame, following - Opengl / USD Camera convention. - - .. note:: - OpenGL convention follows the camera aligned with forward axis -Z and up axis +Y. - - Shape is (N, 4) where N is the number of sensors. - """ - return convert_orientation_convention(self.quat_w_world, origin="world", target="opengl")
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/tiled_camera.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/tiled_camera.html deleted file mode 100644 index bf0cb66407..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/tiled_camera.html +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.camera.tiled_camera — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.camera.tiled_camera 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import math
-import numpy as np
-import torch
-from collections.abc import Sequence
-from tensordict import TensorDict
-from typing import TYPE_CHECKING, Any
-
-import omni.usd
-import warp as wp
-from omni.isaac.core.prims import XFormPrimView
-from pxr import UsdGeom
-
-from omni.isaac.lab.utils.warp.kernels import reshape_tiled_image
-
-from ..sensor_base import SensorBase
-from .camera import Camera
-
-if TYPE_CHECKING:
-    from .camera_cfg import TiledCameraCfg
-
-
-
[文档]class TiledCamera(Camera): - r"""The tiled rendering based camera sensor for acquiring RGB and depth data. - - This class inherits from the :class:`Camera` class but uses the tiled-rendering API from Replicator to acquire - the visual data. Tiled-rendering concatenates the rendered images from multiple cameras into a single image. - This allows for rendering multiple cameras in parallel and is useful for rendering large scenes with multiple - cameras efficiently. - - The following sensor types are supported: - - - ``"rgb"``: A rendered color image. - - ``"depth"``: An image containing the distance to camera optical center. - - .. attention:: - Please note that the fidelity of RGB images may be lower than the standard camera sensor due to the - tiled rendering process. Various ray tracing effects such as reflections, refractions, and shadows may not be - accurately captured in the RGB images. We are currently working on improving the fidelity of the RGB images. - - .. versionadded:: v1.0.0 - - This feature is available starting from Isaac Sim 4.0. Before this version, the tiled rendering APIs - were not available. - - """ - - cfg: TiledCameraCfg - """The configuration parameters.""" - - SUPPORTED_TYPES: set[str] = {"rgb", "depth"} - """The set of sensor types that are supported.""" - -
[文档] def __init__(self, cfg: TiledCameraCfg): - """Initializes the tiled camera sensor. - - Args: - cfg: The configuration parameters. - - Raises: - RuntimeError: If no camera prim is found at the given path. - ValueError: If the provided data types are not supported by the camera. - """ - super().__init__(cfg)
- - def __del__(self): - """Unsubscribes from callbacks and detach from the replicator registry.""" - # unsubscribe from callbacks - SensorBase.__del__(self) - # detach from the replicator registry - self._annotator.detach(self.render_product_paths) - - def __str__(self) -> str: - """Returns: A string containing information about the instance.""" - # message for class - return ( - f"Tiled Camera @ '{self.cfg.prim_path}': \n" - f"\tdata types : {self.data.output.sorted_keys} \n" - f"\tupdate period (s): {self.cfg.update_period}\n" - f"\tshape : {self.image_shape}\n" - f"\tnumber of sensors : {self._view.count}" - ) - - """ - Operations - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - if not self._is_initialized: - raise RuntimeError( - "TiledCamera could not be initialized. Please ensure --enable_cameras is used to enable rendering." - ) - # reset the timestamps - SensorBase.reset(self, env_ids) - # resolve None - if env_ids is None: - env_ids = slice(None) - # reset the frame count - self._frame[env_ids] = 0
- - """ - Implementation. - """ - - def _initialize_impl(self): - """Initializes the sensor handles and internal buffers. - - This function creates handles and registers the provided data types with the replicator registry to - be able to access the data from the sensor. It also initializes the internal buffers to store the data. - - Raises: - RuntimeError: If the number of camera prims in the view does not match the number of environments. - RuntimeError: If replicator was not found. - """ - try: - import omni.replicator.core as rep - except ModuleNotFoundError: - raise RuntimeError( - "Replicator was not found for rendering. Please use --enable_cameras to enable rendering." - ) - - # Initialize parent class - SensorBase._initialize_impl(self) - # Create a view for the sensor - self._view = XFormPrimView(self.cfg.prim_path, reset_xform_properties=False) - self._view.initialize() - # Check that sizes are correct - if self._view.count != self._num_envs: - raise RuntimeError( - f"Number of camera prims in the view ({self._view.count}) does not match" - f" the number of environments ({self._num_envs})." - ) - - # Create all env_ids buffer - self._ALL_INDICES = torch.arange(self._view.count, device=self._device, dtype=torch.long) - # Create frame count buffer - self._frame = torch.zeros(self._view.count, device=self._device, dtype=torch.long) - - # Obtain current stage - stage = omni.usd.get_context().get_stage() - # Convert all encapsulated prims to Camera - for cam_prim_path in self._view.prim_paths: - # Get camera prim - cam_prim = stage.GetPrimAtPath(cam_prim_path) - # Check if prim is a camera - if not cam_prim.IsA(UsdGeom.Camera): - raise RuntimeError(f"Prim at path '{cam_prim_path}' is not a Camera.") - # Add to list - sensor_prim = UsdGeom.Camera(cam_prim) - self._sensor_prims.append(sensor_prim) - - # start the orchestrator (if not already started) - rep.orchestrator._orchestrator._is_started = True - # Create a tiled sensor from the camera prims - rep_sensor = rep.create.tiled_sensor( - cameras=self._view.prim_paths, - camera_resolution=[self.image_shape[1], self.image_shape[0]], - tiled_resolution=self._tiled_image_shape(), - output_types=self.cfg.data_types, - ) - # Get render product - render_prod_path = rep.create.render_product(camera=rep_sensor, resolution=self._tiled_image_shape()) - if not isinstance(render_prod_path, str): - render_prod_path = render_prod_path.path - self._render_product_paths = [render_prod_path] - # Attach the annotator - self._annotator = rep.AnnotatorRegistry.get_annotator("RtxSensorGpu", device=self.device, do_array_copy=False) - self._annotator.attach(self._render_product_paths) - - # Create internal buffers - self._create_buffers() - - def _update_buffers_impl(self, env_ids: Sequence[int]): - # Increment frame count - self._frame[env_ids] += 1 - - # Extract the flattened image buffer - tiled_data_buffer = self._annotator.get_data() - if isinstance(tiled_data_buffer, np.ndarray): - tiled_data_buffer = wp.array(tiled_data_buffer, device=self.device) - else: - tiled_data_buffer = tiled_data_buffer.to(device=self.device) - - # The offset is needed when the buffer contains rgb and depth (the buffer has RGB data first and then depth) - offset = self._data.output["rgb"].numel() if "rgb" in self.cfg.data_types else 0 - for data_type in self.cfg.data_types: - wp.launch( - kernel=reshape_tiled_image, - dim=(self._view.count, self.cfg.height, self.cfg.width), - inputs=[ - tiled_data_buffer, - wp.from_torch(self._data.output[data_type]), # zero-copy alias - *list(self._data.output[data_type].shape[1:]), # height, width, num_channels - self._tiling_grid_shape()[0], # num_tiles_x - offset if data_type == "depth" else 0, - ], - device=self.device, - ) - - """ - Private Helpers - """ - - def _check_supported_data_types(self, cfg: TiledCameraCfg): - """Checks if the data types are supported by the camera.""" - if not set(cfg.data_types).issubset(TiledCamera.SUPPORTED_TYPES): - raise ValueError( - f"The TiledCamera class only supports the following types {TiledCamera.SUPPORTED_TYPES} but the" - f" following where provided: {cfg.data_types}" - ) - - def _create_buffers(self): - """Create buffers for storing data.""" - # create the data object - # -- pose of the cameras - self._data.pos_w = torch.zeros((self._view.count, 3), device=self._device) - self._data.quat_w_world = torch.zeros((self._view.count, 4), device=self._device) - self._update_poses(self._ALL_INDICES) - # -- intrinsic matrix - self._data.intrinsic_matrices = torch.zeros((self._view.count, 3, 3), device=self._device) - self._update_intrinsic_matrices(self._ALL_INDICES) - self._data.image_shape = self.image_shape - # -- output data - data_dict = dict() - if "rgb" in self.cfg.data_types: - data_dict["rgb"] = torch.zeros( - (self._view.count, self.cfg.height, self.cfg.width, 3), device=self.device - ).contiguous() - if "depth" in self.cfg.data_types: - data_dict["depth"] = torch.zeros( - (self._view.count, self.cfg.height, self.cfg.width, 1), device=self.device - ).contiguous() - self._data.output = TensorDict(data_dict, batch_size=self._view.count, device=self.device) - - def _tiled_image_shape(self) -> tuple[int, int]: - """Returns a tuple containing the dimension of the tiled image.""" - cols, rows = self._tiling_grid_shape() - return (self.cfg.width * cols, self.cfg.height * rows) - - def _tiling_grid_shape(self) -> tuple[int, int]: - """Returns a tuple containing the tiling grid dimension.""" - cols = round(math.sqrt(self._view.count)) - rows = math.ceil(self._view.count / cols) - return (cols, rows) - - def _create_annotator_data(self): - # we do not need to create annotator data for the tiled camera sensor - raise RuntimeError("This function should not be called for the tiled camera sensor.") - - def _process_annotator_output(self, name: str, output: Any) -> tuple[torch.tensor, dict | None]: - # we do not need to process annotator output for the tiled camera sensor - raise RuntimeError("This function should not be called for the tiled camera sensor.") - - """ - Internal simulation callbacks. - """ - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - # call parent - super()._invalidate_initialize_callback(event) - # set all existing views to None to invalidate them - self._view = None
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/tiled_camera_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/tiled_camera_cfg.html deleted file mode 100644 index 40a44e569b..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/camera/tiled_camera_cfg.html +++ /dev/null @@ -1,636 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.camera.tiled_camera_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.camera.tiled_camera_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.sim import FisheyeCameraCfg, PinholeCameraCfg
-from omni.isaac.lab.utils import configclass
-
-from ..sensor_base_cfg import SensorBaseCfg
-from .tiled_camera import TiledCamera
-
-
-
[文档]@configclass -class TiledCameraCfg(SensorBaseCfg): - """Configuration for a tiled rendering-based camera sensor.""" - -
[文档] @configclass - class OffsetCfg: - """The offset pose of the sensor's frame from the sensor's parent frame.""" - - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" - - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation (w, x, y, z) w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0).""" - - convention: Literal["opengl", "ros", "world"] = "ros" - """The convention in which the frame offset is applied. Defaults to "ros". - - - ``"opengl"`` - forward axis: ``-Z`` - up axis: ``+Y`` - Offset is applied in the OpenGL (Usd.Camera) convention. - - ``"ros"`` - forward axis: ``+Z`` - up axis: ``-Y`` - Offset is applied in the ROS convention. - - ``"world"`` - forward axis: ``+X`` - up axis: ``+Z`` - Offset is applied in the World Frame convention. - """
- - class_type: type = TiledCamera - - offset: OffsetCfg = OffsetCfg() - """The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity. - - Note: - The parent frame is the frame the sensor attaches to. For example, the parent frame of a - camera at path ``/World/envs/env_0/Robot/Camera`` is ``/World/envs/env_0/Robot``. - """ - - spawn: PinholeCameraCfg | FisheyeCameraCfg | None = MISSING - """Spawn configuration for the asset. - - If None, then the prim is not spawned by the asset. Instead, it is assumed that the - asset is already present in the scene. - """ - - data_types: list[str] = ["rgb"] - """List of sensor names/types to enable for the camera. Defaults to ["rgb"]. - - Please refer to the :class:`TiledCamera` class for a list of available data types. - """ - - width: int = MISSING - """Width of the image in pixels.""" - - height: int = MISSING - """Height of the image in pixels.""" - - return_latest_camera_pose: bool = False - """Whether to return the latest camera pose when fetching the camera's data. Defaults to False. - - If True, the latest camera pose is returned in the camera's data which will slow down performance - due to the use of :class:`XformPrimView`. - If False, the pose of the camera during initialization is returned. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor.html deleted file mode 100644 index 8351a48649..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor.html +++ /dev/null @@ -1,964 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.contact_sensor.contact_sensor — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.contact_sensor.contact_sensor 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# Ignore optional memory usage warning globally
-# pyright: reportOptionalSubscript=false
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-import omni.physics.tensors.impl.api as physx
-from pxr import PhysxSchema
-
-import omni.isaac.lab.sim as sim_utils
-import omni.isaac.lab.utils.string as string_utils
-from omni.isaac.lab.markers import VisualizationMarkers
-from omni.isaac.lab.utils.math import convert_quat
-
-from ..sensor_base import SensorBase
-from .contact_sensor_data import ContactSensorData
-
-if TYPE_CHECKING:
-    from .contact_sensor_cfg import ContactSensorCfg
-
-
-
[文档]class ContactSensor(SensorBase): - """A contact reporting sensor. - - The contact sensor reports the normal contact forces on a rigid body in the world frame. - It relies on the `PhysX ContactReporter`_ API to be activated on the rigid bodies. - - To enable the contact reporter on a rigid body, please make sure to enable the - :attr:`omni.isaac.lab.sim.spawner.RigidObjectSpawnerCfg.activate_contact_sensors` on your - asset spawner configuration. This will enable the contact reporter on all the rigid bodies - in the asset. - - The sensor can be configured to report the contact forces on a set of bodies with a given - filter pattern. Please check the documentation on `RigidContactView`_ for more details. - - .. _PhysX ContactReporter: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_contact_report_a_p_i.html - .. _RigidContactView: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#omni.isaac.core.prims.RigidContactView - """ - - cfg: ContactSensorCfg - """The configuration parameters.""" - -
[文档] def __init__(self, cfg: ContactSensorCfg): - """Initializes the contact sensor object. - - Args: - cfg: The configuration parameters. - """ - # initialize base class - super().__init__(cfg) - # Create empty variables for storing output data - self._data: ContactSensorData = ContactSensorData() - # initialize self._body_physx_view for running in extension mode - self._body_physx_view = None
- - def __str__(self) -> str: - """Returns: A string containing information about the instance.""" - return ( - f"Contact sensor @ '{self.cfg.prim_path}': \n" - f"\tview type : {self.body_physx_view.__class__}\n" - f"\tupdate period (s) : {self.cfg.update_period}\n" - f"\tnumber of bodies : {self.num_bodies}\n" - f"\tbody names : {self.body_names}\n" - ) - - """ - Properties - """ - - @property - def num_instances(self) -> int: - return self.body_physx_view.count - - @property - def data(self) -> ContactSensorData: - # update sensors if needed - self._update_outdated_buffers() - # return the data - return self._data - - @property - def num_bodies(self) -> int: - """Number of bodies with contact sensors attached.""" - return self._num_bodies - - @property - def body_names(self) -> list[str]: - """Ordered names of bodies with contact sensors attached.""" - prim_paths = self.body_physx_view.prim_paths[: self.num_bodies] - return [path.split("/")[-1] for path in prim_paths] - - @property - def body_physx_view(self) -> physx.RigidBodyView: - """View for the rigid bodies captured (PhysX). - - Note: - Use this view with caution. It requires handling of tensors in a specific way. - """ - return self._body_physx_view - - @property - def contact_physx_view(self) -> physx.RigidContactView: - """Contact reporter view for the bodies (PhysX). - - Note: - Use this view with caution. It requires handling of tensors in a specific way. - """ - return self._contact_physx_view - - """ - Operations - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - # reset the timers and counters - super().reset(env_ids) - # resolve None - if env_ids is None: - env_ids = slice(None) - # reset accumulative data buffers - self._data.net_forces_w[env_ids] = 0.0 - self._data.net_forces_w_history[env_ids] = 0.0 - if self.cfg.history_length > 0: - self._data.net_forces_w_history[env_ids] = 0.0 - # reset force matrix - if len(self.cfg.filter_prim_paths_expr) != 0: - self._data.force_matrix_w[env_ids] = 0.0 - # reset the current air time - if self.cfg.track_air_time: - self._data.current_air_time[env_ids] = 0.0 - self._data.last_air_time[env_ids] = 0.0 - self._data.current_contact_time[env_ids] = 0.0 - self._data.last_contact_time[env_ids] = 0.0
- -
[文档] def find_bodies(self, name_keys: str | Sequence[str], preserve_order: bool = False) -> tuple[list[int], list[str]]: - """Find bodies in the articulation based on the name keys. - - Args: - name_keys: A regular expression or a list of regular expressions to match the body names. - preserve_order: Whether to preserve the order of the name keys in the output. Defaults to False. - - Returns: - A tuple of lists containing the body indices and names. - """ - return string_utils.resolve_matching_names(name_keys, self.body_names, preserve_order)
- -
[文档] def compute_first_contact(self, dt: float, abs_tol: float = 1.0e-8) -> torch.Tensor: - """Checks if bodies that have established contact within the last :attr:`dt` seconds. - - This function checks if the bodies have established contact within the last :attr:`dt` seconds - by comparing the current contact time with the given time period. If the contact time is less - than the given time period, then the bodies are considered to be in contact. - - Note: - The function assumes that :attr:`dt` is a factor of the sensor update time-step. In other - words :math:`dt / dt_sensor = n`, where :math:`n` is a natural number. This is always true - if the sensor is updated by the physics or the environment stepping time-step and the sensor - is read by the environment stepping time-step. - - Args: - dt: The time period since the contact was established. - abs_tol: The absolute tolerance for the comparison. - - Returns: - A boolean tensor indicating the bodies that have established contact within the last - :attr:`dt` seconds. Shape is (N, B), where N is the number of sensors and B is the - number of bodies in each sensor. - - Raises: - RuntimeError: If the sensor is not configured to track contact time. - """ - # check if the sensor is configured to track contact time - if not self.cfg.track_air_time: - raise RuntimeError( - "The contact sensor is not configured to track contact time." - "Please enable the 'track_air_time' in the sensor configuration." - ) - # check if the bodies are in contact - currently_in_contact = self.data.current_contact_time > 0.0 - less_than_dt_in_contact = self.data.current_contact_time < (dt + abs_tol) - return currently_in_contact * less_than_dt_in_contact
- -
[文档] def compute_first_air(self, dt: float, abs_tol: float = 1.0e-8) -> torch.Tensor: - """Checks if bodies that have broken contact within the last :attr:`dt` seconds. - - This function checks if the bodies have broken contact within the last :attr:`dt` seconds - by comparing the current air time with the given time period. If the air time is less - than the given time period, then the bodies are considered to not be in contact. - - Note: - It assumes that :attr:`dt` is a factor of the sensor update time-step. In other words, - :math:`dt / dt_sensor = n`, where :math:`n` is a natural number. This is always true if - the sensor is updated by the physics or the environment stepping time-step and the sensor - is read by the environment stepping time-step. - - Args: - dt: The time period since the contract is broken. - abs_tol: The absolute tolerance for the comparison. - - Returns: - A boolean tensor indicating the bodies that have broken contact within the last :attr:`dt` seconds. - Shape is (N, B), where N is the number of sensors and B is the number of bodies in each sensor. - - Raises: - RuntimeError: If the sensor is not configured to track contact time. - """ - # check if the sensor is configured to track contact time - if not self.cfg.track_air_time: - raise RuntimeError( - "The contact sensor is not configured to track contact time." - "Please enable the 'track_air_time' in the sensor configuration." - ) - # check if the sensor is configured to track contact time - currently_detached = self.data.current_air_time > 0.0 - less_than_dt_detached = self.data.current_air_time < (dt + abs_tol) - return currently_detached * less_than_dt_detached
- - """ - Implementation. - """ - - def _initialize_impl(self): - super()._initialize_impl() - # create simulation view - self._physics_sim_view = physx.create_simulation_view(self._backend) - self._physics_sim_view.set_subspace_roots("/") - # check that only rigid bodies are selected - leaf_pattern = self.cfg.prim_path.rsplit("/", 1)[-1] - template_prim_path = self._parent_prims[0].GetPath().pathString - body_names = list() - for prim in sim_utils.find_matching_prims(template_prim_path + "/" + leaf_pattern): - # check if prim has contact reporter API - if prim.HasAPI(PhysxSchema.PhysxContactReportAPI): - prim_path = prim.GetPath().pathString - body_names.append(prim_path.rsplit("/", 1)[-1]) - # check that there is at least one body with contact reporter API - if not body_names: - raise RuntimeError( - f"Sensor at path '{self.cfg.prim_path}' could not find any bodies with contact reporter API." - "\nHINT: Make sure to enable 'activate_contact_sensors' in the corresponding asset spawn configuration." - ) - - # construct regex expression for the body names - body_names_regex = r"(" + "|".join(body_names) + r")" - body_names_regex = f"{self.cfg.prim_path.rsplit('/', 1)[0]}/{body_names_regex}" - # convert regex expressions to glob expressions for PhysX - body_names_glob = body_names_regex.replace(".*", "*") - filter_prim_paths_glob = [expr.replace(".*", "*") for expr in self.cfg.filter_prim_paths_expr] - - # create a rigid prim view for the sensor - self._body_physx_view = self._physics_sim_view.create_rigid_body_view(body_names_glob) - self._contact_physx_view = self._physics_sim_view.create_rigid_contact_view( - body_names_glob, filter_patterns=filter_prim_paths_glob - ) - # resolve the true count of bodies - self._num_bodies = self.body_physx_view.count // self._num_envs - # check that contact reporter succeeded - if self._num_bodies != len(body_names): - raise RuntimeError( - "Failed to initialize contact reporter for specified bodies." - f"\n\tInput prim path : {self.cfg.prim_path}" - f"\n\tResolved prim paths: {body_names_regex}" - ) - - # prepare data buffers - self._data.net_forces_w = torch.zeros(self._num_envs, self._num_bodies, 3, device=self._device) - # optional buffers - # -- history of net forces - if self.cfg.history_length > 0: - self._data.net_forces_w_history = torch.zeros( - self._num_envs, self.cfg.history_length, self._num_bodies, 3, device=self._device - ) - else: - self._data.net_forces_w_history = self._data.net_forces_w.unsqueeze(1) - # -- pose of sensor origins - if self.cfg.track_pose: - self._data.pos_w = torch.zeros(self._num_envs, self._num_bodies, 3, device=self._device) - self._data.quat_w = torch.zeros(self._num_envs, self._num_bodies, 4, device=self._device) - # -- air/contact time between contacts - if self.cfg.track_air_time: - self._data.last_air_time = torch.zeros(self._num_envs, self._num_bodies, device=self._device) - self._data.current_air_time = torch.zeros(self._num_envs, self._num_bodies, device=self._device) - self._data.last_contact_time = torch.zeros(self._num_envs, self._num_bodies, device=self._device) - self._data.current_contact_time = torch.zeros(self._num_envs, self._num_bodies, device=self._device) - # force matrix: (num_envs, num_bodies, num_filter_shapes, 3) - if len(self.cfg.filter_prim_paths_expr) != 0: - num_filters = self.contact_physx_view.filter_count - self._data.force_matrix_w = torch.zeros( - self._num_envs, self._num_bodies, num_filters, 3, device=self._device - ) - - def _update_buffers_impl(self, env_ids: Sequence[int]): - """Fills the buffers of the sensor data.""" - # default to all sensors - if len(env_ids) == self._num_envs: - env_ids = slice(None) - - # obtain the contact forces - # TODO: We are handling the indexing ourself because of the shape; (N, B) vs expected (N * B). - # This isn't the most efficient way to do this, but it's the easiest to implement. - net_forces_w = self.contact_physx_view.get_net_contact_forces(dt=self._sim_physics_dt) - self._data.net_forces_w[env_ids, :, :] = net_forces_w.view(-1, self._num_bodies, 3)[env_ids] - # update contact force history - if self.cfg.history_length > 0: - self._data.net_forces_w_history[env_ids, 1:] = self._data.net_forces_w_history[env_ids, :-1].clone() - self._data.net_forces_w_history[env_ids, 0] = self._data.net_forces_w[env_ids] - - # obtain the contact force matrix - if len(self.cfg.filter_prim_paths_expr) != 0: - # shape of the filtering matrix: (num_envs, num_bodies, num_filter_shapes, 3) - num_filters = self.contact_physx_view.filter_count - # acquire and shape the force matrix - force_matrix_w = self.contact_physx_view.get_contact_force_matrix(dt=self._sim_physics_dt) - force_matrix_w = force_matrix_w.view(-1, self._num_bodies, num_filters, 3) - self._data.force_matrix_w[env_ids] = force_matrix_w[env_ids] - # obtain the pose of the sensor origin - if self.cfg.track_pose: - pose = self.body_physx_view.get_transforms().view(-1, self._num_bodies, 7)[env_ids] - pose[..., 3:] = convert_quat(pose[..., 3:], to="wxyz") - self._data.pos_w[env_ids], self._data.quat_w[env_ids] = pose.split([3, 4], dim=-1) - # obtain the air time - if self.cfg.track_air_time: - # -- time elapsed since last update - # since this function is called every frame, we can use the difference to get the elapsed time - elapsed_time = self._timestamp[env_ids] - self._timestamp_last_update[env_ids] - # -- check contact state of bodies - is_contact = torch.norm(self._data.net_forces_w[env_ids, :, :], dim=-1) > self.cfg.force_threshold - is_first_contact = (self._data.current_air_time[env_ids] > 0) * is_contact - is_first_detached = (self._data.current_contact_time[env_ids] > 0) * ~is_contact - # -- update the last contact time if body has just become in contact - self._data.last_air_time[env_ids] = torch.where( - is_first_contact, - self._data.current_air_time[env_ids] + elapsed_time.unsqueeze(-1), - self._data.last_air_time[env_ids], - ) - # -- increment time for bodies that are not in contact - self._data.current_air_time[env_ids] = torch.where( - ~is_contact, self._data.current_air_time[env_ids] + elapsed_time.unsqueeze(-1), 0.0 - ) - # -- update the last contact time if body has just detached - self._data.last_contact_time[env_ids] = torch.where( - is_first_detached, - self._data.current_contact_time[env_ids] + elapsed_time.unsqueeze(-1), - self._data.last_contact_time[env_ids], - ) - # -- increment time for bodies that are in contact - self._data.current_contact_time[env_ids] = torch.where( - is_contact, self._data.current_contact_time[env_ids] + elapsed_time.unsqueeze(-1), 0.0 - ) - - def _set_debug_vis_impl(self, debug_vis: bool): - # set visibility of markers - # note: parent only deals with callbacks. not their visibility - if debug_vis: - # create markers if necessary for the first tome - if not hasattr(self, "contact_visualizer"): - self.contact_visualizer = VisualizationMarkers(self.cfg.visualizer_cfg) - # set their visibility to true - self.contact_visualizer.set_visibility(True) - else: - if hasattr(self, "contact_visualizer"): - self.contact_visualizer.set_visibility(False) - - def _debug_vis_callback(self, event): - # safely return if view becomes invalid - # note: this invalidity happens because of isaac sim view callbacks - if self.body_physx_view is None: - return - # marker indices - # 0: contact, 1: no contact - net_contact_force_w = torch.norm(self._data.net_forces_w, dim=-1) - marker_indices = torch.where(net_contact_force_w > self.cfg.force_threshold, 0, 1) - # check if prim is visualized - if self.cfg.track_pose: - frame_origins: torch.Tensor = self._data.pos_w - else: - pose = self.body_physx_view.get_transforms() - frame_origins = pose.view(-1, self._num_bodies, 7)[:, :, :3] - # visualize - self.contact_visualizer.visualize(frame_origins.view(-1, 3), marker_indices=marker_indices.view(-1)) - - """ - Internal simulation callbacks. - """ - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - # call parent - super()._invalidate_initialize_callback(event) - # set all existing views to None to invalidate them - self._physics_sim_view = None - self._body_physx_view = None - self._contact_physx_view = None
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor_cfg.html deleted file mode 100644 index 504fa4c249..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor_cfg.html +++ /dev/null @@ -1,617 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.contact_sensor.contact_sensor_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.contact_sensor.contact_sensor_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from omni.isaac.lab.markers import VisualizationMarkersCfg
-from omni.isaac.lab.markers.config import CONTACT_SENSOR_MARKER_CFG
-from omni.isaac.lab.utils import configclass
-
-from ..sensor_base_cfg import SensorBaseCfg
-from .contact_sensor import ContactSensor
-
-
-
[文档]@configclass -class ContactSensorCfg(SensorBaseCfg): - """Configuration for the contact sensor.""" - - class_type: type = ContactSensor - - track_pose: bool = False - """Whether to track the pose of the sensor's origin. Defaults to False.""" - - track_air_time: bool = False - """Whether to track the air/contact time of the bodies (time between contacts). Defaults to False.""" - - force_threshold: float = 1.0 - """The threshold on the norm of the contact force that determines whether two bodies are in collision or not. - - This value is only used for tracking the mode duration (the time in contact or in air), - if :attr:`track_air_time` is True. - """ - - filter_prim_paths_expr: list[str] = list() - """The list of primitive paths (or expressions) to filter contacts with. Defaults to an empty list, in which case - no filtering is applied. - - The contact sensor allows reporting contacts between the primitive specified with :attr:`prim_path` and - other primitives in the scene. For instance, in a scene containing a robot, a ground plane and an object, - you can obtain individual contact reports of the base of the robot with the ground plane and the object. - - .. note:: - The expression in the list can contain the environment namespace regex ``{ENV_REGEX_NS}`` which - will be replaced with the environment namespace. - - Example: ``{ENV_REGEX_NS}/Object`` will be replaced with ``/World/envs/env_.*/Object``. - - """ - - visualizer_cfg: VisualizationMarkersCfg = CONTACT_SENSOR_MARKER_CFG.replace(prim_path="/Visuals/ContactSensor") - """The configuration object for the visualization markers. Defaults to CONTACT_SENSOR_MARKER_CFG. - - .. note:: - This attribute is only used when debug visualization is enabled. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor_data.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor_data.html deleted file mode 100644 index 80c5bafc05..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/contact_sensor/contact_sensor_data.html +++ /dev/null @@ -1,657 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.contact_sensor.contact_sensor_data — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.contact_sensor.contact_sensor_data 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# needed to import for allowing type-hinting: torch.Tensor | None
-from __future__ import annotations
-
-import torch
-from dataclasses import dataclass
-
-
-
[文档]@dataclass -class ContactSensorData: - """Data container for the contact reporting sensor.""" - - pos_w: torch.Tensor | None = None - """Position of the sensor origin in world frame. - - Shape is (N, 3), where N is the number of sensors. - - Note: - If the :attr:`ContactSensorCfg.track_pose` is False, then this qunatity is None. - """ - - quat_w: torch.Tensor | None = None - """Orientation of the sensor origin in quaternion (w, x, y, z) in world frame. - - Shape is (N, 4), where N is the number of sensors. - - Note: - If the :attr:`ContactSensorCfg.track_pose` is False, then this qunatity is None. - """ - - net_forces_w: torch.Tensor | None = None - """The net contact forces in world frame. - - Shape is (N, B, 3), where N is the number of sensors and B is the number of bodies in each sensor. - """ - - net_forces_w_history: torch.Tensor | None = None - """The net contact forces in world frame. - - Shape is (N, T, B, 3), where N is the number of sensors, T is the configured history length - and B is the number of bodies in each sensor. - - In the history dimension, the first index is the most recent and the last index is the oldest. - """ - - force_matrix_w: torch.Tensor | None = None - """The contact forces filtered between the sensor bodies and filtered bodies in world frame. - - Shape is (N, B, M, 3), where N is the number of sensors, B is number of bodies in each sensor - and ``M`` is the number of filtered bodies. - - Note: - If the :attr:`ContactSensorCfg.filter_prim_paths_expr` is empty, then this quantity is None. - """ - - last_air_time: torch.Tensor | None = None - """Time spent (in s) in the air before the last contact. - - Shape is (N, B), where N is the number of sensors and B is the number of bodies in each sensor. - - Note: - If the :attr:`ContactSensorCfg.track_air_time` is False, then this quantity is None. - """ - - current_air_time: torch.Tensor | None = None - """Time spent (in s) in the air since the last detach. - - Shape is (N, B), where N is the number of sensors and B is the number of bodies in each sensor. - - Note: - If the :attr:`ContactSensorCfg.track_air_time` is False, then this quantity is None. - """ - - last_contact_time: torch.Tensor | None = None - """Time spent (in s) in contact before the last detach. - - Shape is (N, B), where N is the number of sensors and B is the number of bodies in each sensor. - - Note: - If the :attr:`ContactSensorCfg.track_air_time` is False, then this quantity is None. - """ - - current_contact_time: torch.Tensor | None = None - """Time spent (in s) in contact since the last contact. - - Shape is (N, B), where N is the number of sensors and B is the number of bodies in each sensor. - - Note: - If the :attr:`ContactSensorCfg.track_air_time` is False, then this quantity is None. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer.html deleted file mode 100644 index 10533de40e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer.html +++ /dev/null @@ -1,932 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.frame_transformer.frame_transformer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.frame_transformer.frame_transformer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-import carb
-import omni.physics.tensors.impl.api as physx
-from pxr import UsdPhysics
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.markers import VisualizationMarkers
-from omni.isaac.lab.utils.math import (
-    combine_frame_transforms,
-    convert_quat,
-    is_identity_pose,
-    subtract_frame_transforms,
-)
-
-from ..sensor_base import SensorBase
-from .frame_transformer_data import FrameTransformerData
-
-if TYPE_CHECKING:
-    from .frame_transformer_cfg import FrameTransformerCfg
-
-
-
[文档]class FrameTransformer(SensorBase): - """A sensor for reporting frame transforms. - - This class provides an interface for reporting the transform of one or more frames (target frames) - with respect to another frame (source frame). The source frame is specified by the user as a prim path - (:attr:`FrameTransformerCfg.prim_path`) and the target frames are specified by the user as a list of - prim paths (:attr:`FrameTransformerCfg.target_frames`). - - The source frame and target frames are assumed to be rigid bodies. The transform of the target frames - with respect to the source frame is computed by first extracting the transform of the source frame - and target frames from the physics engine and then computing the relative transform between the two. - - Additionally, the user can specify an offset for the source frame and each target frame. This is useful - for specifying the transform of the desired frame with respect to the body's center of mass, for instance. - - A common example of using this sensor is to track the position and orientation of the end effector of a - robotic manipulator. In this case, the source frame would be the body corresponding to the base frame of the - manipulator, and the target frame would be the body corresponding to the end effector. Since the end-effector is - typically a fictitious body, the user may need to specify an offset from the end-effector to the body of the - manipulator. - - .. note:: - - Currently, this implementation only handles frames within an articulation. This is because the frame - regex expressions are resolved based on their parent prim path. This can be extended to handle - frames outside of articulation by using the frame prim path instead. However, this would require - additional checks to ensure that the user-specified frames are valid which is not currently implemented. - - .. warning:: - - The implementation assumes that the parent body of a target frame is not the same as that - of the source frame (i.e. :attr:`FrameTransformerCfg.prim_path`). While a corner case, this can occur - if the user specifies the same prim path for both the source frame and target frame. In this case, - the target frame will be ignored and not reported. This is a limitation of the current implementation - and will be fixed in a future release. - - """ - - cfg: FrameTransformerCfg - """The configuration parameters.""" - -
[文档] def __init__(self, cfg: FrameTransformerCfg): - """Initializes the frame transformer object. - - Args: - cfg: The configuration parameters. - """ - # initialize base class - super().__init__(cfg) - # Create empty variables for storing output data - self._data: FrameTransformerData = FrameTransformerData()
- - def __str__(self) -> str: - """Returns: A string containing information about the instance.""" - return ( - f"FrameTransformer @ '{self.cfg.prim_path}': \n" - f"\ttracked body frames: {[self._source_frame_body_name] + self._target_frame_body_names} \n" - f"\tnumber of envs: {self._num_envs}\n" - f"\tsource body frame: {self._source_frame_body_name}\n" - f"\ttarget frames (count: {self._target_frame_names}): {len(self._target_frame_names)}\n" - ) - - """ - Properties - """ - - @property - def data(self) -> FrameTransformerData: - # update sensors if needed - self._update_outdated_buffers() - # return the data - return self._data - - """ - Operations - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - # reset the timers and counters - super().reset(env_ids) - # resolve None - if env_ids is None: - env_ids = ...
- - """ - Implementation. - """ - - def _initialize_impl(self): - super()._initialize_impl() - - # resolve source frame offset - source_frame_offset_pos = torch.tensor(self.cfg.source_frame_offset.pos, device=self.device) - source_frame_offset_quat = torch.tensor(self.cfg.source_frame_offset.rot, device=self.device) - # Only need to perform offsetting of source frame if the position offsets is non-zero and rotation offset is - # not the identity quaternion for efficiency in _update_buffer_impl - self._apply_source_frame_offset = True - # Handle source frame offsets - if is_identity_pose(source_frame_offset_pos, source_frame_offset_quat): - carb.log_verbose(f"No offset application needed for source frame as it is identity: {self.cfg.prim_path}") - self._apply_source_frame_offset = False - else: - carb.log_verbose(f"Applying offset to source frame as it is not identity: {self.cfg.prim_path}") - # Store offsets as tensors (duplicating each env's offsets for ease of multiplication later) - self._source_frame_offset_pos = source_frame_offset_pos.unsqueeze(0).repeat(self._num_envs, 1) - self._source_frame_offset_quat = source_frame_offset_quat.unsqueeze(0).repeat(self._num_envs, 1) - - # Keep track of mapping from the rigid body name to the desired frame, as there may be multiple frames - # based upon the same body name and we don't want to create unnecessary views - body_names_to_frames: dict[str, set[str]] = {} - # The offsets associated with each target frame - target_offsets: dict[str, dict[str, torch.Tensor]] = {} - # The frames whose offsets are not identity - non_identity_offset_frames: list[str] = [] - - # Only need to perform offsetting of target frame if any of the position offsets are non-zero or any of the - # rotation offsets are not the identity quaternion for efficiency in _update_buffer_impl - self._apply_target_frame_offset = False - - # Collect all target frames, their associated body prim paths and their offsets so that we can extract - # the prim, check that it has the appropriate rigid body API in a single loop. - # First element is None because user can't specify source frame name - frames = [None] + [target_frame.name for target_frame in self.cfg.target_frames] - frame_prim_paths = [self.cfg.prim_path] + [target_frame.prim_path for target_frame in self.cfg.target_frames] - # First element is None because source frame offset is handled separately - frame_offsets = [None] + [target_frame.offset for target_frame in self.cfg.target_frames] - for frame, prim_path, offset in zip(frames, frame_prim_paths, frame_offsets): - # Find correct prim - matching_prims = sim_utils.find_matching_prims(prim_path) - if len(matching_prims) == 0: - raise ValueError( - f"Failed to create frame transformer for frame '{frame}' with path '{prim_path}'." - " No matching prims were found." - ) - for prim in matching_prims: - # Get the prim path of the matching prim - matching_prim_path = prim.GetPath().pathString - # Check if it is a rigid prim - if not prim.HasAPI(UsdPhysics.RigidBodyAPI): - raise ValueError( - f"While resolving expression '{prim_path}' found a prim '{matching_prim_path}' which is not a" - " rigid body. The class only supports transformations between rigid bodies." - ) - - # Get the name of the body - body_name = matching_prim_path.rsplit("/", 1)[-1] - # Use body name if frame isn't specified by user - frame_name = frame if frame is not None else body_name - - # Keep track of which frames are associated with which bodies - if body_name in body_names_to_frames: - body_names_to_frames[body_name].add(frame_name) - else: - body_names_to_frames[body_name] = {frame_name} - - if offset is not None: - offset_pos = torch.tensor(offset.pos, device=self.device) - offset_quat = torch.tensor(offset.rot, device=self.device) - # Check if we need to apply offsets (optimized code path in _update_buffer_impl) - if not is_identity_pose(offset_pos, offset_quat): - non_identity_offset_frames.append(frame_name) - self._apply_target_frame_offset = True - - target_offsets[frame_name] = {"pos": offset_pos, "quat": offset_quat} - - if not self._apply_target_frame_offset: - carb.log_info( - f"No offsets application needed from '{self.cfg.prim_path}' to target frames as all" - f" are identity: {frames[1:]}" - ) - else: - carb.log_info( - f"Offsets application needed from '{self.cfg.prim_path}' to the following target frames:" - f" {non_identity_offset_frames}" - ) - - # The names of bodies that RigidPrimView will be tracking to later extract transforms from - tracked_body_names = list(body_names_to_frames.keys()) - # Construct regex expression for the body names - body_names_regex = r"(" + "|".join(tracked_body_names) + r")" - body_names_regex = f"{self.cfg.prim_path.rsplit('/', 1)[0]}/{body_names_regex}" - # Create simulation view - self._physics_sim_view = physx.create_simulation_view(self._backend) - self._physics_sim_view.set_subspace_roots("/") - # Create a prim view for all frames and initialize it - # order of transforms coming out of view will be source frame followed by target frame(s) - self._frame_physx_view = self._physics_sim_view.create_rigid_body_view(body_names_regex.replace(".*", "*")) - - # Determine the order in which regex evaluated body names so we can later index into frame transforms - # by frame name correctly - all_prim_paths = self._frame_physx_view.prim_paths - - # Only need first env as the names and their ordering are the same across environments - first_env_prim_paths = all_prim_paths[0 : len(tracked_body_names)] - first_env_body_names = [first_env_prim_path.split("/")[-1] for first_env_prim_path in first_env_prim_paths] - - # Re-parse the list as it may have moved when resolving regex above - # -- source frame - self._source_frame_body_name = self.cfg.prim_path.split("/")[-1] - source_frame_index = first_env_body_names.index(self._source_frame_body_name) - # -- target frames - self._target_frame_body_names = first_env_body_names[:] - self._target_frame_body_names.remove(self._source_frame_body_name) - - # Determine indices into all tracked body frames for both source and target frames - all_ids = torch.arange(self._num_envs * len(tracked_body_names)) - self._source_frame_body_ids = torch.arange(self._num_envs) * len(tracked_body_names) + source_frame_index - self._target_frame_body_ids = all_ids[~torch.isin(all_ids, self._source_frame_body_ids)] - - # The name of each of the target frame(s) - either user specified or defaulted to the body name - self._target_frame_names: list[str] = [] - # The position and rotation components of target frame offsets - target_frame_offset_pos = [] - target_frame_offset_quat = [] - # Stores the indices of bodies that need to be duplicated. For instance, if body "LF_SHANK" is needed - # for 2 frames, this list enables us to duplicate the body to both frames when doing the calculations - # when updating sensor in _update_buffers_impl - duplicate_frame_indices = [] - - # Go through each body name and determine the number of duplicates we need for that frame - # and extract the offsets. This is all done to handles the case where multiple frames - # reference the same body, but have different names and/or offsets - for i, body_name in enumerate(self._target_frame_body_names): - for frame in body_names_to_frames[body_name]: - target_frame_offset_pos.append(target_offsets[frame]["pos"]) - target_frame_offset_quat.append(target_offsets[frame]["quat"]) - self._target_frame_names.append(frame) - duplicate_frame_indices.append(i) - - # To handle multiple environments, need to expand so [0, 1, 1, 2] with 2 environments becomes - # [0, 1, 1, 2, 3, 4, 4, 5]. Again, this is a optimization to make _update_buffer_impl more efficient - duplicate_frame_indices = torch.tensor(duplicate_frame_indices, device=self.device) - num_target_body_frames = len(tracked_body_names) - 1 - self._duplicate_frame_indices = torch.cat( - [duplicate_frame_indices + num_target_body_frames * env_num for env_num in range(self._num_envs)] - ) - - # Stack up all the frame offsets for shape (num_envs, num_frames, 3) and (num_envs, num_frames, 4) - self._target_frame_offset_pos = torch.stack(target_frame_offset_pos).repeat(self._num_envs, 1) - self._target_frame_offset_quat = torch.stack(target_frame_offset_quat).repeat(self._num_envs, 1) - - # fill the data buffer - self._data.target_frame_names = self._target_frame_names - self._data.source_pos_w = torch.zeros(self._num_envs, 3, device=self._device) - self._data.source_quat_w = torch.zeros(self._num_envs, 4, device=self._device) - self._data.target_pos_w = torch.zeros(self._num_envs, len(duplicate_frame_indices), 3, device=self._device) - self._data.target_quat_w = torch.zeros(self._num_envs, len(duplicate_frame_indices), 4, device=self._device) - self._data.target_pos_source = torch.zeros_like(self._data.target_pos_w) - self._data.target_quat_source = torch.zeros_like(self._data.target_quat_w) - - def _update_buffers_impl(self, env_ids: Sequence[int]): - """Fills the buffers of the sensor data.""" - # default to all sensors - if len(env_ids) == self._num_envs: - env_ids = ... - - # Extract transforms from view - shape is: - # (the total number of source and target body frames being tracked * self._num_envs, 7) - transforms = self._frame_physx_view.get_transforms() - # Convert quaternions as PhysX uses xyzw form - transforms[:, 3:] = convert_quat(transforms[:, 3:], to="wxyz") - - # Process source frame transform - source_frames = transforms[self._source_frame_body_ids] - # Only apply offset if the offsets will result in a coordinate frame transform - if self._apply_source_frame_offset: - source_pos_w, source_quat_w = combine_frame_transforms( - source_frames[:, :3], - source_frames[:, 3:], - self._source_frame_offset_pos, - self._source_frame_offset_quat, - ) - else: - source_pos_w = source_frames[:, :3] - source_quat_w = source_frames[:, 3:] - - # Process target frame transforms - target_frames = transforms[self._target_frame_body_ids] - duplicated_target_frame_pos_w = target_frames[self._duplicate_frame_indices, :3] - duplicated_target_frame_quat_w = target_frames[self._duplicate_frame_indices, 3:] - # Only apply offset if the offsets will result in a coordinate frame transform - if self._apply_target_frame_offset: - target_pos_w, target_quat_w = combine_frame_transforms( - duplicated_target_frame_pos_w, - duplicated_target_frame_quat_w, - self._target_frame_offset_pos, - self._target_frame_offset_quat, - ) - else: - target_pos_w = duplicated_target_frame_pos_w - target_quat_w = duplicated_target_frame_quat_w - - # Compute the transform of the target frame with respect to the source frame - total_num_frames = len(self._target_frame_names) - target_pos_source, target_quat_source = subtract_frame_transforms( - source_pos_w.unsqueeze(1).expand(-1, total_num_frames, -1).reshape(-1, 3), - source_quat_w.unsqueeze(1).expand(-1, total_num_frames, -1).reshape(-1, 4), - target_pos_w, - target_quat_w, - ) - - # Update buffers - # note: The frame names / ordering don't change so no need to update them after initialization - self._data.source_pos_w[:] = source_pos_w.view(-1, 3) - self._data.source_quat_w[:] = source_quat_w.view(-1, 4) - self._data.target_pos_w[:] = target_pos_w.view(-1, total_num_frames, 3) - self._data.target_quat_w[:] = target_quat_w.view(-1, total_num_frames, 4) - self._data.target_pos_source[:] = target_pos_source.view(-1, total_num_frames, 3) - self._data.target_quat_source[:] = target_quat_source.view(-1, total_num_frames, 4) - - def _set_debug_vis_impl(self, debug_vis: bool): - # set visibility of markers - # note: parent only deals with callbacks. not their visibility - if debug_vis: - if not hasattr(self, "frame_visualizer"): - self.frame_visualizer = VisualizationMarkers(self.cfg.visualizer_cfg) - # set their visibility to true - self.frame_visualizer.set_visibility(True) - else: - if hasattr(self, "frame_visualizer"): - self.frame_visualizer.set_visibility(False) - - def _debug_vis_callback(self, event): - # Update the visualized markers - if self.frame_visualizer is not None: - self.frame_visualizer.visualize(self._data.target_pos_w.view(-1, 3), self._data.target_quat_w.view(-1, 4)) - - """ - Internal simulation callbacks. - """ - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - # call parent - super()._invalidate_initialize_callback(event) - # set all existing views to None to invalidate them - self._physics_sim_view = None - self._frame_physx_view = None
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer_cfg.html deleted file mode 100644 index be76f7495a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer_cfg.html +++ /dev/null @@ -1,631 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.frame_transformer.frame_transformer_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.frame_transformer.frame_transformer_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.markers.config import FRAME_MARKER_CFG, VisualizationMarkersCfg
-from omni.isaac.lab.utils import configclass
-
-from ..sensor_base_cfg import SensorBaseCfg
-from .frame_transformer import FrameTransformer
-
-
-
[文档]@configclass -class OffsetCfg: - """The offset pose of one frame relative to another frame.""" - - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation (w, x, y, z) w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0)."""
- - -
[文档]@configclass -class FrameTransformerCfg(SensorBaseCfg): - """Configuration for the frame transformer sensor.""" - -
[文档] @configclass - class FrameCfg: - """Information specific to a coordinate frame.""" - - prim_path: str = MISSING - """The prim path corresponding to the parent rigid body. - - This prim should be part of the same articulation as :attr:`FrameTransformerCfg.prim_path`. - """ - name: str | None = None - """User-defined name for the new coordinate frame. Defaults to None. - - If None, then the name is extracted from the leaf of the prim path. - """ - - offset: OffsetCfg = OffsetCfg() - """The pose offset from the parent prim frame."""
- - class_type: type = FrameTransformer - - prim_path: str = MISSING - """The prim path of the body to transform from (source frame).""" - - source_frame_offset: OffsetCfg = OffsetCfg() - """The pose offset from the source prim frame.""" - - target_frames: list[FrameCfg] = MISSING - """A list of the target frames. - - This allows a single FrameTransformer to handle multiple target prims. For example, in a quadruped, - we can use a single FrameTransformer to track each foot's position and orientation in the body - frame using four frame offsets. - """ - - visualizer_cfg: VisualizationMarkersCfg = FRAME_MARKER_CFG.replace(prim_path="/Visuals/FrameTransformer") - """The configuration object for the visualization markers. Defaults to FRAME_MARKER_CFG. - - Note: - This attribute is only used when debug visualization is enabled. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer_data.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer_data.html deleted file mode 100644 index 76b097d561..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/frame_transformer/frame_transformer_data.html +++ /dev/null @@ -1,650 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.frame_transformer.frame_transformer_data — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.frame_transformer.frame_transformer_data 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-import warnings
-from dataclasses import dataclass
-
-
-
[文档]@dataclass -class FrameTransformerData: - """Data container for the frame transformer sensor.""" - - target_frame_names: list[str] = None - """Target frame names (this denotes the order in which that frame data is ordered). - - The frame names are resolved from the :attr:`FrameTransformerCfg.FrameCfg.name` field. - This usually follows the order in which the frames are defined in the config. However, in - the case of regex matching, the order may be different. - """ - - target_pos_source: torch.Tensor = None - """Position of the target frame(s) relative to source frame. - - Shape is (N, M, 3), where N is the number of environments, and M is the number of target frames. - """ - - target_quat_source: torch.Tensor = None - """Orientation of the target frame(s) relative to source frame quaternion (w, x, y, z). - - Shape is (N, M, 4), where N is the number of environments, and M is the number of target frames. - """ - - target_pos_w: torch.Tensor = None - """Position of the target frame(s) after offset (in world frame). - - Shape is (N, M, 3), where N is the number of environments, and M is the number of target frames. - """ - - target_quat_w: torch.Tensor = None - """Orientation of the target frame(s) after offset (in world frame) quaternion (w, x, y, z). - - Shape is (N, M, 4), where N is the number of environments, and M is the number of target frames. - """ - - source_pos_w: torch.Tensor = None - """Position of the source frame after offset (in world frame). - - Shape is (N, 3), where N is the number of environments. - """ - - source_quat_w: torch.Tensor = None - """Orientation of the source frame after offset (in world frame) quaternion (w, x, y, z). - - Shape is (N, 4), where N is the number of environments. - """ - - @property - def target_rot_source(self) -> torch.Tensor: - """Alias for :attr:`target_quat_source`. - - .. deprecated:: v0.2.1 - Use :attr:`target_quat_source` instead. Will be removed in v0.3.0. - """ - warnings.warn("'target_rot_source' is deprecated, use 'target_quat_source' instead.", DeprecationWarning) - return self.target_quat_source - - @property - def target_rot_w(self) -> torch.Tensor: - """Alias for :attr:`target_quat_w`. - - .. deprecated:: v0.2.1 - Use :attr:`target_quat_w` instead. Will be removed in v0.3.0. - """ - warnings.warn("'target_rot_w' is deprecated, use 'target_quat_w' instead.", DeprecationWarning) - return self.target_quat_w - - @property - def source_rot_w(self) -> torch.Tensor: - """Alias for :attr:`source_quat_w`. - - .. deprecated:: v0.2.1 - Use :attr:`source_quat_w` instead. Will be removed in v0.3.0. - """ - warnings.warn("'source_rot_w' is deprecated, use 'source_quat_w' instead.", DeprecationWarning) - return self.source_quat_w
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster.html deleted file mode 100644 index 3a3613c5d0..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster.html +++ /dev/null @@ -1,858 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.ray_caster.ray_caster — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.ray_caster.ray_caster 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import numpy as np
-import re
-import torch
-from collections.abc import Sequence
-from typing import TYPE_CHECKING, ClassVar
-
-import carb
-import omni.physics.tensors.impl.api as physx
-import warp as wp
-from omni.isaac.core.prims import XFormPrimView
-from pxr import UsdGeom, UsdPhysics
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.markers import VisualizationMarkers
-from omni.isaac.lab.terrains.trimesh.utils import make_plane
-from omni.isaac.lab.utils.math import convert_quat, quat_apply, quat_apply_yaw
-from omni.isaac.lab.utils.warp import convert_to_warp_mesh, raycast_mesh
-
-from ..sensor_base import SensorBase
-from .ray_caster_data import RayCasterData
-
-if TYPE_CHECKING:
-    from .ray_caster_cfg import RayCasterCfg
-
-
-
[文档]class RayCaster(SensorBase): - """A ray-casting sensor. - - The ray-caster uses a set of rays to detect collisions with meshes in the scene. The rays are - defined in the sensor's local coordinate frame. The sensor can be configured to ray-cast against - a set of meshes with a given ray pattern. - - The meshes are parsed from the list of primitive paths provided in the configuration. These are then - converted to warp meshes and stored in the `warp_meshes` list. The ray-caster then ray-casts against - these warp meshes using the ray pattern provided in the configuration. - - .. note:: - Currently, only static meshes are supported. Extending the warp mesh to support dynamic meshes - is a work in progress. - """ - - cfg: RayCasterCfg - """The configuration parameters.""" - meshes: ClassVar[dict[str, wp.Mesh]] = {} - """The warp meshes available for raycasting. - - The keys correspond to the prim path for the meshes, and values are the corresponding warp Mesh objects. - - Note: - We store a global dictionary of all warp meshes to prevent re-loading the mesh for different ray-cast sensor instances. - """ - -
[文档] def __init__(self, cfg: RayCasterCfg): - """Initializes the ray-caster object. - - Args: - cfg: The configuration parameters. - """ - # check if sensor path is valid - # note: currently we do not handle environment indices if there is a regex pattern in the leaf - # For example, if the prim path is "/World/Sensor_[1,2]". - sensor_path = cfg.prim_path.split("/")[-1] - sensor_path_is_regex = re.match(r"^[a-zA-Z0-9/_]+$", sensor_path) is None - if sensor_path_is_regex: - raise RuntimeError( - f"Invalid prim path for the ray-caster sensor: {self.cfg.prim_path}." - "\n\tHint: Please ensure that the prim path does not contain any regex patterns in the leaf." - ) - # Initialize base class - super().__init__(cfg) - # Create empty variables for storing output data - self._data = RayCasterData()
- - def __str__(self) -> str: - """Returns: A string containing information about the instance.""" - return ( - f"Ray-caster @ '{self.cfg.prim_path}': \n" - f"\tview type : {self._view.__class__}\n" - f"\tupdate period (s) : {self.cfg.update_period}\n" - f"\tnumber of meshes : {len(RayCaster.meshes)}\n" - f"\tnumber of sensors : {self._view.count}\n" - f"\tnumber of rays/sensor: {self.num_rays}\n" - f"\ttotal number of rays : {self.num_rays * self._view.count}" - ) - - """ - Properties - """ - - @property - def num_instances(self) -> int: - return self._view.count - - @property - def data(self) -> RayCasterData: - # update sensors if needed - self._update_outdated_buffers() - # return the data - return self._data - - """ - Operations. - """ - -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - # reset the timers and counters - super().reset(env_ids) - # resolve None - if env_ids is None: - env_ids = slice(None) - # resample the drift - self.drift[env_ids].uniform_(*self.cfg.drift_range)
- - """ - Implementation. - """ - - def _initialize_impl(self): - super()._initialize_impl() - # create simulation view - self._physics_sim_view = physx.create_simulation_view(self._backend) - self._physics_sim_view.set_subspace_roots("/") - # check if the prim at path is an articulated or rigid prim - # we do this since for physics-based view classes we can access their data directly - # otherwise we need to use the xform view class which is slower - found_supported_prim_class = False - prim = sim_utils.find_first_matching_prim(self.cfg.prim_path) - if prim is None: - raise RuntimeError(f"Failed to find a prim at path expression: {self.cfg.prim_path}") - # create view based on the type of prim - if prim.HasAPI(UsdPhysics.ArticulationRootAPI): - self._view = self._physics_sim_view.create_articulation_view(self.cfg.prim_path.replace(".*", "*")) - found_supported_prim_class = True - elif prim.HasAPI(UsdPhysics.RigidBodyAPI): - self._view = self._physics_sim_view.create_rigid_body_view(self.cfg.prim_path.replace(".*", "*")) - found_supported_prim_class = True - else: - self._view = XFormPrimView(self.cfg.prim_path, reset_xform_properties=False) - found_supported_prim_class = True - carb.log_warn(f"The prim at path {prim.GetPath().pathString} is not a physics prim! Using XFormPrimView.") - # check if prim view class is found - if not found_supported_prim_class: - raise RuntimeError(f"Failed to find a valid prim view class for the prim paths: {self.cfg.prim_path}") - - # load the meshes by parsing the stage - self._initialize_warp_meshes() - # initialize the ray start and directions - self._initialize_rays_impl() - - def _initialize_warp_meshes(self): - # check number of mesh prims provided - if len(self.cfg.mesh_prim_paths) != 1: - raise NotImplementedError( - f"RayCaster currently only supports one mesh prim. Received: {len(self.cfg.mesh_prim_paths)}" - ) - - # read prims to ray-cast - for mesh_prim_path in self.cfg.mesh_prim_paths: - # check if mesh already casted into warp mesh - if mesh_prim_path in RayCaster.meshes: - continue - - # check if the prim is a plane - handle PhysX plane as a special case - # if a plane exists then we need to create an infinite mesh that is a plane - mesh_prim = sim_utils.get_first_matching_child_prim( - mesh_prim_path, lambda prim: prim.GetTypeName() == "Plane" - ) - # if we did not find a plane then we need to read the mesh - if mesh_prim is None: - # obtain the mesh prim - mesh_prim = sim_utils.get_first_matching_child_prim( - mesh_prim_path, lambda prim: prim.GetTypeName() == "Mesh" - ) - # check if valid - if mesh_prim is None or not mesh_prim.IsValid(): - raise RuntimeError(f"Invalid mesh prim path: {mesh_prim_path}") - # cast into UsdGeomMesh - mesh_prim = UsdGeom.Mesh(mesh_prim) - # read the vertices and faces - points = np.asarray(mesh_prim.GetPointsAttr().Get()) - indices = np.asarray(mesh_prim.GetFaceVertexIndicesAttr().Get()) - wp_mesh = convert_to_warp_mesh(points, indices, device=self.device) - # print info - carb.log_info( - f"Read mesh prim: {mesh_prim.GetPath()} with {len(points)} vertices and {len(indices)} faces." - ) - else: - mesh = make_plane(size=(2e6, 2e6), height=0.0, center_zero=True) - wp_mesh = convert_to_warp_mesh(mesh.vertices, mesh.faces, device=self.device) - # print info - carb.log_info(f"Created infinite plane mesh prim: {mesh_prim.GetPath()}.") - # add the warp mesh to the list - RayCaster.meshes[mesh_prim_path] = wp_mesh - - # throw an error if no meshes are found - if all([mesh_prim_path not in RayCaster.meshes for mesh_prim_path in self.cfg.mesh_prim_paths]): - raise RuntimeError( - f"No meshes found for ray-casting! Please check the mesh prim paths: {self.cfg.mesh_prim_paths}" - ) - - def _initialize_rays_impl(self): - # compute ray stars and directions - self.ray_starts, self.ray_directions = self.cfg.pattern_cfg.func(self.cfg.pattern_cfg, self._device) - self.num_rays = len(self.ray_directions) - # apply offset transformation to the rays - offset_pos = torch.tensor(list(self.cfg.offset.pos), device=self._device) - offset_quat = torch.tensor(list(self.cfg.offset.rot), device=self._device) - self.ray_directions = quat_apply(offset_quat.repeat(len(self.ray_directions), 1), self.ray_directions) - self.ray_starts += offset_pos - # repeat the rays for each sensor - self.ray_starts = self.ray_starts.repeat(self._view.count, 1, 1) - self.ray_directions = self.ray_directions.repeat(self._view.count, 1, 1) - # prepare drift - self.drift = torch.zeros(self._view.count, 3, device=self.device) - # fill the data buffer - self._data.pos_w = torch.zeros(self._view.count, 3, device=self._device) - self._data.quat_w = torch.zeros(self._view.count, 4, device=self._device) - self._data.ray_hits_w = torch.zeros(self._view.count, self.num_rays, 3, device=self._device) - - def _update_buffers_impl(self, env_ids: Sequence[int]): - """Fills the buffers of the sensor data.""" - # obtain the poses of the sensors - if isinstance(self._view, XFormPrimView): - pos_w, quat_w = self._view.get_world_poses(env_ids) - elif isinstance(self._view, physx.ArticulationView): - pos_w, quat_w = self._view.get_root_transforms()[env_ids].split([3, 4], dim=-1) - quat_w = convert_quat(quat_w, to="wxyz") - elif isinstance(self._view, physx.RigidBodyView): - pos_w, quat_w = self._view.get_transforms()[env_ids].split([3, 4], dim=-1) - quat_w = convert_quat(quat_w, to="wxyz") - else: - raise RuntimeError(f"Unsupported view type: {type(self._view)}") - # note: we clone here because we are read-only operations - pos_w = pos_w.clone() - quat_w = quat_w.clone() - # apply drift - pos_w += self.drift[env_ids] - # store the poses - self._data.pos_w[env_ids] = pos_w - self._data.quat_w[env_ids] = quat_w - - # ray cast based on the sensor poses - if self.cfg.attach_yaw_only: - # only yaw orientation is considered and directions are not rotated - ray_starts_w = quat_apply_yaw(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids]) - ray_starts_w += pos_w.unsqueeze(1) - ray_directions_w = self.ray_directions[env_ids] - else: - # full orientation is considered - ray_starts_w = quat_apply(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids]) - ray_starts_w += pos_w.unsqueeze(1) - ray_directions_w = quat_apply(quat_w.repeat(1, self.num_rays), self.ray_directions[env_ids]) - # ray cast and store the hits - # TODO: Make this work for multiple meshes? - self._data.ray_hits_w[env_ids] = raycast_mesh( - ray_starts_w, - ray_directions_w, - max_dist=self.cfg.max_distance, - mesh=RayCaster.meshes[self.cfg.mesh_prim_paths[0]], - )[0] - - def _set_debug_vis_impl(self, debug_vis: bool): - # set visibility of markers - # note: parent only deals with callbacks. not their visibility - if debug_vis: - if not hasattr(self, "ray_visualizer"): - self.ray_visualizer = VisualizationMarkers(self.cfg.visualizer_cfg) - # set their visibility to true - self.ray_visualizer.set_visibility(True) - else: - if hasattr(self, "ray_visualizer"): - self.ray_visualizer.set_visibility(False) - - def _debug_vis_callback(self, event): - # show ray hit positions - self.ray_visualizer.visualize(self._data.ray_hits_w.view(-1, 3)) - - """ - Internal simulation callbacks. - """ - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - # call parent - super()._invalidate_initialize_callback(event) - # set all existing views to None to invalidate them - self._physics_sim_view = None - self._view = None
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_camera.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_camera.html deleted file mode 100644 index 97ee8e267a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_camera.html +++ /dev/null @@ -1,962 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.ray_caster.ray_caster_camera — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.ray_caster.ray_caster_camera 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from tensordict import TensorDict
-from typing import TYPE_CHECKING, ClassVar, Literal
-
-import omni.physics.tensors.impl.api as physx
-from omni.isaac.core.prims import XFormPrimView
-
-import omni.isaac.lab.utils.math as math_utils
-from omni.isaac.lab.sensors.camera import CameraData
-from omni.isaac.lab.sensors.camera.utils import convert_orientation_convention, create_rotation_matrix_from_view
-from omni.isaac.lab.utils.warp import raycast_mesh
-
-from .ray_caster import RayCaster
-
-if TYPE_CHECKING:
-    from .ray_caster_camera_cfg import RayCasterCameraCfg
-
-
-
[文档]class RayCasterCamera(RayCaster): - """A ray-casting camera sensor. - - The ray-caster camera uses a set of rays to get the distances to meshes in the scene. The rays are - defined in the sensor's local coordinate frame. The sensor has the same interface as the - :class:`omni.isaac.lab.sensors.Camera` that implements the camera class through USD camera prims. - However, this class provides a faster image generation. The sensor converts meshes from the list of - primitive paths provided in the configuration to Warp meshes. The camera then ray-casts against these - Warp meshes only. - - Currently, only the following annotators are supported: - - - ``"distance_to_camera"``: An image containing the distance to camera optical center. - - ``"distance_to_image_plane"``: An image containing distances of 3D points from camera plane along camera's z-axis. - - ``"normals"``: An image containing the local surface normal vectors at each pixel. - - .. note:: - Currently, only static meshes are supported. Extending the warp mesh to support dynamic meshes - is a work in progress. - """ - - cfg: RayCasterCameraCfg - """The configuration parameters.""" - UNSUPPORTED_TYPES: ClassVar[set[str]] = { - "rgb", - "instance_id_segmentation", - "instance_id_segmentation_fast", - "instance_segmentation", - "instance_segmentation_fast", - "semantic_segmentation", - "skeleton_data", - "motion_vectors", - "bounding_box_2d_tight", - "bounding_box_2d_tight_fast", - "bounding_box_2d_loose", - "bounding_box_2d_loose_fast", - "bounding_box_3d", - "bounding_box_3d_fast", - } - """A set of sensor types that are not supported by the ray-caster camera.""" - -
[文档] def __init__(self, cfg: RayCasterCameraCfg): - """Initializes the camera object. - - Args: - cfg: The configuration parameters. - - Raises: - ValueError: If the provided data types are not supported by the ray-caster camera. - """ - # perform check on supported data types - self._check_supported_data_types(cfg) - # initialize base class - super().__init__(cfg) - # create empty variables for storing output data - self._data = CameraData()
- - def __str__(self) -> str: - """Returns: A string containing information about the instance.""" - return ( - f"Ray-Caster-Camera @ '{self.cfg.prim_path}': \n" - f"\tview type : {self._view.__class__}\n" - f"\tupdate period (s) : {self.cfg.update_period}\n" - f"\tnumber of meshes : {len(RayCaster.meshes)}\n" - f"\tnumber of sensors : {self._view.count}\n" - f"\tnumber of rays/sensor: {self.num_rays}\n" - f"\ttotal number of rays : {self.num_rays * self._view.count}\n" - f"\timage shape : {self.image_shape}" - ) - - """ - Properties - """ - - @property - def data(self) -> CameraData: - # update sensors if needed - self._update_outdated_buffers() - # return the data - return self._data - - @property - def image_shape(self) -> tuple[int, int]: - """A tuple containing (height, width) of the camera sensor.""" - return (self.cfg.pattern_cfg.height, self.cfg.pattern_cfg.width) - - @property - def frame(self) -> torch.tensor: - """Frame number when the measurement took place.""" - return self._frame - - """ - Operations. - """ - -
[文档] def set_intrinsic_matrices( - self, matrices: torch.Tensor, focal_length: float = 1.0, env_ids: Sequence[int] | None = None - ): - """Set the intrinsic matrix of the camera. - - Args: - matrices: The intrinsic matrices for the camera. Shape is (N, 3, 3). - focal_length: Focal length to use when computing aperture values. Defaults to 1.0. - env_ids: A sensor ids to manipulate. Defaults to None, which means all sensor indices. - """ - # resolve env_ids - if env_ids is None: - env_ids = slice(None) - # save new intrinsic matrices and focal length - self._data.intrinsic_matrices[env_ids] = matrices.to(self._device) - self._focal_length = focal_length - # recompute ray directions - self.ray_starts[env_ids], self.ray_directions[env_ids] = self.cfg.pattern_cfg.func( - self.cfg.pattern_cfg, self._data.intrinsic_matrices[env_ids], self._device - )
- -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - # reset the timestamps - super().reset(env_ids) - # resolve None - if env_ids is None: - env_ids = slice(None) - # reset the data - # note: this recomputation is useful if one performs events such as randomizations on the camera poses. - pos_w, quat_w = self._compute_camera_world_poses(env_ids) - self._data.pos_w[env_ids] = pos_w - self._data.quat_w_world[env_ids] = quat_w - # Reset the frame count - self._frame[env_ids] = 0
- -
[文档] def set_world_poses( - self, - positions: torch.Tensor | None = None, - orientations: torch.Tensor | None = None, - env_ids: Sequence[int] | None = None, - convention: Literal["opengl", "ros", "world"] = "ros", - ): - """Set the pose of the camera w.r.t. the world frame using specified convention. - - Since different fields use different conventions for camera orientations, the method allows users to - set the camera poses in the specified convention. Possible conventions are: - - - :obj:`"opengl"` - forward axis: -Z - up axis +Y - Offset is applied in the OpenGL (Usd.Camera) convention - - :obj:`"ros"` - forward axis: +Z - up axis -Y - Offset is applied in the ROS convention - - :obj:`"world"` - forward axis: +X - up axis +Z - Offset is applied in the World Frame convention - - See :meth:`omni.isaac.lab.sensors.camera.utils.convert_orientation_convention` for more details - on the conventions. - - Args: - positions: The cartesian coordinates (in meters). Shape is (N, 3). - Defaults to None, in which case the camera position in not changed. - orientations: The quaternion orientation in (w, x, y, z). Shape is (N, 4). - Defaults to None, in which case the camera orientation in not changed. - env_ids: A sensor ids to manipulate. Defaults to None, which means all sensor indices. - convention: The convention in which the poses are fed. Defaults to "ros". - - Raises: - RuntimeError: If the camera prim is not set. Need to call :meth:`initialize` method first. - """ - # resolve env_ids - if env_ids is None: - env_ids = self._ALL_INDICES - - # get current positions - pos_w, quat_w = self._compute_view_world_poses(env_ids) - if positions is not None: - # transform to camera frame - pos_offset_world_frame = positions - pos_w - self._offset_pos[env_ids] = math_utils.quat_apply(math_utils.quat_inv(quat_w), pos_offset_world_frame) - if orientations is not None: - # convert rotation matrix from input convention to world - quat_w_set = convert_orientation_convention(orientations, origin=convention, target="world") - self._offset_quat[env_ids] = math_utils.quat_mul(math_utils.quat_inv(quat_w), quat_w_set) - - # update the data - pos_w, quat_w = self._compute_camera_world_poses(env_ids) - self._data.pos_w[env_ids] = pos_w - self._data.quat_w_world[env_ids] = quat_w
- -
[文档] def set_world_poses_from_view( - self, eyes: torch.Tensor, targets: torch.Tensor, env_ids: Sequence[int] | None = None - ): - """Set the poses of the camera from the eye position and look-at target position. - - Args: - eyes: The positions of the camera's eye. Shape is N, 3). - targets: The target locations to look at. Shape is (N, 3). - env_ids: A sensor ids to manipulate. Defaults to None, which means all sensor indices. - - Raises: - RuntimeError: If the camera prim is not set. Need to call :meth:`initialize` method first. - NotImplementedError: If the stage up-axis is not "Y" or "Z". - """ - # camera position and rotation in opengl convention - orientations = math_utils.quat_from_matrix(create_rotation_matrix_from_view(eyes, targets, device=self._device)) - self.set_world_poses(eyes, orientations, env_ids, convention="opengl")
- - """ - Implementation. - """ - - def _initialize_rays_impl(self): - # Create all indices buffer - self._ALL_INDICES = torch.arange(self._view.count, device=self._device, dtype=torch.long) - # Create frame count buffer - self._frame = torch.zeros(self._view.count, device=self._device, dtype=torch.long) - # create buffers - self._create_buffers() - # compute intrinsic matrices - self._compute_intrinsic_matrices() - # compute ray stars and directions - self.ray_starts, self.ray_directions = self.cfg.pattern_cfg.func( - self.cfg.pattern_cfg, self._data.intrinsic_matrices, self._device - ) - self.num_rays = self.ray_directions.shape[1] - # create buffer to store ray hits - self.ray_hits_w = torch.zeros(self._view.count, self.num_rays, 3, device=self._device) - # set offsets - quat_w = convert_orientation_convention( - torch.tensor([self.cfg.offset.rot], device=self._device), origin=self.cfg.offset.convention, target="world" - ) - self._offset_quat = quat_w.repeat(self._view.count, 1) - self._offset_pos = torch.tensor(list(self.cfg.offset.pos), device=self._device).repeat(self._view.count, 1) - - def _update_buffers_impl(self, env_ids: Sequence[int]): - """Fills the buffers of the sensor data.""" - # increment frame count - self._frame[env_ids] += 1 - - # compute poses from current view - pos_w, quat_w = self._compute_camera_world_poses(env_ids) - # update the data - self._data.pos_w[env_ids] = pos_w - self._data.quat_w_world[env_ids] = quat_w - - # note: full orientation is considered - ray_starts_w = math_utils.quat_apply(quat_w.repeat(1, self.num_rays), self.ray_starts[env_ids]) - ray_starts_w += pos_w.unsqueeze(1) - ray_directions_w = math_utils.quat_apply(quat_w.repeat(1, self.num_rays), self.ray_directions[env_ids]) - # ray cast and store the hits - # TODO: Make ray-casting work for multiple meshes? - # necessary for regular dictionaries. - self.ray_hits_w, ray_depth, ray_normal, _ = raycast_mesh( - ray_starts_w, - ray_directions_w, - mesh=RayCasterCamera.meshes[self.cfg.mesh_prim_paths[0]], - max_dist=self.cfg.max_distance, - return_distance=any( - [name in self.cfg.data_types for name in ["distance_to_image_plane", "distance_to_camera"]] - ), - return_normal="normals" in self.cfg.data_types, - ) - # update output buffers - if "distance_to_image_plane" in self.cfg.data_types: - # note: data is in camera frame so we only take the first component (z-axis of camera frame) - distance_to_image_plane = ( - math_utils.quat_apply( - math_utils.quat_inv(quat_w).repeat(1, self.num_rays), - (ray_depth[:, :, None] * ray_directions_w), - ) - )[:, :, 0] - self._data.output["distance_to_image_plane"][env_ids] = distance_to_image_plane.view(-1, *self.image_shape) - if "distance_to_camera" in self.cfg.data_types: - self._data.output["distance_to_camera"][env_ids] = ray_depth.view(-1, *self.image_shape) - if "normals" in self.cfg.data_types: - self._data.output["normals"][env_ids] = ray_normal.view(-1, *self.image_shape, 3) - - def _debug_vis_callback(self, event): - # in case it crashes be safe - if not hasattr(self, "ray_hits_w"): - return - # show ray hit positions - self.ray_visualizer.visualize(self.ray_hits_w.view(-1, 3)) - - """ - Private Helpers - """ - - def _check_supported_data_types(self, cfg: RayCasterCameraCfg): - """Checks if the data types are supported by the ray-caster camera.""" - # check if there is any intersection in unsupported types - # reason: we cannot obtain this data from simplified warp-based ray caster - common_elements = set(cfg.data_types) & RayCasterCamera.UNSUPPORTED_TYPES - if common_elements: - raise ValueError( - f"RayCasterCamera class does not support the following sensor types: {common_elements}." - "\n\tThis is because these sensor types cannot be obtained in a fast way using ''warp''." - "\n\tHint: If you need to work with these sensor types, we recommend using the USD camera" - " interface from the omni.isaac.lab.sensors.camera module." - ) - - def _create_buffers(self): - """Create buffers for storing data.""" - # prepare drift - self.drift = torch.zeros(self._view.count, 3, device=self.device) - # create the data object - # -- pose of the cameras - self._data.pos_w = torch.zeros((self._view.count, 3), device=self._device) - self._data.quat_w_world = torch.zeros((self._view.count, 4), device=self._device) - # -- intrinsic matrix - self._data.intrinsic_matrices = torch.zeros((self._view.count, 3, 3), device=self._device) - self._data.intrinsic_matrices[:, 2, 2] = 1.0 - self._data.image_shape = self.image_shape - # -- output data - # create the buffers to store the annotator data. - self._data.output = TensorDict({}, batch_size=self._view.count, device=self.device) - self._data.info = [{name: None for name in self.cfg.data_types}] * self._view.count - for name in self.cfg.data_types: - if name in ["distance_to_image_plane", "distance_to_camera"]: - shape = (self.cfg.pattern_cfg.height, self.cfg.pattern_cfg.width) - elif name in ["normals"]: - shape = (self.cfg.pattern_cfg.height, self.cfg.pattern_cfg.width, 3) - else: - raise ValueError(f"Received unknown data type: {name}. Please check the configuration.") - # allocate tensor to store the data - self._data.output[name] = torch.zeros((self._view.count, *shape), device=self._device) - - def _compute_intrinsic_matrices(self): - """Computes the intrinsic matrices for the camera based on the config provided.""" - # get the sensor properties - pattern_cfg = self.cfg.pattern_cfg - # compute the intrinsic matrix - vertical_aperture = pattern_cfg.horizontal_aperture * pattern_cfg.height / pattern_cfg.width - f_x = pattern_cfg.width * pattern_cfg.focal_length / pattern_cfg.horizontal_aperture - f_y = pattern_cfg.height * pattern_cfg.focal_length / vertical_aperture - c_x = pattern_cfg.horizontal_aperture_offset * f_x + pattern_cfg.width / 2 - c_y = pattern_cfg.vertical_aperture_offset * f_y + pattern_cfg.height / 2 - # allocate the intrinsic matrices - self._data.intrinsic_matrices[:, 0, 0] = f_x - self._data.intrinsic_matrices[:, 0, 2] = c_x - self._data.intrinsic_matrices[:, 1, 1] = f_y - self._data.intrinsic_matrices[:, 1, 2] = c_y - # save focal length - self._focal_length = pattern_cfg.focal_length - - def _compute_view_world_poses(self, env_ids: Sequence[int]) -> tuple[torch.Tensor, torch.Tensor]: - """Obtains the pose of the view the camera is attached to in the world frame. - - Returns: - A tuple of the position (in meters) and quaternion (w, x, y, z). - """ - # obtain the poses of the sensors - # note: clone arg doesn't exist for xform prim view so we need to do this manually - if isinstance(self._view, XFormPrimView): - pos_w, quat_w = self._view.get_world_poses(env_ids) - elif isinstance(self._view, physx.ArticulationView): - pos_w, quat_w = self._view.get_root_transforms()[env_ids].split([3, 4], dim=-1) - quat_w = math_utils.convert_quat(quat_w, to="wxyz") - elif isinstance(self._view, physx.RigidBodyView): - pos_w, quat_w = self._view.get_transforms()[env_ids].split([3, 4], dim=-1) - quat_w = math_utils.convert_quat(quat_w, to="wxyz") - else: - raise RuntimeError(f"Unsupported view type: {type(self._view)}") - # return the pose - return pos_w.clone(), quat_w.clone() - - def _compute_camera_world_poses(self, env_ids: Sequence[int]) -> tuple[torch.Tensor, torch.Tensor]: - """Computes the pose of the camera in the world frame. - - This function applies the offset pose to the pose of the view the camera is attached to. - - Returns: - A tuple of the position (in meters) and quaternion (w, x, y, z) in "world" convention. - """ - # get the pose of the view the camera is attached to - pos_w, quat_w = self._compute_view_world_poses(env_ids) - # apply offsets - # need to apply quat because offset relative to parent frame - pos_w += math_utils.quat_apply(quat_w, self._offset_pos[env_ids]) - quat_w = math_utils.quat_mul(quat_w, self._offset_quat[env_ids]) - - return pos_w, quat_w
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_camera_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_camera_cfg.html deleted file mode 100644 index e0fb18969a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_camera_cfg.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.ray_caster.ray_caster_camera_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.ray_caster.ray_caster_camera_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Configuration for the ray-cast camera sensor."""
-
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-from .ray_caster_camera import RayCasterCamera
-from .ray_caster_cfg import RayCasterCfg
-
-
-
[文档]@configclass -class RayCasterCameraCfg(RayCasterCfg): - """Configuration for the ray-cast sensor.""" - -
[文档] @configclass - class OffsetCfg: - """The offset pose of the sensor's frame from the sensor's parent frame.""" - - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" - - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation (w, x, y, z) w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0).""" - - convention: Literal["opengl", "ros", "world"] = "ros" - """The convention in which the frame offset is applied. Defaults to "ros". - - - ``"opengl"`` - forward axis: ``-Z`` - up axis: ``+Y`` - Offset is applied in the OpenGL (Usd.Camera) convention. - - ``"ros"`` - forward axis: ``+Z`` - up axis: ``-Y`` - Offset is applied in the ROS convention. - - ``"world"`` - forward axis: ``+X`` - up axis: ``+Z`` - Offset is applied in the World Frame convention. - - """
- - class_type: type = RayCasterCamera - - offset: OffsetCfg = OffsetCfg() - """The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity.""" - - data_types: list[str] = ["distance_to_image_plane"] - """List of sensor names/types to enable for the camera. Defaults to ["distance_to_image_plane"].""" - - def __post_init__(self): - # for cameras, this quantity should be False always. - self.attach_yaw_only = False
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_cfg.html deleted file mode 100644 index 1da7df3d69..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_cfg.html +++ /dev/null @@ -1,632 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.ray_caster.ray_caster_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.ray_caster.ray_caster_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Configuration for the ray-cast sensor."""
-
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.markers import VisualizationMarkersCfg
-from omni.isaac.lab.markers.config import RAY_CASTER_MARKER_CFG
-from omni.isaac.lab.utils import configclass
-
-from ..sensor_base_cfg import SensorBaseCfg
-from .patterns.patterns_cfg import PatternBaseCfg
-from .ray_caster import RayCaster
-
-
-
[文档]@configclass -class RayCasterCfg(SensorBaseCfg): - """Configuration for the ray-cast sensor.""" - -
[文档] @configclass - class OffsetCfg: - """The offset pose of the sensor's frame from the sensor's parent frame.""" - - pos: tuple[float, float, float] = (0.0, 0.0, 0.0) - """Translation w.r.t. the parent frame. Defaults to (0.0, 0.0, 0.0).""" - rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0) - """Quaternion rotation (w, x, y, z) w.r.t. the parent frame. Defaults to (1.0, 0.0, 0.0, 0.0)."""
- - class_type: type = RayCaster - - mesh_prim_paths: list[str] = MISSING - """The list of mesh primitive paths to ray cast against. - - Note: - Currently, only a single static mesh is supported. We are working on supporting multiple - static meshes and dynamic meshes. - """ - - offset: OffsetCfg = OffsetCfg() - """The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity.""" - - attach_yaw_only: bool = MISSING - """Whether the rays' starting positions and directions only track the yaw orientation. - - This is useful for ray-casting height maps, where only yaw rotation is needed. - """ - - pattern_cfg: PatternBaseCfg = MISSING - """The pattern that defines the local ray starting positions and directions.""" - - max_distance: float = 1e6 - """Maximum distance (in meters) from the sensor to ray cast to. Defaults to 1e6.""" - - drift_range: tuple[float, float] = (0.0, 0.0) - """The range of drift (in meters) to add to the ray starting positions (xyz). Defaults to (0.0, 0.0). - - For floating base robots, this is useful for simulating drift in the robot's pose estimation. - """ - - visualizer_cfg: VisualizationMarkersCfg = RAY_CASTER_MARKER_CFG.replace(prim_path="/Visuals/RayCaster") - """The configuration object for the visualization markers. Defaults to RAY_CASTER_MARKER_CFG. - - Note: - This attribute is only used when debug visualization is enabled. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_data.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_data.html deleted file mode 100644 index 0a8ca59043..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/ray_caster/ray_caster_data.html +++ /dev/null @@ -1,592 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.ray_caster.ray_caster_data — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.ray_caster.ray_caster_data 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-from dataclasses import dataclass
-
-
-
[文档]@dataclass -class RayCasterData: - """Data container for the ray-cast sensor.""" - - pos_w: torch.Tensor = None - """Position of the sensor origin in world frame. - - Shape is (N, 3), where N is the number of sensors. - """ - quat_w: torch.Tensor = None - """Orientation of the sensor origin in quaternion (w, x, y, z) in world frame. - - Shape is (N, 4), where N is the number of sensors. - """ - ray_hits_w: torch.Tensor = None - """The ray hit positions in the world frame. - - Shape is (N, B, 3), where N is the number of sensors, B is the number of rays - in the scan pattern per sensor. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/sensor_base.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/sensor_base.html deleted file mode 100644 index 1458cb767e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/sensor_base.html +++ /dev/null @@ -1,854 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.sensor_base — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.sensor_base 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Base class for sensors.
-
-This class defines an interface for sensors similar to how the :class:`omni.isaac.lab.assets.AssetBase` class works.
-Each sensor class should inherit from this class and implement the abstract methods.
-"""
-
-from __future__ import annotations
-
-import inspect
-import torch
-import weakref
-from abc import ABC, abstractmethod
-from collections.abc import Sequence
-from typing import TYPE_CHECKING, Any
-
-import omni.kit.app
-import omni.timeline
-
-import omni.isaac.lab.sim as sim_utils
-
-if TYPE_CHECKING:
-    from .sensor_base_cfg import SensorBaseCfg
-
-
-
[文档]class SensorBase(ABC): - """The base class for implementing a sensor. - - The implementation is based on lazy evaluation. The sensor data is only updated when the user - tries accessing the data through the :attr:`data` property or sets ``force_compute=True`` in - the :meth:`update` method. This is done to avoid unnecessary computation when the sensor data - is not used. - - The sensor is updated at the specified update period. If the update period is zero, then the - sensor is updated at every simulation step. - """ - -
[文档] def __init__(self, cfg: SensorBaseCfg): - """Initialize the sensor class. - - Args: - cfg: The configuration parameters for the sensor. - """ - # check that config is valid - if cfg.history_length < 0: - raise ValueError(f"History length must be greater than 0! Received: {cfg.history_length}") - # store inputs - self.cfg = cfg - # flag for whether the sensor is initialized - self._is_initialized = False - # flag for whether the sensor is in visualization mode - self._is_visualizing = False - - # note: Use weakref on callbacks to ensure that this object can be deleted when its destructor is called. - # add callbacks for stage play/stop - # The order is set to 10 which is arbitrary but should be lower priority than the default order of 0 - timeline_event_stream = omni.timeline.get_timeline_interface().get_timeline_event_stream() - self._initialize_handle = timeline_event_stream.create_subscription_to_pop_by_type( - int(omni.timeline.TimelineEventType.PLAY), - lambda event, obj=weakref.proxy(self): obj._initialize_callback(event), - order=10, - ) - self._invalidate_initialize_handle = timeline_event_stream.create_subscription_to_pop_by_type( - int(omni.timeline.TimelineEventType.STOP), - lambda event, obj=weakref.proxy(self): obj._invalidate_initialize_callback(event), - order=10, - ) - # add handle for debug visualization (this is set to a valid handle inside set_debug_vis) - self._debug_vis_handle = None - # set initial state of debug visualization - self.set_debug_vis(self.cfg.debug_vis)
- - def __del__(self): - """Unsubscribe from the callbacks.""" - # clear physics events handles - if self._initialize_handle: - self._initialize_handle.unsubscribe() - self._initialize_handle = None - if self._invalidate_initialize_handle: - self._invalidate_initialize_handle.unsubscribe() - self._invalidate_initialize_handle = None - # clear debug visualization - if self._debug_vis_handle: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - - """ - Properties - """ - - @property - def is_initialized(self) -> bool: - """Whether the sensor is initialized. - - Returns True if the sensor is initialized, False otherwise. - """ - return self._is_initialized - - @property - def num_instances(self) -> int: - """Number of instances of the sensor. - - This is equal to the number of sensors per environment multiplied by the number of environments. - """ - return self._num_envs - - @property - def device(self) -> str: - """Memory device for computation.""" - return self._device - - @property - @abstractmethod - def data(self) -> Any: - """Data from the sensor. - - This property is only updated when the user tries to access the data. This is done to avoid - unnecessary computation when the sensor data is not used. - - For updating the sensor when this property is accessed, you can use the following - code snippet in your sensor implementation: - - .. code-block:: python - - # update sensors if needed - self._update_outdated_buffers() - # return the data (where `_data` is the data for the sensor) - return self._data - """ - raise NotImplementedError - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the sensor has a debug visualization implemented.""" - # check if function raises NotImplementedError - source_code = inspect.getsource(self._set_debug_vis_impl) - return "NotImplementedError" not in source_code - - """ - Operations - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Sets whether to visualize the sensor data. - - Args: - debug_vis: Whether to visualize the sensor data. - - Returns: - Whether the debug visualization was successfully set. False if the sensor - does not support debug visualization. - """ - # check if debug visualization is supported - if not self.has_debug_vis_implementation: - return False - # toggle debug visualization objects - self._set_debug_vis_impl(debug_vis) - # toggle debug visualization flag - self._is_visualizing = debug_vis - # toggle debug visualization handles - if debug_vis: - # create a subscriber for the post update event if it doesn't exist - if self._debug_vis_handle is None: - 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) - ) - else: - # remove the subscriber if it exists - if self._debug_vis_handle is not None: - self._debug_vis_handle.unsubscribe() - self._debug_vis_handle = None - # return success - return True
- -
[文档] def reset(self, env_ids: Sequence[int] | None = None): - """Resets the sensor internals. - - Args: - env_ids: The sensor ids to reset. Defaults to None. - """ - # Resolve sensor ids - if env_ids is None: - env_ids = slice(None) - # Reset the timestamp for the sensors - self._timestamp[env_ids] = 0.0 - self._timestamp_last_update[env_ids] = 0.0 - # Set all reset sensors to outdated so that they are updated when data is called the next time. - self._is_outdated[env_ids] = True
- - def update(self, dt: float, force_recompute: bool = False): - # Update the timestamp for the sensors - self._timestamp += dt - self._is_outdated |= self._timestamp - self._timestamp_last_update + 1e-6 >= self.cfg.update_period - # Update the buffers - # TODO (from @mayank): Why is there a history length here when it doesn't mean anything in the sensor base?!? - # It is only for the contact sensor but there we should redefine the update function IMO. - if force_recompute or self._is_visualizing or (self.cfg.history_length > 0): - self._update_outdated_buffers() - - """ - Implementation specific. - """ - - @abstractmethod - def _initialize_impl(self): - """Initializes the sensor-related handles and internal buffers.""" - # Obtain Simulation Context - sim = sim_utils.SimulationContext.instance() - if sim is None: - raise RuntimeError("Simulation Context is not initialized!") - # Obtain device and backend - self._device = sim.device - self._backend = sim.backend - self._sim_physics_dt = sim.get_physics_dt() - # Count number of environments - env_prim_path_expr = self.cfg.prim_path.rsplit("/", 1)[0] - self._parent_prims = sim_utils.find_matching_prims(env_prim_path_expr) - self._num_envs = len(self._parent_prims) - # Boolean tensor indicating whether the sensor data has to be refreshed - self._is_outdated = torch.ones(self._num_envs, dtype=torch.bool, device=self._device) - # Current timestamp (in seconds) - self._timestamp = torch.zeros(self._num_envs, device=self._device) - # Timestamp from last update - self._timestamp_last_update = torch.zeros_like(self._timestamp) - - @abstractmethod - def _update_buffers_impl(self, env_ids: Sequence[int]): - """Fills the sensor data for provided environment ids. - - This function does not perform any time-based checks and directly fills the data into the - data container. - - Args: - env_ids: The indices of the sensors that are ready to capture. - """ - raise NotImplementedError - - def _set_debug_vis_impl(self, debug_vis: bool): - """Set debug visualization into visualization objects. - - This function is responsible for creating the visualization objects if they don't exist - and input ``debug_vis`` is True. If the visualization objects exist, the function should - set their visibility into the stage. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") - - def _debug_vis_callback(self, event): - """Callback for debug visualization. - - This function calls the visualization objects and sets the data to visualize into them. - """ - raise NotImplementedError(f"Debug visualization is not implemented for {self.__class__.__name__}.") - - """ - Internal simulation callbacks. - """ - - def _initialize_callback(self, event): - """Initializes the scene elements. - - Note: - PhysX handles are only enabled once the simulator starts playing. Hence, this function needs to be - called whenever the simulator "plays" from a "stop" state. - """ - if not self._is_initialized: - self._initialize_impl() - self._is_initialized = True - - def _invalidate_initialize_callback(self, event): - """Invalidates the scene elements.""" - self._is_initialized = False - - """ - Helper functions. - """ - - def _update_outdated_buffers(self): - """Fills the sensor data for the outdated sensors.""" - outdated_env_ids = self._is_outdated.nonzero().squeeze(-1) - if len(outdated_env_ids) > 0: - # obtain new data - self._update_buffers_impl(outdated_env_ids) - # update the timestamp from last update - self._timestamp_last_update[outdated_env_ids] = self._timestamp[outdated_env_ids] - # set outdated flag to false for the updated sensors - self._is_outdated[outdated_env_ids] = False
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sensors/sensor_base_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sensors/sensor_base_cfg.html deleted file mode 100644 index 4baa2358b6..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sensors/sensor_base_cfg.html +++ /dev/null @@ -1,605 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sensors.sensor_base_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sensors.sensor_base_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.utils import configclass
-
-from .sensor_base import SensorBase
-
-
-
[文档]@configclass -class SensorBaseCfg: - """Configuration parameters for a sensor.""" - - class_type: type[SensorBase] = MISSING - """The associated sensor class. - - The class should inherit from :class:`omni.isaac.lab.sensors.sensor_base.SensorBase`. - """ - - prim_path: str = MISSING - """Prim path (or expression) to the sensor. - - .. note:: - The expression can contain the environment namespace regex ``{ENV_REGEX_NS}`` which - will be replaced with the environment namespace. - - Example: ``{ENV_REGEX_NS}/Robot/sensor`` will be replaced with ``/World/envs/env_.*/Robot/sensor``. - - """ - - update_period: float = 0.0 - """Update period of the sensor buffers (in seconds). Defaults to 0.0 (update every step).""" - - history_length: int = 0 - """Number of past frames to store in the sensor buffers. Defaults to 0, which means that only - the current data is stored (no history).""" - - debug_vis: bool = False - """Whether to visualize the sensor. Defaults to False."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/asset_converter_base.html b/docs/_build/html/_modules/omni/isaac/lab/sim/converters/asset_converter_base.html deleted file mode 100644 index dcc805fdce..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/asset_converter_base.html +++ /dev/null @@ -1,759 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.converters.asset_converter_base — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.converters.asset_converter_base 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import abc
-import hashlib
-import json
-import os
-import pathlib
-import random
-from datetime import datetime
-
-from omni.isaac.lab.sim.converters.asset_converter_base_cfg import AssetConverterBaseCfg
-from omni.isaac.lab.utils.assets import check_file_path
-from omni.isaac.lab.utils.io import dump_yaml
-
-
-
[文档]class AssetConverterBase(abc.ABC): - """Base class for converting an asset file from different formats into USD format. - - This class provides a common interface for converting an asset file into USD. It does not - provide any implementation for the conversion. The derived classes must implement the - :meth:`_convert_asset` method to provide the actual conversion. - - The file conversion is lazy if the output directory (:obj:`AssetConverterBaseCfg.usd_dir`) is provided. - In the lazy conversion, the USD file is re-generated only if: - - * The asset file is modified. - * The configuration parameters are modified. - * The USD file does not exist. - - To override this behavior to force conversion, the flag :obj:`AssetConverterBaseCfg.force_usd_conversion` - can be set to True. - - When no output directory is defined, lazy conversion is deactivated and the generated USD file is - stored in folder ``/tmp/IsaacLab/usd_{date}_{time}_{random}``, where the parameters in braces are generated - at runtime. The random identifiers help avoid a race condition where two simultaneously triggered conversions - try to use the same directory for reading/writing the generated files. - - .. note:: - Changes to the parameters :obj:`AssetConverterBaseCfg.asset_path`, :obj:`AssetConverterBaseCfg.usd_dir`, and - :obj:`AssetConverterBaseCfg.usd_file_name` are not considered as modifications in the configuration instance that - trigger USD file re-generation. - - """ - -
[文档] def __init__(self, cfg: AssetConverterBaseCfg): - """Initializes the class. - - Args: - cfg: The configuration instance for converting an asset file to USD format. - - Raises: - ValueError: When provided asset file does not exist. - """ - # check if the asset file exists - if not check_file_path(cfg.asset_path): - raise ValueError(f"The asset path does not exist: {cfg.asset_path}") - # save the inputs - self.cfg = cfg - - # resolve USD directory name - if cfg.usd_dir is None: - # a folder in "/tmp/IsaacLab" by the name: usd_{date}_{time}_{random} - time_tag = datetime.now().strftime("%Y%m%d_%H%M%S") - self._usd_dir = f"/tmp/IsaacLab/usd_{time_tag}_{random.randrange(10000)}" - else: - self._usd_dir = cfg.usd_dir - - # resolve the file name from asset file name if not provided - if cfg.usd_file_name is None: - usd_file_name = pathlib.PurePath(cfg.asset_path).stem - else: - usd_file_name = cfg.usd_file_name - # add USD extension if not provided - if not (usd_file_name.endswith(".usd") or usd_file_name.endswith(".usda")): - self._usd_file_name = usd_file_name + ".usd" - else: - self._usd_file_name = usd_file_name - - # create the USD directory - os.makedirs(self.usd_dir, exist_ok=True) - # check if usd files exist - self._usd_file_exists = os.path.isfile(self.usd_path) - # path to read/write asset hash file - self._dest_hash_path = os.path.join(self.usd_dir, ".asset_hash") - # create asset hash to check if the asset has changed - self._asset_hash = self._config_to_hash(cfg) - # read the saved hash - try: - with open(self._dest_hash_path) as f: - existing_asset_hash = f.readline() - self._is_same_asset = existing_asset_hash == self._asset_hash - except FileNotFoundError: - self._is_same_asset = False - - # convert the asset to USD if the hash is different or USD file does not exist - if cfg.force_usd_conversion or not self._usd_file_exists or not self._is_same_asset: - # write the updated hash - with open(self._dest_hash_path, "w") as f: - f.write(self._asset_hash) - # convert the asset to USD - self._convert_asset(cfg) - # dump the configuration to a file - dump_yaml(os.path.join(self.usd_dir, "config.yaml"), cfg.to_dict()) - # add comment to top of the saved config file with information about the converter - current_date = datetime.now().strftime("%Y-%m-%d") - current_time = datetime.now().strftime("%H:%M:%S") - generation_comment = ( - f"##\n# Generated by {self.__class__.__name__} on {current_date} at {current_time}.\n##\n" - ) - with open(os.path.join(self.usd_dir, "config.yaml"), "a") as f: - f.write(generation_comment)
- - """ - Properties. - """ - - @property - def usd_dir(self) -> str: - """The absolute path to the directory where the generated USD files are stored.""" - return self._usd_dir - - @property - def usd_file_name(self) -> str: - """The file name of the generated USD file.""" - return self._usd_file_name - - @property - def usd_path(self) -> str: - """The absolute path to the generated USD file.""" - return os.path.join(self.usd_dir, self.usd_file_name) - - @property - def usd_instanceable_meshes_path(self) -> str: - """The relative path to the USD file with meshes. - - The path is with respect to the USD directory :attr:`usd_dir`. This is to ensure that the - mesh references in the generated USD file are resolved relatively. Otherwise, it becomes - difficult to move the USD asset to a different location. - """ - return os.path.join(".", "Props", "instanceable_meshes.usd") - - """ - Implementation specifics. - """ - - @abc.abstractmethod - def _convert_asset(self, cfg: AssetConverterBaseCfg): - """Converts the asset file to USD. - - Args: - cfg: The configuration instance for the input asset to USD conversion. - """ - raise NotImplementedError() - - """ - Private helpers. - """ - - @staticmethod - def _config_to_hash(cfg: AssetConverterBaseCfg) -> str: - """Converts the configuration object and asset file to an MD5 hash of a string. - - .. warning:: - It only checks the main asset file (:attr:`cfg.asset_path`). - - Args: - config : The asset converter configuration object. - - Returns: - An MD5 hash of a string. - """ - - # convert to dict and remove path related info - config_dic = cfg.to_dict() - _ = config_dic.pop("asset_path") - _ = config_dic.pop("usd_dir") - _ = config_dic.pop("usd_file_name") - # convert config dic to bytes - config_bytes = json.dumps(config_dic).encode() - # hash config - md5 = hashlib.md5() - md5.update(config_bytes) - - # read the asset file to observe changes - with open(cfg.asset_path, "rb") as f: - while True: - # read 64kb chunks to avoid memory issues for the large files! - data = f.read(65536) - if not data: - break - md5.update(data) - # return the hash - return md5.hexdigest()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/asset_converter_base_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/converters/asset_converter_base_cfg.html deleted file mode 100644 index ed53cc952e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/asset_converter_base_cfg.html +++ /dev/null @@ -1,611 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.converters.asset_converter_base_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.converters.asset_converter_base_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class AssetConverterBaseCfg: - """The base configuration class for asset converters.""" - - asset_path: str = MISSING - """The absolute path to the asset file to convert into USD.""" - - usd_dir: str | None = None - """The output directory path to store the generated USD file. Defaults to None. - - If None, it is resolved as ``/tmp/IsaacLab/usd_{date}_{time}_{random}``, where - the parameters in braces are runtime generated. - """ - - usd_file_name: str | None = None - """The name of the generated usd file. Defaults to None. - - If None, it is resolved from the asset file name. For example, if the asset file - name is ``"my_asset.urdf"``, then the generated USD file name is ``"my_asset.usd"``. - - If the providing file name does not end with ".usd" or ".usda", then the extension - ".usd" is appended to the file name. - """ - - force_usd_conversion: bool = False - """Force the conversion of the asset file to usd. Defaults to False. - - If True, then the USD file is always generated. It will overwrite the existing USD file if it exists. - """ - - make_instanceable: bool = True - """Make the generated USD file instanceable. Defaults to True. - - Note: - Instancing helps reduce the memory footprint of the asset when multiple copies of the asset are - used in the scene. For more information, please check the USD documentation on - `scene-graph instancing <https://openusd.org/dev/api/_usd__page__scenegraph_instancing.html>`_. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/mesh_converter.html b/docs/_build/html/_modules/omni/isaac/lab/sim/converters/mesh_converter.html deleted file mode 100644 index 3d9c3701fb..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/mesh_converter.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.converters.mesh_converter — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.converters.mesh_converter 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import asyncio
-import os
-
-import omni
-import omni.kit.commands
-import omni.usd
-from omni.isaac.core.utils.extensions import enable_extension
-from pxr import Usd, UsdGeom, UsdPhysics, UsdUtils
-
-from omni.isaac.lab.sim.converters.asset_converter_base import AssetConverterBase
-from omni.isaac.lab.sim.converters.mesh_converter_cfg import MeshConverterCfg
-from omni.isaac.lab.sim.schemas import schemas
-from omni.isaac.lab.sim.utils import export_prim_to_file
-
-
-
[文档]class MeshConverter(AssetConverterBase): - """Converter for a mesh file in OBJ / STL / FBX format to a USD file. - - This class wraps around the `omni.kit.asset_converter`_ extension to provide a lazy implementation - for mesh to USD conversion. It stores the output USD file in an instanceable format since that is - what is typically used in all learning related applications. - - To make the asset instanceable, we must follow a certain structure dictated by how USD scene-graph - instancing and physics work. The rigid body component must be added to each instance and not the - referenced asset (i.e. the prototype prim itself). This is because the rigid body component defines - properties that are specific to each instance and cannot be shared under the referenced asset. For - more information, please check the `documentation <https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/rigid-bodies.html#instancing-rigid-bodies>`_. - - Due to the above, we follow the following structure: - - * ``{prim_path}`` - The root prim that is an Xform with the rigid body and mass APIs if configured. - * ``{prim_path}/geometry`` - The prim that contains the mesh and optionally the materials if configured. - If instancing is enabled, this prim will be an instanceable reference to the prototype prim. - - .. _omni.kit.asset_converter: https://docs.omniverse.nvidia.com/extensions/latest/ext_asset-converter.html - - .. caution:: - When converting STL files, Z-up convention is assumed, even though this is not the default for many CAD - export programs. Asset orientation convention can either be modified directly in the CAD program's export - process or an offset can be added within the config in Isaac Lab. - - """ - - cfg: MeshConverterCfg - """The configuration instance for mesh to USD conversion.""" - -
[文档] def __init__(self, cfg: MeshConverterCfg): - """Initializes the class. - - Args: - cfg: The configuration instance for mesh to USD conversion. - """ - super().__init__(cfg=cfg)
- - """ - Implementation specific methods. - """ - - def _convert_asset(self, cfg: MeshConverterCfg): - """Generate USD from OBJ, STL or FBX. - - It stores the asset in the following format: - - /file_name (default prim) - |- /geometry <- Made instanceable if requested - |- /Looks - |- /mesh - - Args: - cfg: The configuration for conversion of mesh to USD. - - Raises: - RuntimeError: If the conversion using the Omniverse asset converter fails. - """ - # resolve mesh name and format - mesh_file_basename, mesh_file_format = os.path.basename(cfg.asset_path).split(".") - mesh_file_format = mesh_file_format.lower() - - # Convert USD - asyncio.get_event_loop().run_until_complete( - self._convert_mesh_to_usd( - in_file=cfg.asset_path, out_file=self.usd_path, prim_path=f"/{mesh_file_basename}" - ) - ) - # Open converted USD stage - # note: This opens a new stage and does not use the stage created earlier by the user - # create a new stage - stage = Usd.Stage.Open(self.usd_path) - # add USD to stage cache - stage_id = UsdUtils.StageCache.Get().Insert(stage) - # Get the default prim (which is the root prim) -- "/{mesh_file_basename}" - xform_prim = stage.GetDefaultPrim() - geom_prim = stage.GetPrimAtPath(f"/{mesh_file_basename}/geometry") - # Move all meshes to underneath new Xform - for child_mesh_prim in geom_prim.GetChildren(): - if child_mesh_prim.GetTypeName() == "Mesh": - # Apply collider properties to mesh - if cfg.collision_props is not None: - # -- Collision approximation to mesh - # TODO: https://github.com/isaac-orbit/orbit/issues/163 Move this to a new Schema - mesh_collision_api = UsdPhysics.MeshCollisionAPI.Apply(child_mesh_prim) - mesh_collision_api.GetApproximationAttr().Set(cfg.collision_approximation) - # -- Collider properties such as offset, scale, etc. - schemas.define_collision_properties( - prim_path=child_mesh_prim.GetPath(), cfg=cfg.collision_props, stage=stage - ) - # Delete the old Xform and make the new Xform the default prim - stage.SetDefaultPrim(xform_prim) - # Handle instanceable - # Create a new Xform prim that will be the prototype prim - if cfg.make_instanceable: - # Export Xform to a file so we can reference it from all instances - export_prim_to_file( - path=os.path.join(self.usd_dir, self.usd_instanceable_meshes_path), - source_prim_path=geom_prim.GetPath(), - stage=stage, - ) - # Delete the original prim that will now be a reference - geom_prim_path = geom_prim.GetPath().pathString - omni.kit.commands.execute("DeletePrims", paths=[geom_prim_path], stage=stage) - # Update references to exported Xform and make it instanceable - geom_undef_prim = stage.DefinePrim(geom_prim_path) - geom_undef_prim.GetReferences().AddReference(self.usd_instanceable_meshes_path, primPath=geom_prim_path) - geom_undef_prim.SetInstanceable(True) - - # Apply mass and rigid body properties after everything else - # Properties are applied to the top level prim to avoid the case where all instances of this - # asset unintentionally share the same rigid body properties - # apply mass properties - if cfg.mass_props is not None: - schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) - # apply rigid body properties - if cfg.rigid_props is not None: - schemas.define_rigid_body_properties(prim_path=xform_prim.GetPath(), cfg=cfg.rigid_props, stage=stage) - - # Save changes to USD stage - stage.Save() - if stage_id is not None: - UsdUtils.StageCache.Get().Erase(stage_id) - - """ - Helper methods. - """ - - @staticmethod - async def _convert_mesh_to_usd( - in_file: str, out_file: str, prim_path: str = "/World", load_materials: bool = True - ) -> bool: - """Convert mesh from supported file types to USD. - - This function uses the Omniverse Asset Converter extension to convert a mesh file to USD. - It is an asynchronous function and should be called using `asyncio.get_event_loop().run_until_complete()`. - - The converted asset is stored in the USD format in the specified output file. - The USD file has Y-up axis and is scaled to meters. - - The asset hierarchy is arranged as follows: - - .. code-block:: none - prim_path (default prim) - |- /geometry/Looks - |- /geometry/mesh - - Args: - in_file: The file to convert. - out_file: The path to store the output file. - prim_path: The prim path of the mesh. - load_materials: Set to True to enable attaching materials defined in the input file - to the generated USD mesh. Defaults to True. - - Returns: - True if the conversion succeeds. - """ - enable_extension("omni.kit.asset_converter") - enable_extension("omni.usd.metrics.assembler") - - import omni.kit.asset_converter - import omni.usd - from omni.metrics.assembler.core import get_metrics_assembler_interface - - # Create converter context - converter_context = omni.kit.asset_converter.AssetConverterContext() - # Set up converter settings - # Don't import/export materials - converter_context.ignore_materials = not load_materials - converter_context.ignore_animations = True - converter_context.ignore_camera = True - converter_context.ignore_light = True - # Merge all meshes into one - converter_context.merge_all_meshes = True - # Sets world units to meters, this will also scale asset if it's centimeters model. - # This does not work right now :(, so we need to scale the mesh manually - converter_context.use_meter_as_world_unit = True - converter_context.baking_scales = True - # Uses double precision for all transform ops. - converter_context.use_double_precision_to_usd_transform_op = True - - # Create converter task - instance = omni.kit.asset_converter.get_instance() - out_file_non_metric = out_file.replace(".usd", "_non_metric.usd") - task = instance.create_converter_task(in_file, out_file_non_metric, None, converter_context) - # Start conversion task and wait for it to finish - success = True - while True: - success = await task.wait_until_finished() - if not success: - await asyncio.sleep(0.1) - else: - break - - temp_stage = Usd.Stage.CreateInMemory() - UsdGeom.SetStageUpAxis(temp_stage, UsdGeom.Tokens.z) - UsdGeom.SetStageMetersPerUnit(temp_stage, 1.0) - UsdPhysics.SetStageKilogramsPerUnit(temp_stage, 1.0) - - base_prim = temp_stage.DefinePrim(prim_path, "Xform") - prim = temp_stage.DefinePrim(f"{prim_path}/geometry", "Xform") - prim.GetReferences().AddReference(out_file_non_metric) - cache = UsdUtils.StageCache.Get() - cache.Insert(temp_stage) - stage_id = cache.GetId(temp_stage).ToLongInt() - get_metrics_assembler_interface().resolve_stage(stage_id) - temp_stage.SetDefaultPrim(base_prim) - temp_stage.Export(out_file) - return success
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/mesh_converter_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/converters/mesh_converter_cfg.html deleted file mode 100644 index 3bdb93e3fe..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/mesh_converter_cfg.html +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.converters.mesh_converter_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.converters.mesh_converter_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from omni.isaac.lab.sim.converters.asset_converter_base_cfg import AssetConverterBaseCfg
-from omni.isaac.lab.sim.schemas import schemas_cfg
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class MeshConverterCfg(AssetConverterBaseCfg): - """The configuration class for MeshConverter.""" - - mass_props: schemas_cfg.MassPropertiesCfg = None - """Mass properties to apply to the USD. Defaults to None. - - Note: - If None, then no mass properties will be added. - """ - - rigid_props: schemas_cfg.RigidBodyPropertiesCfg = None - """Rigid body properties to apply to the USD. Defaults to None. - - Note: - If None, then no rigid body properties will be added. - """ - - collision_props: schemas_cfg.CollisionPropertiesCfg = None - """Collision properties to apply to the USD. Defaults to None. - - Note: - If None, then no collision properties will be added. - """ - - collision_approximation: str = "convexDecomposition" - """Collision approximation method to use. Defaults to "convexDecomposition". - - Valid options are: - "convexDecomposition", "convexHull", "boundingCube", - "boundingSphere", "meshSimplification", or "none" - - "none" causes no collision mesh to be added. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/urdf_converter.html b/docs/_build/html/_modules/omni/isaac/lab/sim/converters/urdf_converter.html deleted file mode 100644 index d6d30b79ac..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/urdf_converter.html +++ /dev/null @@ -1,726 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.converters.urdf_converter — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.converters.urdf_converter 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import os
-
-import omni.kit.commands
-import omni.usd
-from omni.isaac.core.utils.extensions import enable_extension
-from omni.isaac.version import get_version
-from pxr import Usd
-
-from .asset_converter_base import AssetConverterBase
-from .urdf_converter_cfg import UrdfConverterCfg
-
-_DRIVE_TYPE = {
-    "none": 0,
-    "position": 1,
-    "velocity": 2,
-}
-"""Mapping from drive type name to URDF importer drive number."""
-
-_NORMALS_DIVISION = {
-    "catmullClark": 0,
-    "loop": 1,
-    "bilinear": 2,
-    "none": 3,
-}
-"""Mapping from normals division name to urdf importer normals division number."""
-
-
-
[文档]class UrdfConverter(AssetConverterBase): - """Converter for a URDF description file to a USD file. - - This class wraps around the `omni.isaac.urdf_importer`_ extension to provide a lazy implementation - for URDF to USD conversion. It stores the output USD file in an instanceable format since that is - what is typically used in all learning related applications. - - .. caution:: - The current lazy conversion implementation does not automatically trigger USD generation if - only the mesh files used by the URDF are modified. To force generation, either set - :obj:`AssetConverterBaseCfg.force_usd_conversion` to True or delete the output directory. - - .. note:: - From Isaac Sim 2023.1 onwards, the extension name changed from ``omni.isaac.urdf`` to - ``omni.importer.urdf``. This converter class automatically detects the version of Isaac Sim - and uses the appropriate extension. - - The new extension supports a custom XML tag``"dont_collapse"`` for joints. Setting this parameter - to true in the URDF joint tag prevents the child link from collapsing when the associated joint type - is "fixed". - - .. _omni.isaac.urdf_importer: https://docs.omniverse.nvidia.com/isaacsim/latest/ext_omni_isaac_urdf.html - """ - - cfg: UrdfConverterCfg - """The configuration instance for URDF to USD conversion.""" - -
[文档] def __init__(self, cfg: UrdfConverterCfg): - """Initializes the class. - - Args: - cfg: The configuration instance for URDF to USD conversion. - """ - super().__init__(cfg=cfg)
- - """ - Implementation specific methods. - """ - - def _convert_asset(self, cfg: UrdfConverterCfg): - """Calls underlying Omniverse command to convert URDF to USD. - - Args: - cfg: The URDF conversion configuration. - """ - import_config = self._get_urdf_import_config(cfg) - omni.kit.commands.execute( - "URDFParseAndImportFile", - urdf_path=cfg.asset_path, - import_config=import_config, - dest_path=self.usd_path, - ) - # fix the issue that material paths are not relative - if self.cfg.make_instanceable: - instanced_usd_path = os.path.join(self.usd_dir, self.usd_instanceable_meshes_path) - stage = Usd.Stage.Open(instanced_usd_path) - # resolve all paths relative to layer path - source_layer = stage.GetRootLayer() - omni.usd.resolve_paths(source_layer.identifier, source_layer.identifier) - stage.Save() - - # fix the issue that material paths are not relative - # note: This issue seems to have popped up in Isaac Sim 2023.1.1 - stage = Usd.Stage.Open(self.usd_path) - # resolve all paths relative to layer path - source_layer = stage.GetRootLayer() - omni.usd.resolve_paths(source_layer.identifier, source_layer.identifier) - stage.Save() - - """ - Helper methods. - """ - - def _get_urdf_import_config(self, cfg: UrdfConverterCfg) -> omni.importer.urdf.ImportConfig: - """Create and fill URDF ImportConfig with desired settings - - Args: - cfg: The URDF conversion configuration. - - Returns: - The constructed ``ImportConfig`` object containing the desired settings. - """ - # Enable urdf extension - enable_extension("omni.importer.urdf") - - from omni.importer.urdf import _urdf as omni_urdf - - import_config = omni_urdf.ImportConfig() - - # set the unit scaling factor, 1.0 means meters, 100.0 means cm - import_config.set_distance_scale(1.0) - # set imported robot as default prim - import_config.set_make_default_prim(True) - # add a physics scene to the stage on import if none exists - import_config.set_create_physics_scene(False) - - # -- instancing settings - # meshes will be placed in a separate usd file - import_config.set_make_instanceable(cfg.make_instanceable) - import_config.set_instanceable_usd_path(self.usd_instanceable_meshes_path) - - # -- asset settings - # default density used for links, use 0 to auto-compute - import_config.set_density(cfg.link_density) - # import inertia tensor from urdf, if it is not specified in urdf it will import as identity - import_config.set_import_inertia_tensor(cfg.import_inertia_tensor) - # decompose a convex mesh into smaller pieces for a closer fit - import_config.set_convex_decomp(cfg.convex_decompose_mesh) - import_config.set_subdivision_scheme(_NORMALS_DIVISION["bilinear"]) - - # -- physics settings - # create fix joint for base link - import_config.set_fix_base(cfg.fix_base) - # consolidating links that are connected by fixed joints - import_config.set_merge_fixed_joints(cfg.merge_fixed_joints) - # self collisions between links in the articulation - import_config.set_self_collision(cfg.self_collision) - - # default drive type used for joints - import_config.set_default_drive_type(_DRIVE_TYPE[cfg.default_drive_type]) - # default proportional gains - import_config.set_default_drive_strength(cfg.default_drive_stiffness) - # default derivative gains - import_config.set_default_position_drive_damping(cfg.default_drive_damping) - if get_version()[2] == "4": - # override joint dynamics parsed from urdf - import_config.set_override_joint_dynamics(cfg.override_joint_dynamics) - - return import_config
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/urdf_converter_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/converters/urdf_converter_cfg.html deleted file mode 100644 index 85a83c66f9..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/converters/urdf_converter_cfg.html +++ /dev/null @@ -1,626 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.converters.urdf_converter_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.converters.urdf_converter_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.sim.converters.asset_converter_base_cfg import AssetConverterBaseCfg
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class UrdfConverterCfg(AssetConverterBaseCfg): - """The configuration class for UrdfConverter.""" - - link_density = 0.0 - """Default density used for links. Defaults to 0. - - This setting is only effective if ``"inertial"`` properties are missing in the URDF. - """ - - import_inertia_tensor: bool = True - """Import the inertia tensor from urdf. Defaults to True. - - If the ``"inertial"`` tag is missing, then it is imported as an identity. - """ - - convex_decompose_mesh = False - """Decompose a convex mesh into smaller pieces for a closer fit. Defaults to False.""" - - fix_base: bool = MISSING - """Create a fix joint to the root/base link. Defaults to True.""" - - merge_fixed_joints: bool = False - """Consolidate links that are connected by fixed joints. Defaults to False.""" - - self_collision: bool = False - """Activate self-collisions between links of the articulation. Defaults to False.""" - - default_drive_type: Literal["none", "position", "velocity"] = "none" - """The drive type used for joints. Defaults to ``"none"``. - - The drive type dictates the loaded joint PD gains and USD attributes for joint control: - - * ``"none"``: The joint stiffness and damping are set to 0.0. - * ``"position"``: The joint stiff and damping are set based on the URDF file or provided configuration. - * ``"velocity"``: The joint stiff is set to zero and damping is based on the URDF file or provided configuration. - """ - - override_joint_dynamics: bool = False - """Override the joint dynamics parsed from the URDF file. Defaults to False.""" - - default_drive_stiffness: float = 0.0 - """The default stiffness of the joint drive. Defaults to 0.0.""" - - default_drive_damping: float = 0.0 - """The default damping of the joint drive. Defaults to 0.0. - - Note: - If ``override_joint_dynamics`` is True, the values parsed from the URDF joint tag ``"<dynamics><damping>"`` are used. - Otherwise, it is overridden by the configured value. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/schemas/schemas.html b/docs/_build/html/_modules/omni/isaac/lab/sim/schemas/schemas.html deleted file mode 100644 index 5400fff99e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/schemas/schemas.html +++ /dev/null @@ -1,1216 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.schemas.schemas — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.schemas.schemas 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# needed to import for allowing type-hinting: Usd.Stage | None
-from __future__ import annotations
-
-import carb
-import omni.isaac.core.utils.stage as stage_utils
-import omni.physx.scripts.utils as physx_utils
-from pxr import PhysxSchema, Usd, UsdPhysics
-
-from ..utils import apply_nested, find_global_fixed_joint_prim, safe_set_attribute_on_usd_schema
-from . import schemas_cfg
-
-"""
-Articulation root properties.
-"""
-
-
-
[文档]def define_articulation_root_properties( - prim_path: str, cfg: schemas_cfg.ArticulationRootPropertiesCfg, stage: Usd.Stage | None = None -): - """Apply the articulation root schema on the input prim and set its properties. - - See :func:`modify_articulation_root_properties` for more details on how the properties are set. - - Args: - prim_path: The prim path where to apply the articulation root schema. - cfg: The configuration for the articulation root. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: When the prim path is not valid. - TypeError: When the prim already has conflicting API schemas. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get articulation USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - # check if prim has articulation applied on it - if not UsdPhysics.ArticulationRootAPI(prim): - UsdPhysics.ArticulationRootAPI.Apply(prim) - # set articulation root properties - modify_articulation_root_properties(prim_path, cfg, stage)
- - -
[文档]@apply_nested -def modify_articulation_root_properties( - prim_path: str, cfg: schemas_cfg.ArticulationRootPropertiesCfg, stage: Usd.Stage | None = None -) -> bool: - """Modify PhysX parameters for an articulation root prim. - - The `articulation root`_ marks the root of an articulation tree. For floating articulations, this should be on - the root body. For fixed articulations, this API can be on a direct or indirect parent of the root joint - which is fixed to the world. - - The schema comprises of attributes that belong to the `ArticulationRootAPI`_ and `PhysxArticulationAPI`_. - schemas. The latter contains the PhysX parameters for the articulation root. - - The properties are applied to the articulation root prim. The common properties (such as solver position - and velocity iteration counts, sleep threshold, stabilization threshold) take precedence over those specified - in the rigid body schemas for all the rigid bodies in the articulation. - - .. caution:: - When the attribute :attr:`schemas_cfg.ArticulationRootPropertiesCfg.fix_root_link` is set to True, - a fixed joint is created between the root link and the world frame (if it does not already exist). However, - to deal with physics parser limitations, the articulation root schema needs to be applied to the parent of - the root link. - - .. note:: - This function is decorated with :func:`apply_nested` that set the properties to all the prims - (that have the schema applied on them) under the input prim path. - - .. _articulation root: https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/Articulations.html - .. _ArticulationRootAPI: https://openusd.org/dev/api/class_usd_physics_articulation_root_a_p_i.html - .. _PhysxArticulationAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_articulation_a_p_i.html - - Args: - prim_path: The prim path to the articulation root. - cfg: The configuration for the articulation root. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - - Raises: - NotImplementedError: When the root prim is not a rigid body and a fixed joint is to be created. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get articulation USD prim - articulation_prim = stage.GetPrimAtPath(prim_path) - # check if prim has articulation applied on it - if not UsdPhysics.ArticulationRootAPI(articulation_prim): - return False - # retrieve the articulation api - physx_articulation_api = PhysxSchema.PhysxArticulationAPI(articulation_prim) - if not physx_articulation_api: - physx_articulation_api = PhysxSchema.PhysxArticulationAPI.Apply(articulation_prim) - - # convert to dict - cfg = cfg.to_dict() - # extract non-USD properties - fix_root_link = cfg.pop("fix_root_link", None) - - # set into physx api - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_schema(physx_articulation_api, attr_name, value, camel_case=True) - - # fix root link based on input - # we do the fixed joint processing later to not interfere with setting other properties - if fix_root_link is not None: - # check if a global fixed joint exists under the root prim - existing_fixed_joint_prim = find_global_fixed_joint_prim(prim_path) - - # if we found a fixed joint, enable/disable it based on the input - # otherwise, create a fixed joint between the world and the root link - if existing_fixed_joint_prim is not None: - carb.log_info( - f"Found an existing fixed joint for the articulation: '{prim_path}'. Setting it to: {fix_root_link}." - ) - existing_fixed_joint_prim.GetJointEnabledAttr().Set(fix_root_link) - elif fix_root_link: - carb.log_info(f"Creating a fixed joint for the articulation: '{prim_path}'.") - - # note: we have to assume that the root prim is a rigid body, - # i.e. we don't handle the case where the root prim is not a rigid body but has articulation api on it - # Currently, there is no obvious way to get first rigid body link identified by the PhysX parser - if not articulation_prim.HasAPI(UsdPhysics.RigidBodyAPI): - raise NotImplementedError( - f"The articulation prim '{prim_path}' does not have the RigidBodyAPI applied." - " To create a fixed joint, we need to determine the first rigid body link in" - " the articulation tree. However, this is not implemented yet." - ) - - # create a fixed joint between the root link and the world frame - physx_utils.createJoint(stage=stage, joint_type="Fixed", from_prim=None, to_prim=articulation_prim) - - # Having a fixed joint on a rigid body is not treated as "fixed base articulation". - # instead, it is treated as a part of the maximal coordinate tree. - # Moving the articulation root to the parent solves this issue. This is a limitation of the PhysX parser. - # get parent prim - parent_prim = articulation_prim.GetParent() - # apply api to parent - UsdPhysics.ArticulationRootAPI.Apply(parent_prim) - PhysxSchema.PhysxArticulationAPI.Apply(parent_prim) - - # copy the attributes - # -- usd attributes - usd_articulation_api = UsdPhysics.ArticulationRootAPI(articulation_prim) - for attr_name in usd_articulation_api.GetSchemaAttributeNames(): - attr = articulation_prim.GetAttribute(attr_name) - parent_prim.GetAttribute(attr_name).Set(attr.Get()) - # -- physx attributes - physx_articulation_api = PhysxSchema.PhysxArticulationAPI(articulation_prim) - for attr_name in physx_articulation_api.GetSchemaAttributeNames(): - attr = articulation_prim.GetAttribute(attr_name) - parent_prim.GetAttribute(attr_name).Set(attr.Get()) - - # remove api from root - articulation_prim.RemoveAPI(UsdPhysics.ArticulationRootAPI) - articulation_prim.RemoveAPI(PhysxSchema.PhysxArticulationAPI) - - # success - return True
- - -""" -Rigid body properties. -""" - - -
[文档]def define_rigid_body_properties( - prim_path: str, cfg: schemas_cfg.RigidBodyPropertiesCfg, stage: Usd.Stage | None = None -): - """Apply the rigid body schema on the input prim and set its properties. - - See :func:`modify_rigid_body_properties` for more details on how the properties are set. - - Args: - prim_path: The prim path where to apply the rigid body schema. - cfg: The configuration for the rigid body. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: When the prim path is not valid. - TypeError: When the prim already has conflicting API schemas. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - # check if prim has rigid body applied on it - if not UsdPhysics.RigidBodyAPI(prim): - UsdPhysics.RigidBodyAPI.Apply(prim) - # set rigid body properties - modify_rigid_body_properties(prim_path, cfg, stage)
- - -
[文档]@apply_nested -def modify_rigid_body_properties( - prim_path: str, cfg: schemas_cfg.RigidBodyPropertiesCfg, stage: Usd.Stage | None = None -) -> bool: - """Modify PhysX parameters for a rigid body prim. - - A `rigid body`_ is a single body that can be simulated by PhysX. It can be either dynamic or kinematic. - A dynamic body responds to forces and collisions. A `kinematic body`_ can be moved by the user, but does not - respond to forces. They are similar to having static bodies that can be moved around. - - The schema comprises of attributes that belong to the `RigidBodyAPI`_ and `PhysxRigidBodyAPI`_. - schemas. The latter contains the PhysX parameters for the rigid body. - - .. note:: - This function is decorated with :func:`apply_nested` that sets the properties to all the prims - (that have the schema applied on them) under the input prim path. - - .. _rigid body: https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/RigidBodyOverview.html - .. _kinematic body: https://openusd.org/release/wp_rigid_body_physics.html#kinematic-bodies - .. _RigidBodyAPI: https://openusd.org/dev/api/class_usd_physics_rigid_body_a_p_i.html - .. _PhysxRigidBodyAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_rigid_body_a_p_i.html - - Args: - prim_path: The prim path to the rigid body. - cfg: The configuration for the rigid body. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get rigid-body USD prim - rigid_body_prim = stage.GetPrimAtPath(prim_path) - # check if prim has rigid-body applied on it - if not UsdPhysics.RigidBodyAPI(rigid_body_prim): - return False - # retrieve the USD rigid-body api - usd_rigid_body_api = UsdPhysics.RigidBodyAPI(rigid_body_prim) - # retrieve the physx rigid-body api - physx_rigid_body_api = PhysxSchema.PhysxRigidBodyAPI(rigid_body_prim) - if not physx_rigid_body_api: - physx_rigid_body_api = PhysxSchema.PhysxRigidBodyAPI.Apply(rigid_body_prim) - - # convert to dict - cfg = cfg.to_dict() - # set into USD API - for attr_name in ["rigid_body_enabled", "kinematic_enabled"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_schema(usd_rigid_body_api, attr_name, value, camel_case=True) - # set into PhysX API - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_schema(physx_rigid_body_api, attr_name, value, camel_case=True) - # success - return True
- - -""" -Collision properties. -""" - - -
[文档]def define_collision_properties( - prim_path: str, cfg: schemas_cfg.CollisionPropertiesCfg, stage: Usd.Stage | None = None -): - """Apply the collision schema on the input prim and set its properties. - - See :func:`modify_collision_properties` for more details on how the properties are set. - - Args: - prim_path: The prim path where to apply the rigid body schema. - cfg: The configuration for the collider. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: When the prim path is not valid. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - # check if prim has collision applied on it - if not UsdPhysics.CollisionAPI(prim): - UsdPhysics.CollisionAPI.Apply(prim) - # set collision properties - modify_collision_properties(prim_path, cfg, stage)
- - -
[文档]@apply_nested -def modify_collision_properties( - prim_path: str, cfg: schemas_cfg.CollisionPropertiesCfg, stage: Usd.Stage | None = None -) -> bool: - """Modify PhysX properties of collider prim. - - These properties are based on the `UsdPhysics.CollisionAPI`_ and `PhysxSchema.PhysxCollisionAPI`_ schemas. - For more information on the properties, please refer to the official documentation. - - Tuning these parameters influence the contact behavior of the rigid body. For more information on - tune them and their effect on the simulation, please refer to the - `PhysX documentation <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/AdvancedCollisionDetection.html>`__. - - .. note:: - This function is decorated with :func:`apply_nested` that sets the properties to all the prims - (that have the schema applied on them) under the input prim path. - - .. _UsdPhysics.CollisionAPI: https://openusd.org/dev/api/class_usd_physics_collision_a_p_i.html - .. _PhysxSchema.PhysxCollisionAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_collision_a_p_i.html - - Args: - prim_path: The prim path of parent. - cfg: The configuration for the collider. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - collider_prim = stage.GetPrimAtPath(prim_path) - # check if prim has collision applied on it - if not UsdPhysics.CollisionAPI(collider_prim): - return False - # retrieve the USD collision api - usd_collision_api = UsdPhysics.CollisionAPI(collider_prim) - # retrieve the collision api - physx_collision_api = PhysxSchema.PhysxCollisionAPI(collider_prim) - if not physx_collision_api: - physx_collision_api = PhysxSchema.PhysxCollisionAPI.Apply(collider_prim) - - # convert to dict - cfg = cfg.to_dict() - # set into USD API - for attr_name in ["collision_enabled"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_schema(usd_collision_api, attr_name, value, camel_case=True) - # set into PhysX API - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_schema(physx_collision_api, attr_name, value, camel_case=True) - # success - return True
- - -""" -Mass properties. -""" - - -
[文档]def define_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage | None = None): - """Apply the mass schema on the input prim and set its properties. - - See :func:`modify_mass_properties` for more details on how the properties are set. - - Args: - prim_path: The prim path where to apply the rigid body schema. - cfg: The configuration for the mass properties. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: When the prim path is not valid. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - # check if prim has mass applied on it - if not UsdPhysics.MassAPI(prim): - UsdPhysics.MassAPI.Apply(prim) - # set mass properties - modify_mass_properties(prim_path, cfg, stage)
- - -
[文档]@apply_nested -def modify_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage | None = None) -> bool: - """Set properties for the mass of a rigid body prim. - - These properties are based on the `UsdPhysics.MassAPI` schema. If the mass is not defined, the density is used - to compute the mass. However, in that case, a collision approximation of the rigid body is used to - compute the density. For more information on the properties, please refer to the - `documentation <https://openusd.org/release/wp_rigid_body_physics.html#body-mass-properties>`__. - - .. caution:: - - The mass of an object can be specified in multiple ways and have several conflicting settings - that are resolved based on precedence. Please make sure to understand the precedence rules - before using this property. - - .. note:: - This function is decorated with :func:`apply_nested` that sets the properties to all the prims - (that have the schema applied on them) under the input prim path. - - .. UsdPhysics.MassAPI: https://openusd.org/dev/api/class_usd_physics_mass_a_p_i.html - - Args: - prim_path: The prim path of the rigid body. - cfg: The configuration for the mass properties. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - rigid_prim = stage.GetPrimAtPath(prim_path) - # check if prim has mass API applied on it - if not UsdPhysics.MassAPI(rigid_prim): - return False - # retrieve the USD mass api - usd_physics_mass_api = UsdPhysics.MassAPI(rigid_prim) - - # convert to dict - cfg = cfg.to_dict() - # set into USD API - for attr_name in ["mass", "density"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_schema(usd_physics_mass_api, attr_name, value, camel_case=True) - # success - return True
- - -""" -Contact sensor. -""" - - -
[文档]def activate_contact_sensors(prim_path: str, threshold: float = 0.0, stage: Usd.Stage = None): - """Activate the contact sensor on all rigid bodies under a specified prim path. - - This function adds the PhysX contact report API to all rigid bodies under the specified prim path. - It also sets the force threshold beyond which the contact sensor reports the contact. The contact - reporting API can only be added to rigid bodies. - - Args: - prim_path: The prim path under which to search and prepare contact sensors. - threshold: The threshold for the contact sensor. Defaults to 0.0. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: If the input prim path is not valid. - ValueError: If there are no rigid bodies under the prim path. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get prim - prim: Usd.Prim = stage.GetPrimAtPath(prim_path) - # check if prim is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - # iterate over all children - num_contact_sensors = 0 - all_prims = [prim] - while len(all_prims) > 0: - # get current prim - child_prim = all_prims.pop(0) - # check if prim is a rigid body - # nested rigid bodies are not allowed by SDK so we can safely assume that - # if a prim has a rigid body API, it is a rigid body and we don't need to - # check its children - if child_prim.HasAPI(UsdPhysics.RigidBodyAPI): - # set sleep threshold to zero - rb = PhysxSchema.PhysxRigidBodyAPI.Get(stage, prim.GetPrimPath()) - rb.CreateSleepThresholdAttr().Set(0.0) - # add contact report API with threshold of zero - if not child_prim.HasAPI(PhysxSchema.PhysxContactReportAPI): - carb.log_verbose(f"Adding contact report API to prim: '{child_prim.GetPrimPath()}'") - cr_api = PhysxSchema.PhysxContactReportAPI.Apply(child_prim) - else: - carb.log_verbose(f"Contact report API already exists on prim: '{child_prim.GetPrimPath()}'") - cr_api = PhysxSchema.PhysxContactReportAPI.Get(stage, child_prim.GetPrimPath()) - # set threshold to zero - cr_api.CreateThresholdAttr().Set(threshold) - # increment number of contact sensors - num_contact_sensors += 1 - else: - # add all children to tree - all_prims += child_prim.GetChildren() - # check if no contact sensors were found - if num_contact_sensors == 0: - raise ValueError( - f"No contact sensors added to the prim: '{prim_path}'. This means that no rigid bodies" - " are present under this prim. Please check the prim path." - ) - # success - return True
- - -""" -Joint drive properties. -""" - - -
[文档]@apply_nested -def modify_joint_drive_properties( - prim_path: str, drive_props: schemas_cfg.JointDrivePropertiesCfg, stage: Usd.Stage | None = None -) -> bool: - """Modify PhysX parameters for a joint prim. - - This function checks if the input prim is a prismatic or revolute joint and applies the joint drive schema - on it. If the joint is a tendon (i.e., it has the `PhysxTendonAxisAPI`_ schema applied on it), then the joint - drive schema is not applied. - - Based on the configuration, this method modifies the properties of the joint drive. These properties are - based on the `UsdPhysics.DriveAPI`_ schema. For more information on the properties, please refer to the - official documentation. - - .. caution:: - - We highly recommend modifying joint properties of articulations through the functionalities in the - :mod:`omni.isaac.lab.actuators` module. The methods here are for setting simulation low-level - properties only. - - .. _UsdPhysics.DriveAPI: https://openusd.org/dev/api/class_usd_physics_drive_a_p_i.html - .. _PhysxTendonAxisAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_tendon_axis_a_p_i.html - - Args: - prim_path: The prim path where to apply the joint drive schema. - drive_props: The configuration for the joint drive. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - - Raises: - ValueError: If the input prim path is not valid. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - - # check if prim has joint drive applied on it - if prim.IsA(UsdPhysics.RevoluteJoint): - drive_api_name = "angular" - elif prim.IsA(UsdPhysics.PrismaticJoint): - drive_api_name = "linear" - else: - return False - # check that prim is not a tendon child prim - # note: root prim is what "controls" the tendon so we still want to apply the drive to it - if prim.HasAPI(PhysxSchema.PhysxTendonAxisAPI) and not prim.HasAPI(PhysxSchema.PhysxTendonAxisRootAPI): - return False - - # check if prim has joint drive applied on it - usd_drive_api = UsdPhysics.DriveAPI(prim, drive_api_name) - if not usd_drive_api: - usd_drive_api = UsdPhysics.DriveAPI.Apply(prim, drive_api_name) - - # change the drive type to input - if drive_props.drive_type is not None: - usd_drive_api.CreateTypeAttr().Set(drive_props.drive_type) - - return True
- - -""" -Fixed tendon properties. -""" - - -
[文档]@apply_nested -def modify_fixed_tendon_properties( - prim_path: str, cfg: schemas_cfg.FixedTendonPropertiesCfg, stage: Usd.Stage | None = None -) -> bool: - """Modify PhysX parameters for a fixed tendon attachment prim. - - A `fixed tendon`_ can be used to link multiple degrees of freedom of articulation joints - through length and limit constraints. For instance, it can be used to set up an equality constraint - between a driven and passive revolute joints. - - The schema comprises of attributes that belong to the `PhysxTendonAxisRootAPI`_ schema. - - .. note:: - This function is decorated with :func:`apply_nested` that sets the properties to all the prims - (that have the schema applied on them) under the input prim path. - - .. _fixed tendon: https://nvidia-omniverse.github.io/PhysX/physx/5.3.1/_api_build/class_px_articulation_fixed_tendon.html - .. _PhysxTendonAxisRootAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_tendon_axis_root_a_p_i.html - - Args: - prim_path: The prim path to the tendon attachment. - cfg: The configuration for the tendon attachment. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - - Raises: - ValueError: If the input prim path is not valid. - """ - # obtain stage - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - tendon_prim = stage.GetPrimAtPath(prim_path) - # check if prim has fixed tendon applied on it - has_root_fixed_tendon = tendon_prim.HasAPI(PhysxSchema.PhysxTendonAxisRootAPI) - if not has_root_fixed_tendon: - return False - - # resolve all available instances of the schema since it is multi-instance - for schema_name in tendon_prim.GetAppliedSchemas(): - # only consider the fixed tendon schema - if "PhysxTendonAxisRootAPI" not in schema_name: - continue - # retrieve the USD tendon api - instance_name = schema_name.split(":")[-1] - physx_tendon_axis_api = PhysxSchema.PhysxTendonAxisRootAPI(tendon_prim, instance_name) - - # convert to dict - cfg = cfg.to_dict() - # set into PhysX API - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_schema(physx_tendon_axis_api, attr_name, value, camel_case=True) - # success - return True
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/schemas/schemas_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/schemas/schemas_cfg.html deleted file mode 100644 index 43d4b9c073..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/schemas/schemas_cfg.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.schemas.schemas_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.schemas.schemas_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class ArticulationRootPropertiesCfg: - """Properties to apply to the root of an articulation. - - See :meth:`modify_articulation_root_properties` for more information. - - .. note:: - If the values are None, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - articulation_enabled: bool | None = None - """Whether to enable or disable articulation.""" - enabled_self_collisions: bool | None = None - """Whether to enable or disable self-collisions.""" - solver_position_iteration_count: int | None = None - """Solver position iteration counts for the body.""" - solver_velocity_iteration_count: int | None = None - """Solver position iteration counts for the body.""" - sleep_threshold: float | None = None - """Mass-normalized kinetic energy threshold below which an actor may go to sleep.""" - stabilization_threshold: float | None = None - """The mass-normalized kinetic energy threshold below which an articulation may participate in stabilization.""" - fix_root_link: bool | None = None - """Whether to fix the root link of the articulation. - - * If set to None, the root link is not modified. - * If the articulation already has a fixed root link, this flag will enable or disable the fixed joint. - * If the articulation does not have a fixed root link, this flag will create a fixed joint between the world - frame and the root link. The joint is created with the name "FixedJoint" under the articulation prim. - - .. note:: - This is a non-USD schema property. It is handled by the :meth:`modify_articulation_root_properties` function. - - """
- - -
[文档]@configclass -class RigidBodyPropertiesCfg: - """Properties to apply to a rigid body. - - See :meth:`modify_rigid_body_properties` for more information. - - .. note:: - If the values are None, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - rigid_body_enabled: bool | None = None - """Whether to enable or disable the rigid body.""" - kinematic_enabled: bool | None = None - """Determines whether the body is kinematic or not. - - A kinematic body is a body that is moved through animated poses or through user defined poses. The simulation - still derives velocities for the kinematic body based on the external motion. - - For more information on kinematic bodies, please refer to the `documentation <https://openusd.org/release/wp_rigid_body_physics.html#kinematic-bodies>`_. - """ - disable_gravity: bool | None = None - """Disable gravity for the actor.""" - linear_damping: float | None = None - """Linear damping for the body.""" - angular_damping: float | None = None - """Angular damping for the body.""" - max_linear_velocity: float | None = None - """Maximum linear velocity for rigid bodies (in m/s).""" - max_angular_velocity: float | None = None - """Maximum angular velocity for rigid bodies (in deg/s).""" - max_depenetration_velocity: float | None = None - """Maximum depenetration velocity permitted to be introduced by the solver (in m/s).""" - max_contact_impulse: float | None = None - """The limit on the impulse that may be applied at a contact.""" - enable_gyroscopic_forces: bool | None = None - """Enables computation of gyroscopic forces on the rigid body.""" - retain_accelerations: bool | None = None - """Carries over forces/accelerations over sub-steps.""" - solver_position_iteration_count: int | None = None - """Solver position iteration counts for the body.""" - solver_velocity_iteration_count: int | None = None - """Solver position iteration counts for the body.""" - sleep_threshold: float | None = None - """Mass-normalized kinetic energy threshold below which an actor may go to sleep.""" - stabilization_threshold: float | None = None - """The mass-normalized kinetic energy threshold below which an actor may participate in stabilization."""
- - -
[文档]@configclass -class CollisionPropertiesCfg: - """Properties to apply to colliders in a rigid body. - - See :meth:`modify_collision_properties` for more information. - - .. note:: - If the values are None, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - collision_enabled: bool | None = None - """Whether to enable or disable collisions.""" - contact_offset: float | None = None - """Contact offset for the collision shape (in m). - - The collision detector generates contact points as soon as two shapes get closer than the sum of their - contact offsets. This quantity should be non-negative which means that contact generation can potentially start - before the shapes actually penetrate. - """ - rest_offset: float | None = None - """Rest offset for the collision shape (in m). - - The rest offset quantifies how close a shape gets to others at rest, At rest, the distance between two - vertically stacked objects is the sum of their rest offsets. If a pair of shapes have a positive rest - offset, the shapes will be separated at rest by an air gap. - """ - torsional_patch_radius: float | None = None - """Radius of the contact patch for applying torsional friction (in m). - - It is used to approximate rotational friction introduced by the compression of contacting surfaces. - If the radius is zero, no torsional friction is applied. - """ - min_torsional_patch_radius: float | None = None - """Minimum radius of the contact patch for applying torsional friction (in m)."""
- - -
[文档]@configclass -class MassPropertiesCfg: - """Properties to define explicit mass properties of a rigid body. - - See :meth:`modify_mass_properties` for more information. - - .. note:: - If the values are None, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - mass: float | None = None - """The mass of the rigid body (in kg). - - Note: - If non-zero, the mass is ignored and the density is used to compute the mass. - """ - density: float | None = None - """The density of the rigid body (in kg/m^3). - - The density indirectly defines the mass of the rigid body. It is generally computed using the collision - approximation of the body. - """
- - -
[文档]@configclass -class JointDrivePropertiesCfg: - """Properties to define the drive mechanism of a joint. - - See :meth:`modify_joint_drive_properties` for more information. - - .. note:: - If the values are None, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - drive_type: Literal["force", "acceleration"] | None = None - """Joint drive type to apply. - - If the drive type is "force", then the joint is driven by a force. If the drive type is "acceleration", - then the joint is driven by an acceleration (usually used for kinematic joints). - """
- - -
[文档]@configclass -class FixedTendonPropertiesCfg: - """Properties to define fixed tendons of an articulation. - - See :meth:`modify_fixed_tendon_properties` for more information. - - .. note:: - If the values are None, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - tendon_enabled: bool | None = None - """Whether to enable or disable the tendon.""" - stiffness: float | None = None - """Spring stiffness term acting on the tendon's length.""" - damping: float | None = None - """The damping term acting on both the tendon length and the tendon-length limits.""" - limit_stiffness: float | None = None - """Limit stiffness term acting on the tendon's length limits.""" - offset: float | None = None - """Length offset term for the tendon. - - It defines an amount to be added to the accumulated length computed for the tendon. This allows the application - to actuate the tendon by shortening or lengthening it. - """ - rest_length: float | None = None - """Spring rest length of the tendon."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/simulation_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/simulation_cfg.html deleted file mode 100644 index 19fc30bc89..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/simulation_cfg.html +++ /dev/null @@ -1,804 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.simulation_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.simulation_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Base configuration of the environment.
-
-This module defines the general configuration of the environment. It includes parameters for
-configuring the environment instances, viewer settings, and simulation parameters.
-"""
-
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-from .spawners.materials import RigidBodyMaterialCfg
-
-
-
[文档]@configclass -class PhysxCfg: - """Configuration for PhysX solver-related parameters. - - These parameters are used to configure the PhysX solver. For more information, see the `PhysX 5 SDK - documentation`_. - - PhysX 5 supports GPU-accelerated physics simulation. This is enabled by default, but can be disabled - through the flag `use_gpu`. Unlike CPU PhysX, the GPU simulation feature is not able to dynamically - grow all the buffers. Therefore, it is necessary to provide a reasonable estimate of the buffer sizes - for GPU features. If insufficient buffer sizes are provided, the simulation will fail with errors and - lead to adverse behaviors. The buffer sizes can be adjusted through the `gpu_*` parameters. - - .. _PhysX 5 SDK documentation: https://nvidia-omniverse.github.io/PhysX/physx/5.3.1/_api_build/class_px_scene_desc.html - - """ - - use_gpu: bool = True - """Enable/disable GPU accelerated dynamics simulation. Default is True. - - This enables GPU-accelerated implementations for broad-phase collision checks, contact generation, - shape and body management, and constrained solver. - """ - - solver_type: Literal[0, 1] = 1 - """The type of solver to use.Default is 1 (TGS). - - Available solvers: - - * :obj:`0`: PGS (Projective Gauss-Seidel) - * :obj:`1`: TGS (Truncated Gauss-Seidel) - """ - - min_position_iteration_count: int = 1 - """Minimum number of solver position iterations (rigid bodies, cloth, particles etc.). Default is 1. - - .. note:: - - Each physics actor in Omniverse specifies its own solver iteration count. The solver takes - the number of iterations specified by the actor with the highest iteration and clamps it to - the range ``[min_position_iteration_count, max_position_iteration_count]``. - """ - - max_position_iteration_count: int = 255 - """Maximum number of solver position iterations (rigid bodies, cloth, particles etc.). Default is 255. - - .. note:: - - Each physics actor in Omniverse specifies its own solver iteration count. The solver takes - the number of iterations specified by the actor with the highest iteration and clamps it to - the range ``[min_position_iteration_count, max_position_iteration_count]``. - """ - - min_velocity_iteration_count: int = 0 - """Minimum number of solver position iterations (rigid bodies, cloth, particles etc.). Default is 0. - - .. note:: - - Each physics actor in Omniverse specifies its own solver iteration count. The solver takes - the number of iterations specified by the actor with the highest iteration and clamps it to - the range ``[min_velocity_iteration_count, max_velocity_iteration_count]``. - """ - - max_velocity_iteration_count: int = 255 - """Maximum number of solver position iterations (rigid bodies, cloth, particles etc.). Default is 255. - - .. note:: - - Each physics actor in Omniverse specifies its own solver iteration count. The solver takes - the number of iterations specified by the actor with the highest iteration and clamps it to - the range ``[min_velocity_iteration_count, max_velocity_iteration_count]``. - """ - - enable_ccd: bool = False - """Enable a second broad-phase pass that makes it possible to prevent objects from tunneling through each other. - Default is False.""" - - enable_stabilization: bool = True - """Enable/disable additional stabilization pass in solver. Default is True.""" - - enable_enhanced_determinism: bool = False - """Enable/disable improved determinism at the expense of performance. Defaults to False. - - For more information on PhysX determinism, please check `here`_. - - .. _here: https://nvidia-omniverse.github.io/PhysX/physx/5.3.1/docs/RigidBodyDynamics.html#enhanced-determinism - """ - - bounce_threshold_velocity: float = 0.5 - """Relative velocity threshold for contacts to bounce (in m/s). Default is 0.5 m/s.""" - - friction_offset_threshold: float = 0.04 - """Threshold for contact point to experience friction force (in m). Default is 0.04 m.""" - - friction_correlation_distance: float = 0.025 - """Distance threshold for merging contacts into a single friction anchor point (in m). Default is 0.025 m.""" - - gpu_max_rigid_contact_count: int = 2**23 - """Size of rigid contact stream buffer allocated in pinned host memory. Default is 2 ** 23.""" - - gpu_max_rigid_patch_count: int = 5 * 2**15 - """Size of the rigid contact patch stream buffer allocated in pinned host memory. Default is 5 * 2 ** 15.""" - - gpu_found_lost_pairs_capacity: int = 2**21 - """Capacity of found and lost buffers allocated in GPU global memory. Default is 2 ** 21. - - This is used for the found/lost pair reports in the BP. - """ - - gpu_found_lost_aggregate_pairs_capacity: int = 2**25 - """Capacity of found and lost buffers in aggregate system allocated in GPU global memory. - Default is 2 ** 25. - - This is used for the found/lost pair reports in AABB manager. - """ - - gpu_total_aggregate_pairs_capacity: int = 2**21 - """Capacity of total number of aggregate pairs allocated in GPU global memory. Default is 2 ** 21.""" - - gpu_collision_stack_size: int = 2**26 - """Size of the collision stack buffer allocated in pinned host memory. Default is 2 ** 26.""" - - gpu_heap_capacity: int = 2**26 - """Initial capacity of the GPU and pinned host memory heaps. Additional memory will be allocated - if more memory is required. Default is 2 ** 26.""" - - gpu_temp_buffer_capacity: int = 2**24 - """Capacity of temp buffer allocated in pinned host memory. Default is 2 ** 24.""" - - gpu_max_num_partitions: int = 8 - """Limitation for the partitions in the GPU dynamics pipeline. Default is 8. - - This variable must be power of 2. A value greater than 32 is currently not supported. Range: (1, 32) - """ - - gpu_max_soft_body_contacts: int = 2**20 - """Size of soft body contacts stream buffer allocated in pinned host memory. Default is 2 ** 20.""" - - gpu_max_particle_contacts: int = 2**20 - """Size of particle contacts stream buffer allocated in pinned host memory. Default is 2 ** 20."""
- - -
[文档]@configclass -class SimulationCfg: - """Configuration for simulation physics.""" - - physics_prim_path: str = "/physicsScene" - """The prim path where the USD PhysicsScene is created. Default is "/physicsScene".""" - - dt: float = 1.0 / 60.0 - """The physics simulation time-step (in seconds). Default is 0.0167 seconds.""" - - render_interval: int = 1 - """The number of physics simulation steps per rendering step. Default is 1.""" - - gravity: tuple[float, float, float] = (0.0, 0.0, -9.81) - """The gravity vector (in m/s^2). Default is (0.0, 0.0, -9.81). - - If set to (0.0, 0.0, 0.0), gravity is disabled. - """ - - enable_scene_query_support: bool = False - """Enable/disable scene query support for collision shapes. Default is False. - - This flag allows performing collision queries (raycasts, sweeps, and overlaps) on actors and - attached shapes in the scene. This is useful for implementing custom collision detection logic - outside of the physics engine. - - If set to False, the physics engine does not create the scene query manager and the scene query - functionality will not be available. However, this provides some performance speed-up. - - Note: - This flag is overridden to True inside the :class:`SimulationContext` class when running the simulation - with the GUI enabled. This is to allow certain GUI features to work properly. - """ - - use_fabric: bool = True - """Enable/disable reading of physics buffers directly. Default is True. - - When running the simulation, updates in the states in the scene is normally synchronized with USD. - This leads to an overhead in reading the data and does not scale well with massive parallelization. - This flag allows disabling the synchronization and reading the data directly from the physics buffers. - - It is recommended to set this flag to :obj:`True` when running the simulation with a large number - of primitives in the scene. - - Note: - When enabled, the GUI will not update the physics parameters in real-time. To enable real-time - updates, please set this flag to :obj:`False`. - """ - - disable_contact_processing: bool = False - """Enable/disable contact processing. Default is False. - - By default, the physics engine processes all the contacts in the scene. However, reporting this contact - information can be expensive due to its combinatorial complexity. This flag allows disabling the contact - processing and querying the contacts manually by the user over a limited set of primitives in the scene. - - .. note:: - - It is required to set this flag to :obj:`True` when using the TensorAPIs for contact reporting. - """ - - use_gpu_pipeline: bool = True - """Enable/disable GPU pipeline. Default is True. - - If set to False, the physics data will be read as CPU buffers. - """ - - device: str = "cuda:0" - """The device for running the simulation/environment. Default is ``"cuda:0"``.""" - - physx: PhysxCfg = PhysxCfg() - """PhysX solver settings. Default is PhysxCfg().""" - - physics_material: RigidBodyMaterialCfg = RigidBodyMaterialCfg() - """Default physics material settings for rigid bodies. Default is RigidBodyMaterialCfg(). - - The physics engine defaults to this physics material for all the rigid body prims that do not have any - physics material specified on them. - - The material is created at the path: ``{physics_prim_path}/defaultMaterial``. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/simulation_context.html b/docs/_build/html/_modules/omni/isaac/lab/sim/simulation_context.html deleted file mode 100644 index 8b761968fd..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/simulation_context.html +++ /dev/null @@ -1,1311 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.simulation_context — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.simulation_context 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import builtins
-import enum
-import numpy as np
-import sys
-import torch
-import traceback
-import weakref
-from collections.abc import Iterator
-from contextlib import contextmanager
-from typing import Any
-
-import carb
-import omni.isaac.core.utils.stage as stage_utils
-import omni.physx
-from omni.isaac.core.simulation_context import SimulationContext as _SimulationContext
-from omni.isaac.core.utils.viewports import set_camera_view
-from omni.isaac.version import get_version
-from pxr import Gf, PhysxSchema, Usd, UsdPhysics
-
-from .simulation_cfg import SimulationCfg
-from .spawners import DomeLightCfg, GroundPlaneCfg
-from .utils import bind_physics_material
-
-
-
[文档]class SimulationContext(_SimulationContext): - """A class to control simulation-related events such as physics stepping and rendering. - - The simulation context helps control various simulation aspects. This includes: - - * configure the simulator with different settings such as the physics time-step, the number of physics substeps, - and the physics solver parameters (for more information, see :class:`omni.isaac.lab.sim.SimulationCfg`) - * playing, pausing, stepping and stopping the simulation - * adding and removing callbacks to different simulation events such as physics stepping, rendering, etc. - - This class inherits from the :class:`omni.isaac.core.simulation_context.SimulationContext` class and - adds additional functionalities such as setting up the simulation context with a configuration object, - exposing other commonly used simulator-related functions, and performing version checks of Isaac Sim - to ensure compatibility between releases. - - The simulation context is a singleton object. This means that there can only be one instance - of the simulation context at any given time. This is enforced by the parent class. Therefore, it is - not possible to create multiple instances of the simulation context. Instead, the simulation context - can be accessed using the ``instance()`` method. - - .. attention:: - Since we only support the `PyTorch <https://pytorch.org/>`_ backend for simulation, the - simulation context is configured to use the ``torch`` backend by default. This means that - all the data structures used in the simulation are ``torch.Tensor`` objects. - - The simulation context can be used in two different modes of operations: - - 1. **Standalone python script**: In this mode, the user has full control over the simulation and - can trigger stepping events synchronously (i.e. as a blocking call). In this case the user - has to manually call :meth:`step` step the physics simulation and :meth:`render` to - render the scene. - 2. **Omniverse extension**: In this mode, the user has limited control over the simulation stepping - and all the simulation events are triggered asynchronously (i.e. as a non-blocking call). In this - case, the user can only trigger the simulation to start, pause, and stop. The simulation takes - care of stepping the physics simulation and rendering the scene. - - Based on above, for most functions in this class there is an equivalent function that is suffixed - with ``_async``. The ``_async`` functions are used in the Omniverse extension mode and - the non-``_async`` functions are used in the standalone python script mode. - """ - -
[文档] class RenderMode(enum.IntEnum): - """Different rendering modes for the simulation. - - Render modes correspond to how the viewport and other UI elements (such as listeners to keyboard or mouse - events) are updated. There are three main components that can be updated when the simulation is rendered: - - 1. **UI elements and other extensions**: These are UI elements (such as buttons, sliders, etc.) and other - extensions that are running in the background that need to be updated when the simulation is running. - 2. **Cameras**: These are typically based on Hydra textures and are used to render the scene from different - viewpoints. They can be attached to a viewport or be used independently to render the scene. - 3. **Viewports**: These are windows where you can see the rendered scene. - - Updating each of the above components has a different overhead. For example, updating the viewports is - computationally expensive compared to updating the UI elements. Therefore, it is useful to be able to - control what is updated when the simulation is rendered. This is where the render mode comes in. There are - four different render modes: - - * :attr:`NO_GUI_OR_RENDERING`: The simulation is running without a GUI and off-screen rendering flag is disabled, - so none of the above are updated. - * :attr:`NO_RENDERING`: No rendering, where only 1 is updated at a lower rate. - * :attr:`PARTIAL_RENDERING`: Partial rendering, where only 1 and 2 are updated. - * :attr:`FULL_RENDERING`: Full rendering, where everything (1, 2, 3) is updated. - - .. _Viewports: https://docs.omniverse.nvidia.com/extensions/latest/ext_viewport.html - """ - - NO_GUI_OR_RENDERING = -1 - """The simulation is running without a GUI and off-screen rendering is disabled.""" - NO_RENDERING = 0 - """No rendering, where only other UI elements are updated at a lower rate.""" - PARTIAL_RENDERING = 1 - """Partial rendering, where the simulation cameras and UI elements are updated.""" - FULL_RENDERING = 2 - """Full rendering, where all the simulation viewports, cameras and UI elements are updated."""
- -
[文档] def __init__(self, cfg: SimulationCfg | None = None): - """Creates a simulation context to control the simulator. - - Args: - cfg: The configuration of the simulation. Defaults to None, - in which case the default configuration is used. - """ - # store input - if cfg is None: - cfg = SimulationCfg() - self.cfg = cfg - # check that simulation is running - if stage_utils.get_current_stage() is None: - raise RuntimeError("The stage has not been created. Did you run the simulator?") - - # set flags for simulator - # acquire settings interface - carb_settings_iface = carb.settings.get_settings() - # enable hydra scene-graph instancing - # note: this allows rendering of instanceable assets on the GUI - carb_settings_iface.set_bool("/persistent/omnihydra/useSceneGraphInstancing", True) - # change dispatcher to use the default dispatcher in PhysX SDK instead of carb tasking - # note: dispatcher handles how threads are launched for multi-threaded physics - carb_settings_iface.set_bool("/physics/physxDispatcher", True) - # disable contact processing in omni.physx if requested - # note: helpful when creating contact reporting over limited number of objects in the scene - if self.cfg.disable_contact_processing: - carb_settings_iface.set_bool("/physics/disableContactProcessing", True) - # enable custom geometry for cylinder and cone collision shapes to allow contact reporting for them - # reason: cylinders and cones aren't natively supported by PhysX so we need to use custom geometry flags - # reference: https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/Geometry.html?highlight=capsule#geometry - carb_settings_iface.set_bool("/physics/collisionConeCustomGeometry", False) - carb_settings_iface.set_bool("/physics/collisionCylinderCustomGeometry", False) - # note: we read this once since it is not expected to change during runtime - # read flag for whether a local GUI is enabled - self._local_gui = carb_settings_iface.get("/app/window/enabled") - # read flag for whether livestreaming GUI is enabled - self._livestream_gui = carb_settings_iface.get("/app/livestream/enabled") - - # read flag for whether the Isaac Lab viewport capture pipeline will be used, - # casting None to False if the flag doesn't exist - # this flag is set from the AppLauncher class - self._offscreen_render = bool(carb_settings_iface.get("/isaaclab/render/offscreen")) - # flag for whether any GUI will be rendered (local, livestreamed or viewport) - self._has_gui = self._local_gui or self._livestream_gui - - # store the default render mode - if not self._has_gui and not self._offscreen_render: - # set default render mode - # note: this is the terminal state: cannot exit from this render mode - self.render_mode = self.RenderMode.NO_GUI_OR_RENDERING - # set viewport context to None - self._viewport_context = None - self._viewport_window = None - elif not self._has_gui and self._offscreen_render: - # set default render mode - # note: this is the terminal state: cannot exit from this render mode - self.render_mode = self.RenderMode.PARTIAL_RENDERING - # set viewport context to None - self._viewport_context = None - self._viewport_window = None - else: - # note: need to import here in case the UI is not available (ex. headless mode) - import omni.ui as ui - from omni.kit.viewport.utility import get_active_viewport - - # set default render mode - # note: this can be changed by calling the `set_render_mode` function - self.render_mode = self.RenderMode.FULL_RENDERING - # acquire viewport context - self._viewport_context = get_active_viewport() - self._viewport_context.updates_enabled = True # pyright: ignore [reportOptionalMemberAccess] - # acquire viewport window - # TODO @mayank: Why not just use get_active_viewport_and_window() directly? - self._viewport_window = ui.Workspace.get_window("Viewport") - # counter for periodic rendering - self._render_throttle_counter = 0 - # rendering frequency in terms of number of render calls - self._render_throttle_period = 5 - - # override enable scene querying if rendering is enabled - # this is needed for some GUI features - if self._has_gui: - self.cfg.enable_scene_query_support = True - # set up flatcache/fabric interface (default is None) - # this is needed to flush the flatcache data into Hydra manually when calling `render()` - # ref: https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_physics.html - # note: need to do this here because super().__init__ calls render and this variable is needed - self._fabric_iface = None - # read isaac sim version (this includes build tag, release tag etc.) - # note: we do it once here because it reads the VERSION file from disk and is not expected to change. - self._isaacsim_version = get_version() - - # create a tensor for gravity - # note: this line is needed to create a "tensor" in the device to avoid issues with torch 2.1 onwards. - # the issue is with some heap memory corruption when torch tensor is created inside the asset class. - # you can reproduce the issue by commenting out this line and running the test `test_articulation.py`. - self._gravity_tensor = torch.tensor(self.cfg.gravity, dtype=torch.float32, device=self.cfg.device) - - # add callback to deal the simulation app when simulation is stopped. - # this is needed because physics views go invalid once we stop the simulation - if not builtins.ISAAC_LAUNCHED_FROM_TERMINAL: - timeline_event_stream = omni.timeline.get_timeline_interface().get_timeline_event_stream() - self._app_control_on_stop_handle = timeline_event_stream.create_subscription_to_pop_by_type( - int(omni.timeline.TimelineEventType.STOP), - lambda *args, obj=weakref.proxy(self): obj._app_control_on_stop_callback(*args), - order=15, - ) - else: - self._app_control_on_stop_handle = None - - # flatten out the simulation dictionary - sim_params = self.cfg.to_dict() - if sim_params is not None: - if "physx" in sim_params: - physx_params = sim_params.pop("physx") - sim_params.update(physx_params) - # create a simulation context to control the simulator - super().__init__( - stage_units_in_meters=1.0, - physics_dt=self.cfg.dt, - rendering_dt=self.cfg.dt * self.cfg.render_interval, - backend="torch", - sim_params=sim_params, - physics_prim_path=self.cfg.physics_prim_path, - device=self.cfg.device, - )
- - """ - Operations - New. - """ - -
[文档] def has_gui(self) -> bool: - """Returns whether the simulation has a GUI enabled. - - True if the simulation has a GUI enabled either locally or live-streamed. - """ - return self._has_gui
- -
[文档] def has_rtx_sensors(self) -> bool: - """Returns whether the simulation has any RTX-rendering related sensors. - - This function returns the value of the simulation parameter ``"/isaaclab/render/rtx_sensors"``. - The parameter is set to True when instances of RTX-related sensors (cameras or LiDARs) are - created using Isaac Lab's sensor classes. - - True if the simulation has RTX sensors (such as USD Cameras or LiDARs). - - For more information, please check `NVIDIA RTX documentation`_. - - .. _NVIDIA RTX documentation: https://developer.nvidia.com/rendering-technologies - """ - return self._settings.get_as_bool("/isaaclab/render/rtx_sensors")
- -
[文档] def is_fabric_enabled(self) -> bool: - """Returns whether the fabric interface is enabled. - - When fabric interface is enabled, USD read/write operations are disabled. Instead all applications - read and write the simulation state directly from the fabric interface. This reduces a lot of overhead - that occurs during USD read/write operations. - - For more information, please check `Fabric documentation`_. - - .. _Fabric documentation: https://docs.omniverse.nvidia.com/kit/docs/usdrt/latest/docs/usd_fabric_usdrt.html - """ - return self._fabric_iface is not None
- -
[文档] def get_version(self) -> tuple[int, int, int]: - """Returns the version of the simulator. - - This is a wrapper around the ``omni.isaac.version.get_version()`` function. - - The returned tuple contains the following information: - - * Major version (int): This is the year of the release (e.g. 2022). - * Minor version (int): This is the half-year of the release (e.g. 1 or 2). - * Patch version (int): This is the patch number of the release (e.g. 0). - """ - return int(self._isaacsim_version[2]), int(self._isaacsim_version[3]), int(self._isaacsim_version[4])
- - """ - Operations - New utilities. - """ - -
[文档] @staticmethod - def set_camera_view( - eye: tuple[float, float, float], - target: tuple[float, float, float], - camera_prim_path: str = "/OmniverseKit_Persp", - ): - """Set the location and target of the viewport camera in the stage. - - Note: - This is a wrapper around the :math:`omni.isaac.core.utils.viewports.set_camera_view` function. - It is provided here for convenience to reduce the amount of imports needed. - - Args: - eye: The location of the camera eye. - target: The location of the camera target. - camera_prim_path: The path to the camera primitive in the stage. Defaults to - "/OmniverseKit_Persp". - """ - set_camera_view(eye, target, camera_prim_path)
- -
[文档] def set_render_mode(self, mode: RenderMode): - """Change the current render mode of the simulation. - - Please see :class:`RenderMode` for more information on the different render modes. - - .. note:: - When no GUI is available (locally or livestreamed), we do not need to choose whether the viewport - needs to render or not (since there is no GUI). Thus, in this case, calling the function will not - change the render mode. - - Args: - mode (RenderMode): The rendering mode. If different than SimulationContext's rendering mode, - SimulationContext's mode is changed to the new mode. - - Raises: - ValueError: If the input mode is not supported. - """ - # check if mode change is possible -- not possible when no GUI is available - if not self._has_gui: - carb.log_warn( - f"Cannot change render mode when GUI is disabled. Using the default render mode: {self.render_mode}." - ) - return - # check if there is a mode change - # note: this is mostly needed for GUI when we want to switch between full rendering and no rendering. - if mode != self.render_mode: - if mode == self.RenderMode.FULL_RENDERING: - # display the viewport and enable updates - self._viewport_context.updates_enabled = True # pyright: ignore [reportOptionalMemberAccess] - self._viewport_window.visible = True # pyright: ignore [reportOptionalMemberAccess] - elif mode == self.RenderMode.PARTIAL_RENDERING: - # hide the viewport and disable updates - self._viewport_context.updates_enabled = False # pyright: ignore [reportOptionalMemberAccess] - self._viewport_window.visible = False # pyright: ignore [reportOptionalMemberAccess] - elif mode == self.RenderMode.NO_RENDERING: - # hide the viewport and disable updates - if self._viewport_context is not None: - self._viewport_context.updates_enabled = False # pyright: ignore [reportOptionalMemberAccess] - self._viewport_window.visible = False # pyright: ignore [reportOptionalMemberAccess] - # reset the throttle counter - self._render_throttle_counter = 0 - else: - raise ValueError(f"Unsupported render mode: {mode}! Please check `RenderMode` for details.") - # update render mode - self.render_mode = mode
- -
[文档] def set_setting(self, name: str, value: Any): - """Set simulation settings using the Carbonite SDK. - - .. note:: - If the input setting name does not exist, it will be created. If it does exist, the value will be - overwritten. Please make sure to use the correct setting name. - - To understand the settings interface, please refer to the - `Carbonite SDK <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/settings.html>`_ - documentation. - - Args: - name: The name of the setting. - value: The value of the setting. - """ - self._settings.set(name, value)
- -
[文档] def get_setting(self, name: str) -> Any: - """Read the simulation setting using the Carbonite SDK. - - Args: - name: The name of the setting. - - Returns: - The value of the setting. - """ - return self._settings.get(name)
- - """ - Operations - Override (standalone) - """ - - def reset(self, soft: bool = False): - super().reset(soft=soft) - # perform additional rendering steps to warm up replicator buffers - # this is only needed for the first time we set the simulation - if not soft: - for _ in range(2): - self.render() - -
[文档] def step(self, render: bool = True): - """Steps the simulation. - - .. note:: - This function blocks if the timeline is paused. It only returns when the timeline is playing. - - Args: - render: Whether to render the scene after stepping the physics simulation. - If set to False, the scene is not rendered and only the physics simulation is stepped. - """ - # check if the simulation timeline is paused. in that case keep stepping until it is playing - if not self.is_playing(): - # step the simulator (but not the physics) to have UI still active - while not self.is_playing(): - self.render() - # meantime if someone stops, break out of the loop - if self.is_stopped(): - break - # need to do one step to refresh the app - # reason: physics has to parse the scene again and inform other extensions like hydra-delegate. - # without this the app becomes unresponsive. - # FIXME: This steps physics as well, which we is not good in general. - self.app.update() - - # step the simulation - super().step(render=render)
- -
[文档] def render(self, mode: RenderMode | None = None): - """Refreshes the rendering components including UI elements and view-ports depending on the render mode. - - This function is used to refresh the rendering components of the simulation. This includes updating the - view-ports, UI elements, and other extensions (besides physics simulation) that are running in the - background. The rendering components are refreshed based on the render mode. - - Please see :class:`RenderMode` for more information on the different render modes. - - Args: - mode: The rendering mode. Defaults to None, in which case the current rendering mode is used. - """ - # check if we need to change the render mode - if mode is not None: - self.set_render_mode(mode) - # render based on the render mode - if self.render_mode == self.RenderMode.NO_GUI_OR_RENDERING: - # we never want to render anything here (this is for complete headless mode) - pass - elif self.render_mode == self.RenderMode.NO_RENDERING: - # throttle the rendering frequency to keep the UI responsive - self._render_throttle_counter += 1 - if self._render_throttle_counter % self._render_throttle_period == 0: - self._render_throttle_counter = 0 - # here we don't render viewport so don't need to flush fabric data - # note: we don't call super().render() anymore because they do flush the fabric data - self.set_setting("/app/player/playSimulations", False) - self._app.update() - self.set_setting("/app/player/playSimulations", True) - else: - # manually flush the fabric data to update Hydra textures - if self._fabric_iface is not None: - self._fabric_iface.update(0.0, 0.0) - # render the simulation - # note: we don't call super().render() anymore because they do above operation inside - # and we don't want to do it twice. We may remove it once we drop support for Isaac Sim 2022.2. - self.set_setting("/app/player/playSimulations", False) - self._app.update() - self.set_setting("/app/player/playSimulations", True)
- - """ - Operations - Override (extension) - """ - - async def reset_async(self, soft: bool = False): - # need to load all "physics" information from the USD file - if not soft: - omni.physx.acquire_physx_interface().force_load_physics_from_usd() - # play the simulation - await super().reset_async(soft=soft) - - """ - Initialization/Destruction - Override. - """ - - def _init_stage(self, *args, **kwargs) -> Usd.Stage: - _ = super()._init_stage(*args, **kwargs) - # a stage update here is needed for the case when physics_dt != rendering_dt, otherwise the app crashes - # when in headless mode - self.set_setting("/app/player/playSimulations", False) - self._app.update() - self.set_setting("/app/player/playSimulations", True) - # set additional physx parameters and bind material - self._set_additional_physx_params() - # load flatcache/fabric interface - self._load_fabric_interface() - # return the stage - return self.stage - - async def _initialize_stage_async(self, *args, **kwargs) -> Usd.Stage: - await super()._initialize_stage_async(*args, **kwargs) - # set additional physx parameters and bind material - self._set_additional_physx_params() - # load flatcache/fabric interface - self._load_fabric_interface() - # return the stage - return self.stage - - @classmethod - def clear_instance(cls): - # clear the callback - if cls._instance is not None: - if cls._instance._app_control_on_stop_handle is not None: - cls._instance._app_control_on_stop_handle.unsubscribe() - cls._instance._app_control_on_stop_handle = None - # call parent to clear the instance - super().clear_instance() - - """ - Helper Functions - """ - - def _set_additional_physx_params(self): - """Sets additional PhysX parameters that are not directly supported by the parent class.""" - # obtain the physics scene api - physics_scene: UsdPhysics.Scene = self._physics_context._physics_scene - physx_scene_api: PhysxSchema.PhysxSceneAPI = self._physics_context._physx_scene_api - # assert that scene api is not None - if physx_scene_api is None: - raise RuntimeError("Physics scene API is None! Please create the scene first.") - # set parameters not directly supported by the constructor - # -- Continuous Collision Detection (CCD) - # ref: https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/AdvancedCollisionDetection.html?highlight=ccd#continuous-collision-detection - self._physics_context.enable_ccd(self.cfg.physx.enable_ccd) - # -- GPU collision stack size - physx_scene_api.CreateGpuCollisionStackSizeAttr(self.cfg.physx.gpu_collision_stack_size) - # -- Improved determinism by PhysX - physx_scene_api.CreateEnableEnhancedDeterminismAttr(self.cfg.physx.enable_enhanced_determinism) - - # -- Gravity - # note: Isaac sim only takes the "up-axis" as the gravity direction. But physics allows any direction so we - # need to convert the gravity vector to a direction and magnitude pair explicitly. - gravity = np.asarray(self.cfg.gravity) - gravity_magnitude = np.linalg.norm(gravity) - - # Avoid division by zero - if gravity_magnitude != 0.0: - gravity_direction = gravity / gravity_magnitude - else: - gravity_direction = gravity - - physics_scene.CreateGravityDirectionAttr(Gf.Vec3f(*gravity_direction)) - physics_scene.CreateGravityMagnitudeAttr(gravity_magnitude) - - # position iteration count - physx_scene_api.CreateMinPositionIterationCountAttr(self.cfg.physx.min_position_iteration_count) - physx_scene_api.CreateMaxPositionIterationCountAttr(self.cfg.physx.max_position_iteration_count) - # velocity iteration count - physx_scene_api.CreateMinVelocityIterationCountAttr(self.cfg.physx.min_velocity_iteration_count) - physx_scene_api.CreateMaxVelocityIterationCountAttr(self.cfg.physx.max_velocity_iteration_count) - - # create the default physics material - # this material is used when no material is specified for a primitive - # check: https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/simulation-control/physics-settings.html#physics-materials - material_path = f"{self.cfg.physics_prim_path}/defaultMaterial" - self.cfg.physics_material.func(material_path, self.cfg.physics_material) - # bind the physics material to the scene - bind_physics_material(self.cfg.physics_prim_path, material_path) - - def _load_fabric_interface(self): - """Loads the fabric interface if enabled.""" - if self.cfg.use_fabric: - from omni.physxfabric import get_physx_fabric_interface - - # acquire fabric interface - self._fabric_iface = get_physx_fabric_interface() - - """ - Callbacks. - """ - - def _app_control_on_stop_callback(self, event: carb.events.IEvent): - """Callback to deal with the app when the simulation is stopped. - - Once the simulation is stopped, the physics handles go invalid. After that, it is not possible to - resume the simulation from the last state. This leaves the app in an inconsistent state, where - two possible actions can be taken: - - 1. **Keep the app rendering**: In this case, the simulation is kept running and the app is not shutdown. - However, the physics is not updated and the script cannot be resumed from the last state. The - user has to manually close the app to stop the simulation. - 2. **Shutdown the app**: This is the default behavior. In this case, the app is shutdown and - the simulation is stopped. - - Note: - This callback is used only when running the simulation in a standalone python script. In an extension, - it is expected that the user handles the extension shutdown. - """ - # check if the simulation is stopped - if event.type == int(omni.timeline.TimelineEventType.STOP): - # keep running the simulator when configured to not shutdown the app - if self._has_gui and sys.exc_info()[0] is None: - self.app.print_and_log( - "Simulation is stopped. The app will keep running with physics disabled." - " Press Ctrl+C or close the window to exit the app." - ) - while self.app.is_running(): - self.render() - - # Note: For the following code: - # The method is an exact copy of the implementation in the `omni.isaac.kit.SimulationApp` class. - # We need to remove this method once the SimulationApp class becomes a singleton. - - # make sure that any replicator workflows finish rendering/writing - try: - import omni.replicator.core as rep - - rep_status = rep.orchestrator.get_status() - if rep_status not in [rep.orchestrator.Status.STOPPED, rep.orchestrator.Status.STOPPING]: - rep.orchestrator.stop() - if rep_status != rep.orchestrator.Status.STOPPED: - rep.orchestrator.wait_until_complete() - - # Disable capture on play to avoid replicator engaging on any new timeline events - rep.orchestrator.set_capture_on_play(False) - except Exception: - pass - - # clear the instance and all callbacks - # note: clearing callbacks is important to prevent memory leaks - self.clear_all_callbacks() - - # workaround for exit issues, clean the stage first: - if omni.usd.get_context().can_close_stage(): - omni.usd.get_context().close_stage() - - # print logging information - self.app.print_and_log("Simulation is stopped. Shutting down the app...") - - # Cleanup any running tracy instances so data is not lost - try: - profiler_tracy = carb.profiler.acquire_profiler_interface(plugin_name="carb.profiler-tracy.plugin") - if profiler_tracy: - profiler_tracy.set_capture_mask(0) - profiler_tracy.end(0) - profiler_tracy.shutdown() - except RuntimeError: - # Tracy plugin was not loaded, so profiler never started - skip checks. - pass - - # Disable logging before shutdown to keep the log clean - # Warnings at this point don't matter as the python process is about to be terminated - logging = carb.logging.acquire_logging() - logging.set_level_threshold(carb.logging.LEVEL_ERROR) - - # App shutdown is disabled to prevent crashes on shutdown. Terminating carb is faster - # self._app.shutdown() - self._framework.unload_all_plugins()
- - -@contextmanager -def build_simulation_context( - create_new_stage: bool = True, - gravity_enabled: bool = True, - device: str = "cuda:0", - dt: float = 0.01, - sim_cfg: SimulationCfg | None = None, - add_ground_plane: bool = False, - add_lighting: bool = False, - auto_add_lighting: bool = False, -) -> Iterator[SimulationContext]: - """Context manager to build a simulation context with the provided settings. - - This function facilitates the creation of a simulation context and provides flexibility in configuring various - aspects of the simulation, such as time step, gravity, device, and scene elements like ground plane and - lighting. - - If :attr:`sim_cfg` is None, then an instance of :class:`SimulationCfg` is created with default settings, with parameters - overwritten based on arguments to the function. - - An example usage of the context manager function: - - .. code-block:: python - - with build_simulation_context() as sim: - # Design the scene - - # Play the simulation - sim.reset() - while sim.is_playing(): - sim.step() - - Args: - create_new_stage: Whether to create a new stage. Defaults to True. - gravity_enabled: Whether to enable gravity in the simulation. Defaults to True. - device: Device to run the simulation on. Defaults to "cuda:0". - dt: Time step for the simulation: Defaults to 0.01. - sim_cfg: :class:`omni.isaac.lab.sim.SimulationCfg` to use for the simulation. Defaults to None. - add_ground_plane: Whether to add a ground plane to the simulation. Defaults to False. - add_lighting: Whether to add a dome light to the simulation. Defaults to False. - auto_add_lighting: Whether to automatically add a dome light to the simulation if the simulation has a GUI. - Defaults to False. This is useful for debugging tests in the GUI. - - Yields: - The simulation context to use for the simulation. - - """ - try: - if create_new_stage: - stage_utils.create_new_stage() - - if sim_cfg is None: - # Construct one and overwrite the dt, gravity, and device - sim_cfg = SimulationCfg(dt=dt) - - # Set up gravity - if gravity_enabled: - sim_cfg.gravity = (0.0, 0.0, -9.81) - else: - sim_cfg.gravity = (0.0, 0.0, 0.0) - - # Set device - sim_cfg.device = device - - # Construct simulation context - sim = SimulationContext(sim_cfg) - - if add_ground_plane: - # Ground-plane - cfg = GroundPlaneCfg() - cfg.func("/World/defaultGroundPlane", cfg) - - if add_lighting or (auto_add_lighting and sim.has_gui()): - # Lighting - cfg = DomeLightCfg( - color=(0.1, 0.1, 0.1), - enable_color_temperature=True, - color_temperature=5500, - intensity=10000, - ) - # Dome light named specifically to avoid conflicts - cfg.func(prim_path="/World/defaultDomeLight", cfg=cfg, translation=(0.0, 0.0, 10.0)) - - yield sim - - except Exception: - carb.log_error(traceback.format_exc()) - raise - finally: - if not sim.has_gui(): - # Stop simulation only if we aren't rendering otherwise the app will hang indefinitely - sim.stop() - - # Clear the stage - sim.clear_all_callbacks() - sim.clear_instance() -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/from_files/from_files.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/from_files/from_files.html deleted file mode 100644 index 182987eccf..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/from_files/from_files.html +++ /dev/null @@ -1,837 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.from_files.from_files — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.from_files.from_files 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import carb
-import omni.isaac.core.utils.prims as prim_utils
-import omni.isaac.core.utils.stage as stage_utils
-import omni.kit.commands
-from pxr import Gf, Sdf, Usd
-
-from omni.isaac.lab.sim import converters, schemas
-from omni.isaac.lab.sim.utils import bind_physics_material, bind_visual_material, clone, select_usd_variants
-
-if TYPE_CHECKING:
-    from . import from_files_cfg
-
-
-
[文档]@clone -def spawn_from_usd( - prim_path: str, - cfg: from_files_cfg.UsdFileCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Spawn an asset from a USD file and override the settings with the given config. - - In the case of a USD file, the asset is spawned at the default prim specified in the USD file. - If a default prim is not specified, then the asset is spawned at the root prim. - - In case a prim already exists at the given prim path, then the function does not create a new prim - or throw an error that the prim already exists. Instead, it just takes the existing prim and overrides - the settings with the given config. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which - case the translation specified in the USD file is used. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case the orientation specified in the USD file is used. - - Returns: - The prim of the spawned asset. - - Raises: - FileNotFoundError: If the USD file does not exist at the given path. - """ - # spawn asset from the given usd file - return _spawn_from_usd_file(prim_path, cfg.usd_path, cfg, translation, orientation)
- - -
[文档]@clone -def spawn_from_urdf( - prim_path: str, - cfg: from_files_cfg.UrdfFileCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Spawn an asset from a URDF file and override the settings with the given config. - - It uses the :class:`UrdfConverter` class to create a USD file from URDF. This file is then imported - at the specified prim path. - - In case a prim already exists at the given prim path, then the function does not create a new prim - or throw an error that the prim already exists. Instead, it just takes the existing prim and overrides - the settings with the given config. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which - case the translation specified in the generated USD file is used. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case the orientation specified in the generated USD file is used. - - Returns: - The prim of the spawned asset. - - Raises: - FileNotFoundError: If the URDF file does not exist at the given path. - """ - # urdf loader to convert urdf to usd - urdf_loader = converters.UrdfConverter(cfg) - # spawn asset from the generated usd file - return _spawn_from_usd_file(prim_path, urdf_loader.usd_path, cfg, translation, orientation)
- - -
[文档]def spawn_ground_plane( - prim_path: str, - cfg: from_files_cfg.GroundPlaneCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Spawns a ground plane into the scene. - - This function loads the USD file containing the grid plane asset from Isaac Sim. It may - not work with other assets for ground planes. In those cases, please use the `spawn_from_usd` - function. - - Note: - This function takes keyword arguments to be compatible with other spawners. However, it does not - use any of the kwargs. - - Args: - prim_path: The path to spawn the asset at. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which - case the translation specified in the USD file is used. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case the orientation specified in the USD file is used. - - Returns: - The prim of the spawned asset. - - Raises: - ValueError: If the prim path already exists. - """ - # Spawn Ground-plane - if not prim_utils.is_prim_path_valid(prim_path): - prim_utils.create_prim(prim_path, usd_path=cfg.usd_path, translation=translation, orientation=orientation) - else: - raise ValueError(f"A prim already exists at path: '{prim_path}'.") - - # Create physics material - if cfg.physics_material is not None: - cfg.physics_material.func(f"{prim_path}/physicsMaterial", cfg.physics_material) - # Apply physics material to ground plane - collision_prim_path = prim_utils.get_prim_path( - prim_utils.get_first_matching_child_prim( - prim_path, predicate=lambda x: prim_utils.get_prim_type_name(x) == "Plane" - ) - ) - bind_physics_material(collision_prim_path, f"{prim_path}/physicsMaterial") - - # Scale only the mesh - # Warning: This is specific to the default grid plane asset. - if prim_utils.is_prim_path_valid(f"{prim_path}/Enviroment"): - # compute scale from size - scale = (cfg.size[0] / 100.0, cfg.size[1] / 100.0, 1.0) - # apply scale to the mesh - omni.kit.commands.execute( - "ChangeProperty", - prop_path=Sdf.Path(f"{prim_path}/Enviroment.xformOp:scale"), - value=scale, - prev=None, - ) - - # Change the color of the plane - # Warning: This is specific to the default grid plane asset. - if cfg.color is not None: - prop_path = f"{prim_path}/Looks/theGrid/Shader.inputs:diffuse_tint" - # change the color - omni.kit.commands.execute( - "ChangePropertyCommand", - prop_path=Sdf.Path(prop_path), - value=Gf.Vec3f(*cfg.color), - prev=None, - type_to_create_if_not_exist=Sdf.ValueTypeNames.Color3f, - ) - # Remove the light from the ground plane - # It isn't bright enough and messes up with the user's lighting settings - omni.kit.commands.execute("ToggleVisibilitySelectedPrims", selected_paths=[f"{prim_path}/SphereLight"]) - - # return the prim - return prim_utils.get_prim_at_path(prim_path)
- - -""" -Helper functions. -""" - - -def _spawn_from_usd_file( - prim_path: str, - usd_path: str, - cfg: from_files_cfg.FileCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Spawn an asset from a USD file and override the settings with the given config. - - In case a prim already exists at the given prim path, then the function does not create a new prim - or throw an error that the prim already exists. Instead, it just takes the existing prim and overrides - the settings with the given config. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - usd_path: The path to the USD file to spawn the asset from. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which - case the translation specified in the generated USD file is used. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case the orientation specified in the generated USD file is used. - - Returns: - The prim of the spawned asset. - - Raises: - FileNotFoundError: If the USD file does not exist at the given path. - """ - # check file path exists - stage: Usd.Stage = stage_utils.get_current_stage() - if not stage.ResolveIdentifierToEditTarget(usd_path): - raise FileNotFoundError(f"USD file not found at path: '{usd_path}'.") - # spawn asset if it doesn't exist. - if not prim_utils.is_prim_path_valid(prim_path): - # add prim as reference to stage - prim_utils.create_prim( - prim_path, - usd_path=usd_path, - translation=translation, - orientation=orientation, - scale=cfg.scale, - ) - else: - carb.log_warn(f"A prim already exists at prim path: '{prim_path}'.") - - # modify variants - if hasattr(cfg, "variants") and cfg.variants is not None: - select_usd_variants(prim_path, cfg.variants) - - # modify rigid body properties - if cfg.rigid_props is not None: - schemas.modify_rigid_body_properties(prim_path, cfg.rigid_props) - # modify collision properties - if cfg.collision_props is not None: - schemas.modify_collision_properties(prim_path, cfg.collision_props) - # modify mass properties - if cfg.mass_props is not None: - schemas.modify_mass_properties(prim_path, cfg.mass_props) - # modify articulation root properties - if cfg.articulation_props is not None: - schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props) - # modify tendon properties - if cfg.fixed_tendons_props is not None: - schemas.modify_fixed_tendon_properties(prim_path, cfg.fixed_tendons_props) - - # define drive API on the joints - # note: these are only for setting low-level simulation properties. all others should be set or are - # and overridden by the articulation/actuator properties. - if cfg.joint_drive_props is not None: - schemas.modify_joint_drive_properties(prim_path, cfg.joint_drive_props) - - # apply visual material - if cfg.visual_material is not None: - if not cfg.visual_material_path.startswith("/"): - material_path = f"{prim_path}/{cfg.visual_material_path}" - else: - material_path = cfg.visual_material_path - # create material - cfg.visual_material.func(material_path, cfg.visual_material) - # apply material - bind_visual_material(prim_path, material_path) - - # return the prim - return prim_utils.get_prim_at_path(prim_path) -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/from_files/from_files_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/from_files/from_files_cfg.html deleted file mode 100644 index 7754635efe..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/from_files/from_files_cfg.html +++ /dev/null @@ -1,686 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.from_files.from_files_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.from_files.from_files_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from collections.abc import Callable
-from dataclasses import MISSING
-
-from omni.isaac.lab.sim import converters, schemas
-from omni.isaac.lab.sim.spawners import materials
-from omni.isaac.lab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg
-from omni.isaac.lab.utils import configclass
-from omni.isaac.lab.utils.assets import ISAAC_NUCLEUS_DIR
-
-from . import from_files
-
-
-@configclass
-class FileCfg(RigidObjectSpawnerCfg):
-    """Configuration parameters for spawning an asset from a file.
-
-    Note:
-        By default, all properties are set to None. This means that no properties will be added or modified
-        to the prim outside of the properties available by default when spawning the prim.
-    """
-
-    scale: tuple[float, float, float] | None = None
-    """Scale of the asset. Defaults to None, in which case the scale is not modified."""
-
-    articulation_props: schemas.ArticulationRootPropertiesCfg | None = None
-    """Properties to apply to the articulation root."""
-
-    fixed_tendons_props: schemas.FixedTendonsPropertiesCfg | None = None
-    """Properties to apply to the fixed tendons (if any)."""
-
-    joint_drive_props: schemas.JointDrivePropertiesCfg | None = None
-    """Properties to apply to a joint."""
-
-    visual_material_path: str = "material"
-    """Path to the visual material to use for the prim. Defaults to "material".
-
-    If the path is relative, then it will be relative to the prim's path.
-    This parameter is ignored if `visual_material` is not None.
-    """
-
-    visual_material: materials.VisualMaterialCfg | None = None
-    """Visual material properties to override the visual material properties in the URDF file.
-
-    Note:
-        If None, then no visual material will be added.
-    """
-
-
-
[文档]@configclass -class UsdFileCfg(FileCfg): - """USD file to spawn asset from. - - See :meth:`spawn_from_usd` for more information. - - .. note:: - The configuration parameters include various properties. If not `None`, these properties - are modified on the spawned prim in a nested manner. - """ - - func: Callable = from_files.spawn_from_usd - - usd_path: str = MISSING - """Path to the USD file to spawn asset from.""" - - variants: object | dict[str, str] | None = None - """Variants to select from in the input USD file. Defaults to None, in which case no variants are applied. - - This can either be a configclass object, in which case each attribute is used as a variant set name and its specified value, - or a dictionary mapping between the two. Please check the :meth:`~omni.isaac.lab.sim.utils.select_usd_variants` function - for more information. - """
- - -
[文档]@configclass -class UrdfFileCfg(FileCfg, converters.UrdfConverterCfg): - """URDF file to spawn asset from. - - It uses the :class:`UrdfConverter` class to create a USD file from URDF and spawns the imported - USD file. See :meth:`spawn_from_urdf` for more information. - - .. note:: - The configuration parameters include various properties. If not `None`, these properties - are modified on the spawned prim in a nested manner. - """ - - func: Callable = from_files.spawn_from_urdf
- - -""" -Spawning ground plane. -""" - - -
[文档]@configclass -class GroundPlaneCfg(SpawnerCfg): - """Create a ground plane prim. - - This uses the USD for the standard grid-world ground plane from Isaac Sim by default. - """ - - func: Callable = from_files.spawn_ground_plane - - usd_path: str = f"{ISAAC_NUCLEUS_DIR}/Environments/Grid/default_environment.usd" - """Path to the USD file to spawn asset from. Defaults to the grid-world ground plane.""" - - color: tuple[float, float, float] | None = (0.0, 0.0, 0.0) - """The color of the ground plane. Defaults to (0.0, 0.0, 0.0). - - If None, then the color remains unchanged. - """ - - size: tuple[float, float] = (100.0, 100.0) - """The size of the ground plane. Defaults to 100 m x 100 m.""" - - physics_material: materials.RigidBodyMaterialCfg = materials.RigidBodyMaterialCfg() - """Physics material properties. Defaults to the default rigid body material."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/lights/lights.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/lights/lights.html deleted file mode 100644 index 6a11274d84..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/lights/lights.html +++ /dev/null @@ -1,643 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.lights.lights — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.lights.lights 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import omni.isaac.core.utils.prims as prim_utils
-from pxr import Usd, UsdLux
-
-from omni.isaac.lab.sim.utils import clone, safe_set_attribute_on_usd_prim
-
-if TYPE_CHECKING:
-    from . import lights_cfg
-
-
-
[文档]@clone -def spawn_light( - prim_path: str, - cfg: lights_cfg.LightCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a light prim at the specified prim path with the specified configuration. - - The created prim is based on the `USD.LuxLight <https://openusd.org/dev/api/class_usd_lux_light_a_p_i.html>`_ API. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration for the light source. - translation: The translation of the prim. Defaults to None, in which case this is set to the origin. - orientation: The orientation of the prim as (w, x, y, z). Defaults to None, in which case this - is set to identity. - - Raises: - ValueError: When a prim already exists at the specified prim path. - """ - # check if prim already exists - if prim_utils.is_prim_path_valid(prim_path): - raise ValueError(f"A prim already exists at path: '{prim_path}'.") - # create the prim - prim = prim_utils.create_prim(prim_path, prim_type=cfg.prim_type, translation=translation, orientation=orientation) - - # convert to dict - cfg = cfg.to_dict() - # delete spawner func specific parameters - del cfg["prim_type"] - # delete custom attributes in the config that are not USD parameters - non_usd_cfg_param_names = ["func", "copy_from_source", "visible", "semantic_tags"] - for param_name in non_usd_cfg_param_names: - del cfg[param_name] - # set into USD API - for attr_name, value in cfg.items(): - # special operation for texture properties - # note: this is only used for dome light - if "texture" in attr_name: - light_prim = UsdLux.DomeLight(prim) - if attr_name == "texture_file": - light_prim.CreateTextureFileAttr(value) - elif attr_name == "texture_format": - light_prim.CreateTextureFormatAttr(value) - else: - raise ValueError(f"Unsupported texture attribute: '{attr_name}'.") - else: - if attr_name == "visible_in_primary_ray": - prim_prop_name = attr_name - else: - prim_prop_name = f"inputs:{attr_name}" - # set the attribute - safe_set_attribute_on_usd_prim(prim, prim_prop_name, value, camel_case=True) - # return the prim - return prim
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/lights/lights_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/lights/lights_cfg.html deleted file mode 100644 index 92d43b4c3c..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/lights/lights_cfg.html +++ /dev/null @@ -1,751 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.lights.lights_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.lights.lights_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from collections.abc import Callable
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.sim.spawners.spawner_cfg import SpawnerCfg
-from omni.isaac.lab.utils import configclass
-
-from . import lights
-
-
-
[文档]@configclass -class LightCfg(SpawnerCfg): - """Configuration parameters for creating a light in the scene. - - Please refer to the documentation on `USD LuxLight <https://openusd.org/dev/api/class_usd_lux_light_a_p_i.html>`_ - for more information. - - .. note:: - The default values for the attributes are those specified in the their official documentation. - """ - - func: Callable = lights.spawn_light - - prim_type: str = MISSING - """The prim type name for the light prim.""" - - color: tuple[float, float, float] = (1.0, 1.0, 1.0) - """The color of emitted light, in energy-linear terms. Defaults to white.""" - - enable_color_temperature: bool = False - """Enables color temperature. Defaults to false.""" - - color_temperature: float = 6500.0 - """Color temperature (in Kelvin) representing the white point. The valid range is [1000, 10000]. Defaults to 6500K. - - The `color temperature <https://en.wikipedia.org/wiki/Color_temperature>`_ corresponds to the warmth - or coolness of light. Warmer light has a lower color temperature, while cooler light has a higher - color temperature. - - Note: - It only takes effect when :attr:`enable_color_temperature` is true. - """ - - normalize: bool = False - """Normalizes power by the surface area of the light. Defaults to false. - - This makes it easier to independently adjust the power and shape of the light, by causing the power - to not vary with the area or angular size of the light. - """ - - exposure: float = 0.0 - """Scales the power of the light exponentially as a power of 2. Defaults to 0.0. - - The result is multiplied against the intensity. - """ - - intensity: float = 1.0 - """Scales the power of the light linearly. Defaults to 1.0."""
- - -
[文档]@configclass -class DiskLightCfg(LightCfg): - """Configuration parameters for creating a disk light in the scene. - - A disk light is a light source that emits light from a disk. It is useful for simulating - fluorescent lights. For more information, please refer to the documentation on - `USDLux DiskLight <https://openusd.org/dev/api/class_usd_lux_disk_light.html>`_. - - .. note:: - The default values for the attributes are those specified in the their official documentation. - """ - - prim_type = "DiskLight" - - radius: float = 0.5 - """Radius of the disk (in m). Defaults to 0.5m."""
- - -
[文档]@configclass -class DistantLightCfg(LightCfg): - """Configuration parameters for creating a distant light in the scene. - - A distant light is a light source that is infinitely far away, and emits parallel rays of light. - It is useful for simulating sun/moon light. For more information, please refer to the documentation on - `USDLux DistantLight <https://openusd.org/dev/api/class_usd_lux_distant_light.html>`_. - - .. note:: - The default values for the attributes are those specified in the their official documentation. - """ - - prim_type = "DistantLight" - - angle: float = 0.53 - """Angular size of the light (in degrees). Defaults to 0.53 degrees. - - As an example, the Sun is approximately 0.53 degrees as seen from Earth. - Higher values broaden the light and therefore soften shadow edges. - """
- - -
[文档]@configclass -class DomeLightCfg(LightCfg): - """Configuration parameters for creating a dome light in the scene. - - A dome light is a light source that emits light inwards from all directions. It is also possible to - attach a texture to the dome light, which will be used to emit light. For more information, please refer - to the documentation on `USDLux DomeLight <https://openusd.org/dev/api/class_usd_lux_dome_light.html>`_. - - .. note:: - The default values for the attributes are those specified in the their official documentation. - """ - - prim_type = "DomeLight" - - texture_file: str | None = None - """A color texture to use on the dome, such as an HDR (high dynamic range) texture intended - for IBL (image based lighting). Defaults to None. - - If None, the dome will emit a uniform color. - """ - - texture_format: Literal["automatic", "latlong", "mirroredBall", "angular", "cubeMapVerticalCross"] = "automatic" - """The parametrization format of the color map file. Defaults to "automatic". - - Valid values are: - - * ``"automatic"``: Tries to determine the layout from the file itself. For example, Renderman texture files embed an explicit parameterization. - * ``"latlong"``: Latitude as X, longitude as Y. - * ``"mirroredBall"``: An image of the environment reflected in a sphere, using an implicitly orthogonal projection. - * ``"angular"``: Similar to mirroredBall but the radial dimension is mapped linearly to the angle, providing better sampling at the edges. - * ``"cubeMapVerticalCross"``: A cube map with faces laid out as a vertical cross. - """ - - visible_in_primary_ray: bool = True - """Whether the dome light is visible in the primary ray. Defaults to True. - - If true, the texture in the sky is visible, otherwise the sky is black. - """
- - -
[文档]@configclass -class CylinderLightCfg(LightCfg): - """Configuration parameters for creating a cylinder light in the scene. - - A cylinder light is a light source that emits light from a cylinder. It is useful for simulating - fluorescent lights. For more information, please refer to the documentation on - `USDLux CylinderLight <https://openusd.org/dev/api/class_usd_lux_cylinder_light.html>`_. - - .. note:: - The default values for the attributes are those specified in the their official documentation. - """ - - prim_type = "CylinderLight" - - length: float = 1.0 - """Length of the cylinder (in m). Defaults to 1.0m.""" - - radius: float = 0.5 - """Radius of the cylinder (in m). Defaults to 0.5m.""" - - treat_as_line: bool = False - """Treats the cylinder as a line source, i.e. a zero-radius cylinder. Defaults to false."""
- - -
[文档]@configclass -class SphereLightCfg(LightCfg): - """Configuration parameters for creating a sphere light in the scene. - - A sphere light is a light source that emits light outward from a sphere. For more information, - please refer to the documentation on - `USDLux SphereLight <https://openusd.org/dev/api/class_usd_lux_sphere_light.html>`_. - - .. note:: - The default values for the attributes are those specified in the their official documentation. - """ - - prim_type = "SphereLight" - - radius: float = 0.5 - """Radius of the sphere. Defaults to 0.5m.""" - - treat_as_point: bool = False - """Treats the sphere as a point source, i.e. a zero-radius sphere. Defaults to false."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/physics_materials.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/physics_materials.html deleted file mode 100644 index 53beba068a..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/physics_materials.html +++ /dev/null @@ -1,636 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.materials.physics_materials — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.materials.physics_materials 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import omni.isaac.core.utils.prims as prim_utils
-import omni.isaac.core.utils.stage as stage_utils
-from pxr import PhysxSchema, Usd, UsdPhysics, UsdShade
-
-from omni.isaac.lab.sim.utils import clone, safe_set_attribute_on_usd_schema
-
-if TYPE_CHECKING:
-    from . import physics_materials_cfg
-
-
-
[文档]@clone -def spawn_rigid_body_material(prim_path: str, cfg: physics_materials_cfg.RigidBodyMaterialCfg) -> Usd.Prim: - """Create material with rigid-body physics properties. - - Rigid body materials are used to define the physical properties to meshes of a rigid body. These - include the friction, restitution, and their respective combination modes. For more information on - rigid body material, please refer to the `documentation on PxMaterial <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/class_px_material.html>`_. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration for the physics material. - - Returns: - The spawned rigid body material prim. - - Raises: - ValueError: When a prim already exists at the specified prim path and is not a material. - """ - # create material prim if no prim exists - if not prim_utils.is_prim_path_valid(prim_path): - _ = UsdShade.Material.Define(stage_utils.get_current_stage(), prim_path) - - # obtain prim - prim = prim_utils.get_prim_at_path(prim_path) - # check if prim is a material - if not prim.IsA(UsdShade.Material): - raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") - # retrieve the USD rigid-body api - usd_physics_material_api = UsdPhysics.MaterialAPI(prim) - if not usd_physics_material_api: - usd_physics_material_api = UsdPhysics.MaterialAPI.Apply(prim) - # retrieve the collision api - physx_material_api = PhysxSchema.PhysxMaterialAPI(prim) - if not physx_material_api: - physx_material_api = PhysxSchema.PhysxMaterialAPI.Apply(prim) - - # convert to dict - cfg = cfg.to_dict() - del cfg["func"] - # set into USD API - for attr_name in ["static_friction", "dynamic_friction", "restitution"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_schema(usd_physics_material_api, attr_name, value, camel_case=True) - # set into PhysX API - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_schema(physx_material_api, attr_name, value, camel_case=True) - # return the prim - return prim
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/physics_materials_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/physics_materials_cfg.html deleted file mode 100644 index 505596af90..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/physics_materials_cfg.html +++ /dev/null @@ -1,649 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.materials.physics_materials_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.materials.physics_materials_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from collections.abc import Callable
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-from . import physics_materials
-
-
-
[文档]@configclass -class PhysicsMaterialCfg: - """Configuration parameters for creating a physics material. - - Physics material are PhysX schemas that can be applied to a USD material prim to define the - physical properties related to the material. For example, the friction coefficient, restitution - coefficient, etc. For more information on physics material, please refer to the - `PhysX documentation <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/class_px_base_material.html>`_. - """ - - func: Callable = MISSING - """Function to use for creating the material."""
- - -
[文档]@configclass -class RigidBodyMaterialCfg(PhysicsMaterialCfg): - """Physics material parameters for rigid bodies. - - See :meth:`spawn_rigid_body_material` for more information. - - Note: - The default values are the `default values used by PhysX 5 - <https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/rigid-bodies.html#rigid-body-materials>`_. - """ - - func: Callable = physics_materials.spawn_rigid_body_material - - static_friction: float = 0.5 - """The static friction coefficient. Defaults to 0.5.""" - - dynamic_friction: float = 0.5 - """The dynamic friction coefficient. Defaults to 0.5.""" - - restitution: float = 0.0 - """The restitution coefficient. Defaults to 0.0.""" - - improve_patch_friction: bool = True - """Whether to enable patch friction. Defaults to True.""" - - friction_combine_mode: Literal["average", "min", "multiply", "max"] = "average" - """Determines the way friction will be combined during collisions. Defaults to `"average"`. - - .. attention:: - - When two physics materials with different combine modes collide, the combine mode with the higher - priority will be used. The priority order is provided `here - <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/struct_px_combine_mode.html#pxcombinemode>`_. - """ - - restitution_combine_mode: Literal["average", "min", "multiply", "max"] = "average" - """Determines the way restitution coefficient will be combined during collisions. Defaults to `"average"`. - - .. attention:: - - When two physics materials with different combine modes collide, the combine mode with the higher - priority will be used. The priority order is provided `here - <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/struct_px_combine_mode.html#pxcombinemode>`_. - """ - - compliant_contact_stiffness: float = 0.0 - """Spring stiffness for a compliant contact model using implicit springs. Defaults to 0.0. - - A higher stiffness results in behavior closer to a rigid contact. The compliant contact model is only enabled - if the stiffness is larger than 0. - """ - - compliant_contact_damping: float = 0.0 - """Damping coefficient for a compliant contact model using implicit springs. Defaults to 0.0. - - Irrelevant if compliant contacts are disabled when :obj:`compliant_contact_stiffness` is set to zero and - rigid contacts are active. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/visual_materials.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/visual_materials.html deleted file mode 100644 index 5ad4d4a685..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/visual_materials.html +++ /dev/null @@ -1,679 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.materials.visual_materials — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.materials.visual_materials 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import omni.isaac.core.utils.prims as prim_utils
-import omni.kit.commands
-from pxr import Usd
-
-from omni.isaac.lab.sim.utils import clone, safe_set_attribute_on_usd_prim
-from omni.isaac.lab.utils.assets import NVIDIA_NUCLEUS_DIR
-
-if TYPE_CHECKING:
-    from . import visual_materials_cfg
-
-
-
[文档]@clone -def spawn_preview_surface(prim_path: str, cfg: visual_materials_cfg.PreviewSurfaceCfg) -> Usd.Prim: - """Create a preview surface prim and override the settings with the given config. - - A preview surface is a physically-based surface that handles simple shaders while supporting - both *specular* and *metallic* workflows. All color inputs are in linear color space (RGB). - For more information, see the `documentation <https://openusd.org/release/spec_usdpreviewsurface.html>`__. - - The function calls the USD command `CreatePreviewSurfaceMaterialPrim`_ to create the prim. - - .. _CreatePreviewSurfaceMaterialPrim: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.CreatePreviewSurfaceMaterialPrimCommand.html - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn material if it doesn't exist. - if not prim_utils.is_prim_path_valid(prim_path): - omni.kit.commands.execute("CreatePreviewSurfaceMaterialPrim", mtl_path=prim_path, select_new_prim=False) - else: - raise ValueError(f"A prim already exists at path: '{prim_path}'.") - # obtain prim - prim = prim_utils.get_prim_at_path(f"{prim_path}/Shader") - # apply properties - cfg = cfg.to_dict() - del cfg["func"] - for attr_name, attr_value in cfg.items(): - safe_set_attribute_on_usd_prim(prim, f"inputs:{attr_name}", attr_value, camel_case=True) - # return prim - return prim
- - -
[文档]@clone -def spawn_from_mdl_file(prim_path: str, cfg: visual_materials_cfg.MdlMaterialCfg) -> Usd.Prim: - """Load a material from its MDL file and override the settings with the given config. - - NVIDIA's `Material Definition Language (MDL) <https://www.nvidia.com/en-us/design-visualization/technologies/material-definition-language/>`__ - is a language for defining physically-based materials. The MDL file format is a binary format - that can be loaded by Omniverse and other applications such as Adobe Substance Designer. - To learn more about MDL, see the `documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/materials.html>`_. - - The function calls the USD command `CreateMdlMaterialPrim`_ to create the prim. - - .. _CreateMdlMaterialPrim: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.CreateMdlMaterialPrimCommand.html - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn material if it doesn't exist. - if not prim_utils.is_prim_path_valid(prim_path): - # extract material name from path - material_name = cfg.mdl_path.split("/")[-1].split(".")[0] - omni.kit.commands.execute( - "CreateMdlMaterialPrim", - mtl_url=cfg.mdl_path.format(NVIDIA_NUCLEUS_DIR=NVIDIA_NUCLEUS_DIR), - mtl_name=material_name, - mtl_path=prim_path, - select_new_prim=False, - ) - else: - raise ValueError(f"A prim already exists at path: '{prim_path}'.") - # obtain prim - prim = prim_utils.get_prim_at_path(f"{prim_path}/Shader") - # apply properties - cfg = cfg.to_dict() - del cfg["func"] - del cfg["mdl_path"] - for attr_name, attr_value in cfg.items(): - safe_set_attribute_on_usd_prim(prim, f"inputs:{attr_name}", attr_value, camel_case=False) - # return prim - return prim
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/visual_materials_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/visual_materials_cfg.html deleted file mode 100644 index 97e6242874..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/materials/visual_materials_cfg.html +++ /dev/null @@ -1,673 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.materials.visual_materials_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.materials.visual_materials_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from collections.abc import Callable
-from dataclasses import MISSING
-
-from omni.isaac.lab.utils import configclass
-
-from . import visual_materials
-
-
-
[文档]@configclass -class VisualMaterialCfg: - """Configuration parameters for creating a visual material.""" - - func: Callable = MISSING - """The function to use for creating the material."""
- - -
[文档]@configclass -class PreviewSurfaceCfg(VisualMaterialCfg): - """Configuration parameters for creating a preview surface. - - See :meth:`spawn_preview_surface` for more information. - """ - - func: Callable = visual_materials.spawn_preview_surface - - diffuse_color: tuple[float, float, float] = (0.18, 0.18, 0.18) - """The RGB diffusion color. This is the base color of the surface. Defaults to a dark gray.""" - emissive_color: tuple[float, float, float] = (0.0, 0.0, 0.0) - """The RGB emission component of the surface. Defaults to black.""" - roughness: float = 0.5 - """The roughness for specular lobe. Ranges from 0 (smooth) to 1 (rough). Defaults to 0.5.""" - metallic: float = 0.0 - """The metallic component. Ranges from 0 (dielectric) to 1 (metal). Defaults to 0.""" - opacity: float = 1.0 - """The opacity of the surface. Ranges from 0 (transparent) to 1 (opaque). Defaults to 1. - - Note: - Opacity only affects the surface's appearance during interactive rendering. - """
- - -
[文档]@configclass -class MdlFileCfg(VisualMaterialCfg): - """Configuration parameters for loading an MDL material from a file. - - See :meth:`spawn_from_mdl_file` for more information. - """ - - func: Callable = visual_materials.spawn_from_mdl_file - - mdl_path: str = MISSING - """The path to the MDL material. - - NVIDIA Omniverse provides various MDL materials in the NVIDIA Nucleus. - To use these materials, you can set the path of the material in the nucleus directory - using the ``{NVIDIA_NUCLEUS_DIR}`` variable. This is internally resolved to the path of the - NVIDIA Nucleus directory on the host machine through the attribute - :attr:`omni.isaac.lab.utils.assets.NVIDIA_NUCLEUS_DIR`. - - For example, to use the "Aluminum_Anodized" material, you can set the path to: - ``{NVIDIA_NUCLEUS_DIR}/Materials/Base/Metals/Aluminum_Anodized.mdl``. - """ - project_uvw: bool | None = None - """Whether to project the UVW coordinates of the material. Defaults to None. - - If None, then the default setting in the MDL material will be used. - """ - albedo_brightness: float | None = None - """Multiplier for the diffuse color of the material. Defaults to None. - - If None, then the default setting in the MDL material will be used. - """ - texture_scale: tuple[float, float] | None = None - """The scale of the texture. Defaults to None. - - If None, then the default setting in the MDL material will be used. - """
- - -
[文档]@configclass -class GlassMdlCfg(VisualMaterialCfg): - """Configuration parameters for loading a glass MDL material. - - This is a convenience class for loading a glass MDL material. For more information on - glass materials, see the `documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/materials.html#omniglass>`__. - - .. note:: - The default values are taken from the glass material in the NVIDIA Nucleus. - """ - - func: Callable = visual_materials.spawn_from_mdl_file - - mdl_path: str = "OmniGlass.mdl" - """The path to the MDL material. Defaults to the glass material in the NVIDIA Nucleus.""" - glass_color: tuple[float, float, float] = (1.0, 1.0, 1.0) - """The RGB color or tint of the glass. Defaults to white.""" - frosting_roughness: float = 0.0 - """The amount of reflectivity of the surface. Ranges from 0 (perfectly clear) to 1 (frosted). - Defaults to 0.""" - thin_walled: bool = False - """Whether to perform thin-walled refraction. Defaults to False.""" - glass_ior: float = 1.491 - """The incidence of refraction to control how much light is bent when passing through the glass. - Defaults to 1.491, which is the IOR of glass. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/sensors/sensors.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/sensors/sensors.html deleted file mode 100644 index 3c3e209b3f..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/sensors/sensors.html +++ /dev/null @@ -1,692 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.sensors.sensors — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.sensors.sensors 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import omni.isaac.core.utils.prims as prim_utils
-import omni.kit.commands
-from pxr import Sdf, Usd
-
-from omni.isaac.lab.sim.utils import clone
-from omni.isaac.lab.utils import to_camel_case
-
-if TYPE_CHECKING:
-    from . import sensors_cfg
-
-
-CUSTOM_PINHOLE_CAMERA_ATTRIBUTES = {
-    "projection_type": ("cameraProjectionType", Sdf.ValueTypeNames.Token),
-}
-"""Custom attributes for pinhole camera model.
-
-The dictionary maps the attribute name in the configuration to the attribute name in the USD prim.
-"""
-
-
-CUSTOM_FISHEYE_CAMERA_ATTRIBUTES = {
-    "projection_type": ("cameraProjectionType", Sdf.ValueTypeNames.Token),
-    "fisheye_nominal_width": ("fthetaWidth", Sdf.ValueTypeNames.Float),
-    "fisheye_nominal_height": ("fthetaHeight", Sdf.ValueTypeNames.Float),
-    "fisheye_optical_centre_x": ("fthetaCx", Sdf.ValueTypeNames.Float),
-    "fisheye_optical_centre_y": ("fthetaCy", Sdf.ValueTypeNames.Float),
-    "fisheye_max_fov": ("fthetaMaxFov", Sdf.ValueTypeNames.Float),
-    "fisheye_polynomial_a": ("fthetaPolyA", Sdf.ValueTypeNames.Float),
-    "fisheye_polynomial_b": ("fthetaPolyB", Sdf.ValueTypeNames.Float),
-    "fisheye_polynomial_c": ("fthetaPolyC", Sdf.ValueTypeNames.Float),
-    "fisheye_polynomial_d": ("fthetaPolyD", Sdf.ValueTypeNames.Float),
-    "fisheye_polynomial_e": ("fthetaPolyE", Sdf.ValueTypeNames.Float),
-    "fisheye_polynomial_f": ("fthetaPolyF", Sdf.ValueTypeNames.Float),
-}
-"""Custom attributes for fisheye camera model.
-
-The dictionary maps the attribute name in the configuration to the attribute name in the USD prim.
-"""
-
-
-
[文档]@clone -def spawn_camera( - prim_path: str, - cfg: sensors_cfg.PinholeCameraCfg | sensors_cfg.FisheyeCameraCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a USD camera prim with given projection type. - - The function creates various attributes on the camera prim that specify the camera's properties. - These are later used by ``omni.replicator.core`` to render the scene with the given camera. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn camera if it doesn't exist. - if not prim_utils.is_prim_path_valid(prim_path): - prim_utils.create_prim(prim_path, "Camera", translation=translation, orientation=orientation) - else: - raise ValueError(f"A prim already exists at path: '{prim_path}'.") - - # lock camera from viewport (this disables viewport movement for camera) - if cfg.lock_camera: - omni.kit.commands.execute( - "ChangePropertyCommand", - prop_path=Sdf.Path(f"{prim_path}.omni:kit:cameraLock"), - value=True, - prev=None, - type_to_create_if_not_exist=Sdf.ValueTypeNames.Bool, - ) - # decide the custom attributes to add - if cfg.projection_type == "pinhole": - attribute_types = CUSTOM_PINHOLE_CAMERA_ATTRIBUTES - else: - attribute_types = CUSTOM_FISHEYE_CAMERA_ATTRIBUTES - # custom attributes in the config that are not USD Camera parameters - non_usd_cfg_param_names = ["func", "copy_from_source", "lock_camera", "visible", "semantic_tags"] - - # get camera prim - prim = prim_utils.get_prim_at_path(prim_path) - # create attributes for the fisheye camera model - # note: for pinhole those are already part of the USD camera prim - for attr_name, attr_type in attribute_types.values(): - # check if attribute does not exist - if prim.GetAttribute(attr_name).Get() is None: - # create attribute based on type - prim.CreateAttribute(attr_name, attr_type) - # set attribute values - for param_name, param_value in cfg.__dict__.items(): - # check if value is valid - if param_value is None or param_name in non_usd_cfg_param_names: - continue - # obtain prim property name - if param_name in attribute_types: - # check custom attributes - prim_prop_name = attribute_types[param_name][0] - else: - # convert attribute name in prim to cfg name - prim_prop_name = to_camel_case(param_name, to="cC") - # get attribute from the class - prim.GetAttribute(prim_prop_name).Set(param_value) - # return the prim - return prim_utils.get_prim_at_path(prim_path)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/sensors/sensors_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/sensors/sensors_cfg.html deleted file mode 100644 index 6ef79994df..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/sensors/sensors_cfg.html +++ /dev/null @@ -1,688 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.sensors.sensors_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.sensors.sensors_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from collections.abc import Callable
-from typing import Literal
-
-from omni.isaac.lab.sim.spawners.spawner_cfg import SpawnerCfg
-from omni.isaac.lab.utils import configclass
-
-from . import sensors
-
-
-
[文档]@configclass -class PinholeCameraCfg(SpawnerCfg): - """Configuration parameters for a USD camera prim with pinhole camera settings. - - For more information on the parameters, please refer to the `camera documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/cameras.html>`__. - - .. note:: - The default values are taken from the `Replicator camera <https://docs.omniverse.nvidia.com/py/replicator/1.9.8/source/extensions/omni.replicator.core/docs/API.html#omni.replicator.core.create.camera>`__ - function. - """ - - func: Callable = sensors.spawn_camera - - projection_type: str = "pinhole" - """Type of projection to use for the camera. Defaults to "pinhole". - - Note: - Currently only "pinhole" is supported. - """ - clipping_range: tuple[float, float] = (0.01, 1e6) - """Near and far clipping distances (in m). Defaults to (0.01, 1e6). - - The minimum clipping range will shift the camera forward by the specified distance. Don't set it too high to - avoid issues for distance related data types (e.g., ``distance_to_image_plane``). - """ - focal_length: float = 24.0 - """Perspective focal length (in cm). Defaults to 24.0cm. - - Longer lens lengths narrower FOV, shorter lens lengths wider FOV. - """ - focus_distance: float = 400.0 - """Distance from the camera to the focus plane (in m). Defaults to 400.0. - - The distance at which perfect sharpness is achieved. - """ - f_stop: float = 0.0 - """Lens aperture. Defaults to 0.0, which turns off focusing. - - Controls Distance Blurring. Lower Numbers decrease focus range, larger numbers increase it. - """ - horizontal_aperture: float = 20.955 - """Horizontal aperture (in mm). Defaults to 20.955mm. - - Emulates sensor/film width on a camera. - - Note: - The default value is the horizontal aperture of a 35 mm spherical projector. - """ - horizontal_aperture_offset: float = 0.0 - """Offsets Resolution/Film gate horizontally. Defaults to 0.0.""" - vertical_aperture_offset: float = 0.0 - """Offsets Resolution/Film gate vertically. Defaults to 0.0.""" - lock_camera: bool = True - """Locks the camera in the Omniverse viewport. Defaults to True. - - If True, then the camera remains fixed at its configured transform. This is useful when wanting to view - the camera output on the GUI and not accidentally moving the camera through the GUI interactions. - """
- - -
[文档]@configclass -class FisheyeCameraCfg(PinholeCameraCfg): - """Configuration parameters for a USD camera prim with `fish-eye camera`_ settings. - - For more information on the parameters, please refer to the - `camera documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/cameras.html#fisheye-properties>`__. - - .. note:: - The default values are taken from the `Replicator camera <https://docs.omniverse.nvidia.com/py/replicator/1.9.8/source/extensions/omni.replicator.core/docs/API.html#omni.replicator.core.create.camera>`__ - function. - - .. _fish-eye camera: https://en.wikipedia.org/wiki/Fisheye_lens - """ - - func: Callable = sensors.spawn_camera - - projection_type: Literal[ - "fisheye_orthographic", "fisheye_equidistant", "fisheye_equisolid", "fisheye_polynomial", "fisheye_spherical" - ] = "fisheye_polynomial" - r"""Type of projection to use for the camera. Defaults to "fisheye_polynomial". - - Available options: - - - ``"fisheye_orthographic"``: Fisheye camera model using orthographic correction. - - ``"fisheye_equidistant"``: Fisheye camera model using equidistant correction. - - ``"fisheye_equisolid"``: Fisheye camera model using equisolid correction. - - ``"fisheye_polynomial"``: Fisheye camera model with :math:`360^{\circ}` spherical projection. - - ``"fisheye_spherical"``: Fisheye camera model with :math:`360^{\circ}` full-frame projection. - """ - fisheye_nominal_width: float = 1936.0 - """Nominal width of fisheye lens model (in pixels). Defaults to 1936.0.""" - fisheye_nominal_height: float = 1216.0 - """Nominal height of fisheye lens model (in pixels). Defaults to 1216.0.""" - fisheye_optical_centre_x: float = 970.94244 - """Horizontal optical centre position of fisheye lens model (in pixels). Defaults to 970.94244.""" - fisheye_optical_centre_y: float = 600.37482 - """Vertical optical centre position of fisheye lens model (in pixels). Defaults to 600.37482.""" - fisheye_max_fov: float = 200.0 - """Maximum field of view of fisheye lens model (in degrees). Defaults to 200.0 degrees.""" - fisheye_polynomial_a: float = 0.0 - """First component of fisheye polynomial. Defaults to 0.0.""" - fisheye_polynomial_b: float = 0.00245 - """Second component of fisheye polynomial. Defaults to 0.00245.""" - fisheye_polynomial_c: float = 0.0 - """Third component of fisheye polynomial. Defaults to 0.0.""" - fisheye_polynomial_d: float = 0.0 - """Fourth component of fisheye polynomial. Defaults to 0.0.""" - fisheye_polynomial_e: float = 0.0 - """Fifth component of fisheye polynomial. Defaults to 0.0.""" - fisheye_polynomial_f: float = 0.0 - """Sixth component of fisheye polynomial. Defaults to 0.0."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/shapes/shapes.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/shapes/shapes.html deleted file mode 100644 index 1d45621856..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/shapes/shapes.html +++ /dev/null @@ -1,864 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.shapes.shapes — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.shapes.shapes 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import omni.isaac.core.utils.prims as prim_utils
-from pxr import Usd
-
-from omni.isaac.lab.sim import schemas
-from omni.isaac.lab.sim.utils import bind_physics_material, bind_visual_material, clone
-
-if TYPE_CHECKING:
-    from . import shapes_cfg
-
-
-
[文档]@clone -def spawn_sphere( - prim_path: str, - cfg: shapes_cfg.SphereCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a USDGeom-based sphere prim with the given attributes. - - For more information, see `USDGeomSphere <https://openusd.org/dev/api/class_usd_geom_sphere.html>`_. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn sphere if it doesn't exist. - attributes = {"radius": cfg.radius} - _spawn_geom_from_prim_type(prim_path, cfg, "Sphere", attributes, translation, orientation) - # return the prim - return prim_utils.get_prim_at_path(prim_path)
- - -
[文档]@clone -def spawn_cuboid( - prim_path: str, - cfg: shapes_cfg.CuboidCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a USDGeom-based cuboid prim with the given attributes. - - For more information, see `USDGeomCube <https://openusd.org/dev/api/class_usd_geom_cube.html>`_. - - Note: - Since USD only supports cubes, we set the size of the cube to the minimum of the given size and - scale the cube accordingly. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - - Returns: - The created prim. - - Raises: - If a prim already exists at the given path. - """ - # resolve the scale - size = min(cfg.size) - scale = [dim / size for dim in cfg.size] - # spawn cuboid if it doesn't exist. - attributes = {"size": size} - _spawn_geom_from_prim_type(prim_path, cfg, "Cube", attributes, translation, orientation, scale) - # return the prim - return prim_utils.get_prim_at_path(prim_path)
- - -
[文档]@clone -def spawn_cylinder( - prim_path: str, - cfg: shapes_cfg.CylinderCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a USDGeom-based cylinder prim with the given attributes. - - For more information, see `USDGeomCylinder <https://openusd.org/dev/api/class_usd_geom_cylinder.html>`_. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn cylinder if it doesn't exist. - attributes = {"radius": cfg.radius, "height": cfg.height, "axis": cfg.axis.upper()} - _spawn_geom_from_prim_type(prim_path, cfg, "Cylinder", attributes, translation, orientation) - # return the prim - return prim_utils.get_prim_at_path(prim_path)
- - -
[文档]@clone -def spawn_capsule( - prim_path: str, - cfg: shapes_cfg.CapsuleCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a USDGeom-based capsule prim with the given attributes. - - For more information, see `USDGeomCapsule <https://openusd.org/dev/api/class_usd_geom_capsule.html>`_. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn capsule if it doesn't exist. - attributes = {"radius": cfg.radius, "height": cfg.height, "axis": cfg.axis.upper()} - _spawn_geom_from_prim_type(prim_path, cfg, "Capsule", attributes, translation, orientation) - # return the prim - return prim_utils.get_prim_at_path(prim_path)
- - -
[文档]@clone -def spawn_cone( - prim_path: str, - cfg: shapes_cfg.ConeCfg, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, -) -> Usd.Prim: - """Create a USDGeom-based cone prim with the given attributes. - - For more information, see `USDGeomCone <https://openusd.org/dev/api/class_usd_geom_cone.html>`_. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration instance. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - - Returns: - The created prim. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn cone if it doesn't exist. - attributes = {"radius": cfg.radius, "height": cfg.height, "axis": cfg.axis.upper()} - _spawn_geom_from_prim_type(prim_path, cfg, "Cone", attributes, translation, orientation) - # return the prim - return prim_utils.get_prim_at_path(prim_path)
- - -""" -Helper functions. -""" - - -def _spawn_geom_from_prim_type( - prim_path: str, - cfg: shapes_cfg.ShapeCfg, - prim_type: str, - attributes: dict, - translation: tuple[float, float, float] | None = None, - orientation: tuple[float, float, float, float] | None = None, - scale: tuple[float, float, float] | None = None, -): - """Create a USDGeom-based prim with the given attributes. - - To make the asset instanceable, we must follow a certain structure dictated by how USD scene-graph - instancing and physics work. The rigid body component must be added to each instance and not the - referenced asset (i.e. the prototype prim itself). This is because the rigid body component defines - properties that are specific to each instance and cannot be shared under the referenced asset. For - more information, please check the `documentation <https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/rigid-bodies.html#instancing-rigid-bodies>`_. - - Due to the above, we follow the following structure: - - * ``{prim_path}`` - The root prim that is an Xform with the rigid body and mass APIs if configured. - * ``{prim_path}/geometry`` - The prim that contains the mesh and optionally the materials if configured. - If instancing is enabled, this prim will be an instanceable reference to the prototype prim. - - Args: - prim_path: The prim path to spawn the asset at. - cfg: The config containing the properties to apply. - prim_type: The type of prim to create. - attributes: The attributes to apply to the prim. - translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case - this is set to the origin. - orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None, - in which case this is set to identity. - scale: The scale to apply to the prim. Defaults to None, in which case this is set to identity. - - Raises: - ValueError: If a prim already exists at the given path. - """ - # spawn geometry if it doesn't exist. - if not prim_utils.is_prim_path_valid(prim_path): - prim_utils.create_prim(prim_path, prim_type="Xform", translation=translation, orientation=orientation) - else: - raise ValueError(f"A prim already exists at path: '{prim_path}'.") - - # create all the paths we need for clarity - geom_prim_path = prim_path + "/geometry" - mesh_prim_path = geom_prim_path + "/mesh" - - # create the geometry prim - prim_utils.create_prim(mesh_prim_path, prim_type, scale=scale, attributes=attributes) - # apply collision properties - if cfg.collision_props is not None: - schemas.define_collision_properties(mesh_prim_path, cfg.collision_props) - # apply visual material - if cfg.visual_material is not None: - if not cfg.visual_material_path.startswith("/"): - material_path = f"{geom_prim_path}/{cfg.visual_material_path}" - else: - material_path = cfg.visual_material_path - # create material - cfg.visual_material.func(material_path, cfg.visual_material) - # apply material - bind_visual_material(mesh_prim_path, material_path) - # apply physics material - if cfg.physics_material is not None: - if not cfg.physics_material_path.startswith("/"): - material_path = f"{geom_prim_path}/{cfg.physics_material_path}" - else: - material_path = cfg.physics_material_path - # create material - cfg.physics_material.func(material_path, cfg.physics_material) - # apply material - bind_physics_material(mesh_prim_path, material_path) - - # note: we apply rigid properties in the end to later make the instanceable prim - # apply mass properties - if cfg.mass_props is not None: - schemas.define_mass_properties(prim_path, cfg.mass_props) - # apply rigid body properties - if cfg.rigid_props is not None: - schemas.define_rigid_body_properties(prim_path, cfg.rigid_props) -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/shapes/shapes_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/shapes/shapes_cfg.html deleted file mode 100644 index 27ca20703d..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/shapes/shapes_cfg.html +++ /dev/null @@ -1,685 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.shapes.shapes_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.shapes.shapes_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from collections.abc import Callable
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.sim.spawners import materials
-from omni.isaac.lab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg
-from omni.isaac.lab.utils import configclass
-
-from . import shapes
-
-
-
[文档]@configclass -class ShapeCfg(RigidObjectSpawnerCfg): - """Configuration parameters for a USD Geometry or Geom prim.""" - - visual_material_path: str = "material" - """Path to the visual material to use for the prim. Defaults to "material". - - If the path is relative, then it will be relative to the prim's path. - This parameter is ignored if `visual_material` is not None. - """ - visual_material: materials.VisualMaterialCfg | None = None - """Visual material properties. - - Note: - If None, then no visual material will be added. - """ - - physics_material_path: str = "material" - """Path to the physics material to use for the prim. Defaults to "material". - - If the path is relative, then it will be relative to the prim's path. - This parameter is ignored if `physics_material` is not None. - """ - physics_material: materials.PhysicsMaterialCfg | None = None - """Physics material properties. - - Note: - If None, then no physics material will be added. - """
- - -
[文档]@configclass -class SphereCfg(ShapeCfg): - """Configuration parameters for a sphere prim. - - See :meth:`spawn_sphere` for more information. - """ - - func: Callable = shapes.spawn_sphere - - radius: float = MISSING - """Radius of the sphere (in m)."""
- - -
[文档]@configclass -class CuboidCfg(ShapeCfg): - """Configuration parameters for a cuboid prim. - - See :meth:`spawn_cuboid` for more information. - """ - - func: Callable = shapes.spawn_cuboid - - size: tuple[float, float, float] = MISSING - """Size of the cuboid."""
- - -
[文档]@configclass -class CylinderCfg(ShapeCfg): - """Configuration parameters for a cylinder prim. - - See :meth:`spawn_cylinder` for more information. - """ - - func: Callable = shapes.spawn_cylinder - - radius: float = MISSING - """Radius of the cylinder (in m).""" - height: float = MISSING - """Height of the cylinder (in m).""" - axis: Literal["X", "Y", "Z"] = "Z" - """Axis of the cylinder. Defaults to "Z"."""
- - -
[文档]@configclass -class CapsuleCfg(ShapeCfg): - """Configuration parameters for a capsule prim. - - See :meth:`spawn_capsule` for more information. - """ - - func: Callable = shapes.spawn_capsule - - radius: float = MISSING - """Radius of the capsule (in m).""" - height: float = MISSING - """Height of the capsule (in m).""" - axis: Literal["X", "Y", "Z"] = "Z" - """Axis of the capsule. Defaults to "Z"."""
- - -
[文档]@configclass -class ConeCfg(ShapeCfg): - """Configuration parameters for a cone prim. - - See :meth:`spawn_cone` for more information. - """ - - func: Callable = shapes.spawn_cone - - radius: float = MISSING - """Radius of the cone (in m).""" - height: float = MISSING - """Height of the v (in m).""" - axis: Literal["X", "Y", "Z"] = "Z" - """Axis of the cone. Defaults to "Z"."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/spawner_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/spawner_cfg.html deleted file mode 100644 index 64fe634eb5..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/spawners/spawner_cfg.html +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.spawners.spawner_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.spawners.spawner_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from collections.abc import Callable
-from dataclasses import MISSING
-
-from pxr import Usd
-
-from omni.isaac.lab.sim import schemas
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class SpawnerCfg: - """Configuration parameters for spawning an asset. - - Spawning an asset is done by calling the :attr:`func` function. The function takes in the - prim path to spawn the asset at, the configuration instance and transformation, and returns the - prim path of the spawned asset. - - The function is typically decorated with :func:`omni.isaac.lab.sim.spawner.utils.clone` decorator - that checks if input prim path is a regex expression and spawns the asset at all matching prims. - For this, the decorator uses the Cloner API from Isaac Sim and handles the :attr:`copy_from_source` - parameter. - """ - - func: Callable[..., Usd.Prim] = MISSING - """Function to use for spawning the asset. - - The function takes in the prim path (or expression) to spawn the asset at, the configuration instance - and transformation, and returns the source prim spawned. - """ - - visible: bool = True - """Whether the spawned asset should be visible. Defaults to True.""" - - semantic_tags: list[tuple[str, str]] | None = None - """List of semantic tags to add to the spawned asset. Defaults to None, - which means no semantic tags will be added. - - The semantic tags follow the `Replicator Semantic` tagging system. Each tag is a tuple of the - form ``(type, data)``, where ``type`` is the type of the tag and ``data`` is the semantic label - associated with the tag. For example, to annotate a spawned asset in the class avocado, the semantic - tag would be ``[("class", "avocado")]``. - - You can specify multiple semantic tags by passing in a list of tags. For example, to annotate a - spawned asset in the class avocado and the color green, the semantic tags would be - ``[("class", "avocado"), ("color", "green")]``. - - .. seealso:: - - For more information on the semantics filter, see the documentation for the `semantics schema editor`_. - - .. _semantics schema editor: https://docs.omniverse.nvidia.com/extensions/latest/ext_replicator/semantics_schema_editor.html#semantics-filtering - - """ - - copy_from_source: bool = True - """Whether to copy the asset from the source prim or inherit it. Defaults to True. - - This parameter is only used when cloning prims. If False, then the asset will be inherited from - the source prim, i.e. all USD changes to the source prim will be reflected in the cloned prims. - - .. versionadded:: 2023.1 - - This parameter is only supported from Isaac Sim 2023.1 onwards. If you are using an older - version of Isaac Sim, this parameter will be ignored. - """
- - -
[文档]@configclass -class RigidObjectSpawnerCfg(SpawnerCfg): - """Configuration parameters for spawning a rigid asset. - - Note: - By default, all properties are set to None. This means that no properties will be added or modified - to the prim outside of the properties available by default when spawning the prim. - """ - - mass_props: schemas.MassPropertiesCfg | None = None - """Mass properties.""" - - rigid_props: schemas.RigidBodyPropertiesCfg | None = None - """Rigid body properties. - - For making a rigid object static, set the :attr:`schemas.RigidBodyPropertiesCfg.kinematic_enabled` - as True. This will make the object static and will not be affected by gravity or other forces. - """ - - collision_props: schemas.CollisionPropertiesCfg | None = None - """Properties to apply to all collision meshes.""" - - activate_contact_sensors: bool = False - """Activate contact reporting on all rigid bodies. Defaults to False. - - This adds the PhysxContactReporter API to all the rigid bodies in the given prim path and its children. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/sim/utils.html b/docs/_build/html/_modules/omni/isaac/lab/sim/utils.html deleted file mode 100644 index 0518859dfe..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/sim/utils.html +++ /dev/null @@ -1,1421 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.sim.utils — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.sim.utils 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module with USD-related utilities."""
-
-from __future__ import annotations
-
-import functools
-import inspect
-import re
-from collections.abc import Callable
-from typing import TYPE_CHECKING, Any
-
-import carb
-import omni.isaac.core.utils.stage as stage_utils
-import omni.kit.commands
-from omni.isaac.cloner import Cloner
-from pxr import PhysxSchema, Sdf, Semantics, Usd, UsdGeom, UsdPhysics, UsdShade
-
-from omni.isaac.lab.utils.string import to_camel_case
-
-from . import schemas
-
-if TYPE_CHECKING:
-    from .spawners.spawner_cfg import SpawnerCfg
-
-"""
-Attribute - Setters.
-"""
-
-
-
[文档]def safe_set_attribute_on_usd_schema(schema_api: Usd.APISchemaBase, name: str, value: Any, camel_case: bool): - """Set the value of an attribute on its USD schema if it exists. - - A USD API schema serves as an interface or API for authoring and extracting a set of attributes. - They typically derive from the :class:`pxr.Usd.SchemaBase` class. This function checks if the - attribute exists on the schema and sets the value of the attribute if it exists. - - Args: - schema_api: The USD schema to set the attribute on. - name: The name of the attribute. - value: The value to set the attribute to. - camel_case: Whether to convert the attribute name to camel case. - - Raises: - TypeError: When the input attribute name does not exist on the provided schema API. - """ - # if value is None, do nothing - if value is None: - return - # convert attribute name to camel case - if camel_case: - attr_name = to_camel_case(name, to="CC") - else: - attr_name = name - # retrieve the attribute - # reference: https://openusd.org/dev/api/_usd__page__common_idioms.html#Usd_Create_Or_Get_Property - attr = getattr(schema_api, f"Create{attr_name}Attr", None) - # check if attribute exists - if attr is not None: - attr().Set(value) - else: - # think: do we ever need to create the attribute if it doesn't exist? - # currently, we are not doing this since the schemas are already created with some defaults. - carb.log_error(f"Attribute '{attr_name}' does not exist on prim '{schema_api.GetPath()}'.") - raise TypeError(f"Attribute '{attr_name}' does not exist on prim '{schema_api.GetPath()}'.")
- - -
[文档]def safe_set_attribute_on_usd_prim(prim: Usd.Prim, attr_name: str, value: Any, camel_case: bool): - """Set the value of a attribute on its USD prim. - - The function creates a new attribute if it does not exist on the prim. This is because in some cases (such - as with shaders), their attributes are not exposed as USD prim properties that can be altered. This function - allows us to set the value of the attributes in these cases. - - Args: - prim: The USD prim to set the attribute on. - attr_name: The name of the attribute. - value: The value to set the attribute to. - camel_case: Whether to convert the attribute name to camel case. - """ - # if value is None, do nothing - if value is None: - return - # convert attribute name to camel case - if camel_case: - attr_name = to_camel_case(attr_name, to="cC") - # resolve sdf type based on value - if isinstance(value, bool): - sdf_type = Sdf.ValueTypeNames.Bool - elif isinstance(value, int): - sdf_type = Sdf.ValueTypeNames.Int - elif isinstance(value, float): - sdf_type = Sdf.ValueTypeNames.Float - elif isinstance(value, (tuple, list)) and len(value) == 3 and any(isinstance(v, float) for v in value): - sdf_type = Sdf.ValueTypeNames.Float3 - elif isinstance(value, (tuple, list)) and len(value) == 2 and any(isinstance(v, float) for v in value): - sdf_type = Sdf.ValueTypeNames.Float2 - else: - raise NotImplementedError( - f"Cannot set attribute '{attr_name}' with value '{value}'. Please modify the code to support this type." - ) - # change property - omni.kit.commands.execute( - "ChangePropertyCommand", - prop_path=Sdf.Path(f"{prim.GetPath()}.{attr_name}"), - value=value, - prev=None, - type_to_create_if_not_exist=sdf_type, - usd_context_name=prim.GetStage(), - )
- - -""" -Decorators. -""" - - -
[文档]def apply_nested(func: Callable) -> Callable: - """Decorator to apply a function to all prims under a specified prim-path. - - The function iterates over the provided prim path and all its children to apply input function - to all prims under the specified prim path. - - If the function succeeds to apply to a prim, it will not look at the children of that prim. - This is based on the physics behavior that nested schemas are not allowed. For example, a parent prim - and its child prim cannot both have a rigid-body schema applied on them, or it is not possible to - have nested articulations. - - While traversing the prims under the specified prim path, the function will throw a warning if it - does not succeed to apply the function to any prim. This is because the user may have intended to - apply the function to a prim that does not have valid attributes, or the prim may be an instanced prim. - - Args: - func: The function to apply to all prims under a specified prim-path. The function - must take the prim-path and other arguments. It should return a boolean indicating whether - the function succeeded or not. - - Returns: - The wrapped function that applies the function to all prims under a specified prim-path. - - Raises: - ValueError: If the prim-path does not exist on the stage. - """ - - @functools.wraps(func) - def wrapper(prim_path: str | Sdf.Path, *args, **kwargs): - # map args and kwargs to function signature so we can get the stage - # note: we do this to check if stage is given in arg or kwarg - sig = inspect.signature(func) - bound_args = sig.bind(prim_path, *args, **kwargs) - # get current stage - stage = bound_args.arguments.get("stage") - if stage is None: - stage = stage_utils.get_current_stage() - # get USD prim - prim: Usd.Prim = stage.GetPrimAtPath(prim_path) - # check if prim is valid - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim_path}' is not valid.") - # add iterable to check if property was applied on any of the prims - count_success = 0 - instanced_prim_paths = [] - # iterate over all prims under prim-path - all_prims = [prim] - while len(all_prims) > 0: - # get current prim - child_prim = all_prims.pop(0) - child_prim_path = child_prim.GetPath().pathString # type: ignore - # check if prim is a prototype - if child_prim.IsInstance(): - instanced_prim_paths.append(child_prim_path) - continue - # set properties - success = func(child_prim_path, *args, **kwargs) - # if successful, do not look at children - # this is based on the physics behavior that nested schemas are not allowed - if not success: - all_prims += child_prim.GetChildren() - else: - count_success += 1 - # check if we were successful in applying the function to any prim - if count_success == 0: - carb.log_warn( - f"Could not perform '{func.__name__}' on any prims under: '{prim_path}'." - " This might be because of the following reasons:" - "\n\t(1) The desired attribute does not exist on any of the prims." - "\n\t(2) The desired attribute exists on an instanced prim." - f"\n\t\tDiscovered list of instanced prim paths: {instanced_prim_paths}" - ) - - return wrapper
- - -
[文档]def clone(func: Callable) -> Callable: - """Decorator for cloning a prim based on matching prim paths of the prim's parent. - - The decorator checks if the parent prim path matches any prim paths in the stage. If so, it clones the - spawned prim at each matching prim path. For example, if the input prim path is: ``/World/Table_[0-9]/Bottle``, - the decorator will clone the prim at each matching prim path of the parent prim: ``/World/Table_0/Bottle``, - ``/World/Table_1/Bottle``, etc. - - Note: - For matching prim paths, the decorator assumes that valid prims exist for all matching prim paths. - In case no matching prim paths are found, the decorator raises a ``RuntimeError``. - - Args: - func: The function to decorate. - - Returns: - The decorated function that spawns the prim and clones it at each matching prim path. - It returns the spawned source prim, i.e., the first prim in the list of matching prim paths. - """ - - @functools.wraps(func) - def wrapper(prim_path: str | Sdf.Path, cfg: SpawnerCfg, *args, **kwargs): - # cast prim_path to str type in case its an Sdf.Path - prim_path = str(prim_path) - # check prim path is global - if not prim_path.startswith("/"): - raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.") - # resolve: {SPAWN_NS}/AssetName - # note: this assumes that the spawn namespace already exists in the stage - root_path, asset_path = prim_path.rsplit("/", 1) - # check if input is a regex expression - # note: a valid prim path can only contain alphanumeric characters, underscores, and forward slashes - is_regex_expression = re.match(r"^[a-zA-Z0-9/_]+$", root_path) is None - - # resolve matching prims for source prim path expression - if is_regex_expression and root_path != "": - source_prim_paths = find_matching_prim_paths(root_path) - # if no matching prims are found, raise an error - if len(source_prim_paths) == 0: - raise RuntimeError( - f"Unable to find source prim path: '{root_path}'. Please create the prim before spawning." - ) - else: - source_prim_paths = [root_path] - - # resolve prim paths for spawning and cloning - prim_paths = [f"{source_prim_path}/{asset_path}" for source_prim_path in source_prim_paths] - # spawn single instance - prim = func(prim_paths[0], cfg, *args, **kwargs) - # set the prim visibility - if hasattr(cfg, "visible"): - imageable = UsdGeom.Imageable(prim) - if cfg.visible: - imageable.MakeVisible() - else: - imageable.MakeInvisible() - # set the semantic annotations - if hasattr(cfg, "semantic_tags") and cfg.semantic_tags is not None: - # note: taken from replicator scripts.utils.utils.py - for semantic_type, semantic_value in cfg.semantic_tags: - # deal with spaces by replacing them with underscores - semantic_type_sanitized = semantic_type.replace(" ", "_") - semantic_value_sanitized = semantic_value.replace(" ", "_") - # set the semantic API for the instance - instance_name = f"{semantic_type_sanitized}_{semantic_value_sanitized}" - sem = Semantics.SemanticsAPI.Apply(prim, instance_name) - # create semantic type and data attributes - sem.CreateSemanticTypeAttr() - sem.CreateSemanticDataAttr() - sem.GetSemanticTypeAttr().Set(semantic_type) - sem.GetSemanticDataAttr().Set(semantic_value) - # activate rigid body contact sensors - if hasattr(cfg, "activate_contact_sensors") and cfg.activate_contact_sensors: - schemas.activate_contact_sensors(prim_paths[0], cfg.activate_contact_sensors) - # clone asset using cloner API - if len(prim_paths) > 1: - cloner = Cloner() - # clone the prim - cloner.clone(prim_paths[0], prim_paths[1:], replicate_physics=False, copy_from_source=cfg.copy_from_source) - # return the source prim - return prim - - return wrapper
- - -""" -Material bindings. -""" - - -
[文档]@apply_nested -def bind_visual_material( - prim_path: str | Sdf.Path, - material_path: str | Sdf.Path, - stage: Usd.Stage | None = None, - stronger_than_descendants: bool = True, -): - """Bind a visual material to a prim. - - This function is a wrapper around the USD command `BindMaterialCommand`_. - - .. note:: - The function is decorated with :meth:`apply_nested` to allow applying the function to a prim path - and all its descendants. - - .. _BindMaterialCommand: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.BindMaterialCommand.html - - Args: - prim_path: The prim path where to apply the material. - material_path: The prim path of the material to apply. - stage: The stage where the prim and material exist. - Defaults to None, in which case the current stage is used. - stronger_than_descendants: Whether the material should override the material of its descendants. - Defaults to True. - - Raises: - ValueError: If the provided prim paths do not exist on stage. - """ - # resolve stage - if stage is None: - stage = stage_utils.get_current_stage() - # check if prim and material exists - if not stage.GetPrimAtPath(prim_path).IsValid(): - raise ValueError(f"Target prim '{material_path}' does not exist.") - if not stage.GetPrimAtPath(material_path).IsValid(): - raise ValueError(f"Visual material '{material_path}' does not exist.") - - # resolve token for weaker than descendants - if stronger_than_descendants: - binding_strength = "strongerThanDescendants" - else: - binding_strength = "weakerThanDescendants" - # obtain material binding API - # note: we prefer using the command here as it is more robust than the USD API - success, _ = omni.kit.commands.execute( - "BindMaterialCommand", - prim_path=prim_path, - material_path=material_path, - strength=binding_strength, - stage=stage, - ) - # return success - return success
- - -
[文档]@apply_nested -def bind_physics_material( - prim_path: str | Sdf.Path, - material_path: str | Sdf.Path, - stage: Usd.Stage | None = None, - stronger_than_descendants: bool = True, -): - """Bind a physics material to a prim. - - `Physics material`_ can be applied only to a prim with physics-enabled on them. This includes having - collision APIs, or deformable body APIs, or being a particle system. In case the prim does not have - any of these APIs, the function will not apply the material and return False. - - .. note:: - The function is decorated with :meth:`apply_nested` to allow applying the function to a prim path - and all its descendants. - - .. _Physics material: https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/simulation-control/physics-settings.html#physics-materials - - Args: - prim_path: The prim path where to apply the material. - material_path: The prim path of the material to apply. - stage: The stage where the prim and material exist. - Defaults to None, in which case the current stage is used. - stronger_than_descendants: Whether the material should override the material of its descendants. - Defaults to True. - - Raises: - ValueError: If the provided prim paths do not exist on stage. - """ - # resolve stage - if stage is None: - stage = stage_utils.get_current_stage() - # check if prim and material exists - if not stage.GetPrimAtPath(prim_path).IsValid(): - raise ValueError(f"Target prim '{material_path}' does not exist.") - if not stage.GetPrimAtPath(material_path).IsValid(): - raise ValueError(f"Physics material '{material_path}' does not exist.") - # get USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim has collision applied on it - has_physics_scene_api = prim.HasAPI(PhysxSchema.PhysxSceneAPI) - has_collider = prim.HasAPI(UsdPhysics.CollisionAPI) - has_deformable_body = prim.HasAPI(PhysxSchema.PhysxDeformableBodyAPI) - has_particle_system = prim.IsA(PhysxSchema.PhysxParticleSystem) - if not (has_physics_scene_api or has_collider or has_deformable_body or has_particle_system): - carb.log_verbose( - f"Cannot apply physics material '{material_path}' on prim '{prim_path}'. It is neither a" - " PhysX scene, collider, a deformable body, nor a particle system." - ) - return False - - # obtain material binding API - if prim.HasAPI(UsdShade.MaterialBindingAPI): - material_binding_api = UsdShade.MaterialBindingAPI(prim) - else: - material_binding_api = UsdShade.MaterialBindingAPI.Apply(prim) - # obtain the material prim - material = UsdShade.Material(stage.GetPrimAtPath(material_path)) - # resolve token for weaker than descendants - if stronger_than_descendants: - binding_strength = UsdShade.Tokens.strongerThanDescendants - else: - binding_strength = UsdShade.Tokens.weakerThanDescendants - # apply the material - material_binding_api.Bind(material, bindingStrength=binding_strength, materialPurpose="physics") # type: ignore - # return success - return True
- - -""" -Exporting. -""" - - -
[文档]def export_prim_to_file( - path: str | Sdf.Path, - source_prim_path: str | Sdf.Path, - target_prim_path: str | Sdf.Path | None = None, - stage: Usd.Stage | None = None, -): - """Exports a prim from a given stage to a USD file. - - The function creates a new layer at the provided path and copies the prim to the layer. - It sets the copied prim as the default prim in the target layer. Additionally, it updates - the stage up-axis and meters-per-unit to match the current stage. - - Args: - path: The filepath path to export the prim to. - source_prim_path: The prim path to export. - target_prim_path: The prim path to set as the default prim in the target layer. - Defaults to None, in which case the source prim path is used. - stage: The stage where the prim exists. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: If the prim paths are not global (i.e: do not start with '/'). - """ - # automatically casting to str in case args - # are path types - path = str(path) - source_prim_path = str(source_prim_path) - if target_prim_path is not None: - target_prim_path = str(target_prim_path) - - if not source_prim_path.startswith("/"): - raise ValueError(f"Source prim path '{source_prim_path}' is not global. It must start with '/'.") - if target_prim_path is not None and not target_prim_path.startswith("/"): - raise ValueError(f"Target prim path '{target_prim_path}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage: Usd.Stage = omni.usd.get_context().get_stage() - # get root layer - source_layer = stage.GetRootLayer() - - # only create a new layer if it doesn't exist already - target_layer = Sdf.Find(path) - if target_layer is None: - target_layer = Sdf.Layer.CreateNew(path) - # open the target stage - target_stage = Usd.Stage.Open(target_layer) - - # update stage data - UsdGeom.SetStageUpAxis(target_stage, UsdGeom.GetStageUpAxis(stage)) - UsdGeom.SetStageMetersPerUnit(target_stage, UsdGeom.GetStageMetersPerUnit(stage)) - - # specify the prim to copy - source_prim_path = Sdf.Path(source_prim_path) - if target_prim_path is None: - target_prim_path = source_prim_path - - # copy the prim - Sdf.CreatePrimInLayer(target_layer, target_prim_path) - Sdf.CopySpec(source_layer, source_prim_path, target_layer, target_prim_path) - # set the default prim - target_layer.defaultPrim = Sdf.Path(target_prim_path).name - # resolve all paths relative to layer path - omni.usd.resolve_paths(source_layer.identifier, target_layer.identifier) - # save the stage - target_layer.Save()
- - -""" -USD Prim properties. -""" - - -
[文档]def make_uninstanceable(prim_path: str | Sdf.Path, stage: Usd.Stage | None = None): - """Check if a prim and its descendants are instanced and make them uninstanceable. - - This function checks if the prim at the specified prim path and its descendants are instanced. - If so, it makes the respective prim uninstanceable by disabling instancing on the prim. - - This is useful when we want to modify the properties of a prim that is instanced. For example, if we - want to apply a different material on an instanced prim, we need to make the prim uninstanceable first. - - Args: - prim_path: The prim path to check. - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - """ - # make paths str type if they aren't already - prim_path = str(prim_path) - # check if prim path is global - if not prim_path.startswith("/"): - raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage = stage_utils.get_current_stage() - # get prim - prim: Usd.Prim = stage.GetPrimAtPath(prim_path) - # check if prim is valid - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim_path}' is not valid.") - # iterate over all prims under prim-path - all_prims = [prim] - while len(all_prims) > 0: - # get current prim - child_prim = all_prims.pop(0) - # check if prim is instanced - if child_prim.IsInstance(): - # make the prim uninstanceable - child_prim.SetInstanceable(False) - # add children to list - all_prims += child_prim.GetChildren()
- - -""" -USD Stage traversal. -""" - - -
[文档]def get_first_matching_child_prim( - prim_path: str | Sdf.Path, predicate: Callable[[Usd.Prim], bool], stage: Usd.Stage | None = None -) -> Usd.Prim | None: - """Recursively get the first USD Prim at the path string that passes the predicate function - - Args: - prim_path: The path of the prim in the stage. - predicate: The function to test the prims against. It takes a prim as input and returns a boolean. - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Returns: - The first prim on the path that passes the predicate. If no prim passes the predicate, it returns None. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - """ - # make paths str type if they aren't already - prim_path = str(prim_path) - # check if prim path is global - if not prim_path.startswith("/"): - raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage = stage_utils.get_current_stage() - # get prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim is valid - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim_path}' is not valid.") - # iterate over all prims under prim-path - all_prims = [prim] - while len(all_prims) > 0: - # get current prim - child_prim = all_prims.pop(0) - # check if prim passes predicate - if predicate(child_prim): - return child_prim - # add children to list - all_prims += child_prim.GetChildren() - return None
- - -
[文档]def get_all_matching_child_prims( - prim_path: str | Sdf.Path, - predicate: Callable[[Usd.Prim], bool] = lambda _: True, - depth: int | None = None, - stage: Usd.Stage | None = None, -) -> list[Usd.Prim]: - """Performs a search starting from the root and returns all the prims matching the predicate. - - Args: - prim_path: The root prim path to start the search from. - predicate: The predicate that checks if the prim matches the desired criteria. It takes a prim as input - and returns a boolean. Defaults to a function that always returns True. - depth: The maximum depth for traversal, should be bigger than zero if specified. - Defaults to None (i.e: traversal happens till the end of the tree). - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Returns: - A list containing all the prims matching the predicate. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - """ - # make paths str type if they aren't already - prim_path = str(prim_path) - # check if prim path is global - if not prim_path.startswith("/"): - raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage = stage_utils.get_current_stage() - # get prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim is valid - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim_path}' is not valid.") - # check if depth is valid - if depth is not None and depth <= 0: - raise ValueError(f"Depth must be bigger than zero, got {depth}.") - - # iterate over all prims under prim-path - # list of tuples (prim, current_depth) - all_prims_queue = [(prim, 0)] - output_prims = [] - while len(all_prims_queue) > 0: - # get current prim - child_prim, current_depth = all_prims_queue.pop(0) - # check if prim passes predicate - if predicate(child_prim): - output_prims.append(child_prim) - # add children to list - if depth is None or current_depth < depth: - all_prims_queue += [(child, current_depth + 1) for child in child_prim.GetChildren()] - - return output_prims
- - -
[文档]def find_first_matching_prim(prim_path_regex: str, stage: Usd.Stage | None = None) -> Usd.Prim | None: - """Find the first matching prim in the stage based on input regex expression. - - Args: - prim_path_regex: The regex expression for prim path. - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Returns: - The first prim that matches input expression. If no prim matches, returns None. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - """ - # check prim path is global - if not prim_path_regex.startswith("/"): - raise ValueError(f"Prim path '{prim_path_regex}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage = stage_utils.get_current_stage() - # need to wrap the token patterns in '^' and '$' to prevent matching anywhere in the string - pattern = f"^{prim_path_regex}$" - compiled_pattern = re.compile(pattern) - # obtain matching prim (depth-first search) - for prim in stage.Traverse(): - # check if prim passes predicate - if compiled_pattern.match(prim.GetPath().pathString) is not None: - return prim - return None
- - -
[文档]def find_matching_prims(prim_path_regex: str, stage: Usd.Stage | None = None) -> list[Usd.Prim]: - """Find all the matching prims in the stage based on input regex expression. - - Args: - prim_path_regex: The regex expression for prim path. - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Returns: - A list of prims that match input expression. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - """ - # check prim path is global - if not prim_path_regex.startswith("/"): - raise ValueError(f"Prim path '{prim_path_regex}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage = stage_utils.get_current_stage() - # need to wrap the token patterns in '^' and '$' to prevent matching anywhere in the string - tokens = prim_path_regex.split("/")[1:] - tokens = [f"^{token}$" for token in tokens] - # iterate over all prims in stage (breath-first search) - all_prims = [stage.GetPseudoRoot()] - output_prims = [] - for index, token in enumerate(tokens): - token_compiled = re.compile(token) - for prim in all_prims: - for child in prim.GetAllChildren(): - if token_compiled.match(child.GetName()) is not None: - output_prims.append(child) - if index < len(tokens) - 1: - all_prims = output_prims - output_prims = [] - return output_prims
- - -
[文档]def find_matching_prim_paths(prim_path_regex: str, stage: Usd.Stage | None = None) -> list[str]: - """Find all the matching prim paths in the stage based on input regex expression. - - Args: - prim_path_regex: The regex expression for prim path. - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Returns: - A list of prim paths that match input expression. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - """ - # obtain matching prims - output_prims = find_matching_prims(prim_path_regex, stage) - # convert prims to prim paths - output_prim_paths = [] - for prim in output_prims: - output_prim_paths.append(prim.GetPath().pathString) - return output_prim_paths
- - -
[文档]def find_global_fixed_joint_prim( - prim_path: str | Sdf.Path, check_enabled_only: bool = False, stage: Usd.Stage | None = None -) -> UsdPhysics.Joint | None: - """Find the fixed joint prim under the specified prim path that connects the target to the simulation world. - - A joint is a connection between two bodies. A fixed joint is a joint that does not allow relative motion - between the two bodies. When a fixed joint has only one target body, it is considered to attach the body - to the simulation world. - - This function finds the fixed joint prim that has only one target under the specified prim path. If no such - fixed joint prim exists, it returns None. - - Args: - prim_path: The prim path to search for the fixed joint prim. - check_enabled_only: Whether to consider only enabled fixed joints. Defaults to False. - If False, then all joints (enabled or disabled) are considered. - stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. - - Returns: - The fixed joint prim that has only one target. If no such fixed joint prim exists, it returns None. - - Raises: - ValueError: If the prim path is not global (i.e: does not start with '/'). - ValueError: If the prim path does not exist on the stage. - """ - # check prim path is global - if not prim_path.startswith("/"): - raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.") - # get current stage - if stage is None: - stage = stage_utils.get_current_stage() - - # check if prim exists - prim = stage.GetPrimAtPath(prim_path) - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim_path}' is not valid.") - - fixed_joint_prim = None - # we check all joints under the root prim and classify the asset as fixed base if there exists - # a fixed joint that has only one target (i.e. the root link). - for prim in Usd.PrimRange(prim): - # note: ideally checking if it is FixedJoint would have been enough, but some assets use "Joint" as the - # schema name which makes it difficult to distinguish between the two. - joint_prim = UsdPhysics.Joint(prim) - if joint_prim: - # if check_enabled_only is True, we only consider enabled joints - if check_enabled_only and not joint_prim.GetJointEnabledAttr().Get(): - continue - # check body 0 and body 1 exist - body_0_exist = joint_prim.GetBody0Rel().GetTargets() != [] - body_1_exist = joint_prim.GetBody1Rel().GetTargets() != [] - # if either body 0 or body 1 does not exist, we have a fixed joint that connects to the world - if not (body_0_exist and body_1_exist): - fixed_joint_prim = joint_prim - break - - return fixed_joint_prim
- - -""" -USD Variants. -""" - - -
[文档]def select_usd_variants(prim_path: str, variants: object | dict[str, str], stage: Usd.Stage | None = None): - """Sets the variant selections from the specified variant sets on a USD prim. - - `USD Variants`_ are a very powerful tool in USD composition that allows prims to have different options on - a single asset. This can be done by modifying variations of the same prim parameters per variant option in a set. - This function acts as a script-based utility to set the variant selections for the specified variant sets on a - USD prim. - - The function takes a dictionary or a config class mapping variant set names to variant selections. For instance, - if we have a prim at ``"/World/Table"`` with two variant sets: "color" and "size", we can set the variant - selections as follows: - - .. code-block:: python - - select_usd_variants( - prim_path="/World/Table", - variants={ - "color": "red", - "size": "large", - }, - ) - - Alternatively, we can use a config class to define the variant selections: - - .. code-block:: python - - @configclass - class TableVariants: - color: Literal["blue", "red"] = "red" - size: Literal["small", "large"] = "large" - - select_usd_variants( - prim_path="/World/Table", - variants=TableVariants(), - ) - - Args: - prim_path: The path of the USD prim. - variants: A dictionary or config class mapping variant set names to variant selections. - stage: The USD stage. Defaults to None, in which case, the current stage is used. - - Raises: - ValueError: If the prim at the specified path is not valid. - - .. _USD Variants: https://graphics.pixar.com/usd/docs/USD-Glossary.html#USDGlossary-Variant - """ - # Resolve stage - if stage is None: - stage = stage_utils.get_current_stage() - # Obtain prim - prim = stage.GetPrimAtPath(prim_path) - if not prim.IsValid(): - raise ValueError(f"Prim at path '{prim_path}' is not valid.") - # Convert to dict if we have a configclass object. - if not isinstance(variants, dict): - variants = variants.to_dict() - - existing_variant_sets = prim.GetVariantSets() - for variant_set_name, variant_selection in variants.items(): - # Check if the variant set exists on the prim. - if not existing_variant_sets.HasVariantSet(variant_set_name): - carb.log_warn(f"Variant set '{variant_set_name}' does not exist on prim '{prim_path}'.") - continue - - variant_set = existing_variant_sets.GetVariantSet(variant_set_name) - # Only set the variant selection if it is different from the current selection. - if variant_set.GetVariantSelection() != variant_selection: - variant_set.SetVariantSelection(variant_selection) - carb.log_info( - f"Setting variant selection '{variant_selection}' for variant set '{variant_set_name}' on" - f" prim '{prim_path}'." - )
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/height_field/hf_terrains.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/height_field/hf_terrains.html deleted file mode 100644 index fd0576a0b6..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/height_field/hf_terrains.html +++ /dev/null @@ -1,999 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.height_field.hf_terrains — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.height_field.hf_terrains 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Functions to generate height fields for different terrains."""
-
-from __future__ import annotations
-
-import numpy as np
-import scipy.interpolate as interpolate
-from typing import TYPE_CHECKING
-
-from .utils import height_field_to_mesh
-
-if TYPE_CHECKING:
-    from . import hf_terrains_cfg
-
-
-
[文档]@height_field_to_mesh -def random_uniform_terrain(difficulty: float, cfg: hf_terrains_cfg.HfRandomUniformTerrainCfg) -> np.ndarray: - """Generate a terrain with height sampled uniformly from a specified range. - - .. image:: ../../_static/terrains/height_field/random_uniform_terrain.jpg - :width: 40% - :align: center - - Note: - The :obj:`difficulty` parameter is ignored for this terrain. - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - The height field of the terrain as a 2D numpy array with discretized heights. - The shape of the array is (width, length), where width and length are the number of points - along the x and y axis, respectively. - - Raises: - ValueError: When the downsampled scale is smaller than the horizontal scale. - """ - # check parameters - # -- horizontal scale - if cfg.downsampled_scale is None: - cfg.downsampled_scale = cfg.horizontal_scale - elif cfg.downsampled_scale < cfg.horizontal_scale: - raise ValueError( - "Downsampled scale must be larger than or equal to the horizontal scale:" - f" {cfg.downsampled_scale} < {cfg.horizontal_scale}." - ) - - # switch parameters to discrete units - # -- horizontal scale - width_pixels = int(cfg.size[0] / cfg.horizontal_scale) - length_pixels = int(cfg.size[1] / cfg.horizontal_scale) - # -- downsampled scale - width_downsampled = int(cfg.size[0] / cfg.downsampled_scale) - length_downsampled = int(cfg.size[1] / cfg.downsampled_scale) - # -- height - height_min = int(cfg.noise_range[0] / cfg.vertical_scale) - height_max = int(cfg.noise_range[1] / cfg.vertical_scale) - height_step = int(cfg.noise_step / cfg.vertical_scale) - - # create range of heights possible - height_range = np.arange(height_min, height_max + height_step, height_step) - # sample heights randomly from the range along a grid - height_field_downsampled = np.random.choice(height_range, size=(width_downsampled, length_downsampled)) - # create interpolation function for the sampled heights - x = np.linspace(0, cfg.size[0] * cfg.horizontal_scale, width_downsampled) - y = np.linspace(0, cfg.size[1] * cfg.horizontal_scale, length_downsampled) - func = interpolate.RectBivariateSpline(x, y, height_field_downsampled) - - # interpolate the sampled heights to obtain the height field - x_upsampled = np.linspace(0, cfg.size[0] * cfg.horizontal_scale, width_pixels) - y_upsampled = np.linspace(0, cfg.size[1] * cfg.horizontal_scale, length_pixels) - z_upsampled = func(x_upsampled, y_upsampled) - # round off the interpolated heights to the nearest vertical step - return np.rint(z_upsampled).astype(np.int16)
- - -
[文档]@height_field_to_mesh -def pyramid_sloped_terrain(difficulty: float, cfg: hf_terrains_cfg.HfPyramidSlopedTerrainCfg) -> np.ndarray: - """Generate a terrain with a truncated pyramid structure. - - The terrain is a pyramid-shaped sloped surface with a slope of :obj:`slope` that trims into a flat platform - at the center. The slope is defined as the ratio of the height change along the x axis to the width along the - x axis. For example, a slope of 1.0 means that the height changes by 1 unit for every 1 unit of width. - - If the :obj:`cfg.inverted` flag is set to :obj:`True`, the terrain is inverted such that - the platform is at the bottom. - - .. image:: ../../_static/terrains/height_field/pyramid_sloped_terrain.jpg - :width: 40% - - .. image:: ../../_static/terrains/height_field/inverted_pyramid_sloped_terrain.jpg - :width: 40% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - The height field of the terrain as a 2D numpy array with discretized heights. - The shape of the array is (width, length), where width and length are the number of points - along the x and y axis, respectively. - """ - # resolve terrain configuration - if cfg.inverted: - slope = -cfg.slope_range[0] - difficulty * (cfg.slope_range[1] - cfg.slope_range[0]) - else: - slope = cfg.slope_range[0] + difficulty * (cfg.slope_range[1] - cfg.slope_range[0]) - - # switch parameters to discrete units - # -- horizontal scale - width_pixels = int(cfg.size[0] / cfg.horizontal_scale) - length_pixels = int(cfg.size[1] / cfg.horizontal_scale) - # -- height - # we want the height to be 1/2 of the width since the terrain is a pyramid - height_max = int(slope * cfg.size[0] / 2 / cfg.vertical_scale) - # -- center of the terrain - center_x = int(width_pixels / 2) - center_y = int(length_pixels / 2) - - # create a meshgrid of the terrain - x = np.arange(0, width_pixels) - y = np.arange(0, length_pixels) - xx, yy = np.meshgrid(x, y, sparse=True) - # offset the meshgrid to the center of the terrain - xx = (center_x - np.abs(center_x - xx)) / center_x - yy = (center_y - np.abs(center_y - yy)) / center_y - # reshape the meshgrid to be 2D - xx = xx.reshape(width_pixels, 1) - yy = yy.reshape(1, length_pixels) - # create a sloped surface - hf_raw = np.zeros((width_pixels, length_pixels)) - hf_raw = height_max * xx * yy - - # create a flat platform at the center of the terrain - platform_width = int(cfg.platform_width / cfg.horizontal_scale / 2) - # get the height of the platform at the corner of the platform - x_pf = width_pixels // 2 - platform_width - y_pf = length_pixels // 2 - platform_width - z_pf = hf_raw[x_pf, y_pf] - hf_raw = np.clip(hf_raw, min(0, z_pf), max(0, z_pf)) - - # round off the heights to the nearest vertical step - return np.rint(hf_raw).astype(np.int16)
- - -
[文档]@height_field_to_mesh -def pyramid_stairs_terrain(difficulty: float, cfg: hf_terrains_cfg.HfPyramidStairsTerrainCfg) -> np.ndarray: - """Generate a terrain with a pyramid stair pattern. - - The terrain is a pyramid stair pattern which trims to a flat platform at the center of the terrain. - - If the :obj:`cfg.inverted` flag is set to :obj:`True`, the terrain is inverted such that - the platform is at the bottom. - - .. image:: ../../_static/terrains/height_field/pyramid_stairs_terrain.jpg - :width: 40% - - .. image:: ../../_static/terrains/height_field/inverted_pyramid_stairs_terrain.jpg - :width: 40% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - The height field of the terrain as a 2D numpy array with discretized heights. - The shape of the array is (width, length), where width and length are the number of points - along the x and y axis, respectively. - """ - # resolve terrain configuration - step_height = cfg.step_height_range[0] + difficulty * (cfg.step_height_range[1] - cfg.step_height_range[0]) - if cfg.inverted: - step_height *= -1 - # switch parameters to discrete units - # -- terrain - width_pixels = int(cfg.size[0] / cfg.horizontal_scale) - length_pixels = int(cfg.size[1] / cfg.horizontal_scale) - # -- stairs - step_width = int(cfg.step_width / cfg.horizontal_scale) - step_height = int(step_height / cfg.vertical_scale) - # -- platform - platform_width = int(cfg.platform_width / cfg.horizontal_scale) - - # create a terrain with a flat platform at the center - hf_raw = np.zeros((width_pixels, length_pixels)) - # add the steps - current_step_height = 0 - start_x, start_y = 0, 0 - stop_x, stop_y = width_pixels, length_pixels - while (stop_x - start_x) > platform_width and (stop_y - start_y) > platform_width: - # increment position - # -- x - start_x += step_width - stop_x -= step_width - # -- y - start_y += step_width - stop_y -= step_width - # increment height - current_step_height += step_height - # add the step - hf_raw[start_x:stop_x, start_y:stop_y] = current_step_height - - # round off the heights to the nearest vertical step - return np.rint(hf_raw).astype(np.int16)
- - -
[文档]@height_field_to_mesh -def discrete_obstacles_terrain(difficulty: float, cfg: hf_terrains_cfg.HfDiscreteObstaclesTerrainCfg) -> np.ndarray: - """Generate a terrain with randomly generated obstacles as pillars with positive and negative heights. - - The terrain is a flat platform at the center of the terrain with randomly generated obstacles as pillars - with positive and negative height. The obstacles are randomly generated cuboids with a random width and - height. They are placed randomly on the terrain with a minimum distance of :obj:`cfg.platform_width` - from the center of the terrain. - - .. image:: ../../_static/terrains/height_field/discrete_obstacles_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - The height field of the terrain as a 2D numpy array with discretized heights. - The shape of the array is (width, length), where width and length are the number of points - along the x and y axis, respectively. - """ - # resolve terrain configuration - obs_height = cfg.obstacle_height_range[0] + difficulty * ( - cfg.obstacle_height_range[1] - cfg.obstacle_height_range[0] - ) - - # switch parameters to discrete units - # -- terrain - width_pixels = int(cfg.size[0] / cfg.horizontal_scale) - length_pixels = int(cfg.size[1] / cfg.horizontal_scale) - # -- obstacles - obs_height = int(obs_height / cfg.vertical_scale) - obs_width_min = int(cfg.obstacle_width_range[0] / cfg.horizontal_scale) - obs_width_max = int(cfg.obstacle_width_range[1] / cfg.horizontal_scale) - # -- center of the terrain - platform_width = int(cfg.platform_width / cfg.horizontal_scale) - - # create discrete ranges for the obstacles - # -- shape - obs_width_range = np.arange(obs_width_min, obs_width_max, 4) - obs_length_range = np.arange(obs_width_min, obs_width_max, 4) - # -- position - obs_x_range = np.arange(0, width_pixels, 4) - obs_y_range = np.arange(0, length_pixels, 4) - - # create a terrain with a flat platform at the center - hf_raw = np.zeros((width_pixels, length_pixels)) - # generate the obstacles - for _ in range(cfg.num_obstacles): - # sample size - if cfg.obstacle_height_mode == "choice": - height = np.random.choice([-obs_height, -obs_height // 2, obs_height // 2, obs_height]) - elif cfg.obstacle_height_mode == "fixed": - height = obs_height - else: - raise ValueError(f"Unknown obstacle height mode '{cfg.obstacle_height_mode}'. Must be 'choice' or 'fixed'.") - width = int(np.random.choice(obs_width_range)) - length = int(np.random.choice(obs_length_range)) - # sample position - x_start = int(np.random.choice(obs_x_range)) - y_start = int(np.random.choice(obs_y_range)) - # clip start position to the terrain - if x_start + width > width_pixels: - x_start = width_pixels - width - if y_start + length > length_pixels: - y_start = length_pixels - length - # add to terrain - hf_raw[x_start : x_start + width, y_start : y_start + length] = height - # clip the terrain to the platform - x1 = (width_pixels - platform_width) // 2 - x2 = (width_pixels + platform_width) // 2 - y1 = (length_pixels - platform_width) // 2 - y2 = (length_pixels + platform_width) // 2 - hf_raw[x1:x2, y1:y2] = 0 - # round off the heights to the nearest vertical step - return np.rint(hf_raw).astype(np.int16)
- - -
[文档]@height_field_to_mesh -def wave_terrain(difficulty: float, cfg: hf_terrains_cfg.HfWaveTerrainCfg) -> np.ndarray: - r"""Generate a terrain with a wave pattern. - - The terrain is a flat platform at the center of the terrain with a wave pattern. The wave pattern - is generated by adding sinusoidal waves based on the number of waves and the amplitude of the waves. - - The height of the terrain at a point :math:`(x, y)` is given by: - - .. math:: - - h(x, y) = A \left(\sin\left(\frac{2 \pi x}{\lambda}\right) + \cos\left(\frac{2 \pi y}{\lambda}\right) \right) - - where :math:`A` is the amplitude of the waves, :math:`\lambda` is the wavelength of the waves. - - .. image:: ../../_static/terrains/height_field/wave_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - The height field of the terrain as a 2D numpy array with discretized heights. - The shape of the array is (width, length), where width and length are the number of points - along the x and y axis, respectively. - - Raises: - ValueError: When the number of waves is non-positive. - """ - # check number of waves - if cfg.num_waves < 0: - raise ValueError(f"Number of waves must be a positive integer. Got: {cfg.num_waves}.") - - # resolve terrain configuration - amplitude = cfg.amplitude_range[0] + difficulty * (cfg.amplitude_range[1] - cfg.amplitude_range[0]) - # switch parameters to discrete units - # -- terrain - width_pixels = int(cfg.size[0] / cfg.horizontal_scale) - length_pixels = int(cfg.size[1] / cfg.horizontal_scale) - amplitude_pixels = int(0.5 * amplitude / cfg.vertical_scale) - - # compute the wave number: nu = 2 * pi / lambda - wave_length = length_pixels / cfg.num_waves - wave_number = 2 * np.pi / wave_length - # create meshgrid for the terrain - x = np.arange(0, width_pixels) - y = np.arange(0, length_pixels) - xx, yy = np.meshgrid(x, y, sparse=True) - xx = xx.reshape(width_pixels, 1) - yy = yy.reshape(1, length_pixels) - - # create a terrain with a flat platform at the center - hf_raw = np.zeros((width_pixels, length_pixels)) - # add the waves - hf_raw += amplitude_pixels * (np.cos(yy * wave_number) + np.sin(xx * wave_number)) - # round off the heights to the nearest vertical step - return np.rint(hf_raw).astype(np.int16)
- - -
[文档]@height_field_to_mesh -def stepping_stones_terrain(difficulty: float, cfg: hf_terrains_cfg.HfSteppingStonesTerrainCfg) -> np.ndarray: - """Generate a terrain with a stepping stones pattern. - - The terrain is a stepping stones pattern which trims to a flat platform at the center of the terrain. - - .. image:: ../../_static/terrains/height_field/stepping_stones_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - The height field of the terrain as a 2D numpy array with discretized heights. - The shape of the array is (width, length), where width and length are the number of points - along the x and y axis, respectively. - """ - # resolve terrain configuration - stone_width = cfg.stone_width_range[1] - difficulty * (cfg.stone_width_range[1] - cfg.stone_width_range[0]) - stone_distance = cfg.stone_distance_range[0] + difficulty * ( - cfg.stone_distance_range[1] - cfg.stone_distance_range[0] - ) - - # switch parameters to discrete units - # -- terrain - width_pixels = int(cfg.size[0] / cfg.horizontal_scale) - length_pixels = int(cfg.size[1] / cfg.horizontal_scale) - # -- stones - stone_distance = int(stone_distance / cfg.horizontal_scale) - stone_width = int(stone_width / cfg.horizontal_scale) - stone_height_max = int(cfg.stone_height_max / cfg.vertical_scale) - # -- holes - holes_depth = int(cfg.holes_depth / cfg.vertical_scale) - # -- platform - platform_width = int(cfg.platform_width / cfg.horizontal_scale) - # create range of heights - stone_height_range = np.arange(-stone_height_max - 1, stone_height_max, step=1) - - # create a terrain with a flat platform at the center - hf_raw = np.full((width_pixels, length_pixels), holes_depth) - # add the stones - start_x, start_y = 0, 0 - # -- if the terrain is longer than it is wide then fill the terrain column by column - if length_pixels >= width_pixels: - while start_y < length_pixels: - # ensure that stone stops along y-axis - stop_y = min(length_pixels, start_y + stone_width) - # randomly sample x-position - start_x = np.random.randint(0, stone_width) - stop_x = max(0, start_x - stone_distance) - # fill first stone - hf_raw[0:stop_x, start_y:stop_y] = np.random.choice(stone_height_range) - # fill row with stones - while start_x < width_pixels: - stop_x = min(width_pixels, start_x + stone_width) - hf_raw[start_x:stop_x, start_y:stop_y] = np.random.choice(stone_height_range) - start_x += stone_width + stone_distance - # update y-position - start_y += stone_width + stone_distance - elif width_pixels > length_pixels: - while start_x < width_pixels: - # ensure that stone stops along x-axis - stop_x = min(width_pixels, start_x + stone_width) - # randomly sample y-position - start_y = np.random.randint(0, stone_width) - stop_y = max(0, start_y - stone_distance) - # fill first stone - hf_raw[start_x:stop_x, 0:stop_y] = np.random.choice(stone_height_range) - # fill column with stones - while start_y < length_pixels: - stop_y = min(length_pixels, start_y + stone_width) - hf_raw[start_x:stop_x, start_y:stop_y] = np.random.choice(stone_height_range) - start_y += stone_width + stone_distance - # update x-position - start_x += stone_width + stone_distance - # add the platform in the center - x1 = (width_pixels - platform_width) // 2 - x2 = (width_pixels + platform_width) // 2 - y1 = (length_pixels - platform_width) // 2 - y2 = (length_pixels + platform_width) // 2 - hf_raw[x1:x2, y1:y2] = 0 - # round off the heights to the nearest vertical step - return np.rint(hf_raw).astype(np.int16)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/height_field/hf_terrains_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/height_field/hf_terrains_cfg.html deleted file mode 100644 index 2f34d519df..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/height_field/hf_terrains_cfg.html +++ /dev/null @@ -1,730 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.height_field.hf_terrains_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.height_field.hf_terrains_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-
-from omni.isaac.lab.utils import configclass
-
-from ..terrain_generator_cfg import SubTerrainBaseCfg
-from . import hf_terrains
-
-
-
[文档]@configclass -class HfTerrainBaseCfg(SubTerrainBaseCfg): - """The base configuration for height field terrains.""" - - border_width: float = 0.0 - """The width of the border/padding around the terrain (in m). Defaults to 0.0. - - The border width is subtracted from the :obj:`size` of the terrain. If non-zero, it must be - greater than or equal to the :obj:`horizontal scale`. - """ - horizontal_scale: float = 0.1 - """The discretization of the terrain along the x and y axes (in m). Defaults to 0.1.""" - vertical_scale: float = 0.005 - """The discretization of the terrain along the z axis (in m). Defaults to 0.005.""" - slope_threshold: float | None = None - """The slope threshold above which surfaces are made vertical. Defaults to None, - in which case no correction is applied."""
- - -""" -Different height field terrain configurations. -""" - - -
[文档]@configclass -class HfRandomUniformTerrainCfg(HfTerrainBaseCfg): - """Configuration for a random uniform height field terrain.""" - - function = hf_terrains.random_uniform_terrain - - noise_range: tuple[float, float] = MISSING - """The minimum and maximum height noise (i.e. along z) of the terrain (in m).""" - noise_step: float = MISSING - """The minimum height (in m) change between two points.""" - downsampled_scale: float | None = None - """The distance between two randomly sampled points on the terrain. Defaults to None, - in which case the :obj:`horizontal scale` is used. - - The heights are sampled at this resolution and interpolation is performed for intermediate points. - This must be larger than or equal to the :obj:`horizontal scale`. - """
- - -
[文档]@configclass -class HfPyramidSlopedTerrainCfg(HfTerrainBaseCfg): - """Configuration for a pyramid sloped height field terrain.""" - - function = hf_terrains.pyramid_sloped_terrain - - slope_range: tuple[float, float] = MISSING - """The slope of the terrain (in radians).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0.""" - inverted: bool = False - """Whether the pyramid is inverted. Defaults to False. - - If True, the terrain is inverted such that the platform is at the bottom and the slopes are upwards. - """
- - -
[文档]@configclass -class HfInvertedPyramidSlopedTerrainCfg(HfPyramidSlopedTerrainCfg): - """Configuration for an inverted pyramid sloped height field terrain. - - Note: - This is a subclass of :class:`HfPyramidSlopedTerrainCfg` with :obj:`inverted` set to True. - We make it as a separate class to make it easier to distinguish between the two and match - the naming convention of the other terrains. - """ - - inverted: bool = True
- - -
[文档]@configclass -class HfPyramidStairsTerrainCfg(HfTerrainBaseCfg): - """Configuration for a pyramid stairs height field terrain.""" - - function = hf_terrains.pyramid_stairs_terrain - - step_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the steps (in m).""" - step_width: float = MISSING - """The width of the steps (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0.""" - inverted: bool = False - """Whether the pyramid stairs is inverted. Defaults to False. - - If True, the terrain is inverted such that the platform is at the bottom and the stairs are upwards. - """
- - -
[文档]@configclass -class HfInvertedPyramidStairsTerrainCfg(HfPyramidStairsTerrainCfg): - """Configuration for an inverted pyramid stairs height field terrain. - - Note: - This is a subclass of :class:`HfPyramidStairsTerrainCfg` with :obj:`inverted` set to True. - We make it as a separate class to make it easier to distinguish between the two and match - the naming convention of the other terrains. - """ - - inverted: bool = True
- - -
[文档]@configclass -class HfDiscreteObstaclesTerrainCfg(HfTerrainBaseCfg): - """Configuration for a discrete obstacles height field terrain.""" - - function = hf_terrains.discrete_obstacles_terrain - - obstacle_height_mode: str = "choice" - """The mode to use for the obstacle height. Defaults to "choice". - - The following modes are supported: "choice", "fixed". - """ - obstacle_width_range: tuple[float, float] = MISSING - """The minimum and maximum width of the obstacles (in m).""" - obstacle_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the obstacles (in m).""" - num_obstacles: int = MISSING - """The number of obstacles to generate.""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0."""
- - -
[文档]@configclass -class HfWaveTerrainCfg(HfTerrainBaseCfg): - """Configuration for a wave height field terrain.""" - - function = hf_terrains.wave_terrain - - amplitude_range: tuple[float, float] = MISSING - """The minimum and maximum amplitude of the wave (in m).""" - num_waves: int = 1.0 - """The number of waves to generate. Defaults to 1.0."""
- - -
[文档]@configclass -class HfSteppingStonesTerrainCfg(HfTerrainBaseCfg): - """Configuration for a stepping stones height field terrain.""" - - function = hf_terrains.stepping_stones_terrain - - stone_height_max: float = MISSING - """The maximum height of the stones (in m).""" - stone_width_range: tuple[float, float] = MISSING - """The minimum and maximum width of the stones (in m).""" - stone_distance_range: tuple[float, float] = MISSING - """The minimum and maximum distance between stones (in m).""" - holes_depth: float = -10.0 - """The depth of the holes (negative obstacles). Defaults to -10.0.""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_generator.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_generator.html deleted file mode 100644 index 48584f2a8d..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_generator.html +++ /dev/null @@ -1,884 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.terrain_generator — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.terrain_generator 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import numpy as np
-import os
-import torch
-import trimesh
-
-import carb
-
-from omni.isaac.lab.utils.dict import dict_to_md5_hash
-from omni.isaac.lab.utils.io import dump_yaml
-from omni.isaac.lab.utils.timer import Timer
-from omni.isaac.lab.utils.warp import convert_to_warp_mesh
-
-from .height_field import HfTerrainBaseCfg
-from .terrain_generator_cfg import FlatPatchSamplingCfg, SubTerrainBaseCfg, TerrainGeneratorCfg
-from .trimesh.utils import make_border
-from .utils import color_meshes_by_height, find_flat_patches
-
-
-
[文档]class TerrainGenerator: - """Terrain generator to handle different terrain generation functions. - - The terrains are represented as meshes. These are obtained either from height fields or by using the - `trimesh <https://trimsh.org/trimesh.html>`__ library. The height field representation is more - flexible, but it is less computationally and memory efficient than the trimesh representation. - - All terrain generation functions take in the argument :obj:`difficulty` which determines the complexity - of the terrain. The difficulty is a number between 0 and 1, where 0 is the easiest and 1 is the hardest. - In most cases, the difficulty is used for linear interpolation between different terrain parameters. - For example, in a pyramid stairs terrain the step height is interpolated between the specified minimum - and maximum step height. - - Each sub-terrain has a corresponding configuration class that can be used to specify the parameters - of the terrain. The configuration classes are inherited from the :class:`SubTerrainBaseCfg` class - which contains the common parameters for all terrains. - - If a curriculum is used, the terrains are generated based on their difficulty parameter. - The difficulty is varied linearly over the number of rows (i.e. along x). If a curriculum - is not used, the terrains are generated randomly. - - If the :obj:`cfg.flat_patch_sampling` is specified for a sub-terrain, flat patches are sampled - on the terrain. These can be used for spawning robots, targets, etc. The sampled patches are stored - in the :obj:`flat_patches` dictionary. The key specifies the intention of the flat patches and the - value is a tensor containing the flat patches for each sub-terrain. - - If the flag :obj:`cfg.use_cache` is set to True, the terrains are cached based on their - sub-terrain configurations. This means that if the same sub-terrain configuration is used - multiple times, the terrain is only generated once and then reused. This is useful when - generating complex sub-terrains that take a long time to generate. - """ - - terrain_mesh: trimesh.Trimesh - """A single trimesh.Trimesh object for all the generated sub-terrains.""" - terrain_meshes: list[trimesh.Trimesh] - """List of trimesh.Trimesh objects for all the generated sub-terrains.""" - terrain_origins: np.ndarray - """The origin of each sub-terrain. Shape is (num_rows, num_cols, 3).""" - flat_patches: dict[str, torch.Tensor] - """A dictionary of sampled valid (flat) patches for each sub-terrain. - - The dictionary keys are the names of the flat patch sampling configurations. This maps to a - tensor containing the flat patches for each sub-terrain. The shape of the tensor is - (num_rows, num_cols, num_patches, 3). - - For instance, the key "root_spawn" maps to a tensor containing the flat patches for spawning an asset. - Similarly, the key "target_spawn" maps to a tensor containing the flat patches for setting targets. - """ - -
[文档] def __init__(self, cfg: TerrainGeneratorCfg, device: str = "cpu"): - """Initialize the terrain generator. - - Args: - cfg: Configuration for the terrain generator. - device: The device to use for the flat patches tensor. - """ - # check inputs - if len(cfg.sub_terrains) == 0: - raise ValueError("No sub-terrains specified! Please add at least one sub-terrain.") - # store inputs - self.cfg = cfg - self.device = device - # -- valid patches - self.flat_patches = {} - # set common values to all sub-terrains config - for sub_cfg in self.cfg.sub_terrains.values(): - # size of all terrains - sub_cfg.size = self.cfg.size - # params for height field terrains - if isinstance(sub_cfg, HfTerrainBaseCfg): - sub_cfg.horizontal_scale = self.cfg.horizontal_scale - sub_cfg.vertical_scale = self.cfg.vertical_scale - sub_cfg.slope_threshold = self.cfg.slope_threshold - - # set the seed for reproducibility - if self.cfg.seed is not None: - torch.manual_seed(self.cfg.seed) - np.random.seed(self.cfg.seed) - # create a list of all sub-terrains - self.terrain_meshes = list() - self.terrain_origins = np.zeros((self.cfg.num_rows, self.cfg.num_cols, 3)) - - # parse configuration and add sub-terrains - # create terrains based on curriculum or randomly - if self.cfg.curriculum: - with Timer("[INFO] Generating terrains based on curriculum took"): - self._generate_curriculum_terrains() - else: - with Timer("[INFO] Generating terrains randomly took"): - self._generate_random_terrains() - # add a border around the terrains - self._add_terrain_border() - # combine all the sub-terrains into a single mesh - self.terrain_mesh = trimesh.util.concatenate(self.terrain_meshes) - - # color the terrain mesh - if self.cfg.color_scheme == "height": - self.terrain_mesh = color_meshes_by_height(self.terrain_mesh) - elif self.cfg.color_scheme == "random": - self.terrain_mesh.visual.vertex_colors = np.random.choice( - range(256), size=(len(self.terrain_mesh.vertices), 4) - ) - elif self.cfg.color_scheme == "none": - pass - else: - raise ValueError(f"Invalid color scheme: {self.cfg.color_scheme}.") - - # offset the entire terrain and origins so that it is centered - # -- terrain mesh - transform = np.eye(4) - transform[:2, -1] = -self.cfg.size[0] * self.cfg.num_rows * 0.5, -self.cfg.size[1] * self.cfg.num_cols * 0.5 - self.terrain_mesh.apply_transform(transform) - # -- terrain origins - self.terrain_origins += transform[:3, -1] - # -- valid patches - terrain_origins_torch = torch.tensor(self.terrain_origins, dtype=torch.float, device=self.device).unsqueeze(2) - for name, value in self.flat_patches.items(): - self.flat_patches[name] = value + terrain_origins_torch
- - """ - Terrain generator functions. - """ - - def _generate_random_terrains(self): - """Add terrains based on randomly sampled difficulty parameter.""" - # normalize the proportions of the sub-terrains - proportions = np.array([sub_cfg.proportion for sub_cfg in self.cfg.sub_terrains.values()]) - proportions /= np.sum(proportions) - # create a list of all terrain configs - sub_terrains_cfgs = list(self.cfg.sub_terrains.values()) - - # randomly sample sub-terrains - for index in range(self.cfg.num_rows * self.cfg.num_cols): - # coordinate index of the sub-terrain - (sub_row, sub_col) = np.unravel_index(index, (self.cfg.num_rows, self.cfg.num_cols)) - # randomly sample terrain index - sub_index = np.random.choice(len(proportions), p=proportions) - # randomly sample difficulty parameter - difficulty = np.random.uniform(*self.cfg.difficulty_range) - # generate terrain - mesh, origin = self._get_terrain_mesh(difficulty, sub_terrains_cfgs[sub_index]) - # add to sub-terrains - self._add_sub_terrain(mesh, origin, sub_row, sub_col, sub_terrains_cfgs[sub_index]) - - def _generate_curriculum_terrains(self): - """Add terrains based on the difficulty parameter.""" - # normalize the proportions of the sub-terrains - proportions = np.array([sub_cfg.proportion for sub_cfg in self.cfg.sub_terrains.values()]) - proportions /= np.sum(proportions) - - # find the sub-terrain index for each column - # we generate the terrains based on their proportion (not randomly sampled) - sub_indices = [] - for index in range(self.cfg.num_cols): - sub_index = np.min(np.where(index / self.cfg.num_cols + 0.001 < np.cumsum(proportions))[0]) - sub_indices.append(sub_index) - sub_indices = np.array(sub_indices, dtype=np.int32) - # create a list of all terrain configs - sub_terrains_cfgs = list(self.cfg.sub_terrains.values()) - - # curriculum-based sub-terrains - for sub_col in range(self.cfg.num_cols): - for sub_row in range(self.cfg.num_rows): - # vary the difficulty parameter linearly over the number of rows - lower, upper = self.cfg.difficulty_range - difficulty = (sub_row + np.random.uniform()) / self.cfg.num_rows - difficulty = lower + (upper - lower) * difficulty - # generate terrain - mesh, origin = self._get_terrain_mesh(difficulty, sub_terrains_cfgs[sub_indices[sub_col]]) - # add to sub-terrains - self._add_sub_terrain(mesh, origin, sub_row, sub_col, sub_terrains_cfgs[sub_indices[sub_col]]) - - """ - Internal helper functions. - """ - - def _add_terrain_border(self): - """Add a surrounding border over all the sub-terrains into the terrain meshes.""" - # border parameters - border_size = ( - self.cfg.num_rows * self.cfg.size[0] + 2 * self.cfg.border_width, - self.cfg.num_cols * self.cfg.size[1] + 2 * self.cfg.border_width, - ) - inner_size = (self.cfg.num_rows * self.cfg.size[0], self.cfg.num_cols * self.cfg.size[1]) - border_center = (self.cfg.num_rows * self.cfg.size[0] / 2, self.cfg.num_cols * self.cfg.size[1] / 2, -0.5) - # border mesh - border_meshes = make_border(border_size, inner_size, height=1.0, position=border_center) - border = trimesh.util.concatenate(border_meshes) - # update the faces to have minimal triangles - selector = ~(np.asarray(border.triangles)[:, :, 2] < -0.1).any(1) - border.update_faces(selector) - # add the border to the list of meshes - self.terrain_meshes.append(border) - - def _add_sub_terrain( - self, mesh: trimesh.Trimesh, origin: np.ndarray, row: int, col: int, sub_terrain_cfg: SubTerrainBaseCfg - ): - """Add input sub-terrain to the list of sub-terrains. - - This function adds the input sub-terrain mesh to the list of sub-terrains and updates the origin - of the sub-terrain in the list of origins. It also samples flat patches if specified. - - Args: - mesh: The mesh of the sub-terrain. - origin: The origin of the sub-terrain. - row: The row index of the sub-terrain. - col: The column index of the sub-terrain. - """ - # sample flat patches if specified - if sub_terrain_cfg.flat_patch_sampling is not None: - carb.log_info(f"Sampling flat patches for sub-terrain at (row, col): ({row}, {col})") - # convert the mesh to warp mesh - wp_mesh = convert_to_warp_mesh(mesh.vertices, mesh.faces, device=self.device) - # sample flat patches based on each patch configuration for that sub-terrain - for name, patch_cfg in sub_terrain_cfg.flat_patch_sampling.items(): - patch_cfg: FlatPatchSamplingCfg - # create the flat patches tensor (if not already created) - if name not in self.flat_patches: - self.flat_patches[name] = torch.zeros( - (self.cfg.num_rows, self.cfg.num_cols, patch_cfg.num_patches, 3), device=self.device - ) - # add the flat patches to the tensor - self.flat_patches[name][row, col] = find_flat_patches( - wp_mesh=wp_mesh, - origin=origin, - num_patches=patch_cfg.num_patches, - patch_radius=patch_cfg.patch_radius, - x_range=patch_cfg.x_range, - y_range=patch_cfg.y_range, - z_range=patch_cfg.z_range, - max_height_diff=patch_cfg.max_height_diff, - ) - - # transform the mesh to the correct position - transform = np.eye(4) - transform[0:2, -1] = (row + 0.5) * self.cfg.size[0], (col + 0.5) * self.cfg.size[1] - mesh.apply_transform(transform) - # add mesh to the list - self.terrain_meshes.append(mesh) - # add origin to the list - self.terrain_origins[row, col] = origin + transform[:3, -1] - - def _get_terrain_mesh(self, difficulty: float, cfg: SubTerrainBaseCfg) -> tuple[trimesh.Trimesh, np.ndarray]: - """Generate a sub-terrain mesh based on the input difficulty parameter. - - If caching is enabled, the sub-terrain is cached and loaded from the cache if it exists. - The cache is stored in the cache directory specified in the configuration. - - .. Note: - This function centers the 2D center of the mesh and its specified origin such that the - 2D center becomes :math:`(0, 0)` instead of :math:`(size[0] / 2, size[1] / 2). - - Args: - difficulty: The difficulty parameter. - cfg: The configuration of the sub-terrain. - - Returns: - The sub-terrain mesh and origin. - """ - # add other parameters to the sub-terrain configuration - cfg.difficulty = float(difficulty) - cfg.seed = self.cfg.seed - # generate hash for the sub-terrain - sub_terrain_hash = dict_to_md5_hash(cfg.to_dict()) - # generate the file name - sub_terrain_cache_dir = os.path.join(self.cfg.cache_dir, sub_terrain_hash) - sub_terrain_stl_filename = os.path.join(sub_terrain_cache_dir, "mesh.stl") - sub_terrain_csv_filename = os.path.join(sub_terrain_cache_dir, "origin.csv") - sub_terrain_meta_filename = os.path.join(sub_terrain_cache_dir, "cfg.yaml") - - # check if hash exists - if true, load the mesh and origin and return - if self.cfg.use_cache and os.path.exists(sub_terrain_stl_filename): - # load existing mesh - mesh = trimesh.load_mesh(sub_terrain_stl_filename) - origin = np.loadtxt(sub_terrain_csv_filename, delimiter=",") - # return the generated mesh - return mesh, origin - - # generate the terrain - meshes, origin = cfg.function(difficulty, cfg) - mesh = trimesh.util.concatenate(meshes) - # offset mesh such that they are in their center - transform = np.eye(4) - transform[0:2, -1] = -cfg.size[0] * 0.5, -cfg.size[1] * 0.5 - mesh.apply_transform(transform) - # change origin to be in the center of the sub-terrain - origin += transform[0:3, -1] - - # if caching is enabled, save the mesh and origin - if self.cfg.use_cache: - # create the cache directory - os.makedirs(sub_terrain_cache_dir, exist_ok=True) - # save the data - mesh.export(sub_terrain_stl_filename) - np.savetxt(sub_terrain_csv_filename, origin, delimiter=",", header="x,y,z") - dump_yaml(sub_terrain_meta_filename, cfg) - # return the generated mesh - return mesh, origin
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_generator_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_generator_cfg.html deleted file mode 100644 index 22150acb1d..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_generator_cfg.html +++ /dev/null @@ -1,745 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.terrain_generator_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.terrain_generator_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""
-Configuration classes defining the different terrains available. Each configuration class must
-inherit from ``omni.isaac.lab.terrains.terrains_cfg.TerrainConfig`` and define the following attributes:
-
-- ``name``: Name of the terrain. This is used for the prim name in the USD stage.
-- ``function``: Function to generate the terrain. This function must take as input the terrain difficulty
-  and the configuration parameters and return a `tuple with the `trimesh`` mesh object and terrain origin.
-"""
-
-from __future__ import annotations
-
-import numpy as np
-import trimesh
-from collections.abc import Callable
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-
-@configclass
-class FlatPatchSamplingCfg:
-    """Configuration for sampling flat patches on the sub-terrain.
-
-    For a given sub-terrain, this configuration specifies how to sample flat patches on the terrain.
-    The sampled flat patches can be used for spawning robots, targets, etc.
-
-    Please check the function :meth:`~omni.isaac.lab.terrains.utils.find_flat_patches` for more details.
-    """
-
-    num_patches: int = MISSING
-    """Number of patches to sample."""
-
-    patch_radius: float | list[float] = MISSING
-    """Radius of the patches.
-
-    A list of radii can be provided to check for patches of different sizes. This is useful to deal with
-    cases where the terrain may have holes or obstacles in some areas.
-    """
-
-    x_range: tuple[float, float] = (-1e6, 1e6)
-    """The range of x-coordinates to sample from. Defaults to (-1e6, 1e6).
-
-    This range is internally clamped to the size of the terrain mesh.
-    """
-
-    y_range: tuple[float, float] = (-1e6, 1e6)
-    """The range of y-coordinates to sample from. Defaults to (-1e6, 1e6).
-
-    This range is internally clamped to the size of the terrain mesh.
-    """
-
-    z_range: tuple[float, float] = (-1e6, 1e6)
-    """Allowed range of z-coordinates for the sampled patch. Defaults to (-1e6, 1e6)."""
-
-    max_height_diff: float = MISSING
-    """Maximum allowed height difference between the highest and lowest points on the patch."""
-
-
-
[文档]@configclass -class SubTerrainBaseCfg: - """Base class for terrain configurations. - - All the sub-terrain configurations must inherit from this class. - - The :attr:`size` attribute is the size of the generated sub-terrain. Based on this, the terrain must - extend from :math:`(0, 0)` to :math:`(size[0], size[1])`. - """ - - function: Callable[[float, SubTerrainBaseCfg], tuple[list[trimesh.Trimesh], np.ndarray]] = MISSING - """Function to generate the terrain. - - This function must take as input the terrain difficulty and the configuration parameters and - return a tuple with a list of ``trimesh`` mesh objects and the terrain origin. - """ - - proportion: float = 1.0 - """Proportion of the terrain to generate. Defaults to 1.0. - - This is used to generate a mix of terrains. The proportion corresponds to the probability of sampling - the particular terrain. For example, if there are two terrains, A and B, with proportions 0.3 and 0.7, - respectively, then the probability of sampling terrain A is 0.3 and the probability of sampling terrain B - is 0.7. - """ - - size: tuple[float, float] = MISSING - """The width (along x) and length (along y) of the terrain (in m).""" - - flat_patch_sampling: dict[str, FlatPatchSamplingCfg] | None = None - """Dictionary of configurations for sampling flat patches on the sub-terrain. Defaults to None, - in which case no flat patch sampling is performed. - - The keys correspond to the name of the flat patch sampling configuration and the values are the - corresponding configurations. - """
- - -
[文档]@configclass -class TerrainGeneratorCfg: - """Configuration for the terrain generator.""" - - seed: int | None = None - """The seed for the random number generator. Defaults to None, - in which case the seed is not set.""" - - curriculum: bool = False - """Whether to use the curriculum mode. Defaults to False. - - If True, the terrains are generated based on their difficulty parameter. Otherwise, - they are randomly generated. - """ - - size: tuple[float, float] = MISSING - """The width (along x) and length (along y) of each sub-terrain (in m). - - Note: - This value is passed on to all the sub-terrain configurations. - """ - - border_width: float = 0.0 - """The width of the border around the terrain (in m). Defaults to 0.0.""" - - num_rows: int = 1 - """Number of rows of sub-terrains to generate. Defaults to 1.""" - - num_cols: int = 1 - """Number of columns of sub-terrains to generate. Defaults to 1.""" - - color_scheme: Literal["height", "random", "none"] = "none" - """Color scheme to use for the terrain. Defaults to "none". - - The available color schemes are: - - - "height": Color based on the height of the terrain. - - "random": Random color scheme. - - "none": No color scheme. - """ - - horizontal_scale: float = 0.1 - """The discretization of the terrain along the x and y axes (in m). Defaults to 0.1. - - This value is passed on to all the height field sub-terrain configurations. - """ - - vertical_scale: float = 0.005 - """The discretization of the terrain along the z axis (in m). Defaults to 0.005. - - This value is passed on to all the height field sub-terrain configurations. - """ - - slope_threshold: float | None = 0.75 - """The slope threshold above which surfaces are made vertical. Defaults to 0.75. - - If None no correction is applied. - - This value is passed on to all the height field sub-terrain configurations. - """ - - sub_terrains: dict[str, SubTerrainBaseCfg] = MISSING - """Dictionary of sub-terrain configurations. - - The keys correspond to the name of the sub-terrain configuration and the values are the corresponding - configurations. - """ - - difficulty_range: tuple[float, float] = (0.0, 1.0) - """The range of difficulty values for the sub-terrains. Defaults to (0.0, 1.0). - - If curriculum is enabled, the terrains will be generated based on this range in ascending order - of difficulty. Otherwise, the terrains will be generated based on this range in a random order. - """ - - use_cache: bool = False - """Whether to load the terrain from cache if it exists. Defaults to True.""" - - cache_dir: str = "/tmp/isaaclab/terrains" - """The directory where the terrain cache is stored. Defaults to "/tmp/isaaclab/terrains"."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_importer.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_importer.html deleted file mode 100644 index c6b008eac2..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_importer.html +++ /dev/null @@ -1,925 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.terrain_importer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.terrain_importer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import numpy as np
-import torch
-import trimesh
-from typing import TYPE_CHECKING
-
-import warp
-from pxr import UsdGeom
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.markers import VisualizationMarkers
-from omni.isaac.lab.markers.config import FRAME_MARKER_CFG
-from omni.isaac.lab.utils.warp import convert_to_warp_mesh
-
-from .terrain_generator import TerrainGenerator
-from .trimesh.utils import make_plane
-from .utils import create_prim_from_mesh
-
-if TYPE_CHECKING:
-    from .terrain_importer_cfg import TerrainImporterCfg
-
-
-
[文档]class TerrainImporter: - r"""A class to handle terrain meshes and import them into the simulator. - - We assume that a terrain mesh comprises of sub-terrains that are arranged in a grid with - rows ``num_rows`` and columns ``num_cols``. The terrain origins are the positions of the sub-terrains - where the robot should be spawned. - - Based on the configuration, the terrain importer handles computing the environment origins from the sub-terrain - origins. In a typical setup, the number of sub-terrains (:math:`num\_rows \times num\_cols`) is smaller than - the number of environments (:math:`num\_envs`). In this case, the environment origins are computed by - sampling the sub-terrain origins. - - If a curriculum is used, it is possible to update the environment origins to terrain origins that correspond - to a harder difficulty. This is done by calling :func:`update_terrain_levels`. The idea comes from game-based - curriculum. For example, in a game, the player starts with easy levels and progresses to harder levels. - """ - - meshes: dict[str, trimesh.Trimesh] - """A dictionary containing the names of the meshes and their keys.""" - warp_meshes: dict[str, warp.Mesh] - """A dictionary containing the names of the warp meshes and their keys.""" - terrain_origins: torch.Tensor | None - """The origins of the sub-terrains in the added terrain mesh. Shape is (num_rows, num_cols, 3). - - If None, then it is assumed no sub-terrains exist. The environment origins are computed in a grid. - """ - env_origins: torch.Tensor - """The origins of the environments. Shape is (num_envs, 3).""" - -
[文档] def __init__(self, cfg: TerrainImporterCfg): - """Initialize the terrain importer. - - Args: - cfg: The configuration for the terrain importer. - - Raises: - ValueError: If input terrain type is not supported. - ValueError: If terrain type is 'generator' and no configuration provided for ``terrain_generator``. - ValueError: If terrain type is 'usd' and no configuration provided for ``usd_path``. - ValueError: If terrain type is 'usd' or 'plane' and no configuration provided for ``env_spacing``. - """ - # store inputs - self.cfg = cfg - self.device = sim_utils.SimulationContext.instance().device # type: ignore - - # create a dict of meshes - self.meshes = dict() - self.warp_meshes = dict() - self.env_origins = None - self.terrain_origins = None - # private variables - self._terrain_flat_patches = dict() - - # auto-import the terrain based on the config - if self.cfg.terrain_type == "generator": - # check config is provided - if self.cfg.terrain_generator is None: - raise ValueError("Input terrain type is 'generator' but no value provided for 'terrain_generator'.") - # generate the terrain - terrain_generator = TerrainGenerator(cfg=self.cfg.terrain_generator, device=self.device) - self.import_mesh("terrain", terrain_generator.terrain_mesh) - # configure the terrain origins based on the terrain generator - self.configure_env_origins(terrain_generator.terrain_origins) - # refer to the flat patches - self._terrain_flat_patches = terrain_generator.flat_patches - elif self.cfg.terrain_type == "usd": - # check if config is provided - if self.cfg.usd_path is None: - raise ValueError("Input terrain type is 'usd' but no value provided for 'usd_path'.") - # import the terrain - self.import_usd("terrain", self.cfg.usd_path) - # configure the origins in a grid - self.configure_env_origins() - elif self.cfg.terrain_type == "plane": - # load the plane - self.import_ground_plane("terrain") - # configure the origins in a grid - self.configure_env_origins() - else: - raise ValueError(f"Terrain type '{self.cfg.terrain_type}' not available.") - - # set initial state of debug visualization - self.set_debug_vis(self.cfg.debug_vis)
- - """ - Properties. - """ - - @property - def has_debug_vis_implementation(self) -> bool: - """Whether the terrain importer has a debug visualization implemented. - - This always returns True. - """ - return True - - @property - def flat_patches(self) -> dict[str, torch.Tensor]: - """A dictionary containing the sampled valid (flat) patches for the terrain. - - This is only available if the terrain type is 'generator'. For other terrain types, this feature - is not available and the function returns an empty dictionary. - - Please refer to the :attr:`TerrainGenerator.flat_patches` for more information. - """ - return self._terrain_flat_patches - - """ - Operations - Visibility. - """ - -
[文档] def set_debug_vis(self, debug_vis: bool) -> bool: - """Set the debug visualization of the terrain importer. - - Args: - debug_vis: Whether to visualize the terrain origins. - - Returns: - Whether the debug visualization was successfully set. False if the terrain - importer does not support debug visualization. - - Raises: - RuntimeError: If terrain origins are not configured. - """ - # create a marker if necessary - if debug_vis: - if not hasattr(self, "origin_visualizer"): - self.origin_visualizer = VisualizationMarkers( - cfg=FRAME_MARKER_CFG.replace(prim_path="/Visuals/TerrainOrigin") - ) - if self.terrain_origins is not None: - self.origin_visualizer.visualize(self.terrain_origins.reshape(-1, 3)) - elif self.env_origins is not None: - self.origin_visualizer.visualize(self.env_origins.reshape(-1, 3)) - else: - raise RuntimeError("Terrain origins are not configured.") - # set visibility - self.origin_visualizer.set_visibility(True) - else: - if hasattr(self, "origin_visualizer"): - self.origin_visualizer.set_visibility(False) - # report success - return True
- - """ - Operations - Import. - """ - -
[文档] def import_ground_plane(self, key: str, size: tuple[float, float] = (2.0e6, 2.0e6)): - """Add a plane to the terrain importer. - - Args: - key: The key to store the mesh. - size: The size of the plane. Defaults to (2.0e6, 2.0e6). - - Raises: - ValueError: If a terrain with the same key already exists. - """ - # check if key exists - if key in self.meshes: - raise ValueError(f"Mesh with key {key} already exists. Existing keys: {self.meshes.keys()}.") - # create a plane - mesh = make_plane(size, height=0.0, center_zero=True) - # store the mesh - self.meshes[key] = mesh - # create a warp mesh - device = "cuda" if "cuda" in self.device else "cpu" - self.warp_meshes[key] = convert_to_warp_mesh(mesh.vertices, mesh.faces, device=device) - - # get the mesh - ground_plane_cfg = sim_utils.GroundPlaneCfg(physics_material=self.cfg.physics_material, size=size) - ground_plane_cfg.func(self.cfg.prim_path, ground_plane_cfg)
- -
[文档] def import_mesh(self, key: str, mesh: trimesh.Trimesh): - """Import a mesh into the simulator. - - The mesh is imported into the simulator under the prim path ``cfg.prim_path/{key}``. The created path - contains the mesh as a :class:`pxr.UsdGeom` instance along with visual or physics material prims. - - Args: - key: The key to store the mesh. - mesh: The mesh to import. - - Raises: - ValueError: If a terrain with the same key already exists. - """ - # check if key exists - if key in self.meshes: - raise ValueError(f"Mesh with key {key} already exists. Existing keys: {self.meshes.keys()}.") - # store the mesh - self.meshes[key] = mesh - # create a warp mesh - device = "cuda" if "cuda" in self.device else "cpu" - self.warp_meshes[key] = convert_to_warp_mesh(mesh.vertices, mesh.faces, device=device) - - # get the mesh - mesh = self.meshes[key] - mesh_prim_path = self.cfg.prim_path + f"/{key}" - # import the mesh - create_prim_from_mesh( - mesh_prim_path, - mesh, - visual_material=self.cfg.visual_material, - physics_material=self.cfg.physics_material, - )
- -
[文档] def import_usd(self, key: str, usd_path: str): - """Import a mesh from a USD file. - - We assume that the USD file contains a single mesh. If the USD file contains multiple meshes, then - the first mesh is used. The function mainly helps in registering the mesh into the warp meshes - and the meshes dictionary. - - Note: - We do not apply any material properties to the mesh. The material properties should - be defined in the USD file. - - Args: - key: The key to store the mesh. - usd_path: The path to the USD file. - - Raises: - ValueError: If a terrain with the same key already exists. - """ - # add mesh to the dict - if key in self.meshes: - raise ValueError(f"Mesh with key {key} already exists. Existing keys: {self.meshes.keys()}.") - # add the prim path - cfg = sim_utils.UsdFileCfg(usd_path=usd_path) - cfg.func(self.cfg.prim_path + f"/{key}", cfg) - - # traverse the prim and get the collision mesh - # THINK: Should the user specify the collision mesh? - mesh_prim = sim_utils.get_first_matching_child_prim( - self.cfg.prim_path + f"/{key}", lambda prim: prim.GetTypeName() == "Mesh" - ) - # check if the mesh is valid - if mesh_prim is None: - raise ValueError(f"Could not find any collision mesh in {usd_path}. Please check asset.") - # cast into UsdGeomMesh - mesh_prim = UsdGeom.Mesh(mesh_prim) - # store the mesh - vertices = np.asarray(mesh_prim.GetPointsAttr().Get()) - faces = np.asarray(mesh_prim.GetFaceVertexIndicesAttr().Get()).reshape(-1, 3) - self.meshes[key] = trimesh.Trimesh(vertices=vertices, faces=faces) - # create a warp mesh - device = "cuda" if "cuda" in self.device else "cpu" - self.warp_meshes[key] = convert_to_warp_mesh(vertices, faces, device=device)
- - """ - Operations - Origins. - """ - -
[文档] def configure_env_origins(self, origins: np.ndarray | None = None): - """Configure the origins of the environments based on the added terrain. - - Args: - origins: The origins of the sub-terrains. Shape is (num_rows, num_cols, 3). - """ - # decide whether to compute origins in a grid or based on curriculum - if origins is not None: - # convert to numpy - if isinstance(origins, np.ndarray): - origins = torch.from_numpy(origins) - # store the origins - self.terrain_origins = origins.to(self.device, dtype=torch.float) - # compute environment origins - self.env_origins = self._compute_env_origins_curriculum(self.cfg.num_envs, self.terrain_origins) - else: - self.terrain_origins = None - # check if env spacing is valid - if self.cfg.env_spacing is None: - raise ValueError("Environment spacing must be specified for configuring grid-like origins.") - # compute environment origins - self.env_origins = self._compute_env_origins_grid(self.cfg.num_envs, self.cfg.env_spacing)
- -
[文档] def update_env_origins(self, env_ids: torch.Tensor, move_up: torch.Tensor, move_down: torch.Tensor): - """Update the environment origins based on the terrain levels.""" - # check if grid-like spawning - if self.terrain_origins is None: - return - # update terrain level for the envs - self.terrain_levels[env_ids] += 1 * move_up - 1 * move_down - # robots that solve the last level are sent to a random one - # the minimum level is zero - self.terrain_levels[env_ids] = torch.where( - self.terrain_levels[env_ids] >= self.max_terrain_level, - torch.randint_like(self.terrain_levels[env_ids], self.max_terrain_level), - torch.clip(self.terrain_levels[env_ids], 0), - ) - # update the env origins - self.env_origins[env_ids] = self.terrain_origins[self.terrain_levels[env_ids], self.terrain_types[env_ids]]
- - """ - Internal helpers. - """ - - def _compute_env_origins_curriculum(self, num_envs: int, origins: torch.Tensor) -> torch.Tensor: - """Compute the origins of the environments defined by the sub-terrains origins.""" - # extract number of rows and cols - num_rows, num_cols = origins.shape[:2] - # maximum initial level possible for the terrains - if self.cfg.max_init_terrain_level is None: - max_init_level = num_rows - 1 - else: - max_init_level = min(self.cfg.max_init_terrain_level, num_rows - 1) - # store maximum terrain level possible - self.max_terrain_level = num_rows - # define all terrain levels and types available - self.terrain_levels = torch.randint(0, max_init_level + 1, (num_envs,), device=self.device) - self.terrain_types = torch.div( - torch.arange(num_envs, device=self.device), - (num_envs / num_cols), - rounding_mode="floor", - ).to(torch.long) - # create tensor based on number of environments - env_origins = torch.zeros(num_envs, 3, device=self.device) - env_origins[:] = origins[self.terrain_levels, self.terrain_types] - return env_origins - - def _compute_env_origins_grid(self, num_envs: int, env_spacing: float) -> torch.Tensor: - """Compute the origins of the environments in a grid based on configured spacing.""" - # create tensor based on number of environments - env_origins = torch.zeros(num_envs, 3, device=self.device) - # create a grid of origins - num_rows = np.ceil(num_envs / int(np.sqrt(num_envs))) - num_cols = np.ceil(num_envs / num_rows) - ii, jj = torch.meshgrid( - torch.arange(num_rows, device=self.device), torch.arange(num_cols, device=self.device), indexing="ij" - ) - env_origins[:, 0] = -(ii.flatten()[:num_envs] - (num_rows - 1) / 2) * env_spacing - env_origins[:, 1] = (jj.flatten()[:num_envs] - (num_cols - 1) / 2) * env_spacing - env_origins[:, 2] = 0.0 - return env_origins
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_importer_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_importer_cfg.html deleted file mode 100644 index c441afc4f1..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/terrain_importer_cfg.html +++ /dev/null @@ -1,662 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.terrain_importer_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.terrain_importer_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from dataclasses import MISSING
-from typing import TYPE_CHECKING, Literal
-
-import omni.isaac.lab.sim as sim_utils
-from omni.isaac.lab.utils import configclass
-
-from .terrain_importer import TerrainImporter
-
-if TYPE_CHECKING:
-    from .terrain_generator_cfg import TerrainGeneratorCfg
-
-
-
[文档]@configclass -class TerrainImporterCfg: - """Configuration for the terrain manager.""" - - class_type: type = TerrainImporter - """The class to use for the terrain importer. - - Defaults to :class:`omni.isaac.lab.terrains.terrain_importer.TerrainImporter`. - """ - - collision_group: int = -1 - """The collision group of the terrain. Defaults to -1.""" - - prim_path: str = MISSING - """The absolute path of the USD terrain prim. - - All sub-terrains are imported relative to this prim path. - """ - - num_envs: int = MISSING - """The number of environment origins to consider.""" - - terrain_type: Literal["generator", "plane", "usd"] = "generator" - """The type of terrain to generate. Defaults to "generator". - - Available options are "plane", "usd", and "generator". - """ - - terrain_generator: TerrainGeneratorCfg | None = None - """The terrain generator configuration. - - Only used if ``terrain_type`` is set to "generator". - """ - - usd_path: str | None = None - """The path to the USD file containing the terrain. - - Only used if ``terrain_type`` is set to "usd". - """ - - env_spacing: float | None = None - """The spacing between environment origins when defined in a grid. Defaults to None. - - Note: - This parameter is used only when the ``terrain_type`` is ``"plane"`` or ``"usd"``. - """ - - visual_material: sim_utils.VisualMaterialCfg | None = sim_utils.PreviewSurfaceCfg( - diffuse_color=(0.065, 0.0725, 0.080) - ) - """The visual material of the terrain. Defaults to a dark gray color material. - - The material is created at the path: ``{prim_path}/visualMaterial``. If `None`, then no material is created. - - .. note:: - This parameter is used only when the ``terrain_type`` is ``"generator"``. - """ - - physics_material: sim_utils.RigidBodyMaterialCfg = sim_utils.RigidBodyMaterialCfg() - """The physics material of the terrain. Defaults to a default physics material. - - The material is created at the path: ``{prim_path}/physicsMaterial``. - - .. note:: - This parameter is used only when the ``terrain_type`` is ``"generator"`` or ``"plane"``. - """ - - max_init_terrain_level: int | None = None - """The maximum initial terrain level for defining environment origins. Defaults to None. - - The terrain levels are specified by the number of rows in the grid arrangement of - sub-terrains. If None, then the initial terrain level is set to the maximum - terrain level available (``num_rows - 1``). - - Note: - This parameter is used only when sub-terrain origins are defined. - """ - - debug_vis: bool = False - """Whether to enable visualization of terrain origins for the terrain. Defaults to False."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/trimesh/mesh_terrains.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/trimesh/mesh_terrains.html deleted file mode 100644 index 4490bdc81f..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/trimesh/mesh_terrains.html +++ /dev/null @@ -1,1415 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.trimesh.mesh_terrains — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.trimesh.mesh_terrains 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Functions to generate different terrains using the ``trimesh`` library."""
-
-from __future__ import annotations
-
-import numpy as np
-import scipy.spatial.transform as tf
-import torch
-import trimesh
-from typing import TYPE_CHECKING
-
-from .utils import *  # noqa: F401, F403
-from .utils import make_border, make_plane
-
-if TYPE_CHECKING:
-    from . import mesh_terrains_cfg
-
-
-
[文档]def flat_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshPlaneTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a flat terrain as a plane. - - .. image:: ../../_static/terrains/trimesh/flat_terrain.jpg - :width: 45% - :align: center - - Note: - The :obj:`difficulty` parameter is ignored for this terrain. - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # compute the position of the terrain - origin = (cfg.size[0] / 2.0, cfg.size[1] / 2.0, 0.0) - # compute the vertices of the terrain - plane_mesh = make_plane(cfg.size, 0.0, center_zero=False) - # return the tri-mesh and the position - return [plane_mesh], np.array(origin)
- - -
[文档]def pyramid_stairs_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshPyramidStairsTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a pyramid stair pattern. - - The terrain is a pyramid stair pattern which trims to a flat platform at the center of the terrain. - - If :obj:`cfg.holes` is True, the terrain will have pyramid stairs of length or width - :obj:`cfg.platform_width` (depending on the direction) with no steps in the remaining area. Additionally, - no border will be added. - - .. image:: ../../_static/terrains/trimesh/pyramid_stairs_terrain.jpg - :width: 45% - - .. image:: ../../_static/terrains/trimesh/pyramid_stairs_terrain_with_holes.jpg - :width: 45% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - step_height = cfg.step_height_range[0] + difficulty * (cfg.step_height_range[1] - cfg.step_height_range[0]) - - # compute number of steps in x and y direction - num_steps_x = (cfg.size[0] - 2 * cfg.border_width - cfg.platform_width) // (2 * cfg.step_width) + 1 - num_steps_y = (cfg.size[1] - 2 * cfg.border_width - cfg.platform_width) // (2 * cfg.step_width) + 1 - # we take the minimum number of steps in x and y direction - num_steps = int(min(num_steps_x, num_steps_y)) - - # initialize list of meshes - meshes_list = list() - - # generate the border if needed - if cfg.border_width > 0.0 and not cfg.holes: - # obtain a list of meshes for the border - border_center = [0.5 * cfg.size[0], 0.5 * cfg.size[1], -step_height / 2] - border_inner_size = (cfg.size[0] - 2 * cfg.border_width, cfg.size[1] - 2 * cfg.border_width) - make_borders = make_border(cfg.size, border_inner_size, step_height, border_center) - # add the border meshes to the list of meshes - meshes_list += make_borders - - # generate the terrain - # -- compute the position of the center of the terrain - terrain_center = [0.5 * cfg.size[0], 0.5 * cfg.size[1], 0.0] - terrain_size = (cfg.size[0] - 2 * cfg.border_width, cfg.size[1] - 2 * cfg.border_width) - # -- generate the stair pattern - for k in range(num_steps): - # check if we need to add holes around the steps - if cfg.holes: - box_size = (cfg.platform_width, cfg.platform_width) - else: - box_size = (terrain_size[0] - 2 * k * cfg.step_width, terrain_size[1] - 2 * k * cfg.step_width) - # compute the quantities of the box - # -- location - box_z = terrain_center[2] + k * step_height / 2.0 - box_offset = (k + 0.5) * cfg.step_width - # -- dimensions - box_height = (k + 2) * step_height - # generate the boxes - # top/bottom - box_dims = (box_size[0], cfg.step_width, box_height) - # -- top - box_pos = (terrain_center[0], terrain_center[1] + terrain_size[1] / 2.0 - box_offset, box_z) - box_top = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # -- bottom - box_pos = (terrain_center[0], terrain_center[1] - terrain_size[1] / 2.0 + box_offset, box_z) - box_bottom = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # right/left - if cfg.holes: - box_dims = (cfg.step_width, box_size[1], box_height) - else: - box_dims = (cfg.step_width, box_size[1] - 2 * cfg.step_width, box_height) - # -- right - box_pos = (terrain_center[0] + terrain_size[0] / 2.0 - box_offset, terrain_center[1], box_z) - box_right = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # -- left - box_pos = (terrain_center[0] - terrain_size[0] / 2.0 + box_offset, terrain_center[1], box_z) - box_left = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # add the boxes to the list of meshes - meshes_list += [box_top, box_bottom, box_right, box_left] - - # generate final box for the middle of the terrain - box_dims = ( - terrain_size[0] - 2 * num_steps * cfg.step_width, - terrain_size[1] - 2 * num_steps * cfg.step_width, - (num_steps + 2) * step_height, - ) - box_pos = (terrain_center[0], terrain_center[1], terrain_center[2] + num_steps * step_height / 2) - box_middle = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - meshes_list.append(box_middle) - # origin of the terrain - origin = np.array([terrain_center[0], terrain_center[1], (num_steps + 1) * step_height]) - - return meshes_list, origin
- - -
[文档]def inverted_pyramid_stairs_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshInvertedPyramidStairsTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a inverted pyramid stair pattern. - - The terrain is an inverted pyramid stair pattern which trims to a flat platform at the center of the terrain. - - If :obj:`cfg.holes` is True, the terrain will have pyramid stairs of length or width - :obj:`cfg.platform_width` (depending on the direction) with no steps in the remaining area. Additionally, - no border will be added. - - .. image:: ../../_static/terrains/trimesh/inverted_pyramid_stairs_terrain.jpg - :width: 45% - - .. image:: ../../_static/terrains/trimesh/inverted_pyramid_stairs_terrain_with_holes.jpg - :width: 45% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - step_height = cfg.step_height_range[0] + difficulty * (cfg.step_height_range[1] - cfg.step_height_range[0]) - - # compute number of steps in x and y direction - num_steps_x = (cfg.size[0] - 2 * cfg.border_width - cfg.platform_width) // (2 * cfg.step_width) + 1 - num_steps_y = (cfg.size[1] - 2 * cfg.border_width - cfg.platform_width) // (2 * cfg.step_width) + 1 - # we take the minimum number of steps in x and y direction - num_steps = int(min(num_steps_x, num_steps_y)) - # total height of the terrain - total_height = (num_steps + 1) * step_height - - # initialize list of meshes - meshes_list = list() - - # generate the border if needed - if cfg.border_width > 0.0 and not cfg.holes: - # obtain a list of meshes for the border - border_center = [0.5 * cfg.size[0], 0.5 * cfg.size[1], -0.5 * step_height] - border_inner_size = (cfg.size[0] - 2 * cfg.border_width, cfg.size[1] - 2 * cfg.border_width) - make_borders = make_border(cfg.size, border_inner_size, step_height, border_center) - # add the border meshes to the list of meshes - meshes_list += make_borders - # generate the terrain - # -- compute the position of the center of the terrain - terrain_center = [0.5 * cfg.size[0], 0.5 * cfg.size[1], 0.0] - terrain_size = (cfg.size[0] - 2 * cfg.border_width, cfg.size[1] - 2 * cfg.border_width) - # -- generate the stair pattern - for k in range(num_steps): - # check if we need to add holes around the steps - if cfg.holes: - box_size = (cfg.platform_width, cfg.platform_width) - else: - box_size = (terrain_size[0] - 2 * k * cfg.step_width, terrain_size[1] - 2 * k * cfg.step_width) - # compute the quantities of the box - # -- location - box_z = terrain_center[2] - total_height / 2 - (k + 1) * step_height / 2.0 - box_offset = (k + 0.5) * cfg.step_width - # -- dimensions - box_height = total_height - (k + 1) * step_height - # generate the boxes - # top/bottom - box_dims = (box_size[0], cfg.step_width, box_height) - # -- top - box_pos = (terrain_center[0], terrain_center[1] + terrain_size[1] / 2.0 - box_offset, box_z) - box_top = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # -- bottom - box_pos = (terrain_center[0], terrain_center[1] - terrain_size[1] / 2.0 + box_offset, box_z) - box_bottom = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # right/left - if cfg.holes: - box_dims = (cfg.step_width, box_size[1], box_height) - else: - box_dims = (cfg.step_width, box_size[1] - 2 * cfg.step_width, box_height) - # -- right - box_pos = (terrain_center[0] + terrain_size[0] / 2.0 - box_offset, terrain_center[1], box_z) - box_right = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # -- left - box_pos = (terrain_center[0] - terrain_size[0] / 2.0 + box_offset, terrain_center[1], box_z) - box_left = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - # add the boxes to the list of meshes - meshes_list += [box_top, box_bottom, box_right, box_left] - # generate final box for the middle of the terrain - box_dims = ( - terrain_size[0] - 2 * num_steps * cfg.step_width, - terrain_size[1] - 2 * num_steps * cfg.step_width, - step_height, - ) - box_pos = (terrain_center[0], terrain_center[1], terrain_center[2] - total_height - step_height / 2) - box_middle = trimesh.creation.box(box_dims, trimesh.transformations.translation_matrix(box_pos)) - meshes_list.append(box_middle) - # origin of the terrain - origin = np.array([terrain_center[0], terrain_center[1], -(num_steps + 1) * step_height]) - - return meshes_list, origin
- - -
[文档]def random_grid_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshRandomGridTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with cells of random heights and fixed width. - - The terrain is generated in the x-y plane and has a height of 1.0. It is then divided into a grid of the - specified size :obj:`cfg.grid_width`. Each grid cell is then randomly shifted in the z-direction by a value uniformly - sampled between :obj:`cfg.grid_height_range`. At the center of the terrain, a platform of the specified width - :obj:`cfg.platform_width` is generated. - - If :obj:`cfg.holes` is True, the terrain will have randomized grid cells only along the plane extending - from the platform (like a plus sign). The remaining area remains empty and no border will be added. - - .. image:: ../../_static/terrains/trimesh/random_grid_terrain.jpg - :width: 45% - - .. image:: ../../_static/terrains/trimesh/random_grid_terrain_with_holes.jpg - :width: 45% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - - Raises: - ValueError: If the terrain is not square. This method only supports square terrains. - RuntimeError: If the grid width is large such that the border width is negative. - """ - # check to ensure square terrain - if cfg.size[0] != cfg.size[1]: - raise ValueError(f"The terrain must be square. Received size: {cfg.size}.") - # resolve the terrain configuration - grid_height = cfg.grid_height_range[0] + difficulty * (cfg.grid_height_range[1] - cfg.grid_height_range[0]) - - # initialize list of meshes - meshes_list = list() - # compute the number of boxes in each direction - num_boxes_x = int(cfg.size[0] / cfg.grid_width) - num_boxes_y = int(cfg.size[1] / cfg.grid_width) - # constant parameters - terrain_height = 1.0 - device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") - - # generate the border - border_width = cfg.size[0] - min(num_boxes_x, num_boxes_y) * cfg.grid_width - if border_width > 0: - # compute parameters for the border - border_center = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -terrain_height / 2) - border_inner_size = (cfg.size[0] - border_width, cfg.size[1] - border_width) - # create border meshes - make_borders = make_border(cfg.size, border_inner_size, terrain_height, border_center) - meshes_list += make_borders - else: - raise RuntimeError("Border width must be greater than 0! Adjust the parameter 'cfg.grid_width'.") - - # create a template grid of terrain height - grid_dim = [cfg.grid_width, cfg.grid_width, terrain_height] - grid_position = [0.5 * cfg.grid_width, 0.5 * cfg.grid_width, -terrain_height / 2] - template_box = trimesh.creation.box(grid_dim, trimesh.transformations.translation_matrix(grid_position)) - # extract vertices and faces of the box to create a template - template_vertices = template_box.vertices # (8, 3) - template_faces = template_box.faces - - # repeat the template box vertices to span the terrain (num_boxes_x * num_boxes_y, 8, 3) - vertices = torch.tensor(template_vertices, device=device).repeat(num_boxes_x * num_boxes_y, 1, 1) - # create a meshgrid to offset the vertices - x = torch.arange(0, num_boxes_x, device=device) - y = torch.arange(0, num_boxes_y, device=device) - xx, yy = torch.meshgrid(x, y, indexing="ij") - xx = xx.flatten().view(-1, 1) - yy = yy.flatten().view(-1, 1) - xx_yy = torch.cat((xx, yy), dim=1) - # offset the vertices - offsets = cfg.grid_width * xx_yy + border_width / 2 - vertices[:, :, :2] += offsets.unsqueeze(1) - # mask the vertices to create holes, s.t. only grids along the x and y axis are present - if cfg.holes: - # -- x-axis - mask_x = torch.logical_and( - (vertices[:, :, 0] > (cfg.size[0] - border_width - cfg.platform_width) / 2).all(dim=1), - (vertices[:, :, 0] < (cfg.size[0] + border_width + cfg.platform_width) / 2).all(dim=1), - ) - vertices_x = vertices[mask_x] - # -- y-axis - mask_y = torch.logical_and( - (vertices[:, :, 1] > (cfg.size[1] - border_width - cfg.platform_width) / 2).all(dim=1), - (vertices[:, :, 1] < (cfg.size[1] + border_width + cfg.platform_width) / 2).all(dim=1), - ) - vertices_y = vertices[mask_y] - # -- combine these vertices - vertices = torch.cat((vertices_x, vertices_y)) - # add noise to the vertices to have a random height over each grid cell - num_boxes = len(vertices) - # create noise for the z-axis - h_noise = torch.zeros((num_boxes, 3), device=device) - h_noise[:, 2].uniform_(-grid_height, grid_height) - # reshape noise to match the vertices (num_boxes, 4, 3) - # only the top vertices of the box are affected - vertices_noise = torch.zeros((num_boxes, 4, 3), device=device) - vertices_noise += h_noise.unsqueeze(1) - # add height only to the top vertices of the box - vertices[vertices[:, :, 2] == 0] += vertices_noise.view(-1, 3) - # move to numpy - vertices = vertices.reshape(-1, 3).cpu().numpy() - - # create faces for boxes (num_boxes, 12, 3). Each box has 6 faces, each face has 2 triangles. - faces = torch.tensor(template_faces, device=device).repeat(num_boxes, 1, 1) - face_offsets = torch.arange(0, num_boxes, device=device).unsqueeze(1).repeat(1, 12) * 8 - faces += face_offsets.unsqueeze(2) - # move to numpy - faces = faces.view(-1, 3).cpu().numpy() - # convert to trimesh - grid_mesh = trimesh.Trimesh(vertices=vertices, faces=faces) - meshes_list.append(grid_mesh) - - # add a platform in the center of the terrain that is accessible from all sides - dim = (cfg.platform_width, cfg.platform_width, terrain_height + grid_height) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -terrain_height / 2 + grid_height / 2) - box_platform = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(box_platform) - - # specify the origin of the terrain - origin = np.array([0.5 * cfg.size[0], 0.5 * cfg.size[1], grid_height]) - - return meshes_list, origin
- - -
[文档]def rails_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshRailsTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with box rails as extrusions. - - The terrain contains two sets of box rails created as extrusions. The first set (inner rails) is extruded from - the platform at the center of the terrain, and the second set is extruded between the first set of rails - and the terrain border. Each set of rails is extruded to the same height. - - .. image:: ../../_static/terrains/trimesh/rails_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. this is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - rail_height = cfg.rail_height_range[1] - difficulty * (cfg.rail_height_range[1] - cfg.rail_height_range[0]) - - # initialize list of meshes - meshes_list = list() - # extract quantities - rail_1_thickness, rail_2_thickness = cfg.rail_thickness_range - rail_center = (0.5 * cfg.size[0], 0.5 * cfg.size[1], rail_height * 0.5) - # constants for terrain generation - terrain_height = 1.0 - rail_2_ratio = 0.6 - - # generate first set of rails - rail_1_inner_size = (cfg.platform_width, cfg.platform_width) - rail_1_outer_size = (cfg.platform_width + 2.0 * rail_1_thickness, cfg.platform_width + 2.0 * rail_1_thickness) - meshes_list += make_border(rail_1_outer_size, rail_1_inner_size, rail_height, rail_center) - # generate second set of rails - rail_2_inner_x = cfg.platform_width + (cfg.size[0] - cfg.platform_width) * rail_2_ratio - rail_2_inner_y = cfg.platform_width + (cfg.size[1] - cfg.platform_width) * rail_2_ratio - rail_2_inner_size = (rail_2_inner_x, rail_2_inner_y) - rail_2_outer_size = (rail_2_inner_x + 2.0 * rail_2_thickness, rail_2_inner_y + 2.0 * rail_2_thickness) - meshes_list += make_border(rail_2_outer_size, rail_2_inner_size, rail_height, rail_center) - # generate the ground - dim = (cfg.size[0], cfg.size[1], terrain_height) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -terrain_height / 2) - ground_meshes = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(ground_meshes) - - # specify the origin of the terrain - origin = np.array([pos[0], pos[1], 0.0]) - - return meshes_list, origin
- - -
[文档]def pit_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshPitTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a pit with levels (stairs) leading out of the pit. - - The terrain contains a platform at the center and a staircase leading out of the pit. - The staircase is a series of steps that are aligned along the x- and y- axis. The steps are - created by extruding a ring along the x- and y- axis. If :obj:`is_double_pit` is True, the pit - contains two levels. - - .. image:: ../../_static/terrains/trimesh/pit_terrain.jpg - :width: 40% - - .. image:: ../../_static/terrains/trimesh/pit_terrain_with_two_levels.jpg - :width: 40% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - pit_depth = cfg.pit_depth_range[0] + difficulty * (cfg.pit_depth_range[1] - cfg.pit_depth_range[0]) - - # initialize list of meshes - meshes_list = list() - # extract quantities - inner_pit_size = (cfg.platform_width, cfg.platform_width) - total_depth = pit_depth - # constants for terrain generation - terrain_height = 1.0 - ring_2_ratio = 0.6 - - # if the pit is double, the inner ring is smaller to fit the second level - if cfg.double_pit: - # increase the total height of the pit - total_depth *= 2.0 - # reduce the size of the inner ring - inner_pit_x = cfg.platform_width + (cfg.size[0] - cfg.platform_width) * ring_2_ratio - inner_pit_y = cfg.platform_width + (cfg.size[1] - cfg.platform_width) * ring_2_ratio - inner_pit_size = (inner_pit_x, inner_pit_y) - - # generate the pit (outer ring) - pit_center = [0.5 * cfg.size[0], 0.5 * cfg.size[1], -total_depth * 0.5] - meshes_list += make_border(cfg.size, inner_pit_size, total_depth, pit_center) - # generate the second level of the pit (inner ring) - if cfg.double_pit: - pit_center[2] = -total_depth - meshes_list += make_border(inner_pit_size, (cfg.platform_width, cfg.platform_width), total_depth, pit_center) - # generate the ground - dim = (cfg.size[0], cfg.size[1], terrain_height) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -total_depth - terrain_height / 2) - ground_meshes = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(ground_meshes) - - # specify the origin of the terrain - origin = np.array([pos[0], pos[1], -total_depth]) - - return meshes_list, origin
- - -
[文档]def box_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshBoxTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with boxes (similar to a pyramid). - - The terrain has a ground with boxes on top of it that are stacked on top of each other. - The boxes are created by extruding a rectangle along the z-axis. If :obj:`double_box` is True, - then two boxes of height :obj:`box_height` are stacked on top of each other. - - .. image:: ../../_static/terrains/trimesh/box_terrain.jpg - :width: 40% - - .. image:: ../../_static/terrains/trimesh/box_terrain_with_two_boxes.jpg - :width: 40% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - box_height = cfg.box_height_range[0] + difficulty * (cfg.box_height_range[1] - cfg.box_height_range[0]) - - # initialize list of meshes - meshes_list = list() - # extract quantities - total_height = box_height - if cfg.double_box: - total_height *= 2.0 - # constants for terrain generation - terrain_height = 1.0 - box_2_ratio = 0.6 - - # Generate the top box - dim = (cfg.platform_width, cfg.platform_width, terrain_height + total_height) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], (total_height - terrain_height) / 2) - box_mesh = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(box_mesh) - # Generate the lower box - if cfg.double_box: - # calculate the size of the lower box - outer_box_x = cfg.platform_width + (cfg.size[0] - cfg.platform_width) * box_2_ratio - outer_box_y = cfg.platform_width + (cfg.size[1] - cfg.platform_width) * box_2_ratio - # create the lower box - dim = (outer_box_x, outer_box_y, terrain_height + total_height / 2) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], (total_height - terrain_height) / 2 - total_height / 4) - box_mesh = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(box_mesh) - # Generate the ground - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -terrain_height / 2) - dim = (cfg.size[0], cfg.size[1], terrain_height) - ground_mesh = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(ground_mesh) - - # specify the origin of the terrain - origin = np.array([pos[0], pos[1], total_height]) - - return meshes_list, origin
- - -
[文档]def gap_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshGapTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a gap around the platform. - - The terrain has a ground with a platform in the middle. The platform is surrounded by a gap - of width :obj:`gap_width` on all sides. - - .. image:: ../../_static/terrains/trimesh/gap_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - gap_width = cfg.gap_width_range[0] + difficulty * (cfg.gap_width_range[1] - cfg.gap_width_range[0]) - - # initialize list of meshes - meshes_list = list() - # constants for terrain generation - terrain_height = 1.0 - terrain_center = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -terrain_height / 2) - - # Generate the outer ring - inner_size = (cfg.platform_width + 2 * gap_width, cfg.platform_width + 2 * gap_width) - meshes_list += make_border(cfg.size, inner_size, terrain_height, terrain_center) - # Generate the inner box - box_dim = (cfg.platform_width, cfg.platform_width, terrain_height) - box = trimesh.creation.box(box_dim, trimesh.transformations.translation_matrix(terrain_center)) - meshes_list.append(box) - - # specify the origin of the terrain - origin = np.array([terrain_center[0], terrain_center[1], 0.0]) - - return meshes_list, origin
- - -
[文档]def floating_ring_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshFloatingRingTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a floating square ring. - - The terrain has a ground with a floating ring in the middle. The ring extends from the center from - :obj:`platform_width` to :obj:`platform_width` + :obj:`ring_width` in the x and y directions. - The thickness of the ring is :obj:`ring_thickness` and the height of the ring from the terrain - is :obj:`ring_height`. - - .. image:: ../../_static/terrains/trimesh/floating_ring_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - """ - # resolve the terrain configuration - ring_height = cfg.ring_height_range[1] - difficulty * (cfg.ring_height_range[1] - cfg.ring_height_range[0]) - ring_width = cfg.ring_width_range[0] + difficulty * (cfg.ring_width_range[1] - cfg.ring_width_range[0]) - - # initialize list of meshes - meshes_list = list() - # constants for terrain generation - terrain_height = 1.0 - - # Generate the floating ring - ring_center = (0.5 * cfg.size[0], 0.5 * cfg.size[1], ring_height + 0.5 * cfg.ring_thickness) - ring_outer_size = (cfg.platform_width + 2 * ring_width, cfg.platform_width + 2 * ring_width) - ring_inner_size = (cfg.platform_width, cfg.platform_width) - meshes_list += make_border(ring_outer_size, ring_inner_size, cfg.ring_thickness, ring_center) - # Generate the ground - dim = (cfg.size[0], cfg.size[1], terrain_height) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -terrain_height / 2) - ground = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(ground) - - # specify the origin of the terrain - origin = np.asarray([pos[0], pos[1], 0.0]) - - return meshes_list, origin
- - -
[文档]def star_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshStarTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a star. - - The terrain has a ground with a cylinder in the middle. The star is made of :obj:`num_bars` bars - with a width of :obj:`bar_width` and a height of :obj:`bar_height`. The bars are evenly - spaced around the cylinder and connect to the peripheral of the terrain. - - .. image:: ../../_static/terrains/trimesh/star_terrain.jpg - :width: 40% - :align: center - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - - Raises: - ValueError: If :obj:`num_bars` is less than 2. - """ - # check the number of bars - if cfg.num_bars < 2: - raise ValueError(f"The number of bars in the star must be greater than 2. Received: {cfg.num_bars}") - - # resolve the terrain configuration - bar_height = cfg.bar_height_range[0] + difficulty * (cfg.bar_height_range[1] - cfg.bar_height_range[0]) - bar_width = cfg.bar_width_range[1] - difficulty * (cfg.bar_width_range[1] - cfg.bar_width_range[0]) - - # initialize list of meshes - meshes_list = list() - # Generate a platform in the middle - platform_center = (0.5 * cfg.size[0], 0.5 * cfg.size[1], -bar_height / 2) - platform_transform = trimesh.transformations.translation_matrix(platform_center) - platform = trimesh.creation.cylinder( - cfg.platform_width * 0.5, bar_height, sections=2 * cfg.num_bars, transform=platform_transform - ) - meshes_list.append(platform) - # Generate bars to connect the platform to the terrain - transform = np.eye(4) - transform[:3, -1] = np.asarray(platform_center) - yaw = 0.0 - for _ in range(cfg.num_bars): - # compute the length of the bar based on the yaw - # length changes since the bar is connected to a square border - bar_length = cfg.size[0] - if yaw < 0.25 * np.pi: - bar_length /= np.math.cos(yaw) - elif yaw < 0.75 * np.pi: - bar_length /= np.math.sin(yaw) - else: - bar_length /= np.math.cos(np.pi - yaw) - # compute the transform of the bar - transform[0:3, 0:3] = tf.Rotation.from_euler("z", yaw).as_matrix() - # add the bar to the mesh - dim = [bar_length - bar_width, bar_width, bar_height] - bar = trimesh.creation.box(dim, transform) - meshes_list.append(bar) - # increment the yaw - yaw += np.pi / cfg.num_bars - # Generate the exterior border - inner_size = (cfg.size[0] - 2 * bar_width, cfg.size[1] - 2 * bar_width) - meshes_list += make_border(cfg.size, inner_size, bar_height, platform_center) - # Generate the ground - ground = make_plane(cfg.size, -bar_height, center_zero=False) - meshes_list.append(ground) - # specify the origin of the terrain - origin = np.asarray([0.5 * cfg.size[0], 0.5 * cfg.size[1], 0.0]) - - return meshes_list, origin
- - -
[文档]def repeated_objects_terrain( - difficulty: float, cfg: mesh_terrains_cfg.MeshRepeatedObjectsTerrainCfg -) -> tuple[list[trimesh.Trimesh], np.ndarray]: - """Generate a terrain with a set of repeated objects. - - The terrain has a ground with a platform in the middle. The objects are randomly placed on the - terrain s.t. they do not overlap with the platform. - - Depending on the object type, the objects are generated with different parameters. The objects - The types of objects that can be generated are: ``"cylinder"``, ``"box"``, ``"cone"``. - - The object parameters are specified in the configuration as curriculum parameters. The difficulty - is used to linearly interpolate between the minimum and maximum values of the parameters. - - .. image:: ../../_static/terrains/trimesh/repeated_objects_cylinder_terrain.jpg - :width: 30% - - .. image:: ../../_static/terrains/trimesh/repeated_objects_box_terrain.jpg - :width: 30% - - .. image:: ../../_static/terrains/trimesh/repeated_objects_pyramid_terrain.jpg - :width: 30% - - Args: - difficulty: The difficulty of the terrain. This is a value between 0 and 1. - cfg: The configuration for the terrain. - - Returns: - A tuple containing the tri-mesh of the terrain and the origin of the terrain (in m). - - Raises: - ValueError: If the object type is not supported. It must be either a string or a callable. - """ - # import the object functions -- this is done here to avoid circular imports - from .mesh_terrains_cfg import ( - MeshRepeatedBoxesTerrainCfg, - MeshRepeatedCylindersTerrainCfg, - MeshRepeatedPyramidsTerrainCfg, - ) - - # if object type is a string, get the function: make_{object_type} - if isinstance(cfg.object_type, str): - object_func = globals().get(f"make_{cfg.object_type}") - else: - object_func = cfg.object_type - if not callable(object_func): - raise ValueError(f"The attribute 'object_type' must be a string or a callable. Received: {object_func}") - - # Resolve the terrain configuration - # -- pass parameters to make calling simpler - cp_0 = cfg.object_params_start - cp_1 = cfg.object_params_end - # -- common parameters - num_objects = cp_0.num_objects + int(difficulty * (cp_1.num_objects - cp_0.num_objects)) - height = cp_0.height + difficulty * (cp_1.height - cp_0.height) - # -- object specific parameters - # note: SIM114 requires duplicated logical blocks under a single body. - if isinstance(cfg, MeshRepeatedBoxesTerrainCfg): - cp_0: MeshRepeatedBoxesTerrainCfg.ObjectCfg - cp_1: MeshRepeatedBoxesTerrainCfg.ObjectCfg - object_kwargs = { - "length": cp_0.size[0] + difficulty * (cp_1.size[0] - cp_0.size[0]), - "width": cp_0.size[1] + difficulty * (cp_1.size[1] - cp_0.size[1]), - "max_yx_angle": cp_0.max_yx_angle + difficulty * (cp_1.max_yx_angle - cp_0.max_yx_angle), - "degrees": cp_0.degrees, - } - elif isinstance(cfg, MeshRepeatedPyramidsTerrainCfg): # noqa: SIM114 - cp_0: MeshRepeatedPyramidsTerrainCfg.ObjectCfg - cp_1: MeshRepeatedPyramidsTerrainCfg.ObjectCfg - object_kwargs = { - "radius": cp_0.radius + difficulty * (cp_1.radius - cp_0.radius), - "max_yx_angle": cp_0.max_yx_angle + difficulty * (cp_1.max_yx_angle - cp_0.max_yx_angle), - "degrees": cp_0.degrees, - } - elif isinstance(cfg, MeshRepeatedCylindersTerrainCfg): # noqa: SIM114 - cp_0: MeshRepeatedCylindersTerrainCfg.ObjectCfg - cp_1: MeshRepeatedCylindersTerrainCfg.ObjectCfg - object_kwargs = { - "radius": cp_0.radius + difficulty * (cp_1.radius - cp_0.radius), - "max_yx_angle": cp_0.max_yx_angle + difficulty * (cp_1.max_yx_angle - cp_0.max_yx_angle), - "degrees": cp_0.degrees, - } - else: - raise ValueError(f"Unknown terrain configuration: {cfg}") - # constants for the terrain - platform_clearance = 0.1 - - # initialize list of meshes - meshes_list = list() - # compute quantities - origin = np.asarray((0.5 * cfg.size[0], 0.5 * cfg.size[1], 0.5 * height)) - platform_corners = np.asarray([ - [origin[0] - cfg.platform_width / 2, origin[1] - cfg.platform_width / 2], - [origin[0] + cfg.platform_width / 2, origin[1] + cfg.platform_width / 2], - ]) - platform_corners[0, :] *= 1 - platform_clearance - platform_corners[1, :] *= 1 + platform_clearance - # sample center for objects - while True: - object_centers = np.zeros((num_objects, 3)) - object_centers[:, 0] = np.random.uniform(0, cfg.size[0], num_objects) - object_centers[:, 1] = np.random.uniform(0, cfg.size[1], num_objects) - # filter out the centers that are on the platform - is_within_platform_x = np.logical_and( - object_centers[:, 0] >= platform_corners[0, 0], object_centers[:, 0] <= platform_corners[1, 0] - ) - is_within_platform_y = np.logical_and( - object_centers[:, 1] >= platform_corners[0, 1], object_centers[:, 1] <= platform_corners[1, 1] - ) - masks = np.logical_and(is_within_platform_x, is_within_platform_y) - # if there are no objects on the platform, break - if not np.any(masks): - break - - # generate obstacles (but keep platform clean) - for index in range(len(object_centers)): - # randomize the height of the object - ob_height = height + np.random.uniform(-cfg.max_height_noise, cfg.max_height_noise) - if ob_height > 0.0: - object_mesh = object_func(center=object_centers[index], height=ob_height, **object_kwargs) - meshes_list.append(object_mesh) - - # generate a ground plane for the terrain - ground_plane = make_plane(cfg.size, height=0.0, center_zero=False) - meshes_list.append(ground_plane) - # generate a platform in the middle - dim = (cfg.platform_width, cfg.platform_width, 0.5 * height) - pos = (0.5 * cfg.size[0], 0.5 * cfg.size[1], 0.25 * height) - platform = trimesh.creation.box(dim, trimesh.transformations.translation_matrix(pos)) - meshes_list.append(platform) - - return meshes_list, origin
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/trimesh/mesh_terrains_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/trimesh/mesh_terrains_cfg.html deleted file mode 100644 index f2423ee28e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/trimesh/mesh_terrains_cfg.html +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-import omni.isaac.lab.terrains.trimesh.mesh_terrains as mesh_terrains
-import omni.isaac.lab.terrains.trimesh.utils as mesh_utils_terrains
-from omni.isaac.lab.utils import configclass
-
-from ..terrain_generator_cfg import SubTerrainBaseCfg
-
-"""
-Different trimesh terrain configurations.
-"""
-
-
-
[文档]@configclass -class MeshPlaneTerrainCfg(SubTerrainBaseCfg): - """Configuration for a plane mesh terrain.""" - - function = mesh_terrains.flat_terrain
- - -
[文档]@configclass -class MeshPyramidStairsTerrainCfg(SubTerrainBaseCfg): - """Configuration for a pyramid stair mesh terrain.""" - - function = mesh_terrains.pyramid_stairs_terrain - - border_width: float = 0.0 - """The width of the border around the terrain (in m). Defaults to 0.0. - - The border is a flat terrain with the same height as the terrain. - """ - step_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the steps (in m).""" - step_width: float = MISSING - """The width of the steps (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0.""" - holes: bool = False - """If True, the terrain will have holes in the steps. Defaults to False. - - If :obj:`holes` is True, the terrain will have pyramid stairs of length or width - :obj:`platform_width` (depending on the direction) with no steps in the remaining area. Additionally, - no border will be added. - """
- - -
[文档]@configclass -class MeshInvertedPyramidStairsTerrainCfg(MeshPyramidStairsTerrainCfg): - """Configuration for an inverted pyramid stair mesh terrain. - - Note: - This is the same as :class:`MeshPyramidStairsTerrainCfg` except that the steps are inverted. - """ - - function = mesh_terrains.inverted_pyramid_stairs_terrain
- - -
[文档]@configclass -class MeshRandomGridTerrainCfg(SubTerrainBaseCfg): - """Configuration for a random grid mesh terrain.""" - - function = mesh_terrains.random_grid_terrain - - grid_width: float = MISSING - """The width of the grid cells (in m).""" - grid_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the grid cells (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0.""" - holes: bool = False - """If True, the terrain will have holes in the steps. Defaults to False. - - If :obj:`holes` is True, the terrain will have randomized grid cells only along the plane extending - from the platform (like a plus sign). The remaining area remains empty and no border will be added. - """
- - -
[文档]@configclass -class MeshRailsTerrainCfg(SubTerrainBaseCfg): - """Configuration for a terrain with box rails as extrusions.""" - - function = mesh_terrains.rails_terrain - - rail_thickness_range: tuple[float, float] = MISSING - """The thickness of the inner and outer rails (in m).""" - rail_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the rails (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0."""
- - -
[文档]@configclass -class MeshPitTerrainCfg(SubTerrainBaseCfg): - """Configuration for a terrain with a pit that leads out of the pit.""" - - function = mesh_terrains.pit_terrain - - pit_depth_range: tuple[float, float] = MISSING - """The minimum and maximum height of the pit (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0.""" - double_pit: bool = False - """If True, the pit contains two levels of stairs. Defaults to False."""
- - -
[文档]@configclass -class MeshBoxTerrainCfg(SubTerrainBaseCfg): - """Configuration for a terrain with boxes (similar to a pyramid).""" - - function = mesh_terrains.box_terrain - - box_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the box (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0.""" - double_box: bool = False - """If True, the pit contains two levels of stairs/boxes. Defaults to False."""
- - -
[文档]@configclass -class MeshGapTerrainCfg(SubTerrainBaseCfg): - """Configuration for a terrain with a gap around the platform.""" - - function = mesh_terrains.gap_terrain - - gap_width_range: tuple[float, float] = MISSING - """The minimum and maximum width of the gap (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0."""
- - -
[文档]@configclass -class MeshFloatingRingTerrainCfg(SubTerrainBaseCfg): - """Configuration for a terrain with a floating ring around the center.""" - - function = mesh_terrains.floating_ring_terrain - - ring_width_range: tuple[float, float] = MISSING - """The minimum and maximum width of the ring (in m).""" - ring_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the ring (in m).""" - ring_thickness: float = MISSING - """The thickness (along z) of the ring (in m).""" - platform_width: float = 1.0 - """The width of the square platform at the center of the terrain. Defaults to 1.0."""
- - -
[文档]@configclass -class MeshStarTerrainCfg(SubTerrainBaseCfg): - """Configuration for a terrain with a star pattern.""" - - function = mesh_terrains.star_terrain - - num_bars: int = MISSING - """The number of bars per-side the star. Must be greater than 2.""" - bar_width_range: tuple[float, float] = MISSING - """The minimum and maximum width of the bars in the star (in m).""" - bar_height_range: tuple[float, float] = MISSING - """The minimum and maximum height of the bars in the star (in m).""" - platform_width: float = 1.0 - """The width of the cylindrical platform at the center of the terrain. Defaults to 1.0."""
- - -
[文档]@configclass -class MeshRepeatedObjectsTerrainCfg(SubTerrainBaseCfg): - """Base configuration for a terrain with repeated objects.""" - -
[文档] @configclass - class ObjectCfg: - """Configuration of repeated objects.""" - - num_objects: int = MISSING - """The number of objects to add to the terrain.""" - height: float = MISSING - """The height (along z) of the object (in m)."""
- - function = mesh_terrains.repeated_objects_terrain - - object_type: Literal["cylinder", "box", "cone"] | callable = MISSING - """The type of object to generate. - - The type can be a string or a callable. If it is a string, the function will look for a function called - ``make_{object_type}`` in the current module scope. If it is a callable, the function will - use the callable to generate the object. - """ - object_params_start: ObjectCfg = MISSING - """The object curriculum parameters at the start of the curriculum.""" - object_params_end: ObjectCfg = MISSING - """The object curriculum parameters at the end of the curriculum.""" - - max_height_noise: float = 0.0 - """The maximum amount of noise to add to the height of the objects (in m). Defaults to 0.0.""" - platform_width: float = 1.0 - """The width of the cylindrical platform at the center of the terrain. Defaults to 1.0."""
- - -
[文档]@configclass -class MeshRepeatedPyramidsTerrainCfg(MeshRepeatedObjectsTerrainCfg): - """Configuration for a terrain with repeated pyramids.""" - -
[文档] @configclass - class ObjectCfg(MeshRepeatedObjectsTerrainCfg.ObjectCfg): - """Configuration for a curriculum of repeated pyramids.""" - - radius: float = MISSING - """The radius of the pyramids (in m).""" - max_yx_angle: float = 0.0 - """The maximum angle along the y and x axis. Defaults to 0.0.""" - degrees: bool = True - """Whether the angle is in degrees. Defaults to True."""
- - object_type = mesh_utils_terrains.make_cone - - object_params_start: ObjectCfg = MISSING - """The object curriculum parameters at the start of the curriculum.""" - object_params_end: ObjectCfg = MISSING - """The object curriculum parameters at the end of the curriculum."""
- - -
[文档]@configclass -class MeshRepeatedBoxesTerrainCfg(MeshRepeatedObjectsTerrainCfg): - """Configuration for a terrain with repeated boxes.""" - -
[文档] @configclass - class ObjectCfg(MeshRepeatedObjectsTerrainCfg.ObjectCfg): - """Configuration for repeated boxes.""" - - size: tuple[float, float] = MISSING - """The width (along x) and length (along y) of the box (in m).""" - max_yx_angle: float = 0.0 - """The maximum angle along the y and x axis. Defaults to 0.0.""" - degrees: bool = True - """Whether the angle is in degrees. Defaults to True."""
- - object_type = mesh_utils_terrains.make_box - - object_params_start: ObjectCfg = MISSING - """The box curriculum parameters at the start of the curriculum.""" - object_params_end: ObjectCfg = MISSING - """The box curriculum parameters at the end of the curriculum."""
- - -
[文档]@configclass -class MeshRepeatedCylindersTerrainCfg(MeshRepeatedObjectsTerrainCfg): - """Configuration for a terrain with repeated cylinders.""" - -
[文档] @configclass - class ObjectCfg(MeshRepeatedObjectsTerrainCfg.ObjectCfg): - """Configuration for repeated cylinder.""" - - radius: float = MISSING - """The radius of the pyramids (in m).""" - max_yx_angle: float = 0.0 - """The maximum angle along the y and x axis. Defaults to 0.0.""" - degrees: bool = True - """Whether the angle is in degrees. Defaults to True."""
- - object_type = mesh_utils_terrains.make_cylinder - - object_params_start: ObjectCfg = MISSING - """The box curriculum parameters at the start of the curriculum.""" - object_params_end: ObjectCfg = MISSING - """The box curriculum parameters at the end of the curriculum."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/terrains/utils.html b/docs/_build/html/_modules/omni/isaac/lab/terrains/utils.html deleted file mode 100644 index 3e349929b6..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/terrains/utils.html +++ /dev/null @@ -1,837 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.terrains.utils — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.terrains.utils 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# needed to import for allowing type-hinting: np.ndarray | torch.Tensor | None
-from __future__ import annotations
-
-import numpy as np
-import torch
-import trimesh
-
-import warp as wp
-
-from omni.isaac.lab.utils.warp import raycast_mesh
-
-
-
[文档]def color_meshes_by_height(meshes: list[trimesh.Trimesh], **kwargs) -> trimesh.Trimesh: - """ - Color the vertices of a trimesh object based on the z-coordinate (height) of each vertex, - using the Turbo colormap. If the z-coordinates are all the same, the vertices will be colored - with a single color. - - Args: - meshes: A list of trimesh objects. - - Keyword Args: - color: A list of 3 integers in the range [0,255] representing the RGB - color of the mesh. Used when the z-coordinates of all vertices are the same. - color_map: The name of the color map to be used. Defaults to "turbo". - - Returns: - A trimesh object with the vertices colored based on the z-coordinate (height) of each vertex. - """ - # Combine all meshes into a single mesh - mesh = trimesh.util.concatenate(meshes) - # Get the z-coordinates of each vertex - heights = mesh.vertices[:, 2] - # Check if the z-coordinates are all the same - if np.max(heights) == np.min(heights): - # Obtain a single color: light blue - color = kwargs.pop("color", [172, 216, 230, 255]) - color = np.asarray(color, dtype=np.uint8) - # Set the color for all vertices - mesh.visual.vertex_colors = color - else: - # Normalize the heights to [0,1] - heights_normalized = (heights - np.min(heights)) / (np.max(heights) - np.min(heights)) - # clip lower and upper bounds to have better color mapping - heights_normalized = np.clip(heights_normalized, 0.1, 0.9) - # Get the color for each vertex based on the height - color_map = kwargs.pop("color_map", "turbo") - colors = trimesh.visual.color.interpolate(heights_normalized, color_map=color_map) - # Set the vertex colors - mesh.visual.vertex_colors = colors - # Return the mesh - return mesh
- - -
[文档]def create_prim_from_mesh(prim_path: str, mesh: trimesh.Trimesh, **kwargs): - """Create a USD prim with mesh defined from vertices and triangles. - - The function creates a USD prim with a mesh defined from vertices and triangles. It performs the - following steps: - - - Create a USD Xform prim at the path :obj:`prim_path`. - - Create a USD prim with a mesh defined from the input vertices and triangles at the path :obj:`{prim_path}/mesh`. - - Assign a physics material to the mesh at the path :obj:`{prim_path}/physicsMaterial`. - - Assign a visual material to the mesh at the path :obj:`{prim_path}/visualMaterial`. - - Args: - prim_path: The path to the primitive to be created. - mesh: The mesh to be used for the primitive. - - Keyword Args: - translation: The translation of the terrain. Defaults to None. - orientation: The orientation of the terrain. Defaults to None. - visual_material: The visual material to apply. Defaults to None. - physics_material: The physics material to apply. Defaults to None. - """ - # need to import these here to prevent isaacsim launching when importing this module - import omni.isaac.core.utils.prims as prim_utils - from pxr import UsdGeom - - import omni.isaac.lab.sim as sim_utils - - # create parent prim - prim_utils.create_prim(prim_path, "Xform") - # create mesh prim - prim = prim_utils.create_prim( - f"{prim_path}/mesh", - "Mesh", - translation=kwargs.get("translation"), - orientation=kwargs.get("orientation"), - attributes={ - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", - }, - ) - # apply collider properties - collider_cfg = sim_utils.CollisionPropertiesCfg(collision_enabled=True) - sim_utils.define_collision_properties(prim.GetPrimPath(), collider_cfg) - # add rgba color to the mesh primvars - if mesh.visual.vertex_colors is not None: - # obtain color from the mesh - rgba_colors = np.asarray(mesh.visual.vertex_colors).astype(np.float32) / 255.0 - # displayColor is a primvar attribute that is used to color the mesh - color_prim_attr = prim.GetAttribute("primvars:displayColor") - color_prim_var = UsdGeom.Primvar(color_prim_attr) - color_prim_var.SetInterpolation(UsdGeom.Tokens.vertex) - color_prim_attr.Set(rgba_colors[:, :3]) - # displayOpacity is a primvar attribute that is used to set the opacity of the mesh - display_prim_attr = prim.GetAttribute("primvars:displayOpacity") - display_prim_var = UsdGeom.Primvar(display_prim_attr) - display_prim_var.SetInterpolation(UsdGeom.Tokens.vertex) - display_prim_var.Set(rgba_colors[:, 3]) - - # create visual material - if kwargs.get("visual_material") is not None: - visual_material_cfg: sim_utils.VisualMaterialCfg = kwargs.get("visual_material") - # spawn the material - visual_material_cfg.func(f"{prim_path}/visualMaterial", visual_material_cfg) - sim_utils.bind_visual_material(prim.GetPrimPath(), f"{prim_path}/visualMaterial") - # create physics material - if kwargs.get("physics_material") is not None: - physics_material_cfg: sim_utils.RigidBodyMaterialCfg = kwargs.get("physics_material") - # spawn the material - physics_material_cfg.func(f"{prim_path}/physicsMaterial", physics_material_cfg) - sim_utils.bind_physics_material(prim.GetPrimPath(), f"{prim_path}/physicsMaterial")
- - -
[文档]def find_flat_patches( - wp_mesh: wp.Mesh, - num_patches: int, - patch_radius: float | list[float], - origin: np.ndarray | torch.Tensor | tuple[float, float, float], - x_range: tuple[float, float], - y_range: tuple[float, float], - z_range: tuple[float, float], - max_height_diff: float, -): - """Finds flat patches of given radius in the input mesh. - - The function finds flat patches of given radius based on the search space defined by the input ranges. - The search space is characterized by origin in the mesh frame, and the x, y, and z ranges. The x and y - ranges are used to sample points in the 2D region around the origin, and the z range is used to filter - patches based on the height of the points. - - The function performs rejection sampling to find the patches based on the following steps: - - 1. Sample patch locations in the 2D region around the origin. - 2. Define a ring of points around each patch location to query the height of the points using ray-casting. - 3. Reject patches that are outside the z range or have a height difference that is too large. - 4. Keep sampling until all patches are valid. - - Args: - wp_mesh: The warp mesh to find patches in. - num_patches: The desired number of patches to find. - patch_radius: The radii used to form patches. If a list is provided, multiple patch sizes are checked. - This is useful to deal with holes or other artifacts in the mesh. - origin: The origin defining the center of the search space. This is specified in the mesh frame. - x_range: The range of X coordinates to sample from. - y_range: The range of Y coordinates to sample from. - z_range: The range of valid Z coordinates used for filtering patches. - max_height_diff: The maximum allowable distance between the lowest and highest points - on a patch to consider it as valid. If the difference is greater than this value, - the patch is rejected. - - Returns: - A tensor of shape (num_patches, 3) containing the flat patches. The patches are defined in the mesh frame. - - Raises: - RuntimeError: If the function fails to find valid patches. This can happen if the input parameters - are not suitable for finding valid patches and maximum number of iterations is reached. - """ - # set device to warp mesh device - device = wp.device_to_torch(wp_mesh.device) - - # resolve inputs to consistent type - # -- patch radii - if isinstance(patch_radius, float): - patch_radius = [patch_radius] - # -- origin - if isinstance(origin, np.ndarray): - origin = torch.from_numpy(origin).to(torch.float).to(device) - elif isinstance(origin, torch.Tensor): - origin = origin.to(device) - else: - origin = torch.tensor(origin, dtype=torch.float, device=device) - - # create ranges for the x and y coordinates around the origin. - # The provided ranges are bounded by the mesh's bounding box. - x_range = ( - max(x_range[0] + origin[0].item(), wp_mesh.points.numpy()[:, 0].min()), - min(x_range[1] + origin[0].item(), wp_mesh.points.numpy()[:, 0].max()), - ) - y_range = ( - max(y_range[0] + origin[1].item(), wp_mesh.points.numpy()[:, 1].min()), - min(y_range[1] + origin[1].item(), wp_mesh.points.numpy()[:, 1].max()), - ) - z_range = ( - z_range[0] + origin[2].item(), - z_range[1] + origin[2].item(), - ) - - # create a circle of points around (0, 0) to query validity of the patches - # the ring of points is uniformly distributed around the circle - angle = torch.linspace(0, 2 * np.pi, 10, device=device) - query_x = [] - query_y = [] - for radius in patch_radius: - query_x.append(radius * torch.cos(angle)) - query_y.append(radius * torch.sin(angle)) - query_x = torch.cat(query_x).unsqueeze(1) # dim: (num_radii * 10, 1) - query_y = torch.cat(query_y).unsqueeze(1) # dim: (num_radii * 10, 1) - # dim: (num_radii * 10, 3) - query_points = torch.cat([query_x, query_y, torch.zeros_like(query_x)], dim=-1) - - # create buffers - # -- a buffer to store indices of points that are not valid - points_ids = torch.arange(num_patches, device=device) - # -- a buffer to store the flat patches locations - flat_patches = torch.zeros(num_patches, 3, device=device) - - # sample points and raycast to find the height. - # 1. Reject points that are outside the z_range or have a height difference that is too large. - # 2. Keep sampling until all points are valid. - iter_count = 0 - while len(points_ids) > 0 and iter_count < 10000: - # sample points in the 2D region around the origin - pos_x = torch.empty(len(points_ids), device=device).uniform_(*x_range) - pos_y = torch.empty(len(points_ids), device=device).uniform_(*y_range) - flat_patches[points_ids, :2] = torch.stack([pos_x, pos_y], dim=-1) - - # define the query points to check validity of the patch - # dim: (num_patches, num_radii * 10, 3) - points = flat_patches[points_ids].unsqueeze(1) + query_points - points[..., 2] = 100.0 - # ray-cast direction is downwards - dirs = torch.zeros_like(points) - dirs[..., 2] = -1.0 - - # ray-cast to find the height of the patches - ray_hits = raycast_mesh(points.view(-1, 3), dirs.view(-1, 3), wp_mesh)[0] - heights = ray_hits.view(points.shape)[..., 2] - # set the height of the patches - # note: for invalid patches, they would be overwritten in the next iteration - # so it's safe to set the height to the last value - flat_patches[points_ids, 2] = heights[..., -1] - - # check validity - # -- height is within the z range - not_valid = torch.any(torch.logical_or(heights < z_range[0], heights > z_range[1]), dim=1) - # -- height difference is within the max height difference - not_valid = torch.logical_or(not_valid, (heights.max(dim=1)[0] - heights.min(dim=1)[0]) > max_height_diff) - - # remove invalid patches indices - points_ids = points_ids[not_valid] - # increment count - iter_count += 1 - - # check all patches are valid - if len(points_ids) > 0: - raise RuntimeError( - "Failed to find valid patches! Please check the input parameters." - f"\n\tMaximum number of iterations reached: {iter_count}" - f"\n\tNumber of invalid patches: {len(points_ids)}" - f"\n\tMaximum height difference: {max_height_diff}" - ) - - # return the flat patches (in the mesh frame) - return flat_patches - origin
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/array.html b/docs/_build/html/_modules/omni/isaac/lab/utils/array.html deleted file mode 100644 index 9ab97ade0b..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/array.html +++ /dev/null @@ -1,658 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.array — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.array 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module containing utilities for working with different array backends."""
-
-# needed to import for allowing type-hinting: torch.device | str | None
-from __future__ import annotations
-
-import numpy as np
-import torch
-from typing import Union
-
-import warp as wp
-
-TensorData = Union[np.ndarray, torch.Tensor, wp.array]
-"""Type definition for a tensor data.
-
-Union of numpy, torch, and warp arrays.
-"""
-
-TENSOR_TYPES = {
-    "numpy": np.ndarray,
-    "torch": torch.Tensor,
-    "warp": wp.array,
-}
-"""A dictionary containing the types for each backend.
-
-The keys are the name of the backend ("numpy", "torch", "warp") and the values are the corresponding type
-(``np.ndarray``, ``torch.Tensor``, ``wp.array``).
-"""
-
-TENSOR_TYPE_CONVERSIONS = {
-    "numpy": {wp.array: lambda x: x.numpy(), torch.Tensor: lambda x: x.detach().cpu().numpy()},
-    "torch": {wp.array: lambda x: wp.torch.to_torch(x), np.ndarray: lambda x: torch.from_numpy(x)},
-    "warp": {np.array: lambda x: wp.array(x), torch.Tensor: lambda x: wp.torch.from_torch(x)},
-}
-"""A nested dictionary containing the conversion functions for each backend.
-
-The keys of the outer dictionary are the name of target backend ("numpy", "torch", "warp"). The keys of the
-inner dictionary are the source backend (``np.ndarray``, ``torch.Tensor``, ``wp.array``).
-"""
-
-
-
[文档]def convert_to_torch( - array: TensorData, - dtype: torch.dtype = None, - device: torch.device | str | None = None, -) -> torch.Tensor: - """Converts a given array into a torch tensor. - - The function tries to convert the array to a torch tensor. If the array is a numpy/warp arrays, or python - list/tuples, it is converted to a torch tensor. If the array is already a torch tensor, it is returned - directly. - - If ``device`` is None, then the function deduces the current device of the data. For numpy arrays, - this defaults to "cpu", for torch tensors it is "cpu" or "cuda", and for warp arrays it is "cuda". - - Note: - Since PyTorch does not support unsigned integer types, unsigned integer arrays are converted to - signed integer arrays. This is done by casting the array to the corresponding signed integer type. - - Args: - array: The input array. It can be a numpy array, warp array, python list/tuple, or torch tensor. - dtype: Target data-type for the tensor. - device: The target device for the tensor. Defaults to None. - - Returns: - The converted array as torch tensor. - """ - # Convert array to tensor - # if the datatype is not currently supported by torch we need to improvise - # supported types are: https://pytorch.org/docs/stable/tensors.html - if isinstance(array, torch.Tensor): - tensor = array - elif isinstance(array, np.ndarray): - if array.dtype == np.uint32: - array = array.astype(np.int32) - # need to deal with object arrays (np.void) separately - tensor = torch.from_numpy(array) - elif isinstance(array, wp.array): - if array.dtype == wp.uint32: - array = array.view(wp.int32) - tensor = wp.to_torch(array) - else: - tensor = torch.Tensor(array) - # Convert tensor to the right device - if device is not None and str(tensor.device) != str(device): - tensor = tensor.to(device) - # Convert dtype of tensor if requested - if dtype is not None and tensor.dtype != dtype: - tensor = tensor.type(dtype) - - return tensor
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/assets.html b/docs/_build/html/_modules/omni/isaac/lab/utils/assets.html deleted file mode 100644 index 194e59300c..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/assets.html +++ /dev/null @@ -1,691 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.assets — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.assets 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module that defines the host-server where assets and resources are stored.
-
-By default, we use the Isaac Sim Nucleus Server for hosting assets and resources. This makes
-distribution of the assets easier and makes the repository smaller in size code-wise.
-
-For more information, please check information on `Omniverse Nucleus`_.
-
-.. _Omniverse Nucleus: https://docs.omniverse.nvidia.com/nucleus/latest/overview/overview.html
-"""
-
-import io
-import os
-import tempfile
-from typing import Literal
-
-import carb
-import omni.client
-
-NUCLEUS_ASSET_ROOT_DIR = carb.settings.get_settings().get("/persistent/isaac/asset_root/cloud")
-"""Path to the root directory on the Nucleus Server."""
-
-NVIDIA_NUCLEUS_DIR = f"{NUCLEUS_ASSET_ROOT_DIR}/NVIDIA"
-"""Path to the root directory on the NVIDIA Nucleus Server."""
-
-ISAAC_NUCLEUS_DIR = f"{NUCLEUS_ASSET_ROOT_DIR}/Isaac"
-"""Path to the ``Isaac`` directory on the NVIDIA Nucleus Server."""
-
-ISAACLAB_NUCLEUS_DIR = f"{ISAAC_NUCLEUS_DIR}/IsaacLab"
-"""Path to the ``Isaac/IsaacLab`` directory on the NVIDIA Nucleus Server."""
-
-
-
[文档]def check_file_path(path: str) -> Literal[0, 1, 2]: - """Checks if a file exists on the Nucleus Server or locally. - - Args: - path: The path to the file. - - Returns: - The status of the file. Possible values are listed below. - - * :obj:`0` if the file does not exist - * :obj:`1` if the file exists locally - * :obj:`2` if the file exists on the Nucleus Server - """ - if os.path.isfile(path): - return 1 - elif omni.client.stat(path)[0] == omni.client.Result.OK: - return 2 - else: - return 0
- - -
[文档]def retrieve_file_path(path: str, download_dir: str | None = None, force_download: bool = True) -> str: - """Retrieves the path to a file on the Nucleus Server or locally. - - If the file exists locally, then the absolute path to the file is returned. - If the file exists on the Nucleus Server, then the file is downloaded to the local machine - and the absolute path to the file is returned. - - Args: - path: The path to the file. - download_dir: The directory where the file should be downloaded. Defaults to None, in which - case the file is downloaded to the system's temporary directory. - force_download: Whether to force download the file from the Nucleus Server. This will overwrite - the local file if it exists. Defaults to True. - - Returns: - The path to the file on the local machine. - - Raises: - FileNotFoundError: When the file not found locally or on Nucleus Server. - RuntimeError: When the file cannot be copied from the Nucleus Server to the local machine. This - can happen when the file already exists locally and :attr:`force_download` is set to False. - """ - # check file status - file_status = check_file_path(path) - if file_status == 1: - return os.path.abspath(path) - elif file_status == 2: - # resolve download directory - if download_dir is None: - download_dir = tempfile.gettempdir() - else: - download_dir = os.path.abspath(download_dir) - # create download directory if it does not exist - if not os.path.exists(download_dir): - os.makedirs(download_dir) - # download file in temp directory using os - file_name = os.path.basename(omni.client.break_url(path).path) - target_path = os.path.join(download_dir, file_name) - # check if file already exists locally - if not os.path.isfile(target_path) or force_download: - # copy file to local machine - result = omni.client.copy(path, target_path) - if result != omni.client.Result.OK and force_download: - raise RuntimeError(f"Unable to copy file: '{path}'. Is the Nucleus Server running?") - return os.path.abspath(target_path) - else: - raise FileNotFoundError(f"Unable to find the file: {path}")
- - -
[文档]def read_file(path: str) -> io.BytesIO: - """Reads a file from the Nucleus Server or locally. - - Args: - path: The path to the file. - - Raises: - FileNotFoundError: When the file not found locally or on Nucleus Server. - - Returns: - The content of the file. - """ - # check file status - file_status = check_file_path(path) - if file_status == 1: - with open(path, "rb") as f: - return io.BytesIO(f.read()) - elif file_status == 2: - file_content = omni.client.read_file(path)[2] - return io.BytesIO(memoryview(file_content).tobytes()) - else: - raise FileNotFoundError(f"Unable to find the file: {path}")
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/circular_buffer.html b/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/circular_buffer.html deleted file mode 100644 index 5e39f4e959..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/circular_buffer.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.buffers.circular_buffer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.buffers.circular_buffer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-from collections.abc import Sequence
-
-
-
[文档]class CircularBuffer: - """Circular buffer for storing a history of batched tensor data. - - This class implements a circular buffer for storing a history of batched tensor data. The buffer is - initialized with a maximum length and a batch size. The data is stored in a circular fashion, and the - data can be retrieved in a LIFO (Last-In-First-Out) fashion. The buffer is designed to be used in - multi-environment settings, where each environment has its own data. - - The shape of the appended data is expected to be (batch_size, ...), where the first dimension is the - batch dimension. Correspondingly, the shape of the ring buffer is (max_len, batch_size, ...). - """ - -
[文档] def __init__(self, max_len: int, batch_size: int, device: str): - """Initialize the circular buffer. - - Args: - max_len: The maximum length of the circular buffer. The minimum allowed value is 1. - batch_size: The batch dimension of the data. - device: The device used for processing. - - Raises: - ValueError: If the buffer size is less than one. - """ - if max_len < 1: - raise ValueError(f"The buffer size should be greater than zero. However, it is set to {max_len}!") - # set the parameters - self._batch_size = batch_size - self._device = device - self._ALL_INDICES = torch.arange(batch_size, device=device) - - # max length tensor for comparisons - self._max_len = torch.full((batch_size,), max_len, dtype=torch.int, device=device) - # number of data pushes passed since the last call to :meth:`reset` - self._num_pushes = torch.zeros(batch_size, dtype=torch.long, device=device) - # the pointer to the current head of the circular buffer (-1 means not initialized) - self._pointer: int = -1 - # the actual buffer for data storage - # note: this is initialized on the first call to :meth:`append` - self._buffer: torch.Tensor = None # type: ignore
- - """ - Properties. - """ - - @property - def batch_size(self) -> int: - """The batch size of the ring buffer.""" - return self._batch_size - - @property - def device(self) -> str: - """The device used for processing.""" - return self._device - - @property - def max_length(self) -> int: - """The maximum length of the ring buffer.""" - return int(self._max_len[0].item()) - - @property - def current_length(self) -> torch.Tensor: - """The current length of the buffer. Shape is (batch_size,). - - Since the buffer is circular, the current length is the minimum of the number of pushes - and the maximum length. - """ - return torch.minimum(self._num_pushes, self._max_len) - - """ - Operations. - """ - -
[文档] def reset(self, batch_ids: Sequence[int] | None = None): - """Reset the circular buffer at the specified batch indices. - - Args: - batch_ids: Elements to reset in the batch dimension. Default is None, which resets all the batch indices. - """ - # resolve all indices - if batch_ids is None: - batch_ids = slice(None) - # reset the number of pushes for the specified batch indices - # note: we don't need to reset the buffer since it will be overwritten. The pointer handles this. - self._num_pushes[batch_ids] = 0
- -
[文档] def append(self, data: torch.Tensor): - """Append the data to the circular buffer. - - Args: - data: The data to append to the circular buffer. The first dimension should be the batch dimension. - Shape is (batch_size, ...). - - Raises: - ValueError: If the input data has a different batch size than the buffer. - """ - # check the batch size - if data.shape[0] != self.batch_size: - raise ValueError(f"The input data has {data.shape[0]} environments while expecting {self.batch_size}") - - # at the fist call, initialize the buffer - if self._buffer is None: - self._pointer = -1 - self._buffer = torch.empty((self.max_length, *data.shape), dtype=data.dtype, device=self._device) - # move the head to the next slot - self._pointer = (self._pointer + 1) % self.max_length - # add the new data to the last layer - self._buffer[self._pointer] = data.to(self._device) - # increment number of number of pushes - self._num_pushes += 1
- - def __getitem__(self, key: torch.Tensor) -> torch.Tensor: - """Retrieve the data from the circular buffer in last-in-first-out (LIFO) fashion. - - If the requested index is larger than the number of pushes since the last call to :meth:`reset`, - the oldest stored data is returned. - - Args: - key: The index to retrieve from the circular buffer. The index should be less than the number of pushes - since the last call to :meth:`reset`. Shape is (batch_size,). - - Returns: - The data from the circular buffer. Shape is (batch_size, ...). - - Raises: - ValueError: If the input key has a different batch size than the buffer. - RuntimeError: If the buffer is empty. - """ - # check the batch size - if len(key) != self.batch_size: - raise ValueError(f"The argument 'key' has length {key.shape[0]}, while expecting {self.batch_size}") - # check if the buffer is empty - if torch.any(self._num_pushes == 0) or self._buffer is None: - raise RuntimeError("Attempting to retrieve data on an empty circular buffer. Please append data first.") - - # admissible lag - valid_keys = torch.minimum(key, self._num_pushes - 1) - # the index in the circular buffer (pointer points to the last+1 index) - index_in_buffer = torch.remainder(self._pointer - valid_keys, self.max_length) - # return output - return self._buffer[index_in_buffer, self._ALL_INDICES]
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/delay_buffer.html b/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/delay_buffer.html deleted file mode 100644 index 6beb8d3821..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/delay_buffer.html +++ /dev/null @@ -1,740 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.buffers.delay_buffer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.buffers.delay_buffer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-# needed because we concatenate int and torch.Tensor in the type hints
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-
-from .circular_buffer import CircularBuffer
-
-
-
[文档]class DelayBuffer: - """Delay buffer that allows retrieving stored data with delays. - - This class uses a batched circular buffer to store input data. Different to a standard circular buffer, - which uses the LIFO (last-in-first-out) principle to retrieve the data, the delay buffer class allows - retrieving data based on the lag set by the user. For instance, if the delay set inside the buffer - is 1, then the second last entry from the stream is retrieved. If it is 2, then the third last entry - and so on. - - The class supports storing a batched tensor data. This means that the shape of the appended data - is expected to be (batch_size, ...), where the first dimension is the batch dimension. Correspondingly, - the delay can be set separately for each batch index. If the requested delay is larger than the current - length of the underlying buffer, the most recent entry is returned. - - .. note:: - By default, the delay buffer has no delay, meaning that the data is returned as is. - """ - -
[文档] def __init__(self, history_length: int, batch_size: int, device: str): - """Initialize the delay buffer. - - Args: - history_length: The history of the buffer, i.e., the number of time steps in the past that the data - will be buffered. It is recommended to set this value equal to the maximum time-step lag that - is expected. The minimum acceptable value is zero, which means only the latest data is stored. - batch_size: The batch dimension of the data. - device: The device used for processing. - """ - # set the parameters - self._history_length = max(0, history_length) - - # the buffer size: current data plus the history length - self._circular_buffer = CircularBuffer(self._history_length + 1, batch_size, device) - - # the minimum and maximum lags across all environments. - self._min_time_lag = 0 - self._max_time_lag = 0 - # the lags for each environment. - self._time_lags = torch.zeros(batch_size, dtype=torch.int, device=device)
- - """ - Properties. - """ - - @property - def batch_size(self) -> int: - """The batch size of the ring buffer.""" - return self._circular_buffer.batch_size - - @property - def device(self) -> str: - """The device used for processing.""" - return self._circular_buffer.device - - @property - def history_length(self) -> int: - """The history length of the delay buffer. - - If zero, only the latest data is stored. If one, the latest and the previous data are stored, and so on. - """ - return self._history_length - - @property - def min_time_lag(self) -> int: - """Minimum amount of time steps that can be delayed. - - This value cannot be negative or larger than :attr:`max_time_lag`. - """ - return self._min_time_lag - - @property - def max_time_lag(self) -> int: - """Maximum amount of time steps that can be delayed. - - This value cannot be greater than :attr:`history_length`. - """ - return self._max_time_lag - - @property - def time_lags(self) -> torch.Tensor: - """The time lag across each batch index. - - The shape of the tensor is (batch_size, ). The value at each index represents the delay for that index. - This value is used to retrieve the data from the buffer. - """ - return self._time_lags - - """ - Operations. - """ - -
[文档] def set_time_lag(self, time_lag: int | torch.Tensor, batch_ids: Sequence[int] | None = None): - """Sets the time lag for the delay buffer across the provided batch indices. - - Args: - time_lag: The desired delay for the buffer. - - * If an integer is provided, the same delay is set for the provided batch indices. - * If a tensor is provided, the delay is set for each batch index separately. The shape of the tensor - should be (len(batch_ids),). - - batch_ids: The batch indices for which the time lag is set. Default is None, which sets the time lag - for all batch indices. - - Raises: - TypeError: If the type of the :attr:`time_lag` is not int or integer tensor. - ValueError: If the minimum time lag is negative or the maximum time lag is larger than the history length. - """ - # resolve batch indices - if batch_ids is None: - batch_ids = slice(None) - - # parse requested time_lag - if isinstance(time_lag, int): - # set the time lags across provided batch indices - self._time_lags[batch_ids] = time_lag - elif isinstance(time_lag, torch.Tensor): - # check valid dtype for time_lag: must be int or long - if time_lag.dtype not in [torch.int, torch.long]: - raise TypeError(f"Invalid dtype for time_lag: {time_lag.dtype}. Expected torch.int or torch.long.") - # set the time lags - self._time_lags[batch_ids] = time_lag.to(device=self.device) - else: - raise TypeError(f"Invalid type for time_lag: {type(time_lag)}. Expected int or integer tensor.") - - # compute the min and max time lag - self._min_time_lag = int(torch.min(self._time_lags).item()) - self._max_time_lag = int(torch.max(self._time_lags).item()) - # check that time_lag is feasible - if self._min_time_lag < 0: - raise ValueError(f"The minimum time lag cannot be negative. Received: {self._min_time_lag}") - if self._max_time_lag > self._history_length: - raise ValueError( - f"The maximum time lag cannot be larger than the history length. Received: {self._max_time_lag}" - )
- -
[文档] def reset(self, batch_ids: Sequence[int] | None = None): - """Reset the data in the delay buffer at the specified batch indices. - - Args: - batch_ids: Elements to reset in the batch dimension. Default is None, which resets all the batch indices. - """ - self._circular_buffer.reset(batch_ids)
- -
[文档] def compute(self, data: torch.Tensor) -> torch.Tensor: - """Append the input data to the buffer and returns a stale version of the data based on time lag delay. - - If the requested delay is larger than the number of buffered data points since the last reset, - the function returns the latest data. For instance, if the delay is set to 2 and only one data point - is stored in the buffer, the function will return the latest data. If the delay is set to 2 and three - data points are stored, the function will return the first data point. - - Args: - data: The input data. Shape is (batch_size, ...). - - Returns: - The delayed version of the data from the stored buffer. Shape is (batch_size, ...). - """ - # add the new data to the last layer - self._circular_buffer.append(data) - # return output - delayed_data = self._circular_buffer[self._time_lags] - return delayed_data.clone()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/timestamped_buffer.html b/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/timestamped_buffer.html deleted file mode 100644 index 3e5c5d4aee..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/buffers/timestamped_buffer.html +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.buffers.timestamped_buffer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.buffers.timestamped_buffer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-from dataclasses import dataclass
-
-
-
[文档]@dataclass -class TimestampedBuffer: - """A buffer class containing data and its timestamp. - - This class is a simple data container that stores a tensor and its timestamp. The timestamp is used to - track the last update of the buffer. The timestamp is set to -1.0 by default, indicating that the buffer - has not been updated yet. The timestamp should be updated whenever the data in the buffer is updated. This - way the buffer can be used to check whether the data is outdated and needs to be refreshed. - - The buffer is useful for creating lazy buffers that only update the data when it is outdated. This can be - useful when the data is expensive to compute or retrieve. For example usage, refer to the data classes in - the :mod:`omni.isaac.lab.assets` module. - """ - - data: torch.Tensor = None # type: ignore - """The data stored in the buffer. Default is None, indicating that the buffer is empty.""" - - timestamp: float = -1.0 - """Timestamp at the last update of the buffer. Default is -1.0, indicating that the buffer has not been updated."""
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/configclass.html b/docs/_build/html/_modules/omni/isaac/lab/utils/configclass.html deleted file mode 100644 index 8610aa4d70..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/configclass.html +++ /dev/null @@ -1,983 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.configclass — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.configclass 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module that provides a wrapper around the Python 3.7 onwards ``dataclasses`` module."""
-
-import inspect
-from collections.abc import Callable
-from copy import deepcopy
-from dataclasses import MISSING, Field, dataclass, field, replace
-from typing import Any, ClassVar
-
-from .dict import class_to_dict, update_class_from_dict
-
-_CONFIGCLASS_METHODS = ["to_dict", "from_dict", "replace", "copy"]
-"""List of class methods added at runtime to dataclass."""
-
-"""
-Wrapper around dataclass.
-"""
-
-
-def __dataclass_transform__():
-    """Add annotations decorator for PyLance."""
-    return lambda a: a
-
-
-
[文档]@__dataclass_transform__() -def configclass(cls, **kwargs): - """Wrapper around `dataclass` functionality to add extra checks and utilities. - - As of Python 3.7, the standard dataclasses have two main issues which makes them non-generic for - configuration use-cases. These include: - - 1. Requiring a type annotation for all its members. - 2. Requiring explicit usage of :meth:`field(default_factory=...)` to reinitialize mutable variables. - - This function provides a decorator that wraps around Python's `dataclass`_ utility to deal with - the above two issues. It also provides additional helper functions for dictionary <-> class - conversion and easily copying class instances. - - Usage: - - .. code-block:: python - - from dataclasses import MISSING - - from omni.isaac.lab.utils.configclass import configclass - - - @configclass - class ViewerCfg: - eye: list = [7.5, 7.5, 7.5] # field missing on purpose - lookat: list = field(default_factory=[0.0, 0.0, 0.0]) - - - @configclass - class EnvCfg: - num_envs: int = MISSING - episode_length: int = 2000 - viewer: ViewerCfg = ViewerCfg() - - # create configuration instance - env_cfg = EnvCfg(num_envs=24) - - # print information as a dictionary - print(env_cfg.to_dict()) - - # create a copy of the configuration - env_cfg_copy = env_cfg.copy() - - # replace arbitrary fields using keyword arguments - env_cfg_copy = env_cfg_copy.replace(num_envs=32) - - Args: - cls: The class to wrap around. - **kwargs: Additional arguments to pass to :func:`dataclass`. - - Returns: - The wrapped class. - - .. _dataclass: https://docs.python.org/3/library/dataclasses.html - """ - # add type annotations - _add_annotation_types(cls) - # add field factory - _process_mutable_types(cls) - # copy mutable members - # note: we check if user defined __post_init__ function exists and augment it with our own - if hasattr(cls, "__post_init__"): - setattr(cls, "__post_init__", _combined_function(cls.__post_init__, _custom_post_init)) - else: - setattr(cls, "__post_init__", _custom_post_init) - # add helper functions for dictionary conversion - setattr(cls, "to_dict", _class_to_dict) - setattr(cls, "from_dict", _update_class_from_dict) - setattr(cls, "replace", _replace_class_with_kwargs) - setattr(cls, "copy", _copy_class) - # wrap around dataclass - cls = dataclass(cls, **kwargs) - # return wrapped class - return cls
- - -""" -Dictionary <-> Class operations. - -These are redefined here to add new docstrings. -""" - - -def _class_to_dict(obj: object) -> dict[str, Any]: - """Convert an object into dictionary recursively. - - Returns: - Converted dictionary mapping. - """ - return class_to_dict(obj) - - -def _update_class_from_dict(obj, data: dict[str, Any]) -> None: - """Reads a dictionary and sets object variables recursively. - - This function performs in-place update of the class member attributes. - - Args: - data: Input (nested) dictionary to update from. - - Raises: - TypeError: When input is not a dictionary. - ValueError: When dictionary has a value that does not match default config type. - KeyError: When dictionary has a key that does not exist in the default config type. - """ - return update_class_from_dict(obj, data, _ns="") - - -def _replace_class_with_kwargs(obj: object, **kwargs) -> object: - """Return a new object replacing specified fields with new values. - - This is especially useful for frozen classes. Example usage: - - .. code-block:: python - - @configclass(frozen=True) - class C: - x: int - y: int - - c = C(1, 2) - c1 = c.replace(x=3) - assert c1.x == 3 and c1.y == 2 - - Args: - obj: The object to replace. - **kwargs: The fields to replace and their new values. - - Returns: - The new object. - """ - return replace(obj, **kwargs) - - -def _copy_class(obj: object) -> object: - """Return a new object with the same fields as the original.""" - return replace(obj) - - -""" -Private helper functions. -""" - - -def _add_annotation_types(cls): - """Add annotations to all elements in the dataclass. - - By definition in Python, a field is defined as a class variable that has a type annotation. - - In case type annotations are not provided, dataclass ignores those members when :func:`__dict__()` is called. - This function adds these annotations to the class variable to prevent any issues in case the user forgets to - specify the type annotation. - - This makes the following a feasible operation: - - @dataclass - class State: - pos = (0.0, 0.0, 0.0) - ^^ - If the function is NOT used, the following type-error is returned: - TypeError: 'pos' is a field but has no type annotation - """ - # get type hints - hints = {} - # iterate over class inheritance - # we add annotations from base classes first - for base in reversed(cls.__mro__): - # check if base is object - if base is object: - continue - # get base class annotations - ann = base.__dict__.get("__annotations__", {}) - # directly add all annotations from base class - hints.update(ann) - # iterate over base class members - # Note: Do not change this to dir(base) since it orders the members alphabetically. - # This is not desirable since the order of the members is important in some cases. - for key in base.__dict__: - # get class member - value = getattr(base, key) - # skip members - if _skippable_class_member(key, value, hints): - continue - # add type annotations for members that don't have explicit type annotations - # for these, we deduce the type from the default value - if not isinstance(value, type): - if key not in hints: - # check if var type is not MISSING - # we cannot deduce type from MISSING! - if value is MISSING: - raise TypeError( - f"Missing type annotation for '{key}' in class '{cls.__name__}'." - " Please add a type annotation or set a default value." - ) - # add type annotation - hints[key] = type(value) - elif key != value.__name__: - # note: we don't want to add type annotations for nested configclass. Thus, we check if - # the name of the type matches the name of the variable. - # since Python 3.10, type hints are stored as strings - hints[key] = f"type[{value.__name__}]" - - # Note: Do not change this line. `cls.__dict__.get("__annotations__", {})` is different from - # `cls.__annotations__` because of inheritance. - cls.__annotations__ = cls.__dict__.get("__annotations__", {}) - cls.__annotations__ = hints - - -def _process_mutable_types(cls): - """Initialize all mutable elements through :obj:`dataclasses.Field` to avoid unnecessary complaints. - - By default, dataclass requires usage of :obj:`field(default_factory=...)` to reinitialize mutable objects every time a new - class instance is created. If a member has a mutable type and it is created without specifying the `field(default_factory=...)`, - then Python throws an error requiring the usage of `default_factory`. - - Additionally, Python only explicitly checks for field specification when the type is a list, set or dict. This misses the - use-case where the type is class itself. Thus, the code silently carries a bug with it which can lead to undesirable effects. - - This function deals with this issue - - This makes the following a feasible operation: - - @dataclass - class State: - pos: list = [0.0, 0.0, 0.0] - ^^ - If the function is NOT used, the following value-error is returned: - ValueError: mutable default <class 'list'> for field pos is not allowed: use default_factory - """ - # note: Need to set this up in the same order as annotations. Otherwise, it - # complains about missing positional arguments. - ann = cls.__dict__.get("__annotations__", {}) - - # iterate over all class members and store them in a dictionary - class_members = {} - for base in reversed(cls.__mro__): - # check if base is object - if base is object: - continue - # iterate over base class members - for key in base.__dict__: - # get class member - f = getattr(base, key) - # skip members - if _skippable_class_member(key, f): - continue - # store class member if it is not a type or if it is already present in annotations - if not isinstance(f, type) or key in ann: - class_members[key] = f - # iterate over base class data fields - # in previous call, things that became a dataclass field were removed from class members - # so we need to add them back here as a dataclass field directly - for key, f in base.__dict__.get("__dataclass_fields__", {}).items(): - # store class member - if not isinstance(f, type): - class_members[key] = f - - # check that all annotations are present in class members - # note: mainly for debugging purposes - if len(class_members) != len(ann): - raise ValueError( - f"In class '{cls.__name__}', number of annotations ({len(ann)}) does not match number of class members" - f" ({len(class_members)}). Please check that all class members have type annotations and/or a default" - " value. If you don't want to specify a default value, please use the literal `dataclasses.MISSING`." - ) - # iterate over annotations and add field factory for mutable types - for key in ann: - # find matching field in class - value = class_members.get(key, MISSING) - # check if key belongs to ClassVar - # in that case, we cannot use default_factory! - origin = getattr(ann[key], "__origin__", None) - if origin is ClassVar: - continue - # check if f is MISSING - # note: commented out for now since it causes issue with inheritance - # of dataclasses when parent have some positional and some keyword arguments. - # Ref: https://stackoverflow.com/questions/51575931/class-inheritance-in-python-3-7-dataclasses - # TODO: check if this is fixed in Python 3.10 - # if f is MISSING: - # continue - if isinstance(value, Field): - setattr(cls, key, value) - elif not isinstance(value, type): - # create field factory for mutable types - value = field(default_factory=_return_f(value)) - setattr(cls, key, value) - - -def _custom_post_init(obj): - """Deepcopy all elements to avoid shared memory issues for mutable objects in dataclasses initialization. - - This function is called explicitly instead of as a part of :func:`_process_mutable_types()` to prevent mapping - proxy type i.e. a read only proxy for mapping objects. The error is thrown when using hierarchical data-classes - for configuration. - """ - for key in dir(obj): - # skip dunder members - if key.startswith("__"): - continue - # get data member - value = getattr(obj, key) - # duplicate data members - if not callable(value): - setattr(obj, key, deepcopy(value)) - - -def _combined_function(f1: Callable, f2: Callable) -> Callable: - """Combine two functions into one. - - Args: - f1: The first function. - f2: The second function. - - Returns: - The combined function. - """ - - def _combined(*args, **kwargs): - # call both functions - f1(*args, **kwargs) - f2(*args, **kwargs) - - return _combined - - -""" -Helper functions -""" - - -def _skippable_class_member(key: str, value: Any, hints: dict | None = None) -> bool: - """Check if the class member should be skipped in configclass processing. - - The following members are skipped: - - * Dunder members: ``__name__``, ``__module__``, ``__qualname__``, ``__annotations__``, ``__dict__``. - * Manually-added special class functions: From :obj:`_CONFIGCLASS_METHODS`. - * Members that are already present in the type annotations. - * Functions bounded to class object or class. - - Args: - key: The class member name. - value: The class member value. - hints: The type hints for the class. Defaults to None, in which case, the - members existence in type hints are not checked. - - Returns: - True if the class member should be skipped, False otherwise. - """ - # skip dunder members - if key.startswith("__"): - return True - # skip manually-added special class functions - if key in _CONFIGCLASS_METHODS: - return True - # check if key is already present - if hints is not None and key in hints: - return True - # skip functions bounded to class - if callable(value): - signature = inspect.signature(value) - if "self" in signature.parameters or "cls" in signature.parameters: - return True - # Otherwise, don't skip - return False - - -def _return_f(f: Any) -> Callable[[], Any]: - """Returns default factory function for creating mutable/immutable variables. - - This function should be used to create default factory functions for variables. - - Example: - - .. code-block:: python - - value = field(default_factory=_return_f(value)) - setattr(cls, key, value) - """ - - def _wrap(): - if isinstance(f, Field): - if f.default_factory is MISSING: - return deepcopy(f.default) - else: - return f.default_factory - else: - return deepcopy(f) - - return _wrap -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/dict.html b/docs/_build/html/_modules/omni/isaac/lab/utils/dict.html deleted file mode 100644 index d031e4ab05..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/dict.html +++ /dev/null @@ -1,818 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.dict — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.dict 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module for utilities for working with dictionaries."""
-
-import collections.abc
-import hashlib
-import json
-from collections.abc import Iterable, Mapping
-from typing import Any
-
-from .array import TENSOR_TYPE_CONVERSIONS, TENSOR_TYPES
-from .string import callable_to_string, string_to_callable
-
-"""
-Dictionary <-> Class operations.
-"""
-
-
-
[文档]def class_to_dict(obj: object) -> dict[str, Any]: - """Convert an object into dictionary recursively. - - Note: - Ignores all names starting with "__" (i.e. built-in methods). - - Args: - obj: An instance of a class to convert. - - Raises: - ValueError: When input argument is not an object. - - Returns: - Converted dictionary mapping. - """ - # check that input data is class instance - if not hasattr(obj, "__class__"): - raise ValueError(f"Expected a class instance. Received: {type(obj)}.") - # convert object to dictionary - if isinstance(obj, dict): - obj_dict = obj - else: - obj_dict = obj.__dict__ - # convert to dictionary - data = dict() - for key, value in obj_dict.items(): - # disregard builtin attributes - if key.startswith("__"): - continue - # check if attribute is callable -- function - if callable(value): - data[key] = callable_to_string(value) - # check if attribute is a dictionary - elif hasattr(value, "__dict__") or isinstance(value, dict): - data[key] = class_to_dict(value) - else: - data[key] = value - return data
- - -
[文档]def update_class_from_dict(obj, data: dict[str, Any], _ns: str = "") -> None: - """Reads a dictionary and sets object variables recursively. - - This function performs in-place update of the class member attributes. - - Args: - obj: An instance of a class to update. - data: Input dictionary to update from. - _ns: Namespace of the current object. This is useful for nested configuration - classes or dictionaries. Defaults to "". - - Raises: - TypeError: When input is not a dictionary. - ValueError: When dictionary has a value that does not match default config type. - KeyError: When dictionary has a key that does not exist in the default config type. - """ - for key, value in data.items(): - # key_ns is the full namespace of the key - key_ns = _ns + "/" + key - # check if key is present in the object - if hasattr(obj, key): - obj_mem = getattr(obj, key) - if isinstance(obj_mem, Mapping): - # Note: We don't handle two-level nested dictionaries. Just use configclass if this is needed. - # iterate over the dictionary to look for callable values - for k, v in obj_mem.items(): - if callable(v): - value[k] = string_to_callable(value[k]) - setattr(obj, key, value) - elif isinstance(value, Mapping): - # recursively call if it is a dictionary - update_class_from_dict(obj_mem, value, _ns=key_ns) - elif isinstance(value, Iterable) and not isinstance(value, str): - # check length of value to be safe - if len(obj_mem) != len(value) and obj_mem is not None: - raise ValueError( - f"[Config]: Incorrect length under namespace: {key_ns}." - f" Expected: {len(obj_mem)}, Received: {len(value)}." - ) - # set value - setattr(obj, key, value) - elif callable(obj_mem): - # update function name - value = string_to_callable(value) - setattr(obj, key, value) - elif isinstance(value, type(obj_mem)): - # check that they are type-safe - setattr(obj, key, value) - else: - raise ValueError( - f"[Config]: Incorrect type under namespace: {key_ns}." - f" Expected: {type(obj_mem)}, Received: {type(value)}." - ) - else: - raise KeyError(f"[Config]: Key not found under namespace: {key_ns}.")
- - -""" -Dictionary <-> Hashable operations. -""" - - -
[文档]def dict_to_md5_hash(data: object) -> str: - """Convert a dictionary into a hashable key using MD5 hash. - - Args: - data: Input dictionary or configuration object to convert. - - Returns: - A string object of double length containing only hexadecimal digits. - """ - # convert to dictionary - if isinstance(data, dict): - encoded_buffer = json.dumps(data, sort_keys=True).encode() - else: - encoded_buffer = json.dumps(class_to_dict(data), sort_keys=True).encode() - # compute hash using MD5 - data_hash = hashlib.md5() - data_hash.update(encoded_buffer) - # return the hash key - return data_hash.hexdigest()
- - -""" -Dictionary operations. -""" - - -
[文档]def convert_dict_to_backend( - data: dict, backend: str = "numpy", array_types: Iterable[str] = ("numpy", "torch", "warp") -) -> dict: - """Convert all arrays or tensors in a dictionary to a given backend. - - This function iterates over the dictionary, converts all arrays or tensors with the given types to - the desired backend, and stores them in a new dictionary. It also works with nested dictionaries. - - Currently supported backends are "numpy", "torch", and "warp". - - Note: - This function only converts arrays or tensors. Other types of data are left unchanged. Mutable types - (e.g. lists) are referenced by the new dictionary, so they are not copied. - - Args: - data: An input dict containing array or tensor data as values. - backend: The backend ("numpy", "torch", "warp") to which arrays in this dict should be converted. - Defaults to "numpy". - array_types: A list containing the types of arrays that should be converted to - the desired backend. Defaults to ("numpy", "torch", "warp"). - - Raises: - ValueError: If the specified ``backend`` or ``array_types`` are unknown, i.e. not in the list of supported - backends ("numpy", "torch", "warp"). - - Returns: - The updated dict with the data converted to the desired backend. - """ - # THINK: Should we also support converting to a specific device, e.g. "cuda:0"? - # Check the backend is valid. - if backend not in TENSOR_TYPE_CONVERSIONS: - raise ValueError(f"Unknown backend '{backend}'. Supported backends are 'numpy', 'torch', and 'warp'.") - # Define the conversion functions for each backend. - tensor_type_conversions = TENSOR_TYPE_CONVERSIONS[backend] - - # Parse the array types and convert them to the corresponding types: "numpy" -> np.ndarray, etc. - parsed_types = list() - for t in array_types: - # Check type is valid. - if t not in TENSOR_TYPES: - raise ValueError(f"Unknown array type: '{t}'. Supported array types are 'numpy', 'torch', and 'warp'.") - # Exclude types that match the backend, since we do not need to convert these. - if t == backend: - continue - # Convert the string types to the corresponding types. - parsed_types.append(TENSOR_TYPES[t]) - - # Convert the data to the desired backend. - output_dict = dict() - for key, value in data.items(): - # Obtain the data type of the current value. - data_type = type(value) - # -- arrays - if data_type in parsed_types: - # check if we have a known conversion. - if data_type not in tensor_type_conversions: - raise ValueError(f"No registered conversion for data type: {data_type} to {backend}!") - # convert the data to the desired backend. - output_dict[key] = tensor_type_conversions[data_type](value) - # -- nested dictionaries - elif isinstance(data[key], dict): - output_dict[key] = convert_dict_to_backend(value) - # -- everything else - else: - output_dict[key] = value - - return output_dict
- - -
[文档]def update_dict(orig_dict: dict, new_dict: collections.abc.Mapping) -> dict: - """Updates existing dictionary with values from a new dictionary. - - This function mimics the dict.update() function. However, it works for - nested dictionaries as well. - - Args: - orig_dict: The original dictionary to insert items to. - new_dict: The new dictionary to insert items from. - - Returns: - The updated dictionary. - """ - for keyname, value in new_dict.items(): - if isinstance(value, collections.abc.Mapping): - orig_dict[keyname] = update_dict(orig_dict.get(keyname, {}), value) - else: - orig_dict[keyname] = value - return orig_dict
- - - -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/interpolation/linear_interpolation.html b/docs/_build/html/_modules/omni/isaac/lab/utils/interpolation/linear_interpolation.html deleted file mode 100644 index 4a342da4e3..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/interpolation/linear_interpolation.html +++ /dev/null @@ -1,648 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.interpolation.linear_interpolation — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.interpolation.linear_interpolation 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import torch
-
-
-
[文档]class LinearInterpolation: - """Linearly interpolates a sampled scalar function for arbitrary query points. - - This class implements a linear interpolation for a scalar function. The function maps from real values, x, to - real values, y. It expects a set of samples from the function's domain, x, and the corresponding values, y. - The class allows querying the function's values at any arbitrary point. - - The interpolation is done by finding the two closest points in x to the query point and then linearly - interpolating between the corresponding y values. For the query points that are outside the input points, - the class does a zero-order-hold extrapolation based on the boundary values. This means that the class - returns the value of the closest point in x. - """ - -
[文档] def __init__(self, x: torch.Tensor, y: torch.Tensor, device: str): - """Initializes the linear interpolation. - - The scalar function maps from real values, x, to real values, y. The input to the class is a set of samples - from the function's domain, x, and the corresponding values, y. - - Note: - The input tensor x should be sorted in ascending order. - - Args: - x: An vector of samples from the function's domain. The values should be sorted in ascending order. - Shape is (num_samples,) - y: The function's values associated to the input x. Shape is (num_samples,) - device: The device used for processing. - - Raises: - ValueError: If the input tensors are empty or have different sizes. - ValueError: If the input tensor x is not sorted in ascending order. - """ - # make sure that input tensors are 1D of size (num_samples,) - self._x = x.view(-1).clone().to(device=device) - self._y = y.view(-1).clone().to(device=device) - - # make sure sizes are correct - if self._x.numel() == 0: - raise ValueError("Input tensor x is empty!") - if self._x.numel() != self._y.numel(): - raise ValueError(f"Input tensors x and y have different sizes: {self._x.numel()} != {self._y.numel()}") - # make sure that x is sorted - if torch.any(self._x[1:] < self._x[:-1]): - raise ValueError("Input tensor x is not sorted in ascending order!")
- -
[文档] def compute(self, q: torch.Tensor) -> torch.Tensor: - """Calculates a linearly interpolated values for the query points. - - Args: - q: The query points. It can have any arbitrary shape. - - Returns: - The interpolated values at query points. It has the same shape as the input tensor. - """ - # serialized q - q_1d = q.view(-1) - # Number of elements in the x that are strictly smaller than query points (use int32 instead of int64) - num_smaller_elements = torch.sum(self._x.unsqueeze(1) < q_1d.unsqueeze(0), dim=0, dtype=torch.int) - - # The index pointing to the first element in x such that x[lower_bound_i] < q_i - # If a point is smaller that all x elements, it will assign 0 - lower_bound = torch.clamp(num_smaller_elements - 1, min=0) - # The index pointing to the first element in x such that x[upper_bound_i] >= q_i - # If a point is greater than all x elements, it will assign the last elements' index - upper_bound = torch.clamp(num_smaller_elements, max=self._x.numel() - 1) - - # compute the weight as: (q_i - x_lb) / (x_ub - x_lb) - weight = (q_1d - self._x[lower_bound]) / (self._x[upper_bound] - self._x[lower_bound]) - # If a point is out of bounds assign weight 0.0 - weight[upper_bound == lower_bound] = 0.0 - - # Perform linear interpolation - fq = self._y[lower_bound] + weight * (self._y[upper_bound] - self._y[lower_bound]) - - # deserialized fq - fq = fq.view(q.shape) - return fq
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/io/pkl.html b/docs/_build/html/_modules/omni/isaac/lab/utils/io/pkl.html deleted file mode 100644 index eac83be1c2..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/io/pkl.html +++ /dev/null @@ -1,613 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.io.pkl — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.io.pkl 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Utilities for file I/O with pickle."""
-
-import os
-import pickle
-from typing import Any
-
-
-
[文档]def load_pickle(filename: str) -> Any: - """Loads an input PKL file safely. - - Args: - filename: The path to pickled file. - - Raises: - FileNotFoundError: When the specified file does not exist. - - Returns: - The data read from the input file. - """ - if not os.path.exists(filename): - raise FileNotFoundError(f"File not found: {filename}") - with open(filename, "rb") as f: - data = pickle.load(f) - return data
- - -
[文档]def dump_pickle(filename: str, data: Any): - """Saves data into a pickle file safely. - - Note: - The function creates any missing directory along the file's path. - - Args: - filename: The path to save the file at. - data: The data to save. - """ - # check ending - if not filename.endswith("pkl"): - filename += ".pkl" - # create directory - if not os.path.exists(os.path.dirname(filename)): - os.makedirs(os.path.dirname(filename), exist_ok=True) - # save data - with open(filename, "wb") as f: - pickle.dump(data, f)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/io/yaml.html b/docs/_build/html/_modules/omni/isaac/lab/utils/io/yaml.html deleted file mode 100644 index 84522c9cb2..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/io/yaml.html +++ /dev/null @@ -1,618 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.io.yaml — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.io.yaml 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Utilities for file I/O with yaml."""
-
-import os
-import yaml
-
-from omni.isaac.lab.utils import class_to_dict
-
-
-
[文档]def load_yaml(filename: str) -> dict: - """Loads an input PKL file safely. - - Args: - filename: The path to pickled file. - - Raises: - FileNotFoundError: When the specified file does not exist. - - Returns: - The data read from the input file. - """ - if not os.path.exists(filename): - raise FileNotFoundError(f"File not found: {filename}") - with open(filename) as f: - data = yaml.full_load(f) - return data
- - -
[文档]def dump_yaml(filename: str, data: dict | object, sort_keys: bool = False): - """Saves data into a YAML file safely. - - Note: - The function creates any missing directory along the file's path. - - Args: - filename: The path to save the file at. - data: The data to save either a dictionary or class object. - sort_keys: Whether to sort the keys in the output file. Defaults to False. - """ - # check ending - if not filename.endswith("yaml"): - filename += ".yaml" - # create directory - if not os.path.exists(os.path.dirname(filename)): - os.makedirs(os.path.dirname(filename), exist_ok=True) - # convert data into dictionary - if not isinstance(data, dict): - data = class_to_dict(data) - # save data - with open(filename, "w") as f: - yaml.dump(data, f, default_flow_style=False, sort_keys=sort_keys)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/math.html b/docs/_build/html/_modules/omni/isaac/lab/utils/math.html deleted file mode 100644 index 5d2cd09421..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/math.html +++ /dev/null @@ -1,1858 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.math — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.math 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module containing utilities for various math operations."""
-
-# needed to import for allowing type-hinting: torch.Tensor | np.ndarray
-from __future__ import annotations
-
-import numpy as np
-import torch
-import torch.nn.functional
-from typing import Literal
-
-"""
-General
-"""
-
-
-@torch.jit.script
-def scale_transform(x: torch.Tensor, lower: torch.Tensor, upper: torch.Tensor) -> torch.Tensor:
-    """Normalizes a given input tensor to a range of [-1, 1].
-
-    .. note::
-        It uses pytorch broadcasting functionality to deal with batched input.
-
-    Args:
-        x: Input tensor of shape (N, dims).
-        lower: The minimum value of the tensor. Shape is (N, dims) or (dims,).
-        upper: The maximum value of the tensor. Shape is (N, dims) or (dims,).
-
-    Returns:
-        Normalized transform of the tensor. Shape is (N, dims).
-    """
-    # default value of center
-    offset = (lower + upper) * 0.5
-    # return normalized tensor
-    return 2 * (x - offset) / (upper - lower)
-
-
-@torch.jit.script
-def unscale_transform(x: torch.Tensor, lower: torch.Tensor, upper: torch.Tensor) -> torch.Tensor:
-    """De-normalizes a given input tensor from range of [-1, 1] to (lower, upper).
-
-    .. note::
-        It uses pytorch broadcasting functionality to deal with batched input.
-
-    Args:
-        x: Input tensor of shape (N, dims).
-        lower: The minimum value of the tensor. Shape is (N, dims) or (dims,).
-        upper: The maximum value of the tensor. Shape is (N, dims) or (dims,).
-
-    Returns:
-        De-normalized transform of the tensor. Shape is (N, dims).
-    """
-    # default value of center
-    offset = (lower + upper) * 0.5
-    # return normalized tensor
-    return x * (upper - lower) * 0.5 + offset
-
-
-@torch.jit.script
-def saturate(x: torch.Tensor, lower: torch.Tensor, upper: torch.Tensor) -> torch.Tensor:
-    """Clamps a given input tensor to (lower, upper).
-
-    It uses pytorch broadcasting functionality to deal with batched input.
-
-    Args:
-        x: Input tensor of shape (N, dims).
-        lower: The minimum value of the tensor. Shape is (N, dims) or (dims,).
-        upper: The maximum value of the tensor. Shape is (N, dims) or (dims,).
-
-    Returns:
-        Clamped transform of the tensor. Shape is (N, dims).
-    """
-    return torch.max(torch.min(x, upper), lower)
-
-
-@torch.jit.script
-def normalize(x: torch.Tensor, eps: float = 1e-9) -> torch.Tensor:
-    """Normalizes a given input tensor to unit length.
-
-    Args:
-        x: Input tensor of shape (N, dims).
-        eps: A small value to avoid division by zero. Defaults to 1e-9.
-
-    Returns:
-        Normalized tensor of shape (N, dims).
-    """
-    return x / x.norm(p=2, dim=-1).clamp(min=eps, max=None).unsqueeze(-1)
-
-
-@torch.jit.script
-def wrap_to_pi(angles: torch.Tensor) -> torch.Tensor:
-    """Wraps input angles (in radians) to the range [-pi, pi].
-
-    Args:
-        angles: Input angles of any shape.
-
-    Returns:
-        Angles in the range [-pi, pi].
-    """
-    angles = angles.clone()
-    angles %= 2 * torch.pi
-    angles -= 2 * torch.pi * (angles > torch.pi)
-    return angles
-
-
-@torch.jit.script
-def copysign(mag: float, other: torch.Tensor) -> torch.Tensor:
-    """Create a new floating-point tensor with the magnitude of input and the sign of other, element-wise.
-
-    Note:
-        The implementation follows from `torch.copysign`. The function allows a scalar magnitude.
-
-    Args:
-        mag: The magnitude scalar.
-        other: The tensor containing values whose signbits are applied to magnitude.
-
-    Returns:
-        The output tensor.
-    """
-    mag = torch.tensor(mag, device=other.device, dtype=torch.float).repeat(other.shape[0])
-    return torch.abs(mag) * torch.sign(other)
-
-
-"""
-Rotation
-"""
-
-
-@torch.jit.script
-def matrix_from_quat(quaternions: torch.Tensor) -> torch.Tensor:
-    """Convert rotations given as quaternions to rotation matrices.
-
-    Args:
-        quaternions: The quaternion orientation in (w, x, y, z). Shape is (..., 4).
-
-    Returns:
-        Rotation matrices. The shape is (..., 3, 3).
-
-    Reference:
-        https://github.com/facebookresearch/pytorch3d/blob/main/pytorch3d/transforms/rotation_conversions.py#L41-L70
-    """
-    r, i, j, k = torch.unbind(quaternions, -1)
-    # pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
-    two_s = 2.0 / (quaternions * quaternions).sum(-1)
-
-    o = torch.stack(
-        (
-            1 - two_s * (j * j + k * k),
-            two_s * (i * j - k * r),
-            two_s * (i * k + j * r),
-            two_s * (i * j + k * r),
-            1 - two_s * (i * i + k * k),
-            two_s * (j * k - i * r),
-            two_s * (i * k - j * r),
-            two_s * (j * k + i * r),
-            1 - two_s * (i * i + j * j),
-        ),
-        -1,
-    )
-    return o.reshape(quaternions.shape[:-1] + (3, 3))
-
-
-
[文档]def convert_quat(quat: torch.Tensor | np.ndarray, to: Literal["xyzw", "wxyz"] = "xyzw") -> torch.Tensor | np.ndarray: - """Converts quaternion from one convention to another. - - The convention to convert TO is specified as an optional argument. If to == 'xyzw', - then the input is in 'wxyz' format, and vice-versa. - - Args: - quat: The quaternion of shape (..., 4). - to: Convention to convert quaternion to.. Defaults to "xyzw". - - Returns: - The converted quaternion in specified convention. - - Raises: - ValueError: Invalid input argument `to`, i.e. not "xyzw" or "wxyz". - ValueError: Invalid shape of input `quat`, i.e. not (..., 4,). - """ - # check input is correct - if quat.shape[-1] != 4: - msg = f"Expected input quaternion shape mismatch: {quat.shape} != (..., 4)." - raise ValueError(msg) - if to not in ["xyzw", "wxyz"]: - msg = f"Expected input argument `to` to be 'xyzw' or 'wxyz'. Received: {to}." - raise ValueError(msg) - # check if input is numpy array (we support this backend since some classes use numpy) - if isinstance(quat, np.ndarray): - # use numpy functions - if to == "xyzw": - # wxyz -> xyzw - return np.roll(quat, -1, axis=-1) - else: - # xyzw -> wxyz - return np.roll(quat, 1, axis=-1) - else: - # convert to torch (sanity check) - if not isinstance(quat, torch.Tensor): - quat = torch.tensor(quat, dtype=float) - # convert to specified quaternion type - if to == "xyzw": - # wxyz -> xyzw - return quat.roll(-1, dims=-1) - else: - # xyzw -> wxyz - return quat.roll(1, dims=-1)
- - -@torch.jit.script -def quat_conjugate(q: torch.Tensor) -> torch.Tensor: - """Computes the conjugate of a quaternion. - - Args: - q: The quaternion orientation in (w, x, y, z). Shape is (..., 4). - - Returns: - The conjugate quaternion in (w, x, y, z). Shape is (..., 4). - """ - shape = q.shape - q = q.reshape(-1, 4) - return torch.cat((q[:, 0:1], -q[:, 1:]), dim=-1).view(shape) - - -@torch.jit.script -def quat_inv(q: torch.Tensor) -> torch.Tensor: - """Compute the inverse of a quaternion. - - Args: - q: The quaternion orientation in (w, x, y, z). Shape is (N, 4). - - Returns: - The inverse quaternion in (w, x, y, z). Shape is (N, 4). - """ - return normalize(quat_conjugate(q)) - - -@torch.jit.script -def quat_from_euler_xyz(roll: torch.Tensor, pitch: torch.Tensor, yaw: torch.Tensor) -> torch.Tensor: - """Convert rotations given as Euler angles in radians to Quaternions. - - Note: - The euler angles are assumed in XYZ convention. - - Args: - roll: Rotation around x-axis (in radians). Shape is (N,). - pitch: Rotation around y-axis (in radians). Shape is (N,). - yaw: Rotation around z-axis (in radians). Shape is (N,). - - Returns: - The quaternion in (w, x, y, z). Shape is (N, 4). - """ - cy = torch.cos(yaw * 0.5) - sy = torch.sin(yaw * 0.5) - cr = torch.cos(roll * 0.5) - sr = torch.sin(roll * 0.5) - cp = torch.cos(pitch * 0.5) - sp = torch.sin(pitch * 0.5) - # compute quaternion - qw = cy * cr * cp + sy * sr * sp - qx = cy * sr * cp - sy * cr * sp - qy = cy * cr * sp + sy * sr * cp - qz = sy * cr * cp - cy * sr * sp - - return torch.stack([qw, qx, qy, qz], dim=-1) - - -@torch.jit.script -def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor: - """Returns torch.sqrt(torch.max(0, x)) but with a zero sub-gradient where x is 0. - - Reference: - https://github.com/facebookresearch/pytorch3d/blob/main/pytorch3d/transforms/rotation_conversions.py#L91-L99 - """ - ret = torch.zeros_like(x) - positive_mask = x > 0 - ret[positive_mask] = torch.sqrt(x[positive_mask]) - return ret - - -@torch.jit.script -def quat_from_matrix(matrix: torch.Tensor) -> torch.Tensor: - """Convert rotations given as rotation matrices to quaternions. - - Args: - matrix: The rotation matrices. Shape is (..., 3, 3). - - Returns: - The quaternion in (w, x, y, z). Shape is (..., 4). - - Reference: - https://github.com/facebookresearch/pytorch3d/blob/main/pytorch3d/transforms/rotation_conversions.py#L102-L161 - """ - if matrix.size(-1) != 3 or matrix.size(-2) != 3: - raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.") - - batch_dim = matrix.shape[:-2] - m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(matrix.reshape(batch_dim + (9,)), dim=-1) - - q_abs = _sqrt_positive_part( - torch.stack( - [ - 1.0 + m00 + m11 + m22, - 1.0 + m00 - m11 - m22, - 1.0 - m00 + m11 - m22, - 1.0 - m00 - m11 + m22, - ], - dim=-1, - ) - ) - - # we produce the desired quaternion multiplied by each of r, i, j, k - quat_by_rijk = torch.stack( - [ - # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`. - torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1), - # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`. - torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1), - # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`. - torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1), - # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`. - torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1), - ], - dim=-2, - ) - - # We floor here at 0.1 but the exact level is not important; if q_abs is small, - # the candidate won't be picked. - flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device) - quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr)) - - # if not for numerical problems, quat_candidates[i] should be same (up to a sign), - # forall i; we pick the best-conditioned one (with the largest denominator) - return quat_candidates[torch.nn.functional.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :].reshape( - batch_dim + (4,) - ) - - -def _axis_angle_rotation(axis: Literal["X", "Y", "Z"], angle: torch.Tensor) -> torch.Tensor: - """Return the rotation matrices for one of the rotations about an axis of which Euler angles describe, - for each value of the angle given. - - Args: - axis: Axis label "X" or "Y or "Z". - angle: Euler angles in radians of any shape. - - Returns: - Rotation matrices. Shape is (..., 3, 3). - - Reference: - https://github.com/facebookresearch/pytorch3d/blob/main/pytorch3d/transforms/rotation_conversions.py#L164-L191 - """ - cos = torch.cos(angle) - sin = torch.sin(angle) - one = torch.ones_like(angle) - zero = torch.zeros_like(angle) - - if axis == "X": - R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos) - elif axis == "Y": - R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos) - elif axis == "Z": - R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one) - else: - raise ValueError("letter must be either X, Y or Z.") - - return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3)) - - -
[文档]def matrix_from_euler(euler_angles: torch.Tensor, convention: str) -> torch.Tensor: - """ - Convert rotations given as Euler angles in radians to rotation matrices. - - Args: - euler_angles: Euler angles in radians. Shape is (..., 3). - convention: Convention string of three uppercase letters from {"X", "Y", and "Z"}. - For example, "XYZ" means that the rotations should be applied first about x, - then y, then z. - - Returns: - Rotation matrices. Shape is (..., 3, 3). - - Reference: - https://github.com/facebookresearch/pytorch3d/blob/main/pytorch3d/transforms/rotation_conversions.py#L194-L220 - """ - if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3: - raise ValueError("Invalid input euler angles.") - if len(convention) != 3: - raise ValueError("Convention must have 3 letters.") - if convention[1] in (convention[0], convention[2]): - raise ValueError(f"Invalid convention {convention}.") - for letter in convention: - if letter not in ("X", "Y", "Z"): - raise ValueError(f"Invalid letter {letter} in convention string.") - matrices = [_axis_angle_rotation(c, e) for c, e in zip(convention, torch.unbind(euler_angles, -1))] - # return functools.reduce(torch.matmul, matrices) - return torch.matmul(torch.matmul(matrices[0], matrices[1]), matrices[2])
- - -@torch.jit.script -def euler_xyz_from_quat(quat: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: - """Convert rotations given as quaternions to Euler angles in radians. - - Note: - The euler angles are assumed in XYZ convention. - - Args: - quat: The quaternion orientation in (w, x, y, z). Shape is (N, 4). - - Returns: - A tuple containing roll-pitch-yaw. Each element is a tensor of shape (N,). - - Reference: - https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles - """ - q_w, q_x, q_y, q_z = quat[:, 0], quat[:, 1], quat[:, 2], quat[:, 3] - # roll (x-axis rotation) - sin_roll = 2.0 * (q_w * q_x + q_y * q_z) - cos_roll = 1 - 2 * (q_x * q_x + q_y * q_y) - roll = torch.atan2(sin_roll, cos_roll) - - # pitch (y-axis rotation) - sin_pitch = 2.0 * (q_w * q_y - q_z * q_x) - pitch = torch.where(torch.abs(sin_pitch) >= 1, copysign(torch.pi / 2.0, sin_pitch), torch.asin(sin_pitch)) - - # yaw (z-axis rotation) - sin_yaw = 2.0 * (q_w * q_z + q_x * q_y) - cos_yaw = 1 - 2 * (q_y * q_y + q_z * q_z) - yaw = torch.atan2(sin_yaw, cos_yaw) - - return roll % (2 * torch.pi), pitch % (2 * torch.pi), yaw % (2 * torch.pi) # TODO: why not wrap_to_pi here ? - - -@torch.jit.script -def quat_unique(q: torch.Tensor) -> torch.Tensor: - """Convert a unit quaternion to a standard form where the real part is non-negative. - - Quaternion representations have a singularity since ``q`` and ``-q`` represent the same - rotation. This function ensures the real part of the quaternion is non-negative. - - Args: - q: The quaternion orientation in (w, x, y, z). Shape is (..., 4). - - Returns: - Standardized quaternions. Shape is (..., 4). - """ - return torch.where(q[..., 0:1] < 0, -q, q) - - -@torch.jit.script -def quat_mul(q1: torch.Tensor, q2: torch.Tensor) -> torch.Tensor: - """Multiply two quaternions together. - - Args: - q1: The first quaternion in (w, x, y, z). Shape is (..., 4). - q2: The second quaternion in (w, x, y, z). Shape is (..., 4). - - Returns: - The product of the two quaternions in (w, x, y, z). Shape is (..., 4). - - Raises: - ValueError: Input shapes of ``q1`` and ``q2`` are not matching. - """ - # check input is correct - if q1.shape != q2.shape: - msg = f"Expected input quaternion shape mismatch: {q1.shape} != {q2.shape}." - raise ValueError(msg) - # reshape to (N, 4) for multiplication - shape = q1.shape - q1 = q1.reshape(-1, 4) - q2 = q2.reshape(-1, 4) - # extract components from quaternions - w1, x1, y1, z1 = q1[:, 0], q1[:, 1], q1[:, 2], q1[:, 3] - w2, x2, y2, z2 = q2[:, 0], q2[:, 1], q2[:, 2], q2[:, 3] - # perform multiplication - ww = (z1 + x1) * (x2 + y2) - yy = (w1 - y1) * (w2 + z2) - zz = (w1 + y1) * (w2 - z2) - xx = ww + yy + zz - qq = 0.5 * (xx + (z1 - x1) * (x2 - y2)) - w = qq - ww + (z1 - y1) * (y2 - z2) - x = qq - xx + (x1 + w1) * (x2 + w2) - y = qq - yy + (w1 - x1) * (y2 + z2) - z = qq - zz + (z1 + y1) * (w2 - x2) - - return torch.stack([w, x, y, z], dim=-1).view(shape) - - -@torch.jit.script -def quat_box_minus(q1: torch.Tensor, q2: torch.Tensor) -> torch.Tensor: - """The box-minus operator (quaternion difference) between two quaternions. - - Args: - q1: The first quaternion in (w, x, y, z). Shape is (N, 4). - q2: The second quaternion in (w, x, y, z). Shape is (N, 4). - - Returns: - The difference between the two quaternions. Shape is (N, 3). - """ - quat_diff = quat_mul(q1, quat_conjugate(q2)) # q1 * q2^-1 - re = quat_diff[:, 0] # real part, q = [w, x, y, z] = [re, im] - im = quat_diff[:, 1:] # imaginary part - norm_im = torch.norm(im, dim=1) - scale = 2.0 * torch.where(norm_im > 1.0e-7, torch.atan2(norm_im, re) / norm_im, torch.sign(re)) - return scale.unsqueeze(-1) * im - - -@torch.jit.script -def yaw_quat(quat: torch.Tensor) -> torch.Tensor: - """Extract the yaw component of a quaternion. - - Args: - quat: The orientation in (w, x, y, z). Shape is (..., 4) - - Returns: - A quaternion with only yaw component. - """ - shape = quat.shape - quat_yaw = quat.clone().view(-1, 4) - qw = quat_yaw[:, 0] - qx = quat_yaw[:, 1] - qy = quat_yaw[:, 2] - qz = quat_yaw[:, 3] - yaw = torch.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy * qy + qz * qz)) - quat_yaw[:] = 0.0 - quat_yaw[:, 3] = torch.sin(yaw / 2) - quat_yaw[:, 0] = torch.cos(yaw / 2) - quat_yaw = normalize(quat_yaw) - return quat_yaw.view(shape) - - -@torch.jit.script -def quat_apply(quat: torch.Tensor, vec: torch.Tensor) -> torch.Tensor: - """Apply a quaternion rotation to a vector. - - Args: - quat: The quaternion in (w, x, y, z). Shape is (..., 4). - vec: The vector in (x, y, z). Shape is (..., 3). - - Returns: - The rotated vector in (x, y, z). Shape is (..., 3). - """ - # store shape - shape = vec.shape - # reshape to (N, 3) for multiplication - quat = quat.reshape(-1, 4) - vec = vec.reshape(-1, 3) - # extract components from quaternions - xyz = quat[:, 1:] - t = xyz.cross(vec, dim=-1) * 2 - return (vec + quat[:, 0:1] * t + xyz.cross(t, dim=-1)).view(shape) - - -@torch.jit.script -def quat_apply_yaw(quat: torch.Tensor, vec: torch.Tensor) -> torch.Tensor: - """Rotate a vector only around the yaw-direction. - - Args: - quat: The orientation in (w, x, y, z). Shape is (N, 4). - vec: The vector in (x, y, z). Shape is (N, 3). - - Returns: - The rotated vector in (x, y, z). Shape is (N, 3). - """ - quat_yaw = yaw_quat(quat) - return quat_apply(quat_yaw, vec) - - -@torch.jit.script -def quat_rotate(q: torch.Tensor, v: torch.Tensor) -> torch.Tensor: - """Rotate a vector by a quaternion. - - Args: - q: The quaternion in (w, x, y, z). Shape is (N, 4). - v: The vector in (x, y, z). Shape is (N, 3). - - Returns: - The rotated vector in (x, y, z). Shape is (N, 3). - """ - shape = q.shape - q_w = q[:, 0] - q_vec = q[:, 1:] - a = v * (2.0 * q_w**2 - 1.0).unsqueeze(-1) - b = torch.cross(q_vec, v, dim=-1) * q_w.unsqueeze(-1) * 2.0 - c = q_vec * torch.bmm(q_vec.view(shape[0], 1, 3), v.view(shape[0], 3, 1)).squeeze(-1) * 2.0 - return a + b + c - - -@torch.jit.script -def quat_rotate_inverse(q: torch.Tensor, v: torch.Tensor) -> torch.Tensor: - """Rotate a vector by the inverse of a quaternion. - - Args: - q: The quaternion in (w, x, y, z). Shape is (N, 4). - v: The vector in (x, y, z). Shape is (N, 3). - - Returns: - The rotated vector in (x, y, z). Shape is (N, 3). - """ - shape = q.shape - q_w = q[:, 0] - q_vec = q[:, 1:] - a = v * (2.0 * q_w**2 - 1.0).unsqueeze(-1) - b = torch.cross(q_vec, v, dim=-1) * q_w.unsqueeze(-1) * 2.0 - c = q_vec * torch.bmm(q_vec.view(shape[0], 1, 3), v.view(shape[0], 3, 1)).squeeze(-1) * 2.0 - return a - b + c - - -@torch.jit.script -def quat_from_angle_axis(angle: torch.Tensor, axis: torch.Tensor) -> torch.Tensor: - """Convert rotations given as angle-axis to quaternions. - - Args: - angle: The angle turned anti-clockwise in radians around the vector's direction. Shape is (N,). - axis: The axis of rotation. Shape is (N, 3). - - Returns: - The quaternion in (w, x, y, z). Shape is (N, 4). - """ - theta = (angle / 2).unsqueeze(-1) - xyz = normalize(axis) * theta.sin() - w = theta.cos() - return normalize(torch.cat([w, xyz], dim=-1)) - - -@torch.jit.script -def axis_angle_from_quat(quat: torch.Tensor, eps: float = 1.0e-6) -> torch.Tensor: - """Convert rotations given as quaternions to axis/angle. - - Args: - quat: The quaternion orientation in (w, x, y, z). Shape is (..., 4). - eps: The tolerance for Taylor approximation. Defaults to 1.0e-6. - - Returns: - Rotations given as a vector in axis angle form. Shape is (..., 3). - The vector's magnitude is the angle turned anti-clockwise in radians around the vector's direction. - - Reference: - https://github.com/facebookresearch/pytorch3d/blob/main/pytorch3d/transforms/rotation_conversions.py#L526-L554 - """ - # Modified to take in quat as [q_w, q_x, q_y, q_z] - # Quaternion is [q_w, q_x, q_y, q_z] = [cos(theta/2), n_x * sin(theta/2), n_y * sin(theta/2), n_z * sin(theta/2)] - # Axis-angle is [a_x, a_y, a_z] = [theta * n_x, theta * n_y, theta * n_z] - # Thus, axis-angle is [q_x, q_y, q_z] / (sin(theta/2) / theta) - # When theta = 0, (sin(theta/2) / theta) is undefined - # However, as theta --> 0, we can use the Taylor approximation 1/2 - theta^2 / 48 - quat = quat * (1.0 - 2.0 * (quat[..., 0:1] < 0.0)) - mag = torch.linalg.norm(quat[..., 1:], dim=-1) - half_angle = torch.atan2(mag, quat[..., 0]) - angle = 2.0 * half_angle - # check whether to apply Taylor approximation - sin_half_angles_over_angles = torch.where( - angle.abs() > eps, torch.sin(half_angle) / angle, 0.5 - angle * angle / 48 - ) - return quat[..., 1:4] / sin_half_angles_over_angles.unsqueeze(-1) - - -@torch.jit.script -def quat_error_magnitude(q1: torch.Tensor, q2: torch.Tensor) -> torch.Tensor: - """Computes the rotation difference between two quaternions. - - Args: - q1: The first quaternion in (w, x, y, z). Shape is (..., 4). - q2: The second quaternion in (w, x, y, z). Shape is (..., 4). - - Returns: - Angular error between input quaternions in radians. - """ - quat_diff = quat_mul(q1, quat_conjugate(q2)) - return torch.norm(axis_angle_from_quat(quat_diff), dim=-1) - - -@torch.jit.script -def skew_symmetric_matrix(vec: torch.Tensor) -> torch.Tensor: - """Computes the skew-symmetric matrix of a vector. - - Args: - vec: The input vector. Shape is (3,) or (N, 3). - - Returns: - The skew-symmetric matrix. Shape is (1, 3, 3) or (N, 3, 3). - - Raises: - ValueError: If input tensor is not of shape (..., 3). - """ - # check input is correct - if vec.shape[-1] != 3: - raise ValueError(f"Expected input vector shape mismatch: {vec.shape} != (..., 3).") - # unsqueeze the last dimension - if vec.ndim == 1: - vec = vec.unsqueeze(0) - # create a skew-symmetric matrix - skew_sym_mat = torch.zeros(vec.shape[0], 3, 3, device=vec.device, dtype=vec.dtype) - skew_sym_mat[:, 0, 1] = -vec[:, 2] - skew_sym_mat[:, 0, 2] = vec[:, 1] - skew_sym_mat[:, 1, 2] = -vec[:, 0] - skew_sym_mat[:, 1, 0] = vec[:, 2] - skew_sym_mat[:, 2, 0] = -vec[:, 1] - skew_sym_mat[:, 2, 1] = vec[:, 0] - - return skew_sym_mat - - -""" -Transformations -""" - - -
[文档]def is_identity_pose(pos: torch.tensor, rot: torch.tensor) -> bool: - """Checks if input poses are identity transforms. - - The function checks if the input position and orientation are close to zero and - identity respectively using L2-norm. It does NOT check the error in the orientation. - - Args: - pos: The cartesian position. Shape is (N, 3). - rot: The quaternion in (w, x, y, z). Shape is (N, 4). - - Returns: - True if all the input poses result in identity transform. Otherwise, False. - """ - # create identity transformations - pos_identity = torch.zeros_like(pos) - rot_identity = torch.zeros_like(rot) - rot_identity[..., 0] = 1 - # compare input to identity - return torch.allclose(pos, pos_identity) and torch.allclose(rot, rot_identity)
- - -# @torch.jit.script -
[文档]def combine_frame_transforms( - t01: torch.Tensor, q01: torch.Tensor, t12: torch.Tensor | None = None, q12: torch.Tensor | None = None -) -> tuple[torch.Tensor, torch.Tensor]: - r"""Combine transformations between two reference frames into a stationary frame. - - It performs the following transformation operation: :math:`T_{02} = T_{01} \times T_{12}`, - where :math:`T_{AB}` is the homogeneous transformation matrix from frame A to B. - - Args: - t01: Position of frame 1 w.r.t. frame 0. Shape is (N, 3). - q01: Quaternion orientation of frame 1 w.r.t. frame 0 in (w, x, y, z). Shape is (N, 4). - t12: Position of frame 2 w.r.t. frame 1. Shape is (N, 3). - Defaults to None, in which case the position is assumed to be zero. - q12: Quaternion orientation of frame 2 w.r.t. frame 1 in (w, x, y, z). Shape is (N, 4). - Defaults to None, in which case the orientation is assumed to be identity. - - Returns: - A tuple containing the position and orientation of frame 2 w.r.t. frame 0. - Shape of the tensors are (N, 3) and (N, 4) respectively. - """ - # compute orientation - if q12 is not None: - q02 = quat_mul(q01, q12) - else: - q02 = q01 - # compute translation - if t12 is not None: - t02 = t01 + quat_apply(q01, t12) - else: - t02 = t01 - - return t02, q02
- - -# @torch.jit.script -
[文档]def subtract_frame_transforms( - t01: torch.Tensor, q01: torch.Tensor, t02: torch.Tensor | None = None, q02: torch.Tensor | None = None -) -> tuple[torch.Tensor, torch.Tensor]: - r"""Subtract transformations between two reference frames into a stationary frame. - - It performs the following transformation operation: :math:`T_{12} = T_{01}^{-1} \times T_{02}`, - where :math:`T_{AB}` is the homogeneous transformation matrix from frame A to B. - - Args: - t01: Position of frame 1 w.r.t. frame 0. Shape is (N, 3). - q01: Quaternion orientation of frame 1 w.r.t. frame 0 in (w, x, y, z). Shape is (N, 4). - t02: Position of frame 2 w.r.t. frame 0. Shape is (N, 3). - Defaults to None, in which case the position is assumed to be zero. - q02: Quaternion orientation of frame 2 w.r.t. frame 0 in (w, x, y, z). Shape is (N, 4). - Defaults to None, in which case the orientation is assumed to be identity. - - Returns: - A tuple containing the position and orientation of frame 2 w.r.t. frame 1. - Shape of the tensors are (N, 3) and (N, 4) respectively. - """ - # compute orientation - q10 = quat_inv(q01) - if q02 is not None: - q12 = quat_mul(q10, q02) - else: - q12 = q10 - # compute translation - if t02 is not None: - t12 = quat_apply(q10, t02 - t01) - else: - t12 = quat_apply(q10, -t01) - return t12, q12
- - -# @torch.jit.script -
[文档]def compute_pose_error( - t01: torch.Tensor, - q01: torch.Tensor, - t02: torch.Tensor, - q02: torch.Tensor, - rot_error_type: Literal["quat", "axis_angle"] = "axis_angle", -) -> tuple[torch.Tensor, torch.Tensor]: - """Compute the position and orientation error between source and target frames. - - Args: - t01: Position of source frame. Shape is (N, 3). - q01: Quaternion orientation of source frame in (w, x, y, z). Shape is (N, 4). - t02: Position of target frame. Shape is (N, 3). - q02: Quaternion orientation of target frame in (w, x, y, z). Shape is (N, 4). - rot_error_type: The rotation error type to return: "quat", "axis_angle". - Defaults to "axis_angle". - - Returns: - A tuple containing position and orientation error. Shape of position error is (N, 3). - Shape of orientation error depends on the value of :attr:`rot_error_type`: - - - If :attr:`rot_error_type` is "quat", the orientation error is returned - as a quaternion. Shape is (N, 4). - - If :attr:`rot_error_type` is "axis_angle", the orientation error is - returned as an axis-angle vector. Shape is (N, 3). - - Raises: - ValueError: Invalid rotation error type. - """ - # Compute quaternion error (i.e., difference quaternion) - # Reference: https://personal.utdallas.edu/~sxb027100/dock/quaternion.html - # q_current_norm = q_current * q_current_conj - source_quat_norm = quat_mul(q01, quat_conjugate(q01))[:, 0] - # q_current_inv = q_current_conj / q_current_norm - source_quat_inv = quat_conjugate(q01) / source_quat_norm.unsqueeze(-1) - # q_error = q_target * q_current_inv - quat_error = quat_mul(q02, source_quat_inv) - - # Compute position error - pos_error = t02 - t01 - - # return error based on specified type - if rot_error_type == "quat": - return pos_error, quat_error - elif rot_error_type == "axis_angle": - # Convert to axis-angle error - axis_angle_error = axis_angle_from_quat(quat_error) - return pos_error, axis_angle_error - else: - raise ValueError(f"Unsupported orientation error type: {rot_error_type}. Valid: 'quat', 'axis_angle'.")
- - -@torch.jit.script -def apply_delta_pose( - source_pos: torch.Tensor, source_rot: torch.Tensor, delta_pose: torch.Tensor, eps: float = 1.0e-6 -) -> tuple[torch.Tensor, torch.Tensor]: - """Applies delta pose transformation on source pose. - - The first three elements of `delta_pose` are interpreted as cartesian position displacement. - The remaining three elements of `delta_pose` are interpreted as orientation displacement - in the angle-axis format. - - Args: - source_pos: Position of source frame. Shape is (N, 3). - source_rot: Quaternion orientation of source frame in (w, x, y, z). Shape is (N, 4).. - delta_pose: Position and orientation displacements. Shape is (N, 6). - eps: The tolerance to consider orientation displacement as zero. Defaults to 1.0e-6. - - Returns: - A tuple containing the displaced position and orientation frames. - Shape of the tensors are (N, 3) and (N, 4) respectively. - """ - # number of poses given - num_poses = source_pos.shape[0] - device = source_pos.device - - # interpret delta_pose[:, 0:3] as target position displacements - target_pos = source_pos + delta_pose[:, 0:3] - # interpret delta_pose[:, 3:6] as target rotation displacements - rot_actions = delta_pose[:, 3:6] - angle = torch.linalg.vector_norm(rot_actions, dim=1) - axis = rot_actions / angle.unsqueeze(-1) - # change from axis-angle to quat convention - identity_quat = torch.tensor([1.0, 0.0, 0.0, 0.0], device=device).repeat(num_poses, 1) - rot_delta_quat = torch.where( - angle.unsqueeze(-1).repeat(1, 4) > eps, quat_from_angle_axis(angle, axis), identity_quat - ) - # TODO: Check if this is the correct order for this multiplication. - target_rot = quat_mul(rot_delta_quat, source_rot) - - return target_pos, target_rot - - -# @torch.jit.script -
[文档]def transform_points( - points: torch.Tensor, pos: torch.Tensor | None = None, quat: torch.Tensor | None = None -) -> torch.Tensor: - r"""Transform input points in a given frame to a target frame. - - This function transform points from a source frame to a target frame. The transformation is defined by the - position :math:`t` and orientation :math:`R` of the target frame in the source frame. - - .. math:: - p_{target} = R_{target} \times p_{source} + t_{target} - - If the input `points` is a batch of points, the inputs `pos` and `quat` must be either a batch of - positions and quaternions or a single position and quaternion. If the inputs `pos` and `quat` are - a single position and quaternion, the same transformation is applied to all points in the batch. - - If either the inputs :attr:`pos` and :attr:`quat` are None, the corresponding transformation is not applied. - - Args: - points: Points to transform. Shape is (N, P, 3) or (P, 3). - pos: Position of the target frame. Shape is (N, 3) or (3,). - Defaults to None, in which case the position is assumed to be zero. - quat: Quaternion orientation of the target frame in (w, x, y, z). Shape is (N, 4) or (4,). - Defaults to None, in which case the orientation is assumed to be identity. - - Returns: - Transformed points in the target frame. Shape is (N, P, 3) or (P, 3). - - Raises: - ValueError: If the inputs `points` is not of shape (N, P, 3) or (P, 3). - ValueError: If the inputs `pos` is not of shape (N, 3) or (3,). - ValueError: If the inputs `quat` is not of shape (N, 4) or (4,). - """ - points_batch = points.clone() - # check if inputs are batched - is_batched = points_batch.dim() == 3 - # -- check inputs - if points_batch.dim() == 2: - points_batch = points_batch[None] # (P, 3) -> (1, P, 3) - if points_batch.dim() != 3: - raise ValueError(f"Expected points to have dim = 2 or dim = 3: got shape {points.shape}") - if not (pos is None or pos.dim() == 1 or pos.dim() == 2): - raise ValueError(f"Expected pos to have dim = 1 or dim = 2: got shape {pos.shape}") - if not (quat is None or quat.dim() == 1 or quat.dim() == 2): - raise ValueError(f"Expected quat to have dim = 1 or dim = 2: got shape {quat.shape}") - # -- rotation - if quat is not None: - # convert to batched rotation matrix - rot_mat = matrix_from_quat(quat) - if rot_mat.dim() == 2: - rot_mat = rot_mat[None] # (3, 3) -> (1, 3, 3) - # convert points to matching batch size (N, P, 3) -> (N, 3, P) - # and apply rotation - points_batch = torch.matmul(rot_mat, points_batch.transpose_(1, 2)) - # (N, 3, P) -> (N, P, 3) - points_batch = points_batch.transpose_(1, 2) - # -- translation - if pos is not None: - # convert to batched translation vector - if pos.dim() == 1: - pos = pos[None, None, :] # (3,) -> (1, 1, 3) - else: - pos = pos[:, None, :] # (N, 3) -> (N, 1, 3) - # apply translation - points_batch += pos - # -- return points in same shape as input - if not is_batched: - points_batch = points_batch.squeeze(0) # (1, P, 3) -> (P, 3) - - return points_batch
- - -""" -Projection operations. -""" - - -@torch.jit.script -def unproject_depth(depth: torch.Tensor, intrinsics: torch.Tensor) -> torch.Tensor: - r"""Unproject depth image into a pointcloud. - - This function converts depth images into points given the calibration matrix of the camera. - - .. math:: - p_{3D} = K^{-1} \times [u, v, 1]^T \times d - - where :math:`p_{3D}` is the 3D point, :math:`d` is the depth value, :math:`u` and :math:`v` are - the pixel coordinates and :math:`K` is the intrinsic matrix. - - If `depth` is a batch of depth images and `intrinsics` is a single intrinsic matrix, the same - calibration matrix is applied to all depth images in the batch. - - The function assumes that the width and height are both greater than 1. This makes the function - deal with many possible shapes of depth images and intrinsics matrices. - - Args: - depth: The depth measurement. Shape is (H, W) or or (H, W, 1) or (N, H, W) or (N, H, W, 1). - intrinsics: A tensor providing camera's calibration matrix. Shape is (3, 3) or (N, 3, 3). - - Returns: - The 3D coordinates of points. Shape is (P, 3) or (N, P, 3). - - Raises: - ValueError: When depth is not of shape (H, W) or (H, W, 1) or (N, H, W) or (N, H, W, 1). - ValueError: When intrinsics is not of shape (3, 3) or (N, 3, 3). - """ - depth_batch = depth.clone() - intrinsics_batch = intrinsics.clone() - # check if inputs are batched - is_batched = depth_batch.dim() == 4 or (depth_batch.dim() == 3 and depth_batch.shape[-1] != 1) - # make sure inputs are batched - if depth_batch.dim() == 3 and depth_batch.shape[-1] == 1: - depth_batch = depth_batch.squeeze(dim=2) # (H, W, 1) -> (H, W) - if depth_batch.dim() == 2: - depth_batch = depth_batch[None] # (H, W) -> (1, H, W) - if depth_batch.dim() == 4 and depth_batch.shape[-1] == 1: - depth_batch = depth_batch.squeeze(dim=3) # (N, H, W, 1) -> (N, H, W) - if intrinsics_batch.dim() == 2: - intrinsics_batch = intrinsics_batch[None] # (3, 3) -> (1, 3, 3) - # check shape of inputs - if depth_batch.dim() != 3: - raise ValueError(f"Expected depth images to have dim = 2 or 3 or 4: got shape {depth.shape}") - if intrinsics_batch.dim() != 3: - raise ValueError(f"Expected intrinsics to have shape (3, 3) or (N, 3, 3): got shape {intrinsics.shape}") - - # get image height and width - im_height, im_width = depth_batch.shape[1:] - # create image points in homogeneous coordinates (3, H x W) - indices_u = torch.arange(im_width, device=depth.device, dtype=depth.dtype) - indices_v = torch.arange(im_height, device=depth.device, dtype=depth.dtype) - img_indices = torch.stack(torch.meshgrid([indices_u, indices_v], indexing="ij"), dim=0).reshape(2, -1) - pixels = torch.nn.functional.pad(img_indices, (0, 0, 0, 1), mode="constant", value=1.0) - pixels = pixels.unsqueeze(0) # (3, H x W) -> (1, 3, H x W) - - # unproject points into 3D space - points = torch.matmul(torch.inverse(intrinsics_batch), pixels) # (N, 3, H x W) - points = points / points[:, -1, :].unsqueeze(1) # normalize by last coordinate - # flatten depth image (N, H, W) -> (N, H x W) - depth_batch = depth_batch.transpose_(1, 2).reshape(depth_batch.shape[0], -1).unsqueeze(2) - depth_batch = depth_batch.expand(-1, -1, 3) - # scale points by depth - points_xyz = points.transpose_(1, 2) * depth_batch # (N, H x W, 3) - - # return points in same shape as input - if not is_batched: - points_xyz = points_xyz.squeeze(0) - - return points_xyz - - -@torch.jit.script -def project_points(points: torch.Tensor, intrinsics: torch.Tensor) -> torch.Tensor: - r"""Projects 3D points into 2D image plane. - - This project 3D points into a 2D image plane. The transformation is defined by the intrinsic - matrix of the camera. - - .. math:: - - \begin{align} - p &= K \times p_{3D} = \\ - p_{2D} &= \begin{pmatrix} u \\ v \\ d \end{pmatrix} - = \begin{pmatrix} p[0] / p[2] \\ p[1] / p[2] \\ Z \end{pmatrix} - \end{align} - - where :math:`p_{2D} = (u, v, d)` is the projected 3D point, :math:`p_{3D} = (X, Y, Z)` is the - 3D point and :math:`K \in \mathbb{R}^{3 \times 3}` is the intrinsic matrix. - - If `points` is a batch of 3D points and `intrinsics` is a single intrinsic matrix, the same - calibration matrix is applied to all points in the batch. - - Args: - points: The 3D coordinates of points. Shape is (P, 3) or (N, P, 3). - intrinsics: Camera's calibration matrix. Shape is (3, 3) or (N, 3, 3). - - Returns: - Projected 3D coordinates of points. Shape is (P, 3) or (N, P, 3). - """ - points_batch = points.clone() - intrinsics_batch = intrinsics.clone() - # check if inputs are batched - is_batched = points_batch.dim() == 2 - # make sure inputs are batched - if points_batch.dim() == 2: - points_batch = points_batch[None] # (P, 3) -> (1, P, 3) - if intrinsics_batch.dim() == 2: - intrinsics_batch = intrinsics_batch[None] # (3, 3) -> (1, 3, 3) - # check shape of inputs - if points_batch.dim() != 3: - raise ValueError(f"Expected points to have dim = 3: got shape {points.shape}.") - if intrinsics_batch.dim() != 3: - raise ValueError(f"Expected intrinsics to have shape (3, 3) or (N, 3, 3): got shape {intrinsics.shape}.") - # project points into 2D image plane - points_2d = torch.matmul(intrinsics_batch, points_batch.transpose(1, 2)) - points_2d = points_2d / points_2d[:, -1, :].unsqueeze(1) # normalize by last coordinate - points_2d = points_2d.transpose_(1, 2) # (N, 3, P) -> (N, P, 3) - # replace last coordinate with depth - points_2d[:, :, -1] = points_batch[:, :, -1] - # return points in same shape as input - if not is_batched: - points_2d = points_2d.squeeze(0) # (1, 3, P) -> (3, P) - - return points_2d - - -""" -Sampling -""" - - -@torch.jit.script -def default_orientation(num: int, device: str) -> torch.Tensor: - """Returns identity rotation transform. - - Args: - num: The number of rotations to sample. - device: Device to create tensor on. - - Returns: - Identity quaternion in (w, x, y, z). Shape is (num, 4). - """ - quat = torch.zeros((num, 4), dtype=torch.float, device=device) - quat[..., 0] = 1.0 - - return quat - - -@torch.jit.script -def random_orientation(num: int, device: str) -> torch.Tensor: - """Returns sampled rotation in 3D as quaternion. - - Args: - num: The number of rotations to sample. - device: Device to create tensor on. - - Returns: - Sampled quaternion in (w, x, y, z). Shape is (num, 4). - - Reference: - https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.random.html - """ - # sample random orientation from normal distribution - quat = torch.randn((num, 4), dtype=torch.float, device=device) - # normalize the quaternion - return torch.nn.functional.normalize(quat, p=2.0, dim=-1, eps=1e-12) - - -@torch.jit.script -def random_yaw_orientation(num: int, device: str) -> torch.Tensor: - """Returns sampled rotation around z-axis. - - Args: - num: The number of rotations to sample. - device: Device to create tensor on. - - Returns: - Sampled quaternion in (w, x, y, z). Shape is (num, 4). - """ - roll = torch.zeros(num, dtype=torch.float, device=device) - pitch = torch.zeros(num, dtype=torch.float, device=device) - yaw = 2 * torch.pi * torch.rand(num, dtype=torch.float, device=device) - - return quat_from_euler_xyz(roll, pitch, yaw) - - -
[文档]def sample_triangle(lower: float, upper: float, size: int | tuple[int, ...], device: str) -> torch.Tensor: - """Randomly samples tensor from a triangular distribution. - - Args: - lower: The lower range of the sampled tensor. - upper: The upper range of the sampled tensor. - size: The shape of the tensor. - device: Device to create tensor on. - - Returns: - Sampled tensor. Shape is based on :attr:`size`. - """ - # convert to tuple - if isinstance(size, int): - size = (size,) - # create random tensor in the range [-1, 1] - r = 2 * torch.rand(*size, device=device) - 1 - # convert to triangular distribution - r = torch.where(r < 0.0, -torch.sqrt(-r), torch.sqrt(r)) - # rescale back to [0, 1] - r = (r + 1.0) / 2.0 - # rescale to range [lower, upper] - return (upper - lower) * r + lower
- - -
[文档]def sample_uniform( - lower: torch.Tensor | float, upper: torch.Tensor | float, size: int | tuple[int, ...], device: str -) -> torch.Tensor: - """Sample uniformly within a range. - - Args: - lower: Lower bound of uniform range. - upper: Upper bound of uniform range. - size: The shape of the tensor. - device: Device to create tensor on. - - Returns: - Sampled tensor. Shape is based on :attr:`size`. - """ - # convert to tuple - if isinstance(size, int): - size = (size,) - # return tensor - return torch.rand(*size, device=device) * (upper - lower) + lower
- - -
[文档]def sample_log_uniform( - lower: torch.Tensor | float, upper: torch.Tensor | float, size: int | tuple[int, ...], device: str -) -> torch.Tensor: - r"""Sample using log-uniform distribution within a range. - - The log-uniform distribution is defined as a uniform distribution in the log-space. It - is useful for sampling values that span several orders of magnitude. The sampled values - are uniformly distributed in the log-space and then exponentiated to get the final values. - - .. math:: - - x = \exp(\text{uniform}(\log(\text{lower}), \log(\text{upper}))) - - Args: - lower: Lower bound of uniform range. - upper: Upper bound of uniform range. - size: The shape of the tensor. - device: Device to create tensor on. - - Returns: - Sampled tensor. Shape is based on :attr:`size`. - """ - # cast to tensor if not already - if not isinstance(lower, torch.Tensor): - lower = torch.tensor(lower, dtype=torch.float, device=device) - if not isinstance(upper, torch.Tensor): - upper = torch.tensor(upper, dtype=torch.float, device=device) - # sample in log-space and exponentiate - return torch.exp(sample_uniform(torch.log(lower), torch.log(upper), size, device))
- - -
[文档]def sample_gaussian( - mean: torch.Tensor | float, std: torch.Tensor | float, size: int | tuple[int, ...], device: str -) -> torch.Tensor: - """Sample using gaussian distribution. - - Args: - mean: Mean of the gaussian. - std: Std of the gaussian. - size: The shape of the tensor. - device: Device to create tensor on. - - Returns: - Sampled tensor. - """ - if isinstance(mean, float): - if isinstance(size, int): - size = (size,) - return torch.normal(mean=mean, std=std, size=size).to(device=device) - else: - return torch.normal(mean=mean, std=std).to(device=device)
- - -
[文档]def sample_cylinder( - radius: float, h_range: tuple[float, float], size: int | tuple[int, ...], device: str -) -> torch.Tensor: - """Sample 3D points uniformly on a cylinder's surface. - - The cylinder is centered at the origin and aligned with the z-axis. The height of the cylinder is - sampled uniformly from the range :obj:`h_range`, while the radius is fixed to :obj:`radius`. - - The sampled points are returned as a tensor of shape :obj:`(*size, 3)`, i.e. the last dimension - contains the x, y, and z coordinates of the sampled points. - - Args: - radius: The radius of the cylinder. - h_range: The minimum and maximum height of the cylinder. - size: The shape of the tensor. - device: Device to create tensor on. - - Returns: - Sampled tensor. Shape is :obj:`(*size, 3)`. - """ - # sample angles - angles = (torch.rand(size, device=device) * 2 - 1) * torch.pi - h_min, h_max = h_range - # add shape - if isinstance(size, int): - size = (size, 3) - else: - size += (3,) - # allocate a tensor - xyz = torch.zeros(size, device=device) - xyz[..., 0] = radius * torch.cos(angles) - xyz[..., 1] = radius * torch.sin(angles) - xyz[..., 2].uniform_(h_min, h_max) - # return positions - return xyz
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/noise/noise_cfg.html b/docs/_build/html/_modules/omni/isaac/lab/utils/noise/noise_cfg.html deleted file mode 100644 index 65dbd73e4c..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/noise/noise_cfg.html +++ /dev/null @@ -1,658 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.noise.noise_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.noise.noise_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Callable
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-from . import noise_model
-
-
-
[文档]@configclass -class NoiseCfg: - """Base configuration for a noise term.""" - - func: Callable[[torch.Tensor, NoiseCfg], torch.Tensor] = MISSING - """The function to be called for applying the noise. - - Note: - The shape of the input and output tensors must be the same. - """ - operation: Literal["add", "scale", "abs"] = "add" - """The operation to apply the noise on the data. Defaults to "add"."""
- - -
[文档]@configclass -class ConstantNoiseCfg(NoiseCfg): - """Configuration for an additive constant noise term.""" - - func = noise_model.constant_noise - - bias: torch.Tensor | float = 0.0 - """The bias to add. Defaults to 0.0."""
- - -# Backward compatibility -ConstantBiasNoiseCfg = ConstantNoiseCfg - - -
[文档]@configclass -class UniformNoiseCfg(NoiseCfg): - """Configuration for a additive uniform noise term.""" - - func = noise_model.uniform_noise - - n_min: torch.Tensor | float = -1.0 - """The minimum value of the noise. Defaults to -1.0.""" - n_max: torch.Tensor | float = 1.0 - """The maximum value of the noise. Defaults to 1.0."""
- - -# Backward compatibility -AdditiveUniformNoiseCfg = UniformNoiseCfg - - -
[文档]@configclass -class GaussianNoiseCfg(NoiseCfg): - """Configuration for an additive gaussian noise term.""" - - func = noise_model.gaussian_noise - - mean: torch.Tensor | float = 0.0 - """The mean of the noise. Defaults to 0.0.""" - std: torch.Tensor | float = 1.0 - """The standard deviation of the noise. Defaults to 1.0."""
- - -# Backward compatibility -AdditiveGaussianNoiseCfg = GaussianNoiseCfg - - -
[文档]@configclass -class NoiseModelCfg: - """Configuration for a noise model.""" - - class_type: type = noise_model.NoiseModel - """The class type of the noise model.""" - - noise_cfg: NoiseCfg = MISSING - """The noise configuration to use."""
- - -
[文档]@configclass -class NoiseModelWithAdditiveBiasCfg(NoiseModelCfg): - """Configuration for an additive gaussian noise with bias model.""" - - class_type: type = noise_model.NoiseModelWithAdditiveBias - - bias_noise_cfg: NoiseCfg = MISSING
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/noise/noise_model.html b/docs/_build/html/_modules/omni/isaac/lab/utils/noise/noise_model.html deleted file mode 100644 index 8c2b773902..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/noise/noise_model.html +++ /dev/null @@ -1,677 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.noise.noise_model — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.noise.noise_model 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-import torch
-from collections.abc import Sequence
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
-    from . import noise_cfg
-
-
-
[文档]def constant_noise(data: torch.Tensor, cfg: noise_cfg.ConstantNoiseCfg) -> torch.Tensor: - """Constant noise.""" - if cfg.operation == "add": - return data + cfg.bias - elif cfg.operation == "scale": - return data * cfg.bias - elif cfg.operation == "abs": - return torch.zeros_like(data) + cfg.bias - else: - raise ValueError(f"Unknown operation in noise: {cfg.operation}")
- - -
[文档]def uniform_noise(data: torch.Tensor, cfg: noise_cfg.UniformNoiseCfg) -> torch.Tensor: - """Uniform noise.""" - if cfg.operation == "add": - return data + torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min - elif cfg.operation == "scale": - return data * (torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min) - elif cfg.operation == "abs": - return torch.rand_like(data) * (cfg.n_max - cfg.n_min) + cfg.n_min - else: - raise ValueError(f"Unknown operation in noise: {cfg.operation}")
- - -
[文档]def gaussian_noise(data: torch.Tensor, cfg: noise_cfg.GaussianNoiseCfg) -> torch.Tensor: - """Gaussian noise.""" - if cfg.operation == "add": - return data + cfg.mean + cfg.std * torch.randn_like(data) - elif cfg.operation == "scale": - return data * (cfg.mean + cfg.std * torch.randn_like(data)) - elif cfg.operation == "abs": - return cfg.mean + cfg.std * torch.randn_like(data) - else: - raise ValueError(f"Unknown operation in noise: {cfg.operation}")
- - -
[文档]class NoiseModel: - """Base class for noise models.""" - - def __init__(self, num_envs: int, noise_model_cfg: noise_cfg.NoiseModelCfg): - """Initialize the noise model. - - Args: - num_envs: The number of environments. - noise_model_cfg: The noise configuration to use. - """ - self._num_envs = num_envs - self._noise_model_cfg = noise_model_cfg - -
[文档] def apply(self, data: torch.Tensor) -> torch.Tensor: - r"""Apply the noise to the data. - - Args: - data: The data to apply the noise to, which is a tensor of shape (num_envs, \*data_shape). - """ - return self._noise_model_cfg.noise_cfg.func(data, self._noise_model_cfg.noise_cfg)
- -
[文档] def reset(self, env_ids: Sequence[int]): - """Reset the noise model. - - This method can be implemented by derived classes to reset the noise model. - This is useful when implementing temporal noise models such as random walk. - - Args: - env_ids: The environment ids to reset the noise model for. - """ - pass
- - -
[文档]class NoiseModelWithAdditiveBias(NoiseModel): - """Noise model with an additive bias. - - The bias term is sampled from a the specified distribution on reset. - - """ - - def __init__(self, num_envs: int, noise_model_cfg: noise_cfg.NoiseModelWithAdditiveBiasCfg, device: str): - super().__init__(num_envs, noise_model_cfg) - self._device = device - self._bias_noise_cfg = noise_model_cfg.bias_noise_cfg - self._bias = torch.zeros((num_envs, 1), device=self._device) - -
[文档] def apply(self, data: torch.Tensor) -> torch.Tensor: - r"""Apply the noise + bias. - - Args: - data: The data to apply the noise to, which is a tensor of shape (num_envs, \*data_shape). - """ - return super().apply(data) + self._bias
- -
[文档] def reset(self, env_ids: Sequence[int]): - """Reset the noise model. - - This method resets the bias term for the specified environments. - - Args: - env_ids: The environment ids to reset the noise model for. - """ - self._bias[env_ids] = self._bias_noise_cfg.func(self._bias[env_ids], self._bias_noise_cfg)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/string.html b/docs/_build/html/_modules/omni/isaac/lab/utils/string.html deleted file mode 100644 index b938623990..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/string.html +++ /dev/null @@ -1,901 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.string — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.string 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module containing utilities for transforming strings and regular expressions."""
-
-import ast
-import importlib
-import inspect
-import re
-from collections.abc import Callable, Sequence
-from typing import Any
-
-"""
-String formatting.
-"""
-
-
-
[文档]def to_camel_case(snake_str: str, to: str = "cC") -> str: - """Converts a string from snake case to camel case. - - Args: - snake_str: A string in snake case (i.e. with '_') - to: Convention to convert string to. Defaults to "cC". - - Raises: - ValueError: Invalid input argument `to`, i.e. not "cC" or "CC". - - Returns: - A string in camel-case format. - """ - # check input is correct - if to not in ["cC", "CC"]: - msg = "to_camel_case(): Choose a valid `to` argument (CC or cC)" - raise ValueError(msg) - # convert string to lower case and split - components = snake_str.lower().split("_") - if to == "cC": - # We capitalize the first letter of each component except the first one - # with the 'title' method and join them together. - return components[0] + "".join(x.title() for x in components[1:]) - else: - # Capitalize first letter in all the components - return "".join(x.title() for x in components)
- - -
[文档]def to_snake_case(camel_str: str) -> str: - """Converts a string from camel case to snake case. - - Args: - camel_str: A string in camel case. - - Returns: - A string in snake case (i.e. with '_') - """ - camel_str = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", camel_str) - return re.sub("([a-z0-9])([A-Z])", r"\1_\2", camel_str).lower()
- - -""" -String <-> Callable operations. -""" - - -
[文档]def is_lambda_expression(name: str) -> bool: - """Checks if the input string is a lambda expression. - - Args: - name: The input string. - - Returns: - Whether the input string is a lambda expression. - """ - try: - ast.parse(name) - return isinstance(ast.parse(name).body[0], ast.Expr) and isinstance(ast.parse(name).body[0].value, ast.Lambda) - except SyntaxError: - return False
- - -
[文档]def callable_to_string(value: Callable) -> str: - """Converts a callable object to a string. - - Args: - value: A callable object. - - Raises: - ValueError: When the input argument is not a callable object. - - Returns: - A string representation of the callable object. - """ - # check if callable - if not callable(value): - raise ValueError(f"The input argument is not callable: {value}.") - # check if lambda function - if value.__name__ == "<lambda>": - return f"lambda {inspect.getsourcelines(value)[0][0].strip().split('lambda')[1].strip().split(',')[0]}" - else: - # get the module and function name - module_name = value.__module__ - function_name = value.__name__ - # return the string - return f"{module_name}:{function_name}"
- - -
[文档]def string_to_callable(name: str) -> Callable: - """Resolves the module and function names to return the function. - - Args: - name: The function name. The format should be 'module:attribute_name' or a - lambda expression of format: 'lambda x: x'. - - Raises: - ValueError: When the resolved attribute is not a function. - ValueError: When the module cannot be found. - - Returns: - Callable: The function loaded from the module. - """ - try: - if is_lambda_expression(name): - callable_object = eval(name) - else: - mod_name, attr_name = name.split(":") - mod = importlib.import_module(mod_name) - callable_object = getattr(mod, attr_name) - # check if attribute is callable - if callable(callable_object): - return callable_object - else: - raise AttributeError(f"The imported object is not callable: '{name}'") - except (ValueError, ModuleNotFoundError) as e: - msg = ( - f"Could not resolve the input string '{name}' into callable object." - " The format of input should be 'module:attribute_name'.\n" - f"Received the error:\n {e}." - ) - raise ValueError(msg)
- - -""" -Regex operations. -""" - - -
[文档]def resolve_matching_names( - keys: str | Sequence[str], list_of_strings: Sequence[str], preserve_order: bool = False -) -> tuple[list[int], list[str]]: - """Match a list of query regular expressions against a list of strings and return the matched indices and names. - - When a list of query regular expressions is provided, the function checks each target string against each - query regular expression and returns the indices of the matched strings and the matched strings. - - If the :attr:`preserve_order` is True, the ordering of the matched indices and names is the same as the order - of the provided list of strings. This means that the ordering is dictated by the order of the target strings - and not the order of the query regular expressions. - - If the :attr:`preserve_order` is False, the ordering of the matched indices and names is the same as the order - of the provided list of query regular expressions. - - For example, consider the list of strings is ['a', 'b', 'c', 'd', 'e'] and the regular expressions are ['a|c', 'b']. - If :attr:`preserve_order` is False, then the function will return the indices of the matched strings and the - strings as: ([0, 1, 2], ['a', 'b', 'c']). When :attr:`preserve_order` is True, it will return them as: - ([0, 2, 1], ['a', 'c', 'b']). - - Note: - The function does not sort the indices. It returns the indices in the order they are found. - - Args: - keys: A regular expression or a list of regular expressions to match the strings in the list. - list_of_strings: A list of strings to match. - preserve_order: Whether to preserve the order of the query keys in the returned values. Defaults to False. - - Returns: - A tuple of lists containing the matched indices and names. - - Raises: - ValueError: When multiple matches are found for a string in the list. - ValueError: When not all regular expressions are matched. - """ - # resolve name keys - if isinstance(keys, str): - keys = [keys] - # find matching patterns - index_list = [] - names_list = [] - key_idx_list = [] - # book-keeping to check that we always have a one-to-one mapping - # i.e. each target string should match only one regular expression - target_strings_match_found = [None for _ in range(len(list_of_strings))] - keys_match_found = [[] for _ in range(len(keys))] - # loop over all target strings - for target_index, potential_match_string in enumerate(list_of_strings): - for key_index, re_key in enumerate(keys): - if re.fullmatch(re_key, potential_match_string): - # check if match already found - if target_strings_match_found[target_index]: - raise ValueError( - f"Multiple matches for '{potential_match_string}':" - f" '{target_strings_match_found[target_index]}' and '{re_key}'!" - ) - # add to list - target_strings_match_found[target_index] = re_key - index_list.append(target_index) - names_list.append(potential_match_string) - key_idx_list.append(key_index) - # add for regex key - keys_match_found[key_index].append(potential_match_string) - # reorder keys if they should be returned in order of the query keys - if preserve_order: - reordered_index_list = [None] * len(index_list) - global_index = 0 - for key_index in range(len(keys)): - for key_idx_position, key_idx_entry in enumerate(key_idx_list): - if key_idx_entry == key_index: - reordered_index_list[key_idx_position] = global_index - global_index += 1 - # reorder index and names list - index_list_reorder = [None] * len(index_list) - names_list_reorder = [None] * len(index_list) - for idx, reorder_idx in enumerate(reordered_index_list): - index_list_reorder[reorder_idx] = index_list[idx] - names_list_reorder[reorder_idx] = names_list[idx] - # update - index_list = index_list_reorder - names_list = names_list_reorder - # check that all regular expressions are matched - if not all(keys_match_found): - # make this print nicely aligned for debugging - msg = "\n" - for key, value in zip(keys, keys_match_found): - msg += f"\t{key}: {value}\n" - msg += f"Available strings: {list_of_strings}\n" - # raise error - raise ValueError( - f"Not all regular expressions are matched! Please check that the regular expressions are correct: {msg}" - ) - # return - return index_list, names_list
- - -
[文档]def resolve_matching_names_values( - data: dict[str, Any], list_of_strings: Sequence[str], preserve_order: bool = False -) -> tuple[list[int], list[str], list[Any]]: - """Match a list of regular expressions in a dictionary against a list of strings and return - the matched indices, names, and values. - - If the :attr:`preserve_order` is True, the ordering of the matched indices and names is the same as the order - of the provided list of strings. This means that the ordering is dictated by the order of the target strings - and not the order of the query regular expressions. - - If the :attr:`preserve_order` is False, the ordering of the matched indices and names is the same as the order - of the provided list of query regular expressions. - - For example, consider the dictionary is {"a|d|e": 1, "b|c": 2}, the list of strings is ['a', 'b', 'c', 'd', 'e']. - If :attr:`preserve_order` is False, then the function will return the indices of the matched strings, the - matched strings, and the values as: ([0, 1, 2, 3, 4], ['a', 'b', 'c', 'd', 'e'], [1, 2, 2, 1, 1]). When - :attr:`preserve_order` is True, it will return them as: ([0, 3, 4, 1, 2], ['a', 'd', 'e', 'b', 'c'], [1, 1, 1, 2, 2]). - - Args: - data: A dictionary of regular expressions and values to match the strings in the list. - list_of_strings: A list of strings to match. - preserve_order: Whether to preserve the order of the query keys in the returned values. Defaults to False. - - Returns: - A tuple of lists containing the matched indices, names, and values. - - Raises: - TypeError: When the input argument :attr:`data` is not a dictionary. - ValueError: When multiple matches are found for a string in the dictionary. - ValueError: When not all regular expressions in the data keys are matched. - """ - # check valid input - if not isinstance(data, dict): - raise TypeError(f"Input argument `data` should be a dictionary. Received: {data}") - # find matching patterns - index_list = [] - names_list = [] - values_list = [] - key_idx_list = [] - # book-keeping to check that we always have a one-to-one mapping - # i.e. each target string should match only one regular expression - target_strings_match_found = [None for _ in range(len(list_of_strings))] - keys_match_found = [[] for _ in range(len(data))] - # loop over all target strings - for target_index, potential_match_string in enumerate(list_of_strings): - for key_index, (re_key, value) in enumerate(data.items()): - if re.fullmatch(re_key, potential_match_string): - # check if match already found - if target_strings_match_found[target_index]: - raise ValueError( - f"Multiple matches for '{potential_match_string}':" - f" '{target_strings_match_found[target_index]}' and '{re_key}'!" - ) - # add to list - target_strings_match_found[target_index] = re_key - index_list.append(target_index) - names_list.append(potential_match_string) - values_list.append(value) - key_idx_list.append(key_index) - # add for regex key - keys_match_found[key_index].append(potential_match_string) - # reorder keys if they should be returned in order of the query keys - if preserve_order: - reordered_index_list = [None] * len(index_list) - global_index = 0 - for key_index in range(len(data)): - for key_idx_position, key_idx_entry in enumerate(key_idx_list): - if key_idx_entry == key_index: - reordered_index_list[key_idx_position] = global_index - global_index += 1 - # reorder index and names list - index_list_reorder = [None] * len(index_list) - names_list_reorder = [None] * len(index_list) - values_list_reorder = [None] * len(index_list) - for idx, reorder_idx in enumerate(reordered_index_list): - index_list_reorder[reorder_idx] = index_list[idx] - names_list_reorder[reorder_idx] = names_list[idx] - values_list_reorder[reorder_idx] = values_list[idx] - # update - index_list = index_list_reorder - names_list = names_list_reorder - values_list = values_list_reorder - # check that all regular expressions are matched - if not all(keys_match_found): - # make this print nicely aligned for debugging - msg = "\n" - for key, value in zip(data.keys(), keys_match_found): - msg += f"\t{key}: {value}\n" - msg += f"Available strings: {list_of_strings}\n" - # raise error - raise ValueError( - f"Not all regular expressions are matched! Please check that the regular expressions are correct: {msg}" - ) - # return - return index_list, names_list, values_list
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/timer.html b/docs/_build/html/_modules/omni/isaac/lab/utils/timer.html deleted file mode 100644 index d60f831f19..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/timer.html +++ /dev/null @@ -1,698 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.timer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.timer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module for a timer class that can be used for performance measurements."""
-
-from __future__ import annotations
-
-import time
-from contextlib import ContextDecorator
-from typing import Any
-
-
-
[文档]class TimerError(Exception): - """A custom exception used to report errors in use of :class:`Timer` class.""" - - pass
- - -
[文档]class Timer(ContextDecorator): - """A timer for performance measurements. - - A class to keep track of time for performance measurement. - It allows timing via context managers and decorators as well. - - It uses the `time.perf_counter` function to measure time. This function - returns the number of seconds since the epoch as a float. It has the - highest resolution available on the system. - - As a regular object: - - .. code-block:: python - - import time - - from omni.isaac.lab.utils.timer import Timer - - timer = Timer() - timer.start() - time.sleep(1) - print(1 <= timer.time_elapsed <= 2) # Output: True - - time.sleep(1) - timer.stop() - print(2 <= stopwatch.total_run_time) # Output: True - - As a context manager: - - .. code-block:: python - - import time - - from omni.isaac.lab.utils.timer import Timer - - with Timer() as timer: - time.sleep(1) - print(1 <= timer.time_elapsed <= 2) # Output: True - - Reference: https://gist.github.com/sumeet/1123871 - """ - -
[文档] def __init__(self, msg: str | None = None): - """Initializes the timer. - - Args: - msg: The message to display when using the timer - class in a context manager. Defaults to None. - """ - self._msg = msg - self._start_time = None - self._stop_time = None - self._elapsed_time = None
- - def __str__(self) -> str: - """A string representation of the class object. - - Returns: - A string containing the elapsed time. - """ - return f"{self.time_elapsed:0.6f} seconds" - - """ - Properties - """ - - @property - def time_elapsed(self) -> float: - """The number of seconds that have elapsed since this timer started timing. - - Note: - This is used for checking how much time has elapsed while the timer is still running. - """ - return time.perf_counter() - self._start_time - - @property - def total_run_time(self) -> float: - """The number of seconds that elapsed from when the timer started to when it ended.""" - return self._elapsed_time - - """ - Operations - """ - -
[文档] def start(self): - """Start timing.""" - if self._start_time is not None: - raise TimerError("Timer is running. Use .stop() to stop it") - - self._start_time = time.perf_counter()
- -
[文档] def stop(self): - """Stop timing.""" - if self._start_time is None: - raise TimerError("Timer is not running. Use .start() to start it") - - self._stop_time = time.perf_counter() - self._elapsed_time = self._stop_time - self._start_time - self._start_time = None
- - """ - Context managers - """ - - def __enter__(self) -> Timer: - """Start timing and return this `Timer` instance.""" - self.start() - return self - - def __exit__(self, *exc_info: Any): - """Stop timing.""" - self.stop() - # print message - if self._msg is not None: - print(self._msg, f": {self._elapsed_time:0.6f} seconds")
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab/utils/warp/ops.html b/docs/_build/html/_modules/omni/isaac/lab/utils/warp/ops.html deleted file mode 100644 index c3da4a142e..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab/utils/warp/ops.html +++ /dev/null @@ -1,705 +0,0 @@ - - - - - - - - - - - omni.isaac.lab.utils.warp.ops — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab.utils.warp.ops 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Wrapping around warp kernels for compatibility with torch tensors."""
-
-# needed to import for allowing type-hinting: torch.Tensor | None
-from __future__ import annotations
-
-import numpy as np
-import torch
-
-import warp as wp
-
-wp.init()
-
-from . import kernels
-
-
-
[文档]def raycast_mesh( - ray_starts: torch.Tensor, - ray_directions: torch.Tensor, - mesh: wp.Mesh, - max_dist: float = 1e6, - return_distance: bool = False, - return_normal: bool = False, - return_face_id: bool = False, -) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None, torch.Tensor | None]: - """Performs ray-casting against a mesh. - - Note that the `ray_starts` and `ray_directions`, and `ray_hits` should have compatible shapes - and data types to ensure proper execution. Additionally, they all must be in the same frame. - - Args: - ray_starts: The starting position of the rays. Shape (N, 3). - ray_directions: The ray directions for each ray. Shape (N, 3). - mesh: The warp mesh to ray-cast against. - max_dist: The maximum distance to ray-cast. Defaults to 1e6. - return_distance: Whether to return the distance of the ray until it hits the mesh. Defaults to False. - return_normal: Whether to return the normal of the mesh face the ray hits. Defaults to False. - return_face_id: Whether to return the face id of the mesh face the ray hits. Defaults to False. - - Returns: - The ray hit position. Shape (N, 3). - The returned tensor contains :obj:`float('inf')` for missed hits. - The ray hit distance. Shape (N,). - Will only return if :attr:`return_distance` is True, else returns None. - The returned tensor contains :obj:`float('inf')` for missed hits. - The ray hit normal. Shape (N, 3). - Will only return if :attr:`return_normal` is True else returns None. - The returned tensor contains :obj:`float('inf')` for missed hits. - The ray hit face id. Shape (N,). - Will only return if :attr:`return_face_id` is True else returns None. - The returned tensor contains :obj:`int(-1)` for missed hits. - """ - # extract device and shape information - shape = ray_starts.shape - device = ray_starts.device - # device of the mesh - torch_device = wp.device_to_torch(mesh.device) - # reshape the tensors - ray_starts = ray_starts.to(torch_device).view(-1, 3).contiguous() - ray_directions = ray_directions.to(torch_device).view(-1, 3).contiguous() - num_rays = ray_starts.shape[0] - # create output tensor for the ray hits - ray_hits = torch.full((num_rays, 3), float("inf"), device=torch_device).contiguous() - - # map the memory to warp arrays - ray_starts_wp = wp.from_torch(ray_starts, dtype=wp.vec3) - ray_directions_wp = wp.from_torch(ray_directions, dtype=wp.vec3) - ray_hits_wp = wp.from_torch(ray_hits, dtype=wp.vec3) - - if return_distance: - ray_distance = torch.full((num_rays,), float("inf"), device=torch_device).contiguous() - ray_distance_wp = wp.from_torch(ray_distance, dtype=wp.float32) - else: - ray_distance = None - ray_distance_wp = wp.empty((1,), dtype=wp.float32, device=torch_device) - - if return_normal: - ray_normal = torch.full((num_rays, 3), float("inf"), device=torch_device).contiguous() - ray_normal_wp = wp.from_torch(ray_normal, dtype=wp.vec3) - else: - ray_normal = None - ray_normal_wp = wp.empty((1,), dtype=wp.vec3, device=torch_device) - - if return_face_id: - ray_face_id = torch.ones((num_rays,), dtype=torch.int32, device=torch_device).contiguous() * (-1) - ray_face_id_wp = wp.from_torch(ray_face_id, dtype=wp.int32) - else: - ray_face_id = None - ray_face_id_wp = wp.empty((1,), dtype=wp.int32, device=torch_device) - - # launch the warp kernel - wp.launch( - kernel=kernels.raycast_mesh_kernel, - dim=num_rays, - inputs=[ - mesh.id, - ray_starts_wp, - ray_directions_wp, - ray_hits_wp, - ray_distance_wp, - ray_normal_wp, - ray_face_id_wp, - float(max_dist), - int(return_distance), - int(return_normal), - int(return_face_id), - ], - device=mesh.device, - ) - # NOTE: Synchronize is not needed anymore, but we keep it for now. Check with @dhoeller. - wp.synchronize() - - if return_distance: - ray_distance = ray_distance.to(device).view(shape[0], shape[1]) - if return_normal: - ray_normal = ray_normal.to(device).view(shape) - if return_face_id: - ray_face_id = ray_face_id.to(device).view(shape[0], shape[1]) - - return ray_hits.to(device).view(shape), ray_distance, ray_normal, ray_face_id
- - -
[文档]def convert_to_warp_mesh(points: np.ndarray, indices: np.ndarray, device: str) -> wp.Mesh: - """Create a warp mesh object with a mesh defined from vertices and triangles. - - Args: - points: The vertices of the mesh. Shape is (N, 3), where N is the number of vertices. - indices: The triangles of the mesh as references to vertices for each triangle. - Shape is (M, 3), where M is the number of triangles / faces. - device: The device to use for the mesh. - - Returns: - The warp mesh object. - """ - return wp.Mesh( - points=wp.array(points.astype(np.float32), dtype=wp.vec3, device=device), - indices=wp.array(indices.astype(np.int32).flatten(), dtype=wp.int32, device=device), - )
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/data_collector/robomimic_data_collector.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/data_collector/robomimic_data_collector.html deleted file mode 100644 index 3449e374e5..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/data_collector/robomimic_data_collector.html +++ /dev/null @@ -1,845 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.data_collector.robomimic_data_collector — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.data_collector.robomimic_data_collector 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Interface to collect and store data from the environment using format from `robomimic`."""
-
-# needed to import for allowing type-hinting: np.ndarray | torch.Tensor
-from __future__ import annotations
-
-import h5py
-import json
-import numpy as np
-import os
-import torch
-from collections.abc import Iterable
-
-import carb
-
-
-
[文档]class RobomimicDataCollector: - """Data collection interface for robomimic. - - This class implements a data collector interface for saving simulation states to disk. - The data is stored in `HDF5`_ binary data format. The class is useful for collecting - demonstrations. The collected data follows the `structure`_ from robomimic. - - All datasets in `robomimic` require the observations and next observations obtained - from before and after the environment step. These are stored as a dictionary of - observations in the keys "obs" and "next_obs" respectively. - - For certain agents in `robomimic`, the episode data should have the following - additional keys: "actions", "rewards", "dones". This behavior can be altered by changing - the dataset keys required in the training configuration for the respective learning agent. - - For reference on datasets, please check the robomimic `documentation`. - - .. _HDF5: https://www.h5py.org/ - .. _structure: https://robomimic.github.io/docs/datasets/overview.html#dataset-structure - .. _documentation: https://github.com/ARISE-Initiative/robomimic/blob/master/robomimic/config/base_config.py#L167-L173 - """ - -
[文档] def __init__( - self, - env_name: str, - directory_path: str, - filename: str = "test", - num_demos: int = 1, - flush_freq: int = 1, - env_config: dict | None = None, - ): - """Initializes the data collection wrapper. - - Args: - env_name: The name of the environment. - directory_path: The path to store collected data. - filename: The basename of the saved file. Defaults to "test". - num_demos: Number of demonstrations to record until stopping. Defaults to 1. - flush_freq: Frequency to dump data to disk. Defaults to 1. - env_config: The configuration for the environment. Defaults to None. - """ - # save input arguments - self._env_name = env_name - self._env_config = env_config - self._directory = os.path.abspath(directory_path) - self._filename = filename - self._num_demos = num_demos - self._flush_freq = flush_freq - # print info - print(self.__str__()) - - # create directory it doesn't exist - if not os.path.isdir(self._directory): - os.makedirs(self._directory) - - # placeholder for current hdf5 file object - self._h5_file_stream = None - self._h5_data_group = None - self._h5_episode_group = None - - # store count of demos within episode - self._demo_count = 0 - # flags for setting up - self._is_first_interaction = True - self._is_stop = False - # create buffers to store data - self._dataset = dict()
- - def __del__(self): - """Destructor for data collector.""" - if not self._is_stop: - self.close() - - def __str__(self) -> str: - """Represents the data collector as a string.""" - msg = "Dataset collector <class RobomimicDataCollector> object" - msg += f"\tStoring trajectories in directory: {self._directory}\n" - msg += f"\tNumber of demos for collection : {self._num_demos}\n" - msg += f"\tFrequency for saving data to disk: {self._flush_freq}\n" - - return msg - - """ - Properties - """ - - @property - def demo_count(self) -> int: - """The number of demos collected so far.""" - return self._demo_count - - """ - Operations. - """ - -
[文档] def is_stopped(self) -> bool: - """Whether data collection is stopped or not. - - Returns: - True if data collection has stopped. - """ - return self._is_stop
- -
[文档] def reset(self): - """Reset the internals of data logger.""" - # setup the file to store data in - if self._is_first_interaction: - self._demo_count = 0 - self._create_new_file(self._filename) - self._is_first_interaction = False - # clear out existing buffers - self._dataset = dict()
- -
[文档] def add(self, key: str, value: np.ndarray | torch.Tensor): - """Add a key-value pair to the dataset. - - The key can be nested by using the "/" character. For example: - "obs/joint_pos". Currently only two-level nesting is supported. - - Args: - key: The key name. - value: The corresponding value - of shape (N, ...), where `N` is number of environments. - - Raises: - ValueError: When provided key has sub-keys more than 2. Example: "obs/joints/pos", instead - of "obs/joint_pos". - """ - # check if data should be recorded - if self._is_first_interaction: - carb.log_warn("Please call reset before adding new data. Calling reset...") - self.reset() - if self._is_stop: - carb.log_warn(f"Desired number of demonstrations collected: {self._demo_count} >= {self._num_demos}.") - return - # check datatype - if isinstance(value, torch.Tensor): - value = value.cpu().numpy() - else: - value = np.asarray(value) - # check if there are sub-keys - sub_keys = key.split("/") - num_sub_keys = len(sub_keys) - if len(sub_keys) > 2: - raise ValueError(f"Input key '{key}' has elements {num_sub_keys} which is more than two.") - # add key to dictionary if it doesn't exist - for i in range(value.shape[0]): - # demo index - if f"env_{i}" not in self._dataset: - self._dataset[f"env_{i}"] = dict() - # key index - if num_sub_keys == 2: - # create keys - if sub_keys[0] not in self._dataset[f"env_{i}"]: - self._dataset[f"env_{i}"][sub_keys[0]] = dict() - if sub_keys[1] not in self._dataset[f"env_{i}"][sub_keys[0]]: - self._dataset[f"env_{i}"][sub_keys[0]][sub_keys[1]] = list() - # add data to key - self._dataset[f"env_{i}"][sub_keys[0]][sub_keys[1]].append(value[i]) - else: - # create keys - if sub_keys[0] not in self._dataset[f"env_{i}"]: - self._dataset[f"env_{i}"][sub_keys[0]] = list() - # add data to key - self._dataset[f"env_{i}"][sub_keys[0]].append(value[i])
- -
[文档] def flush(self, env_ids: Iterable[int] = (0,)): - """Flush the episode data based on environment indices. - - Args: - env_ids: Environment indices to write data for. Defaults to (0). - """ - # check that data is being recorded - if self._h5_file_stream is None or self._h5_data_group is None: - carb.log_error("No file stream has been opened. Please call reset before flushing data.") - return - - # iterate over each environment and add their data - for index in env_ids: - # data corresponding to demo - env_dataset = self._dataset[f"env_{index}"] - - # create episode group based on demo count - h5_episode_group = self._h5_data_group.create_group(f"demo_{self._demo_count}") - # store number of steps taken - h5_episode_group.attrs["num_samples"] = len(env_dataset["actions"]) - # store other data from dictionary - for key, value in env_dataset.items(): - if isinstance(value, dict): - # create group - key_group = h5_episode_group.create_group(key) - # add sub-keys values - for sub_key, sub_value in value.items(): - key_group.create_dataset(sub_key, data=np.array(sub_value)) - else: - h5_episode_group.create_dataset(key, data=np.array(value)) - # increment total step counts - self._h5_data_group.attrs["total"] += h5_episode_group.attrs["num_samples"] - - # increment total demo counts - self._demo_count += 1 - # reset buffer for environment - self._dataset[f"env_{index}"] = dict() - - # dump at desired frequency - if self._demo_count % self._flush_freq == 0: - self._h5_file_stream.flush() - print(f">>> Flushing data to disk. Collected demos: {self._demo_count} / {self._num_demos}") - - # if demos collected then stop - if self._demo_count >= self._num_demos: - print(f">>> Desired number of demonstrations collected: {self._demo_count} >= {self._num_demos}.") - self.close() - # break out of loop - break
- -
[文档] def close(self): - """Stop recording and save the file at its current state.""" - if not self._is_stop: - print(f">>> Closing recording of data. Collected demos: {self._demo_count} / {self._num_demos}") - # close the file safely - if self._h5_file_stream is not None: - self._h5_file_stream.close() - # mark that data collection is stopped - self._is_stop = True
- - """ - Helper functions. - """ - - def _create_new_file(self, fname: str): - """Create a new HDF5 file for writing episode info into. - - Reference: - https://robomimic.github.io/docs/datasets/overview.html - - Args: - fname: The base name of the file. - """ - if not fname.endswith(".hdf5"): - fname += ".hdf5" - # define path to file - hdf5_path = os.path.join(self._directory, fname) - # construct the stream object - self._h5_file_stream = h5py.File(hdf5_path, "w") - # create group to store data - self._h5_data_group = self._h5_file_stream.create_group("data") - # stores total number of samples accumulated across demonstrations - self._h5_data_group.attrs["total"] = 0 - # store the environment meta-info - # -- we use gym environment type - # Ref: https://github.com/ARISE-Initiative/robomimic/blob/master/robomimic/envs/env_base.py#L15 - env_type = 2 - # -- check if env config provided - if self._env_config is None: - self._env_config = dict() - # -- add info - self._h5_data_group.attrs["env_args"] = json.dumps({ - "env_name": self._env_name, - "type": env_type, - "env_kwargs": self._env_config, - })
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/importer.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/importer.html deleted file mode 100644 index 25956dec79..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/importer.html +++ /dev/null @@ -1,650 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.importer — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.importer 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module with utility for importing all modules in a package recursively."""
-
-from __future__ import annotations
-
-import importlib
-import pkgutil
-import sys
-
-
-
[文档]def import_packages(package_name: str, blacklist_pkgs: list[str] | None = None): - """Import all sub-packages in a package recursively. - - It is easier to use this function to import all sub-packages in a package recursively - than to manually import each sub-package. - - It replaces the need of the following code snippet on the top of each package's ``__init__.py`` file: - - .. code-block:: python - - import .locomotion.velocity - import .manipulation.reach - import .manipulation.lift - - Args: - package_name: The package name. - blacklist_pkgs: The list of blacklisted packages to skip. Defaults to None, - which means no packages are blacklisted. - """ - # Default blacklist - if blacklist_pkgs is None: - blacklist_pkgs = [] - # Import the package itself - package = importlib.import_module(package_name) - # Import all Python files - for _ in _walk_packages(package.__path__, package.__name__ + ".", blacklist_pkgs=blacklist_pkgs): - pass
- - -def _walk_packages( - path: str | None = None, - prefix: str = "", - onerror: callable | None = None, - blacklist_pkgs: list[str] | None = None, -): - """Yields ModuleInfo for all modules recursively on path, or, if path is None, all accessible modules. - - Note: - This function is a modified version of the original ``pkgutil.walk_packages`` function. It adds - the `blacklist_pkgs` argument to skip blacklisted packages. Please refer to the original - ``pkgutil.walk_packages`` function for more details. - """ - if blacklist_pkgs is None: - blacklist_pkgs = [] - - def seen(p, m={}): - if p in m: - return True - m[p] = True # noqa: R503 - - for info in pkgutil.iter_modules(path, prefix): - # check blacklisted - if any([black_pkg_name in info.name for black_pkg_name in blacklist_pkgs]): - continue - - # yield the module info - yield info - - if info.ispkg: - try: - __import__(info.name) - except Exception: - if onerror is not None: - onerror(info.name) - else: - raise - else: - path = getattr(sys.modules[info.name], "__path__", None) or [] - - # don't traverse path items we've seen before - path = [p for p in path if not seen(p)] - - yield from _walk_packages(path, info.name + ".", onerror, blacklist_pkgs) -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/parse_cfg.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/parse_cfg.html deleted file mode 100644 index ba5bf3b616..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/parse_cfg.html +++ /dev/null @@ -1,774 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.parse_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.parse_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Sub-module with utilities for parsing and loading configurations."""
-
-
-import gymnasium as gym
-import importlib
-import inspect
-import os
-import re
-import yaml
-
-from omni.isaac.lab.envs import ManagerBasedRLEnvCfg
-from omni.isaac.lab.utils import update_class_from_dict, update_dict
-
-
-
[文档]def load_cfg_from_registry(task_name: str, entry_point_key: str) -> dict | ManagerBasedRLEnvCfg: - """Load default configuration given its entry point from the gym registry. - - This function loads the configuration object from the gym registry for the given task name. - It supports both YAML and Python configuration files. - - It expects the configuration to be registered in the gym registry as: - - .. code-block:: python - - gym.register( - id="My-Awesome-Task-v0", - ... - kwargs={"env_entry_point_cfg": "path.to.config:ConfigClass"}, - ) - - The parsed configuration object for above example can be obtained as: - - .. code-block:: python - - from omni.isaac.lab_tasks.utils.parse_cfg import load_cfg_from_registry - - cfg = load_cfg_from_registry("My-Awesome-Task-v0", "env_entry_point_cfg") - - Args: - task_name: The name of the environment. - entry_point_key: The entry point key to resolve the configuration file. - - Returns: - The parsed configuration object. This is either a dictionary or a class object. - - Raises: - ValueError: If the entry point key is not available in the gym registry for the task. - """ - # obtain the configuration entry point - cfg_entry_point = gym.spec(task_name).kwargs.get(entry_point_key) - # check if entry point exists - if cfg_entry_point is None: - raise ValueError( - f"Could not find configuration for the environment: '{task_name}'." - f" Please check that the gym registry has the entry point: '{entry_point_key}'." - ) - # parse the default config file - if isinstance(cfg_entry_point, str) and cfg_entry_point.endswith(".yaml"): - if os.path.exists(cfg_entry_point): - # absolute path for the config file - config_file = cfg_entry_point - else: - # resolve path to the module location - mod_name, file_name = cfg_entry_point.split(":") - mod_path = os.path.dirname(importlib.import_module(mod_name).__file__) - # obtain the configuration file path - config_file = os.path.join(mod_path, file_name) - # load the configuration - print(f"[INFO]: Parsing configuration from: {config_file}") - with open(config_file, encoding="utf-8") as f: - cfg = yaml.full_load(f) - else: - if callable(cfg_entry_point): - # resolve path to the module location - mod_path = inspect.getfile(cfg_entry_point) - # load the configuration - cfg_cls = cfg_entry_point() - elif isinstance(cfg_entry_point, str): - # resolve path to the module location - mod_name, attr_name = cfg_entry_point.split(":") - mod = importlib.import_module(mod_name) - cfg_cls = getattr(mod, attr_name) - else: - cfg_cls = cfg_entry_point - # load the configuration - print(f"[INFO]: Parsing configuration from: {cfg_entry_point}") - if callable(cfg_cls): - cfg = cfg_cls() - else: - cfg = cfg_cls - return cfg
- - -
[文档]def parse_env_cfg( - task_name: str, use_gpu: bool | None = None, num_envs: int | None = None, use_fabric: bool | None = None -) -> dict | ManagerBasedRLEnvCfg: - """Parse configuration for an environment and override based on inputs. - - Args: - task_name: The name of the environment. - use_gpu: Whether to use GPU/CPU pipeline. Defaults to None, in which case it is left unchanged. - num_envs: Number of environments to create. Defaults to None, in which case it is left unchanged. - use_fabric: Whether to enable/disable fabric interface. If false, all read/write operations go through USD. - This slows down the simulation but allows seeing the changes in the USD through the USD stage. - Defaults to None, in which case it is left unchanged. - - Returns: - The parsed configuration object. This is either a dictionary or a class object. - - Raises: - ValueError: If the task name is not provided, i.e. None. - """ - # check if a task name is provided - if task_name is None: - raise ValueError("Please provide a valid task name. Hint: Use --task <task_name>.") - # create a dictionary to update from - args_cfg = {"sim": {"physx": dict()}, "scene": dict()} - # resolve pipeline to use (based on input) - if use_gpu is not None: - if not use_gpu: - args_cfg["sim"]["use_gpu_pipeline"] = False - args_cfg["sim"]["physx"]["use_gpu"] = False - args_cfg["sim"]["device"] = "cpu" - else: - args_cfg["sim"]["use_gpu_pipeline"] = True - args_cfg["sim"]["physx"]["use_gpu"] = True - args_cfg["sim"]["device"] = "cuda:0" - - # disable fabric to read/write through USD - if use_fabric is not None: - args_cfg["sim"]["use_fabric"] = use_fabric - - # number of environments - if num_envs is not None: - args_cfg["scene"]["num_envs"] = num_envs - - # load the default configuration - cfg = load_cfg_from_registry(task_name, "env_cfg_entry_point") - # update the main configuration - if isinstance(cfg, dict): - cfg = update_dict(cfg, args_cfg) - else: - update_class_from_dict(cfg, args_cfg) - - return cfg
- - -
[文档]def get_checkpoint_path( - log_path: str, run_dir: str = ".*", checkpoint: str = ".*", other_dirs: list[str] = None, sort_alpha: bool = True -) -> str: - """Get path to the model checkpoint in input directory. - - The checkpoint file is resolved as: ``<log_path>/<run_dir>/<*other_dirs>/<checkpoint>``, where the - :attr:`other_dirs` are intermediate folder names to concatenate. These cannot be regex expressions. - - If :attr:`run_dir` and :attr:`checkpoint` are regex expressions then the most recent (highest alphabetical order) - run and checkpoint are selected. To disable this behavior, set the flag :attr:`sort_alpha` to False. - - Args: - log_path: The log directory path to find models in. - run_dir: The regex expression for the name of the directory containing the run. Defaults to the most - recent directory created inside :attr:`log_path`. - other_dirs: The intermediate directories between the run directory and the checkpoint file. Defaults to - None, which implies that checkpoint file is directly under the run directory. - checkpoint: The regex expression for the model checkpoint file. Defaults to the most recent - torch-model saved in the :attr:`run_dir` directory. - sort_alpha: Whether to sort the runs by alphabetical order. Defaults to True. - If False, the folders in :attr:`run_dir` are sorted by the last modified time. - - Raises: - ValueError: When no runs are found in the input directory. - ValueError: When no checkpoints are found in the input directory. - - Returns: - The path to the model checkpoint. - """ - # check if runs present in directory - try: - # find all runs in the directory that math the regex expression - runs = [ - os.path.join(log_path, run) for run in os.scandir(log_path) if run.is_dir() and re.match(run_dir, run.name) - ] - # sort matched runs by alphabetical order (latest run should be last) - if sort_alpha: - runs.sort() - else: - runs = sorted(runs, key=os.path.getmtime) - # create last run file path - if other_dirs is not None: - run_path = os.path.join(runs[-1], *other_dirs) - else: - run_path = runs[-1] - except IndexError: - raise ValueError(f"No runs present in the directory: '{log_path}' match: '{run_dir}'.") - - # list all model checkpoints in the directory - model_checkpoints = [f for f in os.listdir(run_path) if re.match(checkpoint, f)] - # check if any checkpoints are present - if len(model_checkpoints) == 0: - raise ValueError(f"No checkpoints in the directory: '{run_path}' match '{checkpoint}'.") - # sort alphabetically while ensuring that *_10 comes after *_9 - model_checkpoints.sort(key=lambda m: f"{m:0>15}") - # get latest matched checkpoint file - checkpoint_file = model_checkpoints[-1] - - return os.path.join(run_path, checkpoint_file)
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rl_games.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rl_games.html deleted file mode 100644 index 1ebf3f11c3..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rl_games.html +++ /dev/null @@ -1,910 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.wrappers.rl_games — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.wrappers.rl_games 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to RL-Games vectorized environment.
-
-The following example shows how to wrap an environment for RL-Games and register the environment construction
-for RL-Games :class:`Runner` class:
-
-.. code-block:: python
-
-    from rl_games.common import env_configurations, vecenv
-
-    from omni.isaac.lab_tasks.utils.wrappers.rl_games import RlGamesGpuEnv, RlGamesVecEnvWrapper
-
-    # configuration parameters
-    rl_device = "cuda:0"
-    clip_obs = 10.0
-    clip_actions = 1.0
-
-    # wrap around environment for rl-games
-    env = RlGamesVecEnvWrapper(env, rl_device, clip_obs, clip_actions)
-
-    # register the environment to rl-games registry
-    # note: in agents configuration: environment name must be "rlgpu"
-    vecenv.register(
-        "IsaacRlgWrapper", lambda config_name, num_actors, **kwargs: RlGamesGpuEnv(config_name, num_actors, **kwargs)
-    )
-    env_configurations.register("rlgpu", {"vecenv_type": "IsaacRlgWrapper", "env_creator": lambda **kwargs: env})
-
-"""
-
-# needed to import for allowing type-hinting:gym.spaces.Box | None
-from __future__ import annotations
-
-import gym.spaces  # needed for rl-games incompatibility: https://github.com/Denys88/rl_games/issues/261
-import gymnasium
-import torch
-
-from rl_games.common import env_configurations
-from rl_games.common.vecenv import IVecEnv
-
-from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv, VecEnvObs
-
-"""
-Vectorized environment wrapper.
-"""
-
-
-
[文档]class RlGamesVecEnvWrapper(IVecEnv): - """Wraps around Isaac Lab environment for RL-Games. - - This class wraps around the Isaac Lab environment. Since RL-Games works directly on - GPU buffers, the wrapper handles moving of buffers from the simulation environment - to the same device as the learning agent. Additionally, it performs clipping of - observations and actions. - - For algorithms like asymmetric actor-critic, RL-Games expects a dictionary for - observations. This dictionary contains "obs" and "states" which typically correspond - to the actor and critic observations respectively. - - To use asymmetric actor-critic, the environment observations from :class:`ManagerBasedRLEnv` - must have the key or group name "critic". The observation group is used to set the - :attr:`num_states` (int) and :attr:`state_space` (:obj:`gym.spaces.Box`). These are - used by the learning agent in RL-Games to allocate buffers in the trajectory memory. - Since this is optional for some environments, the wrapper checks if these attributes exist. - If they don't then the wrapper defaults to zero as number of privileged observations. - - .. caution:: - - This class must be the last wrapper in the wrapper chain. This is because the wrapper does not follow - the :class:`gym.Wrapper` interface. Any subsequent wrappers will need to be modified to work with this - wrapper. - - - Reference: - https://github.com/Denys88/rl_games/blob/master/rl_games/common/ivecenv.py - https://github.com/NVIDIA-Omniverse/IsaacGymEnvs - """ - -
[文档] def __init__(self, env: ManagerBasedRLEnv, rl_device: str, clip_obs: float, clip_actions: float): - """Initializes the wrapper instance. - - Args: - env: The environment to wrap around. - rl_device: The device on which agent computations are performed. - clip_obs: The clipping value for observations. - clip_actions: The clipping value for actions. - - Raises: - ValueError: The environment is not inherited from :class:`ManagerBasedRLEnv`. - ValueError: If specified, the privileged observations (critic) are not of type :obj:`gym.spaces.Box`. - """ - # check that input is valid - if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): - raise ValueError(f"The environment must be inherited from ManagerBasedRLEnv. Environment type: {type(env)}") - # initialize the wrapper - self.env = env - # store provided arguments - self._rl_device = rl_device - self._clip_obs = clip_obs - self._clip_actions = clip_actions - self._sim_device = env.unwrapped.device - # information for privileged observations - if self.state_space is None: - self.rlg_num_states = 0 - else: - self.rlg_num_states = self.state_space.shape[0]
- - def __str__(self): - """Returns the wrapper name and the :attr:`env` representation string.""" - return ( - f"<{type(self).__name__}{self.env}>" - f"\n\tObservations clipping: {self._clip_obs}" - f"\n\tActions clipping : {self._clip_actions}" - f"\n\tAgent device : {self._rl_device}" - f"\n\tAsymmetric-learning : {self.rlg_num_states != 0}" - ) - - def __repr__(self): - """Returns the string representation of the wrapper.""" - return str(self) - - """ - Properties -- Gym.Wrapper - """ - - @property - def render_mode(self) -> str | None: - """Returns the :attr:`Env` :attr:`render_mode`.""" - return self.env.render_mode - - @property - def observation_space(self) -> gym.spaces.Box: - """Returns the :attr:`Env` :attr:`observation_space`.""" - # note: rl-games only wants single observation space - policy_obs_space = self.unwrapped.single_observation_space["policy"] - if not isinstance(policy_obs_space, gymnasium.spaces.Box): - raise NotImplementedError( - f"The RL-Games wrapper does not currently support observation space: '{type(policy_obs_space)}'." - f" If you need to support this, please modify the wrapper: {self.__class__.__name__}," - " and if you are nice, please send a merge-request." - ) - # note: maybe should check if we are a sub-set of the actual space. don't do it right now since - # in ManagerBasedRLEnv we are setting action space as (-inf, inf). - return gym.spaces.Box(-self._clip_obs, self._clip_obs, policy_obs_space.shape) - - @property - def action_space(self) -> gym.Space: - """Returns the :attr:`Env` :attr:`action_space`.""" - # note: rl-games only wants single action space - action_space = self.unwrapped.single_action_space - if not isinstance(action_space, gymnasium.spaces.Box): - raise NotImplementedError( - f"The RL-Games wrapper does not currently support action space: '{type(action_space)}'." - f" If you need to support this, please modify the wrapper: {self.__class__.__name__}," - " and if you are nice, please send a merge-request." - ) - # return casted space in gym.spaces.Box (OpenAI Gym) - # note: maybe should check if we are a sub-set of the actual space. don't do it right now since - # in ManagerBasedRLEnv we are setting action space as (-inf, inf). - return gym.spaces.Box(-self._clip_actions, self._clip_actions, action_space.shape) - -
[文档] @classmethod - def class_name(cls) -> str: - """Returns the class name of the wrapper.""" - return cls.__name__
- - @property - def unwrapped(self) -> ManagerBasedRLEnv: - """Returns the base environment of the wrapper. - - This will be the bare :class:`gymnasium.Env` environment, underneath all layers of wrappers. - """ - return self.env.unwrapped - - """ - Properties - """ - - @property - def num_envs(self) -> int: - """Returns the number of sub-environment instances.""" - return self.unwrapped.num_envs - - @property - def device(self) -> str: - """Returns the base environment simulation device.""" - return self.unwrapped.device - - @property - def state_space(self) -> gym.spaces.Box | None: - """Returns the :attr:`Env` :attr:`observation_space`.""" - # note: rl-games only wants single observation space - critic_obs_space = self.unwrapped.single_observation_space.get("critic") - # check if we even have a critic obs - if critic_obs_space is None: - return None - elif not isinstance(critic_obs_space, gymnasium.spaces.Box): - raise NotImplementedError( - f"The RL-Games wrapper does not currently support state space: '{type(critic_obs_space)}'." - f" If you need to support this, please modify the wrapper: {self.__class__.__name__}," - " and if you are nice, please send a merge-request." - ) - # return casted space in gym.spaces.Box (OpenAI Gym) - # note: maybe should check if we are a sub-set of the actual space. don't do it right now since - # in ManagerBasedRLEnv we are setting action space as (-inf, inf). - return gym.spaces.Box(-self._clip_obs, self._clip_obs, critic_obs_space.shape) - -
[文档] def get_number_of_agents(self) -> int: - """Returns number of actors in the environment.""" - return getattr(self, "num_agents", 1)
- -
[文档] def get_env_info(self) -> dict: - """Returns the Gym spaces for the environment.""" - return { - "observation_space": self.observation_space, - "action_space": self.action_space, - "state_space": self.state_space, - }
- - """ - Operations - MDP - """ - - def seed(self, seed: int = -1) -> int: # noqa: D102 - return self.unwrapped.seed(seed) - - def reset(self): # noqa: D102 - obs_dict, _ = self.env.reset() - # process observations and states - return self._process_obs(obs_dict) - - def step(self, actions): # noqa: D102 - # move actions to sim-device - actions = actions.detach().clone().to(device=self._sim_device) - # clip the actions - actions = torch.clamp(actions, -self._clip_actions, self._clip_actions) - # perform environment step - obs_dict, rew, terminated, truncated, extras = self.env.step(actions) - - # move time out information to the extras dict - # this is only needed for infinite horizon tasks - # note: only useful when `value_bootstrap` is True in the agent configuration - if not self.unwrapped.cfg.is_finite_horizon: - extras["time_outs"] = truncated.to(device=self._rl_device) - # process observations and states - obs_and_states = self._process_obs(obs_dict) - # move buffers to rl-device - # note: we perform clone to prevent issues when rl-device and sim-device are the same. - rew = rew.to(device=self._rl_device) - dones = (terminated | truncated).to(device=self._rl_device) - extras = { - k: v.to(device=self._rl_device, non_blocking=True) if hasattr(v, "to") else v for k, v in extras.items() - } - # remap extras from "log" to "episode" - if "log" in extras: - extras["episode"] = extras.pop("log") - - return obs_and_states, rew, dones, extras - - def close(self): # noqa: D102 - return self.env.close() - - """ - Helper functions - """ - - def _process_obs(self, obs_dict: VecEnvObs) -> torch.Tensor | dict[str, torch.Tensor]: - """Processing of the observations and states from the environment. - - Note: - States typically refers to privileged observations for the critic function. It is typically used in - asymmetric actor-critic algorithms. - - Args: - obs_dict: The current observations from environment. - - Returns: - If environment provides states, then a dictionary containing the observations and states is returned. - Otherwise just the observations tensor is returned. - """ - # process policy obs - obs = obs_dict["policy"] - # clip the observations - obs = torch.clamp(obs, -self._clip_obs, self._clip_obs) - # move the buffer to rl-device - obs = obs.to(device=self._rl_device).clone() - - # check if asymmetric actor-critic or not - if self.rlg_num_states > 0: - # acquire states from the environment if it exists - try: - states = obs_dict["critic"] - except AttributeError: - raise NotImplementedError("Environment does not define key 'critic' for privileged observations.") - # clip the states - states = torch.clamp(states, -self._clip_obs, self._clip_obs) - # move buffers to rl-device - states = states.to(self._rl_device).clone() - # convert to dictionary - return {"obs": obs, "states": states} - else: - return obs
- - -""" -Environment Handler. -""" - - -
[文档]class RlGamesGpuEnv(IVecEnv): - """Thin wrapper to create instance of the environment to fit RL-Games runner.""" - - # TODO: Adding this for now but do we really need this? - -
[文档] def __init__(self, config_name: str, num_actors: int, **kwargs): - """Initialize the environment. - - Args: - config_name: The name of the environment configuration. - num_actors: The number of actors in the environment. This is not used in this wrapper. - """ - self.env: RlGamesVecEnvWrapper = env_configurations.configurations[config_name]["env_creator"](**kwargs)
- - def step(self, action): # noqa: D102 - return self.env.step(action) - - def reset(self): # noqa: D102 - return self.env.reset() - -
[文档] def get_number_of_agents(self) -> int: - """Get number of agents in the environment. - - Returns: - The number of agents in the environment. - """ - return self.env.get_number_of_agents()
- -
[文档] def get_env_info(self) -> dict: - """Get the Gym spaces for the environment. - - Returns: - The Gym spaces for the environment. - """ - return self.env.get_env_info()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.html deleted file mode 100644 index abe8659465..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/exporter.html +++ /dev/null @@ -1,714 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.wrappers.rsl_rl.exporter — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.wrappers.rsl_rl.exporter 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import copy
-import os
-import torch
-
-
-
[文档]def export_policy_as_jit(actor_critic: object, normalizer: object | None, path: str, filename="policy.pt"): - """Export policy into a Torch JIT file. - - Args: - actor_critic: The actor-critic torch module. - normalizer: The empirical normalizer module. If None, Identity is used. - path: The path to the saving directory. - filename: The name of exported JIT file. Defaults to "policy.pt". - """ - policy_exporter = _TorchPolicyExporter(actor_critic, normalizer) - policy_exporter.export(path, filename)
- - -
[文档]def export_policy_as_onnx( - actor_critic: object, path: str, normalizer: object | None = None, filename="policy.onnx", verbose=False -): - """Export policy into a Torch ONNX file. - - Args: - actor_critic: The actor-critic torch module. - normalizer: The empirical normalizer module. If None, Identity is used. - path: The path to the saving directory. - filename: The name of exported ONNX file. Defaults to "policy.onnx". - verbose: Whether to print the model summary. Defaults to False. - """ - if not os.path.exists(path): - os.makedirs(path, exist_ok=True) - policy_exporter = _OnnxPolicyExporter(actor_critic, normalizer, verbose) - policy_exporter.export(path, filename)
- - -""" -Helper Classes - Private. -""" - - -class _TorchPolicyExporter(torch.nn.Module): - """Exporter of actor-critic into JIT file.""" - - def __init__(self, actor_critic, normalizer=None): - super().__init__() - self.actor = copy.deepcopy(actor_critic.actor) - self.is_recurrent = actor_critic.is_recurrent - if self.is_recurrent: - self.rnn = copy.deepcopy(actor_critic.memory_a.rnn) - self.rnn.cpu() - self.register_buffer("hidden_state", torch.zeros(self.rnn.num_layers, 1, self.rnn.hidden_size)) - self.register_buffer("cell_state", torch.zeros(self.rnn.num_layers, 1, self.rnn.hidden_size)) - self.forward = self.forward_lstm - self.reset = self.reset_memory - # copy normalizer if exists - if normalizer: - self.normalizer = copy.deepcopy(normalizer) - else: - self.normalizer = torch.nn.Identity() - - def forward_lstm(self, x): - x = self.normalizer(x) - x, (h, c) = self.rnn(x.unsqueeze(0), (self.hidden_state, self.cell_state)) - self.hidden_state[:] = h - self.cell_state[:] = c - x = x.squeeze(0) - return self.actor(x) - - def forward(self, x): - return self.actor(self.normalizer(x)) - - @torch.jit.export - def reset(self): - pass - - def reset_memory(self): - self.hidden_state[:] = 0.0 - self.cell_state[:] = 0.0 - - def export(self, path, filename): - os.makedirs(path, exist_ok=True) - path = os.path.join(path, filename) - self.to("cpu") - traced_script_module = torch.jit.script(self) - traced_script_module.save(path) - - -class _OnnxPolicyExporter(torch.nn.Module): - """Exporter of actor-critic into ONNX file.""" - - def __init__(self, actor_critic, normalizer=None, verbose=False): - super().__init__() - self.verbose = verbose - self.actor = copy.deepcopy(actor_critic.actor) - self.is_recurrent = actor_critic.is_recurrent - if self.is_recurrent: - self.rnn = copy.deepcopy(actor_critic.memory_a.rnn) - self.rnn.cpu() - self.forward = self.forward_lstm - # copy normalizer if exists - if normalizer: - self.normalizer = copy.deepcopy(normalizer) - else: - self.normalizer = torch.nn.Identity() - - def forward_lstm(self, x_in, h_in, c_in): - x_in = self.normalizer(x_in) - x, (h, c) = self.rnn(x_in.unsqueeze(0), (h_in, c_in)) - x = x.squeeze(0) - return self.actor(x), h, c - - def forward(self, x): - return self.actor(self.normalizer(x)) - - def export(self, path, filename): - self.to("cpu") - if self.is_recurrent: - obs = torch.zeros(1, self.rnn.input_size) - h_in = torch.zeros(self.rnn.num_layers, 1, self.rnn.hidden_size) - c_in = torch.zeros(self.rnn.num_layers, 1, self.rnn.hidden_size) - actions, h_out, c_out = self(obs, h_in, c_in) - torch.onnx.export( - self, - (obs, h_in, c_in), - os.path.join(path, filename), - export_params=True, - opset_version=11, - verbose=self.verbose, - input_names=["obs", "h_in", "c_in"], - output_names=["actions", "h_out", "c_out"], - dynamic_axes={}, - ) - else: - obs = torch.zeros(1, self.actor[0].in_features) - torch.onnx.export( - self, - obs, - os.path.join(path, filename), - export_params=True, - opset_version=11, - verbose=self.verbose, - input_names=["obs"], - output_names=["actions"], - dynamic_axes={}, - ) -
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.html deleted file mode 100644 index 8d2de767ec..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/rl_cfg.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.wrappers.rsl_rl.rl_cfg — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.wrappers.rsl_rl.rl_cfg 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-from dataclasses import MISSING
-from typing import Literal
-
-from omni.isaac.lab.utils import configclass
-
-
-
[文档]@configclass -class RslRlPpoActorCriticCfg: - """Configuration for the PPO actor-critic networks.""" - - class_name: str = "ActorCritic" - """The policy class name. Default is ActorCritic.""" - - init_noise_std: float = MISSING - """The initial noise standard deviation for the policy.""" - - actor_hidden_dims: list[int] = MISSING - """The hidden dimensions of the actor network.""" - - critic_hidden_dims: list[int] = MISSING - """The hidden dimensions of the critic network.""" - - activation: str = MISSING - """The activation function for the actor and critic networks."""
- - -
[文档]@configclass -class RslRlPpoAlgorithmCfg: - """Configuration for the PPO algorithm.""" - - class_name: str = "PPO" - """The algorithm class name. Default is PPO.""" - - value_loss_coef: float = MISSING - """The coefficient for the value loss.""" - - use_clipped_value_loss: bool = MISSING - """Whether to use clipped value loss.""" - - clip_param: float = MISSING - """The clipping parameter for the policy.""" - - entropy_coef: float = MISSING - """The coefficient for the entropy loss.""" - - num_learning_epochs: int = MISSING - """The number of learning epochs per update.""" - - num_mini_batches: int = MISSING - """The number of mini-batches per update.""" - - learning_rate: float = MISSING - """The learning rate for the policy.""" - - schedule: str = MISSING - """The learning rate schedule.""" - - gamma: float = MISSING - """The discount factor.""" - - lam: float = MISSING - """The lambda parameter for Generalized Advantage Estimation (GAE).""" - - desired_kl: float = MISSING - """The desired KL divergence.""" - - max_grad_norm: float = MISSING - """The maximum gradient norm."""
- - -
[文档]@configclass -class RslRlOnPolicyRunnerCfg: - """Configuration of the runner for on-policy algorithms.""" - - seed: int = 42 - """The seed for the experiment. Default is 42.""" - - device: str = "cuda:0" - """The device for the rl-agent. Default is cuda:0.""" - - num_steps_per_env: int = MISSING - """The number of steps per environment per update.""" - - max_iterations: int = MISSING - """The maximum number of iterations.""" - - empirical_normalization: bool = MISSING - """Whether to use empirical normalization.""" - - policy: RslRlPpoActorCriticCfg = MISSING - """The policy configuration.""" - - algorithm: RslRlPpoAlgorithmCfg = MISSING - """The algorithm configuration.""" - - ## - # Checkpointing parameters - ## - - save_interval: int = MISSING - """The number of iterations between saves.""" - - experiment_name: str = MISSING - """The experiment name.""" - - run_name: str = "" - """The run name. Default is empty string. - - The name of the run directory is typically the time-stamp at execution. If the run name is not empty, - then it is appended to the run directory's name, i.e. the logging directory's name will become - ``{time-stamp}_{run_name}``. - """ - - ## - # Logging parameters - ## - - logger: Literal["tensorboard", "neptune", "wandb"] = "tensorboard" - """The logger to use. Default is tensorboard.""" - - neptune_project: str = "isaaclab" - """The neptune project name. Default is "isaaclab".""" - - wandb_project: str = "isaaclab" - """The wandb project name. Default is "isaaclab".""" - - ## - # Loading parameters - ## - - resume: bool = False - """Whether to resume. Default is False.""" - - load_run: str = ".*" - """The run directory to load. Default is ".*" (all). - - If regex expression, the latest (alphabetical order) matching run will be loaded. - """ - - load_checkpoint: str = "model_.*.pt" - """The checkpoint file to load. Default is ``"model_.*.pt"`` (all). - - If regex expression, the latest (alphabetical order) matching file will be loaded. - """
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.html deleted file mode 100644 index 6cae93f4f5..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/rsl_rl/vecenv_wrapper.html +++ /dev/null @@ -1,755 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.wrappers.rsl_rl.vecenv_wrapper — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.wrappers.rsl_rl.vecenv_wrapper 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to RSL-RL vectorized environment.
-
-The following example shows how to wrap an environment for RSL-RL:
-
-.. code-block:: python
-
-    from omni.isaac.lab_tasks.utils.wrappers.rsl_rl import RslRlVecEnvWrapper
-
-    env = RslRlVecEnvWrapper(env)
-
-"""
-
-
-import gymnasium as gym
-import torch
-
-from rsl_rl.env import VecEnv
-
-from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv
-
-
-
[文档]class RslRlVecEnvWrapper(VecEnv): - """Wraps around Isaac Lab environment for RSL-RL library - - To use asymmetric actor-critic, the environment instance must have the attributes :attr:`num_privileged_obs` (int). - This is used by the learning agent to allocate buffers in the trajectory memory. Additionally, the returned - observations should have the key "critic" which corresponds to the privileged observations. Since this is - optional for some environments, the wrapper checks if these attributes exist. If they don't then the wrapper - defaults to zero as number of privileged observations. - - .. caution:: - - This class must be the last wrapper in the wrapper chain. This is because the wrapper does not follow - the :class:`gym.Wrapper` interface. Any subsequent wrappers will need to be modified to work with this - wrapper. - - Reference: - https://github.com/leggedrobotics/rsl_rl/blob/master/rsl_rl/env/vec_env.py - """ - -
[文档] def __init__(self, env: ManagerBasedRLEnv): - """Initializes the wrapper. - - Note: - The wrapper calls :meth:`reset` at the start since the RSL-RL runner does not call reset. - - Args: - env: The environment to wrap around. - - Raises: - ValueError: When the environment is not an instance of :class:`ManagerBasedRLEnv`. - """ - # check that input is valid - if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): - raise ValueError( - "The environment must be inherited from ManagerBasedRLEnv or DirectRLEnv. Environment type:" - f" {type(env)}" - ) - # initialize the wrapper - self.env = env - # store information required by wrapper - self.num_envs = self.unwrapped.num_envs - self.device = self.unwrapped.device - self.max_episode_length = self.unwrapped.max_episode_length - if hasattr(self.unwrapped, "action_manager"): - self.num_actions = self.unwrapped.action_manager.total_action_dim - else: - self.num_actions = self.unwrapped.num_actions - if hasattr(self.unwrapped, "observation_manager"): - self.num_obs = self.unwrapped.observation_manager.group_obs_dim["policy"][0] - else: - self.num_obs = self.unwrapped.num_observations - # -- privileged observations - if ( - hasattr(self.unwrapped, "observation_manager") - and "critic" in self.unwrapped.observation_manager.group_obs_dim - ): - self.num_privileged_obs = self.unwrapped.observation_manager.group_obs_dim["critic"][0] - elif hasattr(self.unwrapped, "num_states"): - self.num_privileged_obs = self.unwrapped.num_states - else: - self.num_privileged_obs = 0 - # reset at the start since the RSL-RL runner does not call reset - self.env.reset()
- - def __str__(self): - """Returns the wrapper name and the :attr:`env` representation string.""" - return f"<{type(self).__name__}{self.env}>" - - def __repr__(self): - """Returns the string representation of the wrapper.""" - return str(self) - - """ - Properties -- Gym.Wrapper - """ - - @property - def cfg(self) -> object: - """Returns the configuration class instance of the environment.""" - return self.unwrapped.cfg - - @property - def render_mode(self) -> str | None: - """Returns the :attr:`Env` :attr:`render_mode`.""" - return self.env.render_mode - - @property - def observation_space(self) -> gym.Space: - """Returns the :attr:`Env` :attr:`observation_space`.""" - return self.env.observation_space - - @property - def action_space(self) -> gym.Space: - """Returns the :attr:`Env` :attr:`action_space`.""" - return self.env.action_space - -
[文档] @classmethod - def class_name(cls) -> str: - """Returns the class name of the wrapper.""" - return cls.__name__
- - @property - def unwrapped(self) -> ManagerBasedRLEnv: - """Returns the base environment of the wrapper. - - This will be the bare :class:`gymnasium.Env` environment, underneath all layers of wrappers. - """ - return self.env.unwrapped - - """ - Properties - """ - -
[文档] def get_observations(self) -> tuple[torch.Tensor, dict]: - """Returns the current observations of the environment.""" - if hasattr(self.unwrapped, "observation_manager"): - obs_dict = self.unwrapped.observation_manager.compute() - else: - obs_dict = self.unwrapped._get_observations() - return obs_dict["policy"], {"observations": obs_dict}
- - @property - def episode_length_buf(self) -> torch.Tensor: - """The episode length buffer.""" - return self.unwrapped.episode_length_buf - - @episode_length_buf.setter - def episode_length_buf(self, value: torch.Tensor): - """Set the episode length buffer. - - Note: - This is needed to perform random initialization of episode lengths in RSL-RL. - """ - self.unwrapped.episode_length_buf = value - - """ - Operations - MDP - """ - - def seed(self, seed: int = -1) -> int: # noqa: D102 - return self.unwrapped.seed(seed) - - def reset(self) -> tuple[torch.Tensor, dict]: # noqa: D102 - # reset the environment - obs_dict, _ = self.env.reset() - # return observations - return obs_dict["policy"], {"observations": obs_dict} - - def step(self, actions: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, dict]: - # record step information - obs_dict, rew, terminated, truncated, extras = self.env.step(actions) - # compute dones for compatibility with RSL-RL - dones = (terminated | truncated).to(dtype=torch.long) - # move extra observations to the extras dict - obs = obs_dict["policy"] - extras["observations"] = obs_dict - # move time out information to the extras dict - # this is only needed for infinite horizon tasks - if not self.unwrapped.cfg.is_finite_horizon: - extras["time_outs"] = truncated - - # return the step information - return obs, rew, dones, extras - - def close(self): # noqa: D102 - return self.env.close()
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/sb3.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/sb3.html deleted file mode 100644 index 5808779fea..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/sb3.html +++ /dev/null @@ -1,908 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.wrappers.sb3 — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.wrappers.sb3 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to Stable-Baselines3 vectorized environment.
-
-The following example shows how to wrap an environment for Stable-Baselines3:
-
-.. code-block:: python
-
-    from omni.isaac.lab_tasks.utils.wrappers.sb3 import Sb3VecEnvWrapper
-
-    env = Sb3VecEnvWrapper(env)
-
-"""
-
-# needed to import for allowing type-hinting: torch.Tensor | dict[str, torch.Tensor]
-from __future__ import annotations
-
-import gymnasium as gym
-import numpy as np
-import torch
-import torch.nn as nn  # noqa: F401
-from typing import Any
-
-from stable_baselines3.common.utils import constant_fn
-from stable_baselines3.common.vec_env.base_vec_env import VecEnv, VecEnvObs, VecEnvStepReturn
-
-from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv
-
-"""
-Configuration Parser.
-"""
-
-
-
[文档]def process_sb3_cfg(cfg: dict) -> dict: - """Convert simple YAML types to Stable-Baselines classes/components. - - Args: - cfg: A configuration dictionary. - - Returns: - A dictionary containing the converted configuration. - - Reference: - https://github.com/DLR-RM/rl-baselines3-zoo/blob/0e5eb145faefa33e7d79c7f8c179788574b20da5/utils/exp_manager.py#L358 - """ - - def update_dict(hyperparams: dict[str, Any]) -> dict[str, Any]: - for key, value in hyperparams.items(): - if isinstance(value, dict): - update_dict(value) - else: - if key in ["policy_kwargs", "replay_buffer_class", "replay_buffer_kwargs"]: - hyperparams[key] = eval(value) - elif key in ["learning_rate", "clip_range", "clip_range_vf", "delta_std"]: - if isinstance(value, str): - _, initial_value = value.split("_") - initial_value = float(initial_value) - hyperparams[key] = lambda progress_remaining: progress_remaining * initial_value - elif isinstance(value, (float, int)): - # Negative value: ignore (ex: for clipping) - if value < 0: - continue - hyperparams[key] = constant_fn(float(value)) - else: - raise ValueError(f"Invalid value for {key}: {hyperparams[key]}") - - return hyperparams - - # parse agent configuration and convert to classes - return update_dict(cfg)
- - -""" -Vectorized environment wrapper. -""" - - -
[文档]class Sb3VecEnvWrapper(VecEnv): - """Wraps around Isaac Lab environment for Stable Baselines3. - - Isaac Sim internally implements a vectorized environment. However, since it is - still considered a single environment instance, Stable Baselines tries to wrap - around it using the :class:`DummyVecEnv`. This is only done if the environment - is not inheriting from their :class:`VecEnv`. Thus, this class thinly wraps - over the environment from :class:`ManagerBasedRLEnv`. - - Note: - While Stable-Baselines3 supports Gym 0.26+ API, their vectorized environment - still uses the old API (i.e. it is closer to Gym 0.21). Thus, we implement - the old API for the vectorized environment. - - We also add monitoring functionality that computes the un-discounted episode - return and length. This information is added to the info dicts under key `episode`. - - In contrast to the Isaac Lab environment, stable-baselines expect the following: - - 1. numpy datatype for MDP signals - 2. a list of info dicts for each sub-environment (instead of a dict) - 3. when environment has terminated, the observations from the environment should correspond - to the one after reset. The "real" final observation is passed using the info dicts - under the key ``terminal_observation``. - - .. warning:: - - By the nature of physics stepping in Isaac Sim, it is not possible to forward the - simulation buffers without performing a physics step. Thus, reset is performed - inside the :meth:`step()` function after the actual physics step is taken. - Thus, the returned observations for terminated environments is the one after the reset. - - .. caution:: - - This class must be the last wrapper in the wrapper chain. This is because the wrapper does not follow - the :class:`gym.Wrapper` interface. Any subsequent wrappers will need to be modified to work with this - wrapper. - - Reference: - - 1. https://stable-baselines3.readthedocs.io/en/master/guide/vec_envs.html - 2. https://stable-baselines3.readthedocs.io/en/master/common/monitor.html - - """ - -
[文档] def __init__(self, env: ManagerBasedRLEnv): - """Initialize the wrapper. - - Args: - env: The environment to wrap around. - - Raises: - ValueError: When the environment is not an instance of :class:`ManagerBasedRLEnv`. - """ - # check that input is valid - if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): - raise ValueError( - "The environment must be inherited from ManagerBasedRLEnv or DirectRLEnv. Environment type:" - f" {type(env)}" - ) - # initialize the wrapper - self.env = env - # collect common information - self.num_envs = self.unwrapped.num_envs - self.sim_device = self.unwrapped.device - self.render_mode = self.unwrapped.render_mode - - # obtain gym spaces - # note: stable-baselines3 does not like when we have unbounded action space so - # we set it to some high value here. Maybe this is not general but something to think about. - observation_space = self.unwrapped.single_observation_space["policy"] - action_space = self.unwrapped.single_action_space - if isinstance(action_space, gym.spaces.Box) and not action_space.is_bounded("both"): - action_space = gym.spaces.Box(low=-100, high=100, shape=action_space.shape) - - # initialize vec-env - VecEnv.__init__(self, self.num_envs, observation_space, action_space) - # add buffer for logging episodic information - self._ep_rew_buf = torch.zeros(self.num_envs, device=self.sim_device) - self._ep_len_buf = torch.zeros(self.num_envs, device=self.sim_device)
- - def __str__(self): - """Returns the wrapper name and the :attr:`env` representation string.""" - return f"<{type(self).__name__}{self.env}>" - - def __repr__(self): - """Returns the string representation of the wrapper.""" - return str(self) - - """ - Properties -- Gym.Wrapper - """ - -
[文档] @classmethod - def class_name(cls) -> str: - """Returns the class name of the wrapper.""" - return cls.__name__
- - @property - def unwrapped(self) -> ManagerBasedRLEnv: - """Returns the base environment of the wrapper. - - This will be the bare :class:`gymnasium.Env` environment, underneath all layers of wrappers. - """ - return self.env.unwrapped - - """ - Properties - """ - -
[文档] def get_episode_rewards(self) -> list[float]: - """Returns the rewards of all the episodes.""" - return self._ep_rew_buf.cpu().tolist()
- -
[文档] def get_episode_lengths(self) -> list[int]: - """Returns the number of time-steps of all the episodes.""" - return self._ep_len_buf.cpu().tolist()
- - """ - Operations - MDP - """ - - def seed(self, seed: int | None = None) -> list[int | None]: # noqa: D102 - return [self.unwrapped.seed(seed)] * self.unwrapped.num_envs - - def reset(self) -> VecEnvObs: # noqa: D102 - obs_dict, _ = self.env.reset() - # convert data types to numpy depending on backend - return self._process_obs(obs_dict) - - def step_async(self, actions): # noqa: D102 - # convert input to numpy array - if not isinstance(actions, torch.Tensor): - actions = np.asarray(actions) - actions = torch.from_numpy(actions).to(device=self.sim_device, dtype=torch.float32) - else: - actions = actions.to(device=self.sim_device, dtype=torch.float32) - # convert to tensor - self._async_actions = actions - - def step_wait(self) -> VecEnvStepReturn: # noqa: D102 - # record step information - obs_dict, rew, terminated, truncated, extras = self.env.step(self._async_actions) - # update episode un-discounted return and length - self._ep_rew_buf += rew - self._ep_len_buf += 1 - # compute reset ids - dones = terminated | truncated - reset_ids = (dones > 0).nonzero(as_tuple=False) - - # convert data types to numpy depending on backend - # note: ManagerBasedRLEnv uses torch backend (by default). - obs = self._process_obs(obs_dict) - rew = rew.detach().cpu().numpy() - terminated = terminated.detach().cpu().numpy() - truncated = truncated.detach().cpu().numpy() - dones = dones.detach().cpu().numpy() - # convert extra information to list of dicts - infos = self._process_extras(obs, terminated, truncated, extras, reset_ids) - - # reset info for terminated environments - self._ep_rew_buf[reset_ids] = 0 - self._ep_len_buf[reset_ids] = 0 - - return obs, rew, dones, infos - - def close(self): # noqa: D102 - self.env.close() - - def get_attr(self, attr_name, indices=None): # noqa: D102 - # resolve indices - if indices is None: - indices = slice(None) - num_indices = self.num_envs - else: - num_indices = len(indices) - # obtain attribute value - attr_val = getattr(self.env, attr_name) - # return the value - if not isinstance(attr_val, torch.Tensor): - return [attr_val] * num_indices - else: - return attr_val[indices].detach().cpu().numpy() - - def set_attr(self, attr_name, value, indices=None): # noqa: D102 - raise NotImplementedError("Setting attributes is not supported.") - - def env_method(self, method_name: str, *method_args, indices=None, **method_kwargs): # noqa: D102 - if method_name == "render": - # gymnasium does not support changing render mode at runtime - return self.env.render() - else: - # this isn't properly implemented but it is not necessary. - # mostly done for completeness. - env_method = getattr(self.env, method_name) - return env_method(*method_args, indices=indices, **method_kwargs) - - def env_is_wrapped(self, wrapper_class, indices=None): # noqa: D102 - raise NotImplementedError("Checking if environment is wrapped is not supported.") - - def get_images(self): # noqa: D102 - raise NotImplementedError("Getting images is not supported.") - - """ - Helper functions. - """ - - def _process_obs(self, obs_dict: torch.Tensor | dict[str, torch.Tensor]) -> np.ndarray | dict[str, np.ndarray]: - """Convert observations into NumPy data type.""" - # Sb3 doesn't support asymmetric observation spaces, so we only use "policy" - obs = obs_dict["policy"] - # note: ManagerBasedRLEnv uses torch backend (by default). - if isinstance(obs, dict): - for key, value in obs.items(): - obs[key] = value.detach().cpu().numpy() - elif isinstance(obs, torch.Tensor): - obs = obs.detach().cpu().numpy() - else: - raise NotImplementedError(f"Unsupported data type: {type(obs)}") - return obs - - def _process_extras( - self, obs: np.ndarray, terminated: np.ndarray, truncated: np.ndarray, extras: dict, reset_ids: np.ndarray - ) -> list[dict[str, Any]]: - """Convert miscellaneous information into dictionary for each sub-environment.""" - # create empty list of dictionaries to fill - infos: list[dict[str, Any]] = [dict.fromkeys(extras.keys()) for _ in range(self.num_envs)] - # fill-in information for each sub-environment - # note: This loop becomes slow when number of environments is large. - for idx in range(self.num_envs): - # fill-in episode monitoring info - if idx in reset_ids: - infos[idx]["episode"] = dict() - infos[idx]["episode"]["r"] = float(self._ep_rew_buf[idx]) - infos[idx]["episode"]["l"] = float(self._ep_len_buf[idx]) - else: - infos[idx]["episode"] = None - # fill-in bootstrap information - infos[idx]["TimeLimit.truncated"] = truncated[idx] and not terminated[idx] - # fill-in information from extras - for key, value in extras.items(): - # 1. remap extra episodes information safely - # 2. for others just store their values - if key == "log": - # only log this data for episodes that are terminated - if infos[idx]["episode"] is not None: - for sub_key, sub_value in value.items(): - infos[idx]["episode"][sub_key] = sub_value - else: - infos[idx][key] = value[idx] - # add information about terminal observation separately - if idx in reset_ids: - # extract terminal observations - if isinstance(obs, dict): - terminal_obs = dict.fromkeys(obs.keys()) - for key, value in obs.items(): - terminal_obs[key] = value[idx] - else: - terminal_obs = obs[idx] - # add info to dict - infos[idx]["terminal_observation"] = terminal_obs - else: - infos[idx]["terminal_observation"] = None - # return list of dictionaries - return infos
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/skrl.html b/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/skrl.html deleted file mode 100644 index db36486f40..0000000000 --- a/docs/_build/html/_modules/omni/isaac/lab_tasks/utils/wrappers/skrl.html +++ /dev/null @@ -1,702 +0,0 @@ - - - - - - - - - - - omni.isaac.lab_tasks.utils.wrappers.skrl — Isaac Lab documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
-
-
-
-
- -
- -
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
-
- -
- -
- -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - - -
-

- -
-
- -
-
-
- - - - -
- -

omni.isaac.lab_tasks.utils.wrappers.skrl 源代码

-# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
-# All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-"""Wrapper to configure an :class:`ManagerBasedRLEnv` instance to skrl environment.
-
-The following example shows how to wrap an environment for skrl:
-
-.. code-block:: python
-
-    from omni.isaac.lab_tasks.utils.wrappers.skrl import SkrlVecEnvWrapper
-
-    env = SkrlVecEnvWrapper(env, ml_framework="torch")  # or ml_framework="jax"
-
-Or, equivalently, by directly calling the skrl library API as follows:
-
-.. code-block:: python
-
-    from skrl.envs.torch.wrappers import wrap_env  # for PyTorch, or...
-    from skrl.envs.jax.wrappers import wrap_env    # for JAX
-
-    env = wrap_env(env, wrapper="isaaclab")
-
-"""
-
-# needed to import for type hinting: Agent | list[Agent]
-from __future__ import annotations
-
-from typing import Literal
-
-from omni.isaac.lab.envs import DirectRLEnv, ManagerBasedRLEnv
-
-"""
-Configuration Parser.
-"""
-
-
-
[文档]def process_skrl_cfg(cfg: dict, ml_framework: Literal["torch", "jax", "jax-numpy"] = "torch") -> dict: - """Convert simple YAML types to skrl classes/components. - - Args: - cfg: A configuration dictionary. - ml_framework: The ML framework to use for the wrapper. Defaults to "torch". - - Returns: - A dictionary containing the converted configuration. - - Raises: - ValueError: If the specified ML framework is not valid. - """ - _direct_eval = [ - "learning_rate_scheduler", - "state_preprocessor", - "value_preprocessor", - "input_shape", - "output_shape", - ] - - def reward_shaper_function(scale): - def reward_shaper(rewards, timestep, timesteps): - return rewards * scale - - return reward_shaper - - def update_dict(d): - # import statements according to the ML framework - if ml_framework.startswith("torch"): - from skrl.resources.preprocessors.torch import RunningStandardScaler # noqa: F401 - from skrl.resources.schedulers.torch import KLAdaptiveLR # noqa: F401 - from skrl.utils.model_instantiators.torch import Shape # noqa: F401 - elif ml_framework.startswith("jax"): - from skrl.resources.preprocessors.jax import RunningStandardScaler # noqa: F401 - from skrl.resources.schedulers.jax import KLAdaptiveLR # noqa: F401 - from skrl.utils.model_instantiators.jax import Shape # noqa: F401 - else: - ValueError( - f"Invalid ML framework for skrl: {ml_framework}. Available options are: 'torch', 'jax' or 'jax-numpy'" - ) - - for key, value in d.items(): - if isinstance(value, dict): - update_dict(value) - else: - if key in _direct_eval: - d[key] = eval(value) - elif key.endswith("_kwargs"): - d[key] = value if value is not None else {} - elif key in ["rewards_shaper_scale"]: - d["rewards_shaper"] = reward_shaper_function(value) - - return d - - # parse agent configuration and convert to classes - return update_dict(cfg)
- - -""" -Vectorized environment wrapper. -""" - - -
[文档]def SkrlVecEnvWrapper(env: ManagerBasedRLEnv, ml_framework: Literal["torch", "jax", "jax-numpy"] = "torch"): - """Wraps around Isaac Lab environment for skrl. - - This function wraps around the Isaac Lab environment. Since the :class:`ManagerBasedRLEnv` environment - wrapping functionality is defined within the skrl library itself, this implementation - is maintained for compatibility with the structure of the extension that contains it. - Internally it calls the :func:`wrap_env` from the skrl library API. - - Args: - env: The environment to wrap around. - ml_framework: The ML framework to use for the wrapper. Defaults to "torch". - - Raises: - ValueError: When the environment is not an instance of :class:`ManagerBasedRLEnv`. - ValueError: If the specified ML framework is not valid. - - Reference: - https://skrl.readthedocs.io/en/latest/api/envs/wrapping.html - """ - # check that input is valid - if not isinstance(env.unwrapped, ManagerBasedRLEnv) and not isinstance(env.unwrapped, DirectRLEnv): - raise ValueError( - f"The environment must be inherited from ManagerBasedRLEnv or DirectRLEnv. Environment type: {type(env)}" - ) - - # import statements according to the ML framework - if ml_framework.startswith("torch"): - from skrl.envs.wrappers.torch import wrap_env - elif ml_framework.startswith("jax"): - from skrl.envs.wrappers.jax import wrap_env - else: - ValueError( - f"Invalid ML framework for skrl: {ml_framework}. Available options are: 'torch', 'jax' or 'jax-numpy'" - ) - - # wrap and return the environment - return wrap_env(env, wrapper="isaaclab")
-
- -
- - - - - - -
- -
-
-
- -
- - - - -
- - - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/_sources/index.rst b/docs/_build/html/_sources/index.rst deleted file mode 100644 index 3307425d83..0000000000 --- a/docs/_build/html/_sources/index.rst +++ /dev/null @@ -1,130 +0,0 @@ -Overview -======== - -.. figure:: source/_static/isaaclab.jpg - :width: 100% - :alt: H1 Humanoid example using Isaac Lab - -**Isaac Lab** is a unified and modular framework for robot learning that aims to simplify common workflows -in robotics research (such as RL, learning from demonstrations, and motion planning). It is built upon -`NVIDIA Isaac Sim`_ to leverage the latest simulation capabilities for photo-realistic scenes, and fast -and efficient simulation. The core objectives of the framework are: - -- **Modularity**: Easily customize and add new environments, robots, and sensors. -- **Agility**: Adapt to the changing needs of the community. -- **Openness**: Remain open-sourced to allow the community to contribute and extend the framework. -- **Battery-included**: Include a number of environments, sensors, and tasks that are ready to use. - -Key features available in Isaac Lab include fast and accurate physics simulation provided by PhysX, -tiled rendering APIs for vectorized rendering, domain randomization for improving robustness and adaptability, -and support for running in the cloud. - -For more information about the framework, please refer to the `paper `_ -:cite:`mittal2023orbit`. For clarifications on NVIDIA Isaac ecosystem, please check out the -:doc:`/source/setup/faq` section. - -.. figure:: source/_static/tasks.jpg - :width: 100% - :alt: Example tasks created using Isaac Lab - - -License -======= - -The Isaac Lab framework is open-sourced under the BSD-3-Clause license. -Please refer to :ref:`license` for more details. - -Acknowledgement -=============== -Isaac Lab development initiated from the `Orbit `_ framework. We would appreciate if you would cite it in academic publications as well: - -.. code:: bibtex - - @article{mittal2023orbit, - author={Mittal, Mayank and Yu, Calvin and Yu, Qinxi and Liu, Jingzhou and Rudin, Nikita and Hoeller, David and Yuan, Jia Lin and Singh, Ritvik and Guo, Yunrong and Mazhar, Hammad and Mandlekar, Ajay and Babich, Buck and State, Gavriel and Hutter, Marco and Garg, Animesh}, - journal={IEEE Robotics and Automation Letters}, - title={Orbit: A Unified Simulation Framework for Interactive Robot Learning Environments}, - year={2023}, - volume={8}, - number={6}, - pages={3740-3747}, - doi={10.1109/LRA.2023.3270034} - } - - -Table of Contents -================= - -.. toctree:: - :maxdepth: 2 - :caption: Getting Started - - source/setup/translation - source/setup/installation/index - source/setup/developer - source/setup/sample - source/setup/template - source/setup/faq - -.. toctree:: - :maxdepth: 2 - :caption: Features - - source/features/task_workflows - source/features/multi_gpu - source/features/tiled_rendering - source/features/environments - source/features/actuators - .. source/features/motion_generators - -.. toctree:: - :maxdepth: 1 - :caption: Resources - :titlesonly: - - source/tutorials/index - source/how-to/index - source/deployment/index - -.. toctree:: - :maxdepth: 1 - :caption: Source API - - source/api/index - -.. toctree:: - :maxdepth: 1 - :caption: Migration Guides - :titlesonly: - - source/migration/migrating_from_isaacgymenvs - source/migration/migrating_from_omniisaacgymenvs - source/migration/migrating_from_orbit - -.. toctree:: - :maxdepth: 1 - :caption: References - - source/refs/contributing - source/refs/troubleshooting - source/refs/issues - source/refs/changelog - source/refs/license - source/refs/bibliography - -.. toctree:: - :hidden: - :caption: Project Links - - GitHub - NVIDIA Isaac Sim - NVIDIA PhysX - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. _NVIDIA Isaac Sim: https://docs.omniverse.nvidia.com/isaacsim/latest/index.html diff --git a/docs/_build/html/_sources/source/api/index.rst b/docs/_build/html/_sources/source/api/index.rst deleted file mode 100644 index 4de3c159a5..0000000000 --- a/docs/_build/html/_sources/source/api/index.rst +++ /dev/null @@ -1,57 +0,0 @@ -API Reference -============= - -This page gives an overview of all the modules and classes in the Isaac Lab extensions. - -omni.isaac.lab extension ------------------------- - -The following modules are available in the ``omni.isaac.lab`` extension: - -.. currentmodule:: omni.isaac.lab - -.. autosummary:: - :toctree: lab - - app - actuators - assets - controllers - devices - envs - managers - markers - scene - sensors - sim - terrains - utils - -.. toctree:: - :hidden: - - lab/omni.isaac.lab.envs.mdp - lab/omni.isaac.lab.envs.ui - lab/omni.isaac.lab.sensors.patterns - lab/omni.isaac.lab.sim.converters - lab/omni.isaac.lab.sim.schemas - lab/omni.isaac.lab.sim.spawners - -omni.isaac.lab_tasks extension --------------------------------- - -The following modules are available in the ``omni.isaac.lab_tasks`` extension: - -.. currentmodule:: omni.isaac.lab_tasks - -.. autosummary:: - :toctree: lab_tasks - - utils - - -.. toctree:: - :hidden: - - lab_tasks/omni.isaac.lab_tasks.utils.wrappers - lab_tasks/omni.isaac.lab_tasks.utils.data_collector diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.actuators.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.actuators.rst deleted file mode 100644 index 312747f889..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.actuators.rst +++ /dev/null @@ -1,135 +0,0 @@ -omni.isaac.lab.actuators -======================== - -.. automodule:: omni.isaac.lab.actuators - - .. rubric:: Classes - - .. autosummary:: - - ActuatorBase - ActuatorBaseCfg - ImplicitActuator - ImplicitActuatorCfg - IdealPDActuator - IdealPDActuatorCfg - DCMotor - DCMotorCfg - DelayedPDActuator - DelayedPDActuatorCfg - RemotizedPDActuator - RemotizedPDActuatorCfg - ActuatorNetMLP - ActuatorNetMLPCfg - ActuatorNetLSTM - ActuatorNetLSTMCfg - -Actuator Base -------------- - -.. autoclass:: ActuatorBase - :members: - :inherited-members: - -.. autoclass:: ActuatorBaseCfg - :members: - :inherited-members: - :exclude-members: __init__, class_type - -Implicit Actuator ------------------ - -.. autoclass:: ImplicitActuator - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ImplicitActuatorCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Ideal PD Actuator ------------------ - -.. autoclass:: IdealPDActuator - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: IdealPDActuatorCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -DC Motor Actuator ------------------ - -.. autoclass:: DCMotor - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: DCMotorCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Delayed PD Actuator -------------------- - -.. autoclass:: DelayedPDActuator - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: DelayedPDActuatorCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Remotized PD Actuator ---------------------- - -.. autoclass:: RemotizedPDActuator - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: RemotizedPDActuatorCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -MLP Network Actuator ---------------------- - -.. autoclass:: ActuatorNetMLP - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ActuatorNetMLPCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -LSTM Network Actuator ---------------------- - -.. autoclass:: ActuatorNetLSTM - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ActuatorNetLSTMCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.app.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.app.rst deleted file mode 100644 index 5616fa74ec..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.app.rst +++ /dev/null @@ -1,115 +0,0 @@ -omni.isaac.lab.app -================== - -.. automodule:: omni.isaac.lab.app - - .. rubric:: Classes - - .. autosummary:: - - AppLauncher - - -Environment variables ---------------------- - -The following details the behavior of the class based on the environment variables: - -* **Headless mode**: If the environment variable ``HEADLESS=1``, then SimulationApp will be started in headless mode. - If ``LIVESTREAM={1,2}``, then it will supersede the ``HEADLESS`` envvar and force headlessness. - - * ``HEADLESS=1`` causes the app to run in headless mode. - -* **Livestreaming**: If the environment variable ``LIVESTREAM={1,2}`` , then `livestream`_ is enabled. Any - of the livestream modes being true forces the app to run in headless mode. - - * ``LIVESTREAM=1`` enables streaming via the Isaac `Native Livestream`_ extension. This allows users to - connect through the Omniverse Streaming Client. - * ``LIVESTREAM=2`` enables streaming via the `WebRTC Livestream`_ extension. This allows users to - connect in a browser using the WebRTC protocol. - - .. note:: - - Each Isaac Sim instance can only connect to one streaming client. - Connecting to an Isaac Sim instance that is currently serving a streaming client - results in an error for the second user. - -* **Enable cameras**: If the environment variable ``ENABLE_CAMERAS`` is set to 1, then the - cameras are enabled. This is useful for running the simulator without a GUI but still rendering the - viewport and camera images. - - * ``ENABLE_CAMERAS=1``: Enables the offscreen-render pipeline which allows users to render - the scene without launching a GUI. - - .. note:: - - The off-screen rendering pipeline only works when used in conjunction with the - :class:`omni.isaac.lab.sim.SimulationContext` class. This is because the off-screen rendering - pipeline enables flags that are internally used by the SimulationContext class. - - -To set the environment variables, one can use the following command in the terminal: - -.. code:: bash - - export REMOTE_DEPLOYMENT=3 - export ENABLE_CAMERAS=1 - # run the python script - ./isaaclab.sh -p source/standalone/demo/play_quadrupeds.py - -Alternatively, one can set the environment variables to the python script directly: - -.. code:: bash - - REMOTE_DEPLOYMENT=3 ENABLE_CAMERAS=1 ./isaaclab.sh -p source/standalone/demo/play_quadrupeds.py - - -Overriding the environment variables ------------------------------------- - -The environment variables can be overridden in the python script itself using the :class:`AppLauncher`. -These can be passed as a dictionary, a :class:`argparse.Namespace` object or as keyword arguments. -When the passed arguments are not the default values, then they override the environment variables. - -The following snippet shows how use the :class:`AppLauncher` in different ways: - -.. code:: python - - import argparser - - from omni.isaac.lab.app import AppLauncher - - # add argparse arguments - parser = argparse.ArgumentParser() - # add your own arguments - # .... - # add app launcher arguments for cli - AppLauncher.add_app_launcher_args(parser) - # parse arguments - args = parser.parse_args() - - # launch omniverse isaac-sim app - # -- Option 1: Pass the settings as a Namespace object - app_launcher = AppLauncher(args).app - # -- Option 2: Pass the settings as keywords arguments - app_launcher = AppLauncher(headless=args.headless, livestream=args.livestream) - # -- Option 3: Pass the settings as a dictionary - app_launcher = AppLauncher(vars(args)) - # -- Option 4: Pass no settings - app_launcher = AppLauncher() - - # obtain the launched app - simulation_app = app_launcher.app - - -Simulation App Launcher ------------------------ - -.. autoclass:: AppLauncher - :members: - - -.. _livestream: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html -.. _`Native Livestream`: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html#isaac-sim-setup-kit-remote -.. _`Websocket Livestream`: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html#isaac-sim-setup-livestream-webrtc -.. _`WebRTC Livestream`: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html#isaac-sim-setup-livestream-websocket diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.assets.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.assets.rst deleted file mode 100644 index b77cdb61ef..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.assets.rst +++ /dev/null @@ -1,69 +0,0 @@ -omni.isaac.lab.assets -===================== - -.. automodule:: omni.isaac.lab.assets - - .. rubric:: Classes - - .. autosummary:: - - AssetBase - AssetBaseCfg - RigidObject - RigidObjectData - RigidObjectCfg - Articulation - ArticulationData - ArticulationCfg - -.. currentmodule:: omni.isaac.lab.assets - -Asset Base ----------- - -.. autoclass:: AssetBase - :members: - -.. autoclass:: AssetBaseCfg - :members: - :exclude-members: __init__, class_type - -Rigid Object ------------- - -.. autoclass:: RigidObject - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: RigidObjectData - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__ - -.. autoclass:: RigidObjectCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Articulation ------------- - -.. autoclass:: Articulation - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ArticulationData - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__ - -.. autoclass:: ArticulationCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.controllers.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.controllers.rst deleted file mode 100644 index d56efdd05a..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.controllers.rst +++ /dev/null @@ -1,25 +0,0 @@ -omni.isaac.lab.controllers -========================== - -.. automodule:: omni.isaac.lab.controllers - - .. rubric:: Classes - - .. autosummary:: - - DifferentialIKController - DifferentialIKControllerCfg - -Differential Inverse Kinematics -------------------------------- - -.. autoclass:: DifferentialIKController - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: DifferentialIKControllerCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.devices.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.devices.rst deleted file mode 100644 index 876787ca36..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.devices.rst +++ /dev/null @@ -1,61 +0,0 @@ -omni.isaac.lab.devices -====================== - -.. automodule:: omni.isaac.lab.devices - - .. rubric:: Classes - - .. autosummary:: - - DeviceBase - Se2Gamepad - Se3Gamepad - Se2Keyboard - Se3Keyboard - Se3SpaceMouse - Se3SpaceMouse - -Device Base ------------ - -.. autoclass:: DeviceBase - :members: - -Game Pad --------- - -.. autoclass:: Se2Gamepad - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: Se3Gamepad - :members: - :inherited-members: - :show-inheritance: - -Keyboard --------- - -.. autoclass:: Se2Keyboard - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: Se3Keyboard - :members: - :inherited-members: - :show-inheritance: - -Space Mouse ------------ - -.. autoclass:: Se2SpaceMouse - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: Se3SpaceMouse - :members: - :inherited-members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.mdp.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.mdp.rst deleted file mode 100644 index 0a24a84c85..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.mdp.rst +++ /dev/null @@ -1,54 +0,0 @@ -omni.isaac.lab.envs.mdp -======================= - -.. automodule:: omni.isaac.lab.envs.mdp - -Observations ------------- - -.. automodule:: omni.isaac.lab.envs.mdp.observations - :members: - -Actions -------- - -.. automodule:: omni.isaac.lab.envs.mdp.actions - -.. automodule:: omni.isaac.lab.envs.mdp.actions.actions_cfg - :members: - :show-inheritance: - :exclude-members: __init__, class_type - -Events ------- - -.. automodule:: omni.isaac.lab.envs.mdp.events - :members: - -Commands --------- - -.. automodule:: omni.isaac.lab.envs.mdp.commands - -.. automodule:: omni.isaac.lab.envs.mdp.commands.commands_cfg - :members: - :show-inheritance: - :exclude-members: __init__, class_type - -Rewards -------- - -.. automodule:: omni.isaac.lab.envs.mdp.rewards - :members: - -Terminations ------------- - -.. automodule:: omni.isaac.lab.envs.mdp.terminations - :members: - -Curriculum ----------- - -.. automodule:: omni.isaac.lab.envs.mdp.curriculums - :members: diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.rst deleted file mode 100644 index f87f29d258..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.rst +++ /dev/null @@ -1,68 +0,0 @@ -omni.isaac.lab.envs -=================== - -.. automodule:: omni.isaac.lab.envs - - .. rubric:: Submodules - - .. autosummary:: - - mdp - ui - - .. rubric:: Classes - - .. autosummary:: - - ManagerBasedEnv - ManagerBasedEnvCfg - ManagerBasedRLEnv - ManagerBasedRLEnvCfg - DirectRLEnv - DirectRLEnvCfg - ViewerCfg - -Manager Based Environment -------------------------- - -.. autoclass:: ManagerBasedEnv - :members: - -.. autoclass:: ManagerBasedEnvCfg - :members: - :exclude-members: __init__, class_type - -Manager Based RL Environment ----------------------------- - -.. autoclass:: ManagerBasedRLEnv - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ManagerBasedRLEnvCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Direct RL Environment ---------------------- - -.. autoclass:: DirectRLEnv - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: DirectRLEnvCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Common ------- - -.. autoclass:: ViewerCfg - :members: - :exclude-members: __init__ diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.ui.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.ui.rst deleted file mode 100644 index 7818b780da..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.envs.ui.rst +++ /dev/null @@ -1,31 +0,0 @@ -omni.isaac.lab.envs.ui -====================== - -.. automodule:: omni.isaac.lab.envs.ui - - .. rubric:: Classes - - .. autosummary:: - - BaseEnvWindow - ManagerBasedRLEnvWindow - ViewportCameraController - -Base Environment UI -------------------- - -.. autoclass:: BaseEnvWindow - :members: - -Config Based RL Environment UI ------------------------------- - -.. autoclass:: ManagerBasedRLEnvWindow - :members: - :show-inheritance: - -Viewport Camera Controller --------------------------- - -.. autoclass:: ViewportCameraController - :members: diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.managers.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.managers.rst deleted file mode 100644 index d7ab8c5b22..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.managers.rst +++ /dev/null @@ -1,161 +0,0 @@ -omni.isaac.lab.managers -======================= - -.. automodule:: omni.isaac.lab.managers - - .. rubric:: Classes - - .. autosummary:: - - SceneEntityCfg - ManagerBase - ManagerTermBase - ManagerTermBaseCfg - ObservationManager - ObservationGroupCfg - ObservationTermCfg - ActionManager - ActionTerm - ActionTermCfg - EventManager - EventTermCfg - CommandManager - CommandTerm - CommandTermCfg - RewardManager - RewardTermCfg - TerminationManager - TerminationTermCfg - CurriculumManager - CurriculumTermCfg - -Scene Entity ------------- - -.. autoclass:: SceneEntityCfg - :members: - :exclude-members: __init__ - -Manager Base ------------- - -.. autoclass:: ManagerBase - :members: - -.. autoclass:: ManagerTermBase - :members: - -.. autoclass:: ManagerTermBaseCfg - :members: - :exclude-members: __init__ - -Observation Manager -------------------- - -.. autoclass:: ObservationManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ObservationGroupCfg - :members: - :exclude-members: __init__ - -.. autoclass:: ObservationTermCfg - :members: - :exclude-members: __init__ - -Action Manager --------------- - -.. autoclass:: ActionManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ActionTerm - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ActionTermCfg - :members: - :exclude-members: __init__ - -Event Manager -------------- - -.. autoclass:: EventManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: EventTermCfg - :members: - :exclude-members: __init__ - -Randomization Manager ---------------------- - -.. deprecated:: v0.3 - The Randomization Manager is deprecated and will be removed in v0.4. - Please use the :class:`EventManager` class instead. - -.. autoclass:: RandomizationManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: RandomizationTermCfg - :members: - :exclude-members: __init__ - -Command Manager ---------------- - -.. autoclass:: CommandManager - :members: - -.. autoclass:: CommandTerm - :members: - :exclude-members: __init__, class_type - -.. autoclass:: CommandTermCfg - :members: - :exclude-members: __init__, class_type - - -Reward Manager --------------- - -.. autoclass:: RewardManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: RewardTermCfg - :exclude-members: __init__ - -Termination Manager -------------------- - -.. autoclass:: TerminationManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: TerminationTermCfg - :members: - :exclude-members: __init__ - -Curriculum Manager ------------------- - -.. autoclass:: CurriculumManager - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: CurriculumTermCfg - :members: - :exclude-members: __init__ diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.markers.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.markers.rst deleted file mode 100644 index f6a23e773d..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.markers.rst +++ /dev/null @@ -1,23 +0,0 @@ -omni.isaac.lab.markers -====================== - -.. automodule:: omni.isaac.lab.markers - - .. rubric:: Classes - - .. autosummary:: - - VisualizationMarkers - VisualizationMarkersCfg - -Visualization Markers ---------------------- - -.. autoclass:: VisualizationMarkers - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: VisualizationMarkersCfg - :members: - :exclude-members: __init__ diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.scene.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.scene.rst deleted file mode 100644 index 5a9bba3a33..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.scene.rst +++ /dev/null @@ -1,23 +0,0 @@ -omni.isaac.lab.scene -==================== - -.. automodule:: omni.isaac.lab.scene - - .. rubric:: Classes - - .. autosummary:: - - InteractiveScene - InteractiveSceneCfg - -interactive Scene ------------------ - -.. autoclass:: InteractiveScene - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: InteractiveSceneCfg - :members: - :exclude-members: __init__ diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sensors.patterns.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sensors.patterns.rst deleted file mode 100644 index e2eb9b04e5..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sensors.patterns.rst +++ /dev/null @@ -1,51 +0,0 @@ -omni.isaac.lab.sensors.patterns -=============================== - -.. automodule:: omni.isaac.lab.sensors.patterns - - .. rubric:: Classes - - .. autosummary:: - - PatternBaseCfg - GridPatternCfg - PinholeCameraPatternCfg - BpearlPatternCfg - -Pattern Base ------------- - -.. autoclass:: PatternBaseCfg - :members: - :inherited-members: - :exclude-members: __init__ - -Grid Pattern ------------- - -.. autofunction:: omni.isaac.lab.sensors.patterns.grid_pattern - -.. autoclass:: GridPatternCfg - :members: - :inherited-members: - :exclude-members: __init__, func - -Pinhole Camera Pattern ----------------------- - -.. autofunction:: omni.isaac.lab.sensors.patterns.pinhole_camera_pattern - -.. autoclass:: PinholeCameraPatternCfg - :members: - :inherited-members: - :exclude-members: __init__, func - -RS-Bpearl Pattern ------------------ - -.. autofunction:: omni.isaac.lab.sensors.patterns.bpearl_pattern - -.. autoclass:: BpearlPatternCfg - :members: - :inherited-members: - :exclude-members: __init__, func diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sensors.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sensors.rst deleted file mode 100644 index 9eac25b797..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sensors.rst +++ /dev/null @@ -1,152 +0,0 @@ -omni.isaac.lab.sensors -====================== - -.. automodule:: omni.isaac.lab.sensors - - .. rubric:: Submodules - - .. autosummary:: - - patterns - - .. rubric:: Classes - - .. autosummary:: - - SensorBase - SensorBaseCfg - Camera - CameraData - CameraCfg - TiledCamera - TiledCameraCfg - ContactSensor - ContactSensorData - ContactSensorCfg - FrameTransformer - FrameTransformerData - FrameTransformerCfg - RayCaster - RayCasterData - RayCasterCfg - RayCasterCamera - RayCasterCameraCfg - -Sensor Base ------------ - -.. autoclass:: SensorBase - :members: - -.. autoclass:: SensorBaseCfg - :members: - :exclude-members: __init__, class_type - -USD Camera ----------- - -.. autoclass:: Camera - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: CameraData - :members: - :inherited-members: - :exclude-members: __init__ - -.. autoclass:: CameraCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Tile-Rendered USD Camera ------------------------- - -.. autoclass:: TiledCamera - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: TiledCameraCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Contact Sensor --------------- - -.. autoclass:: ContactSensor - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: ContactSensorData - :members: - :inherited-members: - :exclude-members: __init__ - -.. autoclass:: ContactSensorCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Frame Transformer ------------------ - -.. autoclass:: FrameTransformer - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: FrameTransformerData - :members: - :inherited-members: - :exclude-members: __init__ - -.. autoclass:: FrameTransformerCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -.. autoclass:: OffsetCfg - :members: - :inherited-members: - :exclude-members: __init__ - -Ray-Cast Sensor ---------------- - -.. autoclass:: RayCaster - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: RayCasterData - :members: - :inherited-members: - :exclude-members: __init__ - -.. autoclass:: RayCasterCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type - -Ray-Cast Camera ---------------- - -.. autoclass:: RayCasterCamera - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: RayCasterCameraCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, class_type diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.converters.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.converters.rst deleted file mode 100644 index 1b5ee45c08..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.converters.rst +++ /dev/null @@ -1,54 +0,0 @@ -omni.isaac.lab.sim.converters -============================= - -.. automodule:: omni.isaac.lab.sim.converters - - .. rubric:: Classes - - .. autosummary:: - - AssetConverterBase - AssetConverterBaseCfg - MeshConverter - MeshConverterCfg - UrdfConverter - UrdfConverterCfg - -Asset Converter Base --------------------- - -.. autoclass:: AssetConverterBase - :members: - -.. autoclass:: AssetConverterBaseCfg - :members: - :exclude-members: __init__ - -Mesh Converter --------------- - -.. autoclass:: MeshConverter - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: MeshConverterCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__ - - -URDF Converter --------------- - -.. autoclass:: UrdfConverter - :members: - :inherited-members: - :show-inheritance: - -.. autoclass:: UrdfConverterCfg - :members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__ diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.rst deleted file mode 100644 index 4e37e33b4a..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.rst +++ /dev/null @@ -1,59 +0,0 @@ -omni.isaac.lab.sim -================== - -.. automodule:: omni.isaac.lab.sim - - .. rubric:: Submodules - - .. autosummary:: - - converters - schemas - spawners - utils - - .. rubric:: Classes - - .. autosummary:: - - SimulationContext - SimulationCfg - PhysxCfg - - .. rubric:: Functions - - .. autosummary:: - - simulation_context.build_simulation_context - -Simulation Context ------------------- - -.. autoclass:: SimulationContext - :members: - :show-inheritance: - -Simulation Configuration ------------------------- - -.. autoclass:: SimulationCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -.. autoclass:: PhysxCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -Simulation Context Builder --------------------------- - -.. automethod:: simulation_context.build_simulation_context - -Utilities ---------- - -.. automodule:: omni.isaac.lab.sim.utils - :members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.schemas.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.schemas.rst deleted file mode 100644 index 14fb6f7e63..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.schemas.rst +++ /dev/null @@ -1,90 +0,0 @@ -omni.isaac.lab.sim.schemas -========================== - -.. automodule:: omni.isaac.lab.sim.schemas - - .. rubric:: Classes - - .. autosummary:: - - ArticulationRootPropertiesCfg - RigidBodyPropertiesCfg - CollisionPropertiesCfg - MassPropertiesCfg - JointDrivePropertiesCfg - FixedTendonPropertiesCfg - - .. rubric:: Functions - - .. autosummary:: - - define_articulation_root_properties - modify_articulation_root_properties - define_rigid_body_properties - modify_rigid_body_properties - activate_contact_sensors - define_collision_properties - modify_collision_properties - define_mass_properties - modify_mass_properties - modify_joint_drive_properties - modify_fixed_tendon_properties - -Articulation Root ------------------ - -.. autoclass:: ArticulationRootPropertiesCfg - :members: - :exclude-members: __init__ - -.. autofunction:: define_articulation_root_properties -.. autofunction:: modify_articulation_root_properties - -Rigid Body ----------- - -.. autoclass:: RigidBodyPropertiesCfg - :members: - :exclude-members: __init__ - -.. autofunction:: define_rigid_body_properties -.. autofunction:: modify_rigid_body_properties -.. autofunction:: activate_contact_sensors - -Collision ---------- - -.. autoclass:: CollisionPropertiesCfg - :members: - :exclude-members: __init__ - -.. autofunction:: define_collision_properties -.. autofunction:: modify_collision_properties - -Mass ----- - -.. autoclass:: MassPropertiesCfg - :members: - :exclude-members: __init__ - -.. autofunction:: define_mass_properties -.. autofunction:: modify_mass_properties - -Joint Drive ------------ - -.. autoclass:: JointDrivePropertiesCfg - :members: - :exclude-members: __init__ - -.. autofunction:: modify_joint_drive_properties - -Fixed Tendon ------------- - -.. autoclass:: FixedTendonPropertiesCfg - :members: - :exclude-members: __init__ - -.. autofunction:: modify_fixed_tendon_properties diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.spawners.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.spawners.rst deleted file mode 100644 index 11ccd65640..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.sim.spawners.rst +++ /dev/null @@ -1,236 +0,0 @@ -omni.isaac.lab.sim.spawners -=========================== - -.. automodule:: omni.isaac.lab.sim.spawners - - .. rubric:: Submodules - - .. autosummary:: - - shapes - lights - sensors - from_files - materials - - .. rubric:: Classes - - .. autosummary:: - - SpawnerCfg - RigidObjectSpawnerCfg - -Spawners --------- - -.. autoclass:: SpawnerCfg - :members: - :exclude-members: __init__ - -.. autoclass:: RigidObjectSpawnerCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -Shapes ------- - -.. automodule:: omni.isaac.lab.sim.spawners.shapes - - .. rubric:: Classes - - .. autosummary:: - - ShapeCfg - CapsuleCfg - ConeCfg - CuboidCfg - CylinderCfg - SphereCfg - -.. autoclass:: ShapeCfg - :members: - :exclude-members: __init__, func - -.. autofunction:: spawn_capsule - -.. autoclass:: CapsuleCfg - :members: - :show-inheritance: - :exclude-members: __init__, func - -.. autofunction:: spawn_cone - -.. autoclass:: ConeCfg - :members: - :show-inheritance: - :exclude-members: __init__, func - -.. autofunction:: spawn_cuboid - -.. autoclass:: CuboidCfg - :members: - :show-inheritance: - :exclude-members: __init__, func - -.. autofunction:: spawn_cylinder - -.. autoclass:: CylinderCfg - :members: - :show-inheritance: - :exclude-members: __init__, func - -.. autofunction:: spawn_sphere - -.. autoclass:: SphereCfg - :members: - :show-inheritance: - :exclude-members: __init__, func - - -Lights ------- - -.. automodule:: omni.isaac.lab.sim.spawners.lights - - .. rubric:: Classes - - .. autosummary:: - - LightCfg - CylinderLightCfg - DiskLightCfg - DistantLightCfg - DomeLightCfg - SphereLightCfg - -.. autofunction:: spawn_light - -.. autoclass:: LightCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: CylinderLightCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: DiskLightCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: DistantLightCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: DomeLightCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: SphereLightCfg - :members: - :exclude-members: __init__, func - -Sensors -------- - -.. automodule:: omni.isaac.lab.sim.spawners.sensors - - .. rubric:: Classes - - .. autosummary:: - - PinholeCameraCfg - FisheyeCameraCfg - -.. autofunction:: spawn_camera - -.. autoclass:: PinholeCameraCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: FisheyeCameraCfg - :members: - :exclude-members: __init__, func - -From Files ----------- - -.. automodule:: omni.isaac.lab.sim.spawners.from_files - - .. rubric:: Classes - - .. autosummary:: - - UrdfFileCfg - UsdFileCfg - GroundPlaneCfg - -.. autofunction:: spawn_from_urdf - -.. autoclass:: UrdfFileCfg - :members: - :exclude-members: __init__, func - -.. autofunction:: spawn_from_usd - -.. autoclass:: UsdFileCfg - :members: - :exclude-members: __init__, func - -.. autofunction:: spawn_ground_plane - -.. autoclass:: GroundPlaneCfg - :members: - :exclude-members: __init__, func - -Materials ---------- - -.. automodule:: omni.isaac.lab.sim.spawners.materials - - .. rubric:: Classes - - .. autosummary:: - - VisualMaterialCfg - PreviewSurfaceCfg - MdlFileCfg - GlassMdlCfg - PhysicsMaterialCfg - RigidBodyMaterialCfg - -Visual Materials -~~~~~~~~~~~~~~~~ - -.. autoclass:: VisualMaterialCfg - :members: - :exclude-members: __init__, func - -.. autofunction:: spawn_preview_surface - -.. autoclass:: PreviewSurfaceCfg - :members: - :exclude-members: __init__, func - -.. autofunction:: spawn_from_mdl_file - -.. autoclass:: MdlFileCfg - :members: - :exclude-members: __init__, func - -.. autoclass:: GlassMdlCfg - :members: - :exclude-members: __init__, func - -Physical Materials -~~~~~~~~~~~~~~~~~~ - -.. autoclass:: PhysicsMaterialCfg - :members: - :exclude-members: __init__, func - -.. autofunction:: spawn_rigid_body_material - -.. autoclass:: RigidBodyMaterialCfg - :members: - :exclude-members: __init__, func diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.terrains.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.terrains.rst deleted file mode 100644 index 3d11fa1f1b..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.terrains.rst +++ /dev/null @@ -1,261 +0,0 @@ -omni.isaac.lab.terrains -======================= - -.. automodule:: omni.isaac.lab.terrains - - .. rubric:: Classes - - .. autosummary:: - - TerrainImporter - TerrainImporterCfg - TerrainGenerator - TerrainGeneratorCfg - SubTerrainBaseCfg - - -Terrain importer ----------------- - -.. autoclass:: TerrainImporter - :members: - :show-inheritance: - -.. autoclass:: TerrainImporterCfg - :members: - :exclude-members: __init__, class_type - -Terrain generator ------------------ - -.. autoclass:: TerrainGenerator - :members: - -.. autoclass:: TerrainGeneratorCfg - :members: - :exclude-members: __init__ - -.. autoclass:: SubTerrainBaseCfg - :members: - :exclude-members: __init__ - -Height fields -------------- - -.. automodule:: omni.isaac.lab.terrains.height_field - -All sub-terrains must inherit from the :class:`HfTerrainBaseCfg` class which contains the common -parameters for all terrains generated from height fields. - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfTerrainBaseCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Random Uniform Terrain -^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.height_field.hf_terrains.random_uniform_terrain - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfRandomUniformTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Pyramid Sloped Terrain -^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.height_field.hf_terrains.pyramid_sloped_terrain - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfPyramidSlopedTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfInvertedPyramidSlopedTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Pyramid Stairs Terrain -^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.height_field.hf_terrains.pyramid_stairs_terrain - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfPyramidStairsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfInvertedPyramidStairsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Discrete Obstacles Terrain -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.height_field.hf_terrains.discrete_obstacles_terrain - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfDiscreteObstaclesTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Wave Terrain -^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.height_field.hf_terrains.wave_terrain - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfWaveTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Stepping Stones Terrain -^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.height_field.hf_terrains.stepping_stones_terrain - -.. autoclass:: omni.isaac.lab.terrains.height_field.hf_terrains_cfg.HfSteppingStonesTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Trimesh terrains ----------------- - -.. automodule:: omni.isaac.lab.terrains.trimesh - - -Flat terrain -^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.flat_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshPlaneTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Pyramid terrain -^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.pyramid_stairs_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshPyramidStairsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Inverted pyramid terrain -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.inverted_pyramid_stairs_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshInvertedPyramidStairsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Random grid terrain -^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.random_grid_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshRandomGridTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Rails terrain -^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.rails_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshRailsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Pit terrain -^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.pit_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshPitTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Box terrain -^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.box_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshBoxTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Gap terrain -^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.gap_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshGapTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Floating ring terrain -^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.floating_ring_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshFloatingRingTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Star terrain -^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.star_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshStarTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Repeated Objects Terrain -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: omni.isaac.lab.terrains.trimesh.mesh_terrains.repeated_objects_terrain - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshRepeatedObjectsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshRepeatedPyramidsTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshRepeatedBoxesTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -.. autoclass:: omni.isaac.lab.terrains.trimesh.mesh_terrains_cfg.MeshRepeatedCylindersTerrainCfg - :members: - :show-inheritance: - :exclude-members: __init__, function - -Utilities ---------- - -.. automodule:: omni.isaac.lab.terrains.utils - :members: - :undoc-members: diff --git a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.utils.rst b/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.utils.rst deleted file mode 100644 index b701a75489..0000000000 --- a/docs/_build/html/_sources/source/api/lab/omni.isaac.lab.utils.rst +++ /dev/null @@ -1,120 +0,0 @@ -omni.isaac.lab.utils -==================== - -.. automodule:: omni.isaac.lab.utils - - .. Rubric:: Submodules - - .. autosummary:: - - io - array - assets - buffers - dict - interpolation - math - noise - string - timer - warp - - .. Rubric:: Functions - - .. autosummary:: - - configclass - -Configuration class -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.configclass - :members: - :show-inheritance: - -IO operations -~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.io - :members: - :imported-members: - :show-inheritance: - -Array operations -~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.array - :members: - :show-inheritance: - -Asset operations -~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.assets - :members: - :show-inheritance: - -Buffer operations -~~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.buffers - :members: - :imported-members: - :inherited-members: - :show-inheritance: - -Dictionary operations -~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.dict - :members: - :show-inheritance: - -Interpolation operations -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.interpolation - :members: - :imported-members: - :inherited-members: - :show-inheritance: - -Math operations -~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.math - :members: - :inherited-members: - :show-inheritance: - -Noise operations -~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.noise - :members: - :imported-members: - :inherited-members: - :show-inheritance: - :exclude-members: __init__, func - -String operations -~~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.string - :members: - :show-inheritance: - -Timer operations -~~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.timer - :members: - :show-inheritance: - -Warp operations -~~~~~~~~~~~~~~~ - -.. automodule:: omni.isaac.lab.utils.warp - :members: - :imported-members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.data_collector.rst b/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.data_collector.rst deleted file mode 100644 index b29b1446b0..0000000000 --- a/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.data_collector.rst +++ /dev/null @@ -1,17 +0,0 @@ -omni.isaac.lab_tasks.utils.data_collector -========================================= - -.. automodule:: omni.isaac.lab_tasks.utils.data_collector - - .. Rubric:: Classes - - .. autosummary:: - - RobomimicDataCollector - -Robomimic Data Collector ------------------------- - -.. autoclass:: RobomimicDataCollector - :members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.rst b/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.rst deleted file mode 100644 index 644bcaa439..0000000000 --- a/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.rst +++ /dev/null @@ -1,13 +0,0 @@ -omni.isaac.lab_tasks.utils -========================== - -.. automodule:: omni.isaac.lab_tasks.utils - :members: - :imported-members: - - .. rubric:: Submodules - - .. autosummary:: - - data_collector - wrappers diff --git a/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.wrappers.rst b/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.wrappers.rst deleted file mode 100644 index e70975375d..0000000000 --- a/docs/_build/html/_sources/source/api/lab_tasks/omni.isaac.lab_tasks.utils.wrappers.rst +++ /dev/null @@ -1,33 +0,0 @@ -omni.isaac.lab_tasks.utils.wrappers -=================================== - -.. automodule:: omni.isaac.lab_tasks.utils.wrappers - -RL-Games Wrapper ----------------- - -.. automodule:: omni.isaac.lab_tasks.utils.wrappers.rl_games - :members: - :show-inheritance: - -RSL-RL Wrapper --------------- - -.. automodule:: omni.isaac.lab_tasks.utils.wrappers.rsl_rl - :members: - :imported-members: - :show-inheritance: - -SKRL Wrapper ------------- - -.. automodule:: omni.isaac.lab_tasks.utils.wrappers.skrl - :members: - :show-inheritance: - -Stable-Baselines3 Wrapper -------------------------- - -.. automodule:: omni.isaac.lab_tasks.utils.wrappers.sb3 - :members: - :show-inheritance: diff --git a/docs/_build/html/_sources/source/deployment/cluster.rst b/docs/_build/html/_sources/source/deployment/cluster.rst deleted file mode 100644 index f8ca1b38e8..0000000000 --- a/docs/_build/html/_sources/source/deployment/cluster.rst +++ /dev/null @@ -1,205 +0,0 @@ -.. _deployment-cluster: - - -Cluster Guide -============= - -Clusters are a great way to speed up training and evaluation of learning algorithms. -While the Isaac Lab Docker image can be used to run jobs on a cluster, many clusters only -support singularity images. This is because `singularity`_ is designed for -ease-of-use on shared multi-user systems and high performance computing (HPC) environments. -It does not require root privileges to run containers and can be used to run user-defined -containers. - -Singularity is compatible with all Docker images. In this section, we describe how to -convert the Isaac Lab Docker image into a singularity image and use it to submit jobs to a cluster. - -.. attention:: - - Cluster setup varies across different institutions. The following instructions have been - tested on the `ETH Zurich Euler`_ cluster (which uses the SLURM workload manager), and the - IIT Genoa Franklin cluster (which uses PBS workload manager). - - The instructions may need to be adapted for other clusters. If you have successfully - adapted the instructions for another cluster, please consider contributing to the - documentation. - - -Setup Instructions ------------------- - -In order to export the Docker Image to a singularity image, `apptainer`_ is required. -A detailed overview of the installation procedure for ``apptainer`` can be found in its -`documentation`_. For convenience, we summarize the steps here for a local installation: - -.. code:: bash - - sudo apt update - sudo apt install -y software-properties-common - sudo add-apt-repository -y ppa:apptainer/ppa - sudo apt update - sudo apt install -y apptainer - -For simplicity, we recommend that an SSH connection is set up between the local -development machine and the cluster. Such a connection will simplify the file transfer and prevent -the user cluster password from being requested multiple times. - -.. attention:: - The workflow has been tested with ``apptainer version 1.2.5-1.el7`` and ``docker version 24.0.7``. - - - ``apptainer``: - There have been reported binding issues with previous versions (such as ``apptainer version 1.1.3-1.el7``). Please - ensure that you are using the latest version. - - ``Docker``: - The latest versions (``25.x``) cannot be used as they are not compatible yet with apptainer/ singularity. - - We are waiting for an update from the apptainer team. To track this issue, please check the `forum post`_. - -Configuring the cluster parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -First, you need to configure the cluster-specific parameters in ``docker/.env.base`` file. -The following describes the parameters that need to be configured: -- ``CLUSTER_JOB_SCHEDULER``: - The job scheduler/workload manager used by your cluster. Currently, we support SLURM and - PBS workload managers [SLURM | PBS]. -- ``CLUSTER_ISAAC_SIM_CACHE_DIR``: - The directory on the cluster where the Isaac Sim cache is stored. This directory - has to end on ``docker-isaac-sim``. This directory will be copied to the compute node - and mounted into the singularity container. It should increase the speed of starting - the simulation. -- ``CLUSTER_ISAACLAB_DIR``: - The directory on the cluster where the Isaac Lab code is stored. This directory has to - end on ``isaaclab``. This directory will be copied to the compute node and mounted into - the singularity container. When a job is submitted, the latest local changes will - be copied to the cluster. -- ``CLUSTER_LOGIN``: - The login to the cluster. Typically, this is the user and cluster names, - e.g., ``your_user@euler.ethz.ch``. -- ``CLUSTER_SIF_PATH``: - The path on the cluster where the singularity image will be stored. The image will be - copied to the compute node but not uploaded again to the cluster when a job is submitted. -- ``CLUSTER_PYTHON_EXECUTABLE``: - The path within Isaac Lab to the Python executable that should be executed in the submitted job. - -Exporting to singularity image -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Next, we need to export the Docker image to a singularity image and upload -it to the cluster. This step is only required once when the first job is submitted -or when the Docker image is updated. For instance, due to an upgrade of the Isaac Sim -version, or additional requirements for your project. - -To export to a singularity image, execute the following command: - -.. code:: bash - - ./docker/container.sh push [profile] - -This command will create a singularity image under ``docker/exports`` directory and -upload it to the defined location on the cluster. Be aware that creating the singularity -image can take a while. -``[profile]`` is an optional argument that specifies the container profile to be used. If no profile is -specified, the default profile ``base`` will be used. - -.. note:: - By default, the singularity image is created without root access by providing the ``--fakeroot`` flag to - the ``apptainer build`` command. In case the image creation fails, you can try to create it with root - access by removing the flag in ``docker/container.sh``. - - -Defining the job parameters ---------------------------- - - -The job parameters need to be defined based on the job scheduler used by your cluster. You only need to update the appropriate script for the scheduler available to you. - -- For SLURM, update the parameters in ``docker/cluster/submit_job_slurm.sh``. -- For PBS, update the parameters in ``docker/cluster/submit_job_pbs.sh``. - -For SLURM -~~~~~~~~~ - -The job parameters are defined inside the ``docker/cluster/submit_job_slurm.sh``. -A typical SLURM operation requires specifying the number of CPUs and GPUs, the memory, and -the time limit. For more information, please check the `SLURM documentation`_. - -The default configuration is as follows: - -.. literalinclude:: ../../../docker/cluster/submit_job_slurm.sh - :language: bash - :lines: 12-19 - :linenos: - :lineno-start: 12 - -An essential requirement for the cluster is that the compute node has access to the internet at all times. -This is required to load assets from the Nucleus server. For some cluster architectures, extra modules -must be loaded to allow internet access. - -For instance, on ETH Zurich Euler cluster, the ``eth_proxy`` module needs to be loaded. This can be done -by adding the following line to the ``submit_job_slurm.sh`` script: - -.. literalinclude:: ../../../docker/cluster/submit_job_slurm.sh - :language: bash - :lines: 3-5 - :linenos: - :lineno-start: 3 - -For PBS -~~~~~~~ - -The job parameters are defined inside the ``docker/cluster/submit_job_pbs.sh``. -A typical PBS operation requires specifying the number of CPUs and GPUs, and the time limit. For more -information, please check the `PBS Official Site`_. - -The default configuration is as follows: - -.. literalinclude:: ../../../docker/cluster/submit_job_pbs.sh - :language: bash - :lines: 11-17 - :linenos: - :lineno-start: 11 - - -Submitting a job ----------------- - -To submit a job on the cluster, the following command can be used: - -.. code:: bash - - ./docker/container.sh job [profile] "argument1" "argument2" ... - -This command will copy the latest changes in your code to the cluster and submit a job. Please ensure that -your Python executable's output is stored under ``isaaclab/logs`` as this directory will be copied again -from the compute node to ``CLUSTER_ISAACLAB_DIR``. - -``[profile]`` is an optional argument that specifies which singularity image corresponding to the container profile -will be used. If no profile is specified, the default profile ``base`` will be used. The profile has be defined -directlty after the ``job`` command. All other arguments are passed to the Python executable. If no profile is -defined, all arguments are passed to the Python executable. - -The training arguments are passed to the Python executable. As an example, the standard -ANYmal rough terrain locomotion training can be executed with the following command: - -.. code:: bash - - ./docker/container.sh job --task Isaac-Velocity-Rough-Anymal-C-v0 --headless --video --enable_cameras - -The above will, in addition, also render videos of the training progress and store them under ``isaaclab/logs`` directory. - -.. note:: - - The ``./docker/container.sh job`` command will copy the latest changes in your code to the cluster. However, - it will not delete any files that have been deleted locally. These files will still exist on the cluster - which can lead to issues. In this case, we recommend removing the ``CLUSTER_ISAACLAB_DIR`` directory on - the cluster and re-run the command. - - -.. _Singularity: https://docs.sylabs.io/guides/2.6/user-guide/index.html -.. _ETH Zurich Euler: https://scicomp.ethz.ch/wiki/Euler -.. _PBS Official Site: https://openpbs.org/ -.. _apptainer: https://apptainer.org/ -.. _documentation: https://www.apptainer.org/docs/admin/main/installation.html#install-ubuntu-packages -.. _SLURM documentation: https://www.slurm.schedmd.com/sbatch.html -.. _forum post: https://forums.docker.com/t/trouble-after-upgrade-to-docker-ce-25-0-1-on-debian-12/139613 diff --git a/docs/_build/html/_sources/source/deployment/docker.rst b/docs/_build/html/_sources/source/deployment/docker.rst deleted file mode 100644 index 77a994c2a6..0000000000 --- a/docs/_build/html/_sources/source/deployment/docker.rst +++ /dev/null @@ -1,289 +0,0 @@ -.. _deployment-docker: - - -Docker Guide -============ - -.. caution:: - - Due to the dependency on Isaac Sim docker image, by running this container you are implicitly - agreeing to the `NVIDIA Omniverse EULA`_. If you do not agree to the EULA, do not run this container. - -Setup Instructions ------------------- - -.. note:: - - The following steps are taken from the NVIDIA Omniverse Isaac Sim documentation on `container installation`_. - They have been added here for the sake of completeness. - - -Docker and Docker Compose -~~~~~~~~~~~~~~~~~~~~~~~~~ - -We have tested the container using Docker Engine version 26.0.0 and Docker Compose version 2.25.0 -We recommend using these versions or newer. - -* To install Docker, please follow the instructions for your operating system on the `Docker website`_. -* To install Docker Compose, please follow the instructions for your operating system on the `docker compose`_ page. -* Follow the post-installation steps for Docker on the `post-installation steps`_ page. These steps allow you to run - Docker without using ``sudo``. -* To build and run GPU-accelerated containers, you also need install the `NVIDIA Container Toolkit`_. - Please follow the instructions on the `Container Toolkit website`_ for installation steps. - -.. note:: - - Due to limitations with `snap `_, please make sure - the Isaac Lab directory is placed under the ``/home`` directory tree when using docker. - - -Obtaining the Isaac Sim Container -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Get access to the `Isaac Sim container`_ by joining the NVIDIA Developer Program credentials. -* Generate your `NGC API key`_ to access locked container images from NVIDIA GPU Cloud (NGC). - - * This step requires you to create an NGC account if you do not already have one. - * You would also need to install the NGC CLI to perform operations from the command line. - * Once you have your generated API key and have installed the NGC CLI, you need to log in to NGC - from the terminal. - - .. code:: bash - - ngc config set - -* Use the command line to pull the Isaac Sim container image from NGC. - - .. code:: bash - - docker login nvcr.io - - * For the username, enter ``$oauthtoken`` exactly as shown. It is a special username that is used to - authenticate with NGC. - - .. code:: text - - Username: $oauthtoken - Password: - - -Directory Organization ----------------------- - -The root of the Isaac Lab repository contains the ``docker`` directory that has various files and scripts -needed to run Isaac Lab inside a Docker container. A subset of these are summarized below: - -* ``Dockerfile.base``: Defines the isaaclab image by overlaying Isaac Lab dependencies onto the Isaac Sim Docker image. - ``Dockerfiles`` which end with something else, (i.e. ``Dockerfile.ros2``) build an `image_extension <#isaac-lab-image-extensions>`_. -* ``docker-compose.yaml``: Creates mounts to allow direct editing of Isaac Lab code from the host machine that runs - the container. It also creates several named volumes such as ``isaac-cache-kit`` to - store frequently re-used resources compiled by Isaac Sim, such as shaders, and to retain logs, data, and documents. -* ``base.env``: Stores environment variables required for the ``base`` build process and the container itself. ``.env`` - files which end with something else (i.e. ``.env.ros2``) define these for `image_extension <#isaac-lab-image-extensions>`_. -* ``container.sh``: A script that wraps the ``docker compose`` command to build the image and run the container. - -Running the Container ---------------------- - -.. note:: - - The docker container copies all the files from the repository into the container at the - location ``/workspace/isaaclab`` at build time. This means that any changes made to the files in the container would not - normally be reflected in the repository after the image has been built, i.e. after ``./container.sh start`` is run. - - For a faster development cycle, we mount the following directories in the Isaac Lab repository into the container - so that you can edit their files from the host machine: - - * ``source``: This is the directory that contains the Isaac Lab source code. - * ``docs``: This is the directory that contains the source code for Isaac Lab documentation. This is overlaid except - for the ``_build`` subdirectory where build artifacts are stored. - - -The script ``container.sh`` wraps around three basic ``docker compose`` commands. Each can accept an `image_extension argument <#isaac-lab-image-extensions>`_, -or else they will default to image_extension ``base``: - -1. ``start``: This builds the image and brings up the container in detached mode (i.e. in the background). -2. ``enter``: This begins a new bash process in an existing isaaclab container, and which can be exited - without bringing down the container. -3. ``copy``: This copies the ``logs``, ``data_storage`` and ``docs/_build`` artifacts, from the ``isaac-lab-logs``, ``isaac-lab-data`` and ``isaac-lab-docs`` - volumes respectively, to the ``docker/artifacts`` directory. These artifacts persist between docker - container instances and are shared between image extensions. -4. ``stop``: This brings down the container and removes it. - -The following shows how to launch the container in a detached state and enter it: - -.. code:: bash - - # Launch the container in detached mode - # We don't pass an image extension arg, so it defaults to 'base' - ./docker/container.sh start - # Enter the container - # We pass 'base' explicitly, but if we hadn't it would default to 'base' - ./docker/container.sh enter base - -To copy files from the base container to the host machine, you can use the following command: - -.. code:: bash - - # Copy the file /workspace/isaaclab/logs to the current directory - docker cp isaac-lab-base:/workspace/isaaclab/logs . - -The script ``container.sh`` provides a wrapper around this command to copy the ``logs`` , ``data_storage`` and ``docs/_build`` -directories to the ``docker/artifacts`` directory. This is useful for copying the logs, data and documentation: - -.. code:: - - # stop the container - ./docker/container.sh stop - - -Python Interpreter -~~~~~~~~~~~~~~~~~~ - -The container uses the Python interpreter provided by Isaac Sim. This interpreter is located at -``/isaac-sim/python.sh``. We set aliases inside the container to make it easier to run the Python -interpreter. You can use the following commands to run the Python interpreter: - -.. code:: bash - - # Run the Python interpreter -> points to /isaac-sim/python.sh - python - - -Understanding the mounted volumes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``docker-compose.yaml`` file creates several named volumes that are mounted to the container. -These are summarized below: - -* ``isaac-cache-kit``: This volume is used to store cached Kit resources (`/isaac-sim/kit/cache` in container) -* ``isaac-cache-ov``: This volume is used to store cached OV resources (`/root/.cache/ov` in container) -* ``isaac-cache-pip``: This volume is used to store cached pip resources (`/root/.cache/pip`` in container) -* ``isaac-cache-gl``: This volume is used to store cached GLCache resources (`/root/.cache/nvidia/GLCache` in container) -* ``isaac-cache-compute``: This volume is used to store cached compute resources (`/root/.nv/ComputeCache` in container) -* ``isaac-logs``: This volume is used to store logs generated by Omniverse. (`/root/.nvidia-omniverse/logs` in container) -* ``isaac-carb-logs``: This volume is used to store logs generated by carb. (`/isaac-sim/kit/logs/Kit/Isaac-Sim` in container) -* ``isaac-data``: This volume is used to store data generated by Omniverse. (`/root/.local/share/ov/data` in container) -* ``isaac-docs``: This volume is used to store documents generated by Omniverse. (`/root/Documents` in container) -* ``isaac-lab-docs``: This volume is used to store documentation of Isaac Lab when built inside the container. (`/workspace/isaaclab/docs/_build` in container) -* ``isaac-lab-logs``: This volume is used to store logs generated by Isaac Lab workflows when run inside the container. (`/workspace/isaaclab/logs` in container) -* ``isaac-lab-data``: This volume is used to store whatever data users may want to preserve between container runs. (`/workspace/isaaclab/data_storage` in container) - -To view the contents of these volumes, you can use the following command: - -.. code:: bash - - # list all volumes - docker volume ls - # inspect a specific volume, e.g. isaac-cache-kit - docker volume inspect isaac-cache-kit - - - -Isaac Lab Image Extensions --------------------------- - -The produced image depends upon the arguments passed to ``./container.sh start`` and ``./container.sh stop``. These -commands accept an ``image_extension`` as an additional argument. If no argument is passed, then these -commands default to ``base``. Currently, the only valid ``image_extension`` arguments are (``base``, ``ros2``). -Only one ``image_extension`` can be passed at a time, and the produced container will be named ``isaaclab``. - -.. code:: bash - - # start base by default - ./container.sh start - # stop base explicitly - ./container.sh stop base - # start ros2 container - ./container.sh start ros2 - # stop ros2 container - ./container.sh stop ros2 - -The passed ``image_extension`` argument will build the image defined in ``Dockerfile.${image_extension}``, -with the corresponding `profile`_ in the ``docker-compose.yaml`` and the envars from ``.env.${image_extension}`` -in addition to the ``.env.base``, if any. - -ROS2 Image Extension -~~~~~~~~~~~~~~~~~~~~ - -In ``Dockerfile.ros2``, the container installs ROS2 Humble via an `apt package`_, and it is sourced in the ``.bashrc``. -The exact version is specified by the variable ``ROS_APT_PACKAGE`` in the ``.env.ros2`` file, -defaulting to ``ros-base``. Other relevant ROS2 variables are also specified in the ``.env.ros2`` file, -including variables defining the `various middleware`_ options. The container defaults to ``FastRTPS``, but ``CylconeDDS`` -is also supported. Each of these middlewares can be `tuned`_ using their corresponding ``.xml`` files under ``docker/.ros``. - - -Known Issues ------------- - -Invalid mount config for type "bind" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you see the following error when building the container: - -.. code:: text - - ⠋ Container isaaclab Creating 0.0s - Error response from daemon: invalid mount config for type "bind": bind source path does not exist: ${HOME}/.Xauthority - -This means that the ``.Xauthority`` file is not present in the home directory of the host machine. -The portion of the docker-compose.yaml that enables this is commented out by default, so this shouldn't -happen unless it has been altered. This file is required for X11 forwarding to work. To fix this, you can -create an empty ``.Xauthority`` file in your home directory. - -.. code:: bash - - touch ${HOME}/.Xauthority - -A similar error but requires a different fix: - -.. code:: text - - ⠋ Container isaaclab Creating 0.0s - Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /tmp/.X11-unix - -This means that the folder/files are either not present or not accessible on the host machine. -The portion of the docker-compose.yaml that enables this is commented out by default, so this -shouldn't happen unless it has been altered. This usually happens when you have multiple docker -versions installed on your machine. To fix this, you can try the following: - -* Remove all docker versions from your machine. - - .. code:: bash - - sudo apt remove docker* - sudo apt remove docker docker-engine docker.io containerd runc docker-desktop docker-compose-plugin - sudo snap remove docker - sudo apt clean autoclean && sudo apt autoremove --yes - -* Install the latest version of docker based on the instructions in the setup section. - -WebRTC Streaming -~~~~~~~~~~~~~~~~ - -When streaming the GUI from Isaac Sim, there are `several streaming clients`_ available. There is a `known issue`_ when -attempting to use WebRTC streaming client on Google Chrome and Safari while running Isaac Sim inside a container. -To avoid this problem, we suggest using the Native Streaming Client or using the -Mozilla Firefox browser on which WebRTC works. - -Streaming is the only supported method for visualizing the Isaac GUI from within the container. The Omniverse Streaming Client -is freely available from the Omniverse app, and is easy to use. The other streaming methods similarly require only a web browser. -If users want to use X11 forwarding in order to have the apps behave as local GUI windows, they can uncomment the relevant portions -in docker-compose.yaml. - - -.. _`NVIDIA Omniverse EULA`: https://docs.omniverse.nvidia.com/platform/latest/common/NVIDIA_Omniverse_License_Agreement.html -.. _`container installation`: https://docs.omniverse.nvidia.com/isaacsim/latest/installation/install_container.html -.. _`Docker website`: https://docs.docker.com/desktop/install/linux-install/ -.. _`docker compose`: https://docs.docker.com/compose/install/linux/#install-using-the-repository -.. _`NVIDIA Container Toolkit`: https://github.com/NVIDIA/nvidia-container-toolkit -.. _`Container Toolkit website`: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html -.. _`post-installation steps`: https://docs.docker.com/engine/install/linux-postinstall/ -.. _`Isaac Sim container`: https://catalog.ngc.nvidia.com/orgs/nvidia/containers/isaac-sim -.. _`NGC API key`: https://docs.nvidia.com/ngc/gpu-cloud/ngc-user-guide/index.html#generating-api-key -.. _`several streaming clients`: https://docs.omniverse.nvidia.com/isaacsim/latest/installation/manual_livestream_clients.html -.. _`known issue`: https://forums.developer.nvidia.com/t/unable-to-use-webrtc-when-i-run-runheadless-webrtc-sh-in-remote-headless-container/222916 -.. _`profile`: https://docs.docker.com/compose/compose-file/15-profiles/ -.. _`apt package`: https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html#install-ros-2-packages -.. _`various middleware`: https://docs.ros.org/en/humble/How-To-Guides/Working-with-multiple-RMW-implementations.html -.. _`tuned`: https://docs.ros.org/en/foxy/How-To-Guides/DDS-tuning.html diff --git a/docs/_build/html/_sources/source/deployment/index.rst b/docs/_build/html/_sources/source/deployment/index.rst deleted file mode 100644 index c8e07ef9e2..0000000000 --- a/docs/_build/html/_sources/source/deployment/index.rst +++ /dev/null @@ -1,22 +0,0 @@ -Container Deployment -==================== - -Docker is a tool that allows for the creation of containers, which are isolated environments that can -be used to run applications. They are useful for ensuring that an application can run on any machine -that has Docker installed, regardless of the host machine's operating system or installed libraries. - -We include a Dockerfile and docker-compose.yaml file that can be used to build a Docker image that -contains Isaac Lab and all of its dependencies. This image can then be used to run Isaac Lab in a container. -The Dockerfile is based on the Isaac Sim image provided by NVIDIA, which includes the Omniverse -application launcher and the Isaac Sim application. The Dockerfile installs Isaac Lab and its dependencies -on top of this image. - -The following guides provide instructions for building the Docker image and running Isaac Lab in a -container. - -.. toctree:: - :maxdepth: 1 - - docker - cluster - run_docker_example diff --git a/docs/_build/html/_sources/source/deployment/run_docker_example.rst b/docs/_build/html/_sources/source/deployment/run_docker_example.rst deleted file mode 100644 index b38b5b3226..0000000000 --- a/docs/_build/html/_sources/source/deployment/run_docker_example.rst +++ /dev/null @@ -1,141 +0,0 @@ -Running an example with Docker -============================== - -From the root of the ``Isaac Lab`` repository, the ``docker`` directory contains all the Docker relevant files. These include the three files -(**Dockerfile**, **docker-compose.yaml**, **.env**) which are used by Docker, and an additional script that we use to interface with them, -**container.sh**. - -In this tutorial, we will learn how to use the Isaac Lab Docker container for development. For a detailed description of the Docker setup, -including installation and obtaining access to an Isaac Sim image, please reference the :ref:`deployment-docker`. For a description -of Docker in general, please refer to `their official documentation `_. - - -Building the Container -~~~~~~~~~~~~~~~~~~~~~~ - -To build the Isaac Lab container from the root of the Isaac Lab repository, we will run the following: - - -.. code-block:: console - - ./docker/container.sh start - - -The terminal will first pull the base IsaacSim image, build the Isaac Lab image's additional layers on top of it, and run the Isaac Lab container. -This should take several minutes upon the first build but will be shorter in subsequent runs as Docker's caching prevents repeated work. -If we run the command ``docker container ls`` on the terminal, the output will list the containers that are running on the system. If -everything has been set up correctly, a container with the ``NAME`` **isaaclab** should appear, similar to below: - - -.. code-block:: console - - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 483d1d5e2def isaaclab "bash" 30 seconds ago Up 30 seconds isaaclab - - -Once the container is up and running, we can enter it from our terminal. - -.. code-block:: console - - ./docker/container.sh enter - - -On entering the Isaac Lab container, we are in the terminal as the superuser, ``root``. This environment contains a copy of the -Isaac Lab repository, but also has access to the directories and libraries of Isaac Sim. We can run experiments from this environment -using a few convenient aliases that have been put into the ``root`` **.bashrc**. For instance, we have made the **isaaclab.sh** script -usable from anywhere by typing its alias ``isaaclab``. - -Additionally in the container, we have `bind mounted`_ the ``IsaacLab/source`` directory from the -host machine. This means that if we modify files under this directory from an editor on the host machine, the changes are -reflected immediately within the container without requiring us to rebuild the Docker image. - -We will now run a sample script from within the container to demonstrate how to extract artifacts -from the Isaac Lab Docker container. - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``log_time.py`` script in the ``IsaacLab/source/standalone/tutorials/00_sim`` directory. - -.. dropdown:: Code for log_time.py - :icon: code - - .. literalinclude:: ../../../source/standalone/tutorials/00_sim/log_time.py - :language: python - :emphasize-lines: 46-55, 72-79 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -The Isaac Lab Docker container has several `volumes`_ to facilitate persistent storage between the host computer and the -container. One such volume is the ``/workspace/isaaclab/logs`` directory. -The ``log_time.py`` script designates this directory as the location to which a ``log.txt`` should be written: - -.. literalinclude:: ../../../source/standalone/tutorials/00_sim/log_time.py - :language: python - :start-at: # Specify that the logs must be in logs/docker_tutorial - :end-at: print(f"[INFO] Logging experiment to directory: {log_dir_path}") - - -As the comments note, :func:`os.path.abspath()` will prepend ``/workspace/isaaclab`` because in -the Docker container all python execution is done through ``/workspace/isaaclab/isaaclab.sh``. -The output will be a file, ``log.txt``, with the ``sim_time`` written on a newline at every simulation step: - -.. literalinclude:: ../../../source/standalone/tutorials/00_sim/log_time.py - :language: python - :start-at: # Prepare to count sim_time - :end-at: sim_time += sim_dt - - -Executing the Script -~~~~~~~~~~~~~~~~~~~~ - -We will execute the script to produce a log, adding a ``--headless`` flag to our execution to prevent a GUI: - -.. code-block:: bash - - isaaclab -p source/standalone/tutorials/00_sim/log_time.py --headless - - -Now ``log.txt`` will have been produced at ``/workspace/isaaclab/logs/docker_tutorial``. If we exit the container -by typing ``exit``, we will return to ``IsaacLab/docker`` in our host terminal environment. We can then enter -the following command to retrieve our logs from the Docker container and put them on our host machine: - -.. code-block:: console - - ./container.sh copy - - -We will see a terminal readout reporting the artifacts we have retrieved from the container. If we navigate to -``/isaaclab/docker/artifacts/logs/docker_tutorial``, we will see a copy of the ``log.txt`` file which was produced -by the script above. - -Each of the directories under ``artifacts`` corresponds to Docker `volumes`_ mapped to directories -within the container and the ``container.sh copy`` command copies them from those `volumes`_ to these directories. - -We could return to the Isaac Lab Docker terminal environment by running ``container.sh enter`` again, -but we have retrieved our logs and wish to go inspect them. We can stop the Isaac Lab Docker container with the following command: - -.. code-block:: console - - ./container.sh stop - - -This will bring down the Docker Isaac Lab container. The image will persist and remain available for further use, as will -the contents of any `volumes`_. If we wish to free up the disk space taken by the image, (~20.1GB), and do not mind repeating -the build process when we next run ``./container.sh start``, we may enter the following command to delete the **isaaclab** image: - -.. code-block:: console - - docker image rm isaaclab - -A subsequent run of ``docker image ls``` will show that the image tagged **isaaclab** is now gone. We can repeat the process for the -underlying NVIDIA container if we wish to free up more space. If a more powerful method of freeing resources from Docker is desired, -please consult the documentation for the `docker prune`_ commands. - - -.. _volumes: https://docs.docker.com/storage/volumes/ -.. _bind mounted: https://docs.docker.com/storage/bind-mounts/ -.. _docker prune: https://docs.docker.com/config/pruning/ diff --git a/docs/_build/html/_sources/source/features/actuators.rst b/docs/_build/html/_sources/source/features/actuators.rst deleted file mode 100644 index 049ff84a34..0000000000 --- a/docs/_build/html/_sources/source/features/actuators.rst +++ /dev/null @@ -1,77 +0,0 @@ -.. _feature-actuators: - - -Actuators -========= - -An articulated system comprises of actuated joints, also called the degrees of freedom (DOF). -In a physical system, the actuation typically happens either through active components, such as -electric or hydraulic motors, or passive components, such as springs. These components can introduce -certain non-linear characteristics which includes delays or maximum producible velocity or torque. - -In simulation, the joints are either position, velocity, or torque-controlled. For position and velocity -control, the physics engine internally implements a spring-damp (PD) controller which computes the torques -applied on the actuated joints. In torque-control, the commands are set directly as the joint efforts. -While this mimics an ideal behavior of the joint mechanism, it does not truly model how the drives work -in the physical world. Thus, we provide a mechanism to inject external models to compute the -joint commands that would represent the physical robot's behavior. - -Actuator models ---------------- - -We name two different types of actuator models: - -1. **implicit**: corresponds to the ideal simulation mechanism (provided by physics engine). -2. **explicit**: corresponds to external drive models (implemented by user). - -The explicit actuator model performs two steps: 1) it computes the desired joint torques for tracking -the input commands, and 2) it clips the desired torques based on the motor capabilities. The clipped -torques are the desired actuation efforts that are set into the simulation. - -As an example of an ideal explicit actuator model, we provide the :class:`omni.isaac.lab.actuators.IdealPDActuator` -class, which implements a PD controller with feed-forward effort, and simple clipping based on the configured -maximum effort: - -.. math:: - - \tau_{j, computed} & = k_p * (q - q_{des}) + k_d * (\dot{q} - \dot{q}_{des}) + \tau_{ff} \\ - \tau_{j, applied} & = clip(\tau_{computed}, -\tau_{j, max}, \tau_{j, max}) - - -where, :math:`k_p` and :math:`k_d` are joint stiffness and damping gains, :math:`q` and :math:`\dot{q}` -are the current joint positions and velocities, :math:`q_{des}`, :math:`\dot{q}_{des}` and :math:`\tau_{ff}` -are the desired joint positions, velocities and torques commands. The parameters :math:`\gamma` and -:math:`\tau_{motor, max}` are the gear box ratio and the maximum motor effort possible. - -Actuator groups ---------------- - -The actuator models by themselves are computational blocks that take as inputs the desired joint commands -and output the joint commands to apply into the simulator. They do not contain any knowledge about the -joints they are acting on themselves. These are handled by the :class:`omni.isaac.lab.assets.Articulation` -class, which wraps around the physics engine's articulation class. - -Actuator are collected as a set of actuated joints on an articulation that are using the same actuator model. -For instance, the quadruped, ANYmal-C, uses series elastic actuator, ANYdrive 3.0, for all its joints. This -grouping configures the actuator model for those joints, translates the input commands to the joint level -commands, and returns the articulation action to set into the simulator. Having an arm with a different -actuator model, such as a DC motor, would require configuring a different actuator group. - -The following figure shows the actuator groups for a legged mobile manipulator: - -.. image:: ../_static/actuator-group/actuator-light.svg - :class: only-light - :align: center - :alt: Actuator models for a legged mobile manipulator - :width: 80% - -.. image:: ../_static/actuator-group/actuator-dark.svg - :class: only-dark - :align: center - :width: 80% - :alt: Actuator models for a legged mobile manipulator - -.. seealso:: - - We provide implementations for various explicit actuator models. These are detailed in - `omni.isaac.lab.actuators <../api/lab/omni.isaac.lab.actuators.html>`_ sub-package. diff --git a/docs/_build/html/_sources/source/features/environments.rst b/docs/_build/html/_sources/source/features/environments.rst deleted file mode 100644 index e1b0c0339a..0000000000 --- a/docs/_build/html/_sources/source/features/environments.rst +++ /dev/null @@ -1,234 +0,0 @@ -Environments -============ - -The following lists comprises of all the RL tasks implementations that are available in Isaac Lab. -While we try to keep this list up-to-date, you can always get the latest list of environments by -running the following command: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/environments/list_envs.py - -We are actively working on adding more environments to the list. If you have any environments that -you would like to add to Isaac Lab, please feel free to open a pull request! - -Classic -------- - -Classic environments that are based on IsaacGymEnvs implementation of MuJoCo-style environments. - - -.. table:: - :widths: 33 37 30 - - +------------------+-----------------------------+-------------------------------------------------------------------------+ - | World | Environment ID | Description | - +==================+=============================+=========================================================================+ - | |humanoid| | | |humanoid-link| | Move towards a direction with the MuJoCo humanoid robot | - | | | |humanoid-direct-link| | | - +------------------+-----------------------------+-------------------------------------------------------------------------+ - | |ant| | | |ant-link| | Move towards a direction with the MuJoCo ant robot | - | | | |ant-direct-link| | | - +------------------+-----------------------------+-------------------------------------------------------------------------+ - | |cartpole| | | |cartpole-link| | Move the cart to keep the pole upwards in the classic cartpole control | - | | | |cartpole-direct-link| | | - | | | |cartpole-camera-rgb-link|| | - | | | |cartpole-camera-dpt-link|| | - +------------------+-----------------------------+-------------------------------------------------------------------------+ - -.. |humanoid| image:: ../_static/tasks/classic/humanoid.jpg -.. |ant| image:: ../_static/tasks/classic/ant.jpg -.. |cartpole| image:: ../_static/tasks/classic/cartpole.jpg - -.. |humanoid-link| replace:: `Isaac-Humanoid-v0 `__ -.. |ant-link| replace:: `Isaac-Ant-v0 `__ -.. |cartpole-link| replace:: `Isaac-Cartpole-v0 `__ - -.. |humanoid-direct-link| replace:: `Isaac-Humanoid-Direct-v0 `__ -.. |ant-direct-link| replace:: `Isaac-Ant-Direct-v0 `__ -.. |cartpole-direct-link| replace:: `Isaac-Cartpole-Direct-v0 `__ -.. |cartpole-camera-rgb-link| replace:: `Isaac-Cartpole-RGB-Camera-Direct-v0 `__ -.. |cartpole-camera-dpt-link| replace:: `Isaac-Cartpole-Depth-Camera-Direct-v0 `__ - - -Manipulation ------------- - -Environments based on fixed-arm manipulation tasks. - -For many of these tasks, we include configurations with different arm action spaces. For example, -for the reach environment: - -* |lift-cube-link|: Franka arm with joint position control -* |lift-cube-ik-abs-link|: Franka arm with absolute IK control -* |lift-cube-ik-rel-link|: Franka arm with relative IK control - -.. table:: - :widths: 33 37 30 - - +----------------+---------------------------+-----------------------------------------------------------------------------+ - | World | Environment ID | Description | - +================+===========================+=============================================================================+ - | |reach-franka| | |reach-franka-link| | Move the end-effector to a sampled target pose with the Franka robot | - +----------------+---------------------------+-----------------------------------------------------------------------------+ - | |reach-ur10| | |reach-ur10-link| | Move the end-effector to a sampled target pose with the UR10 robot | - +----------------+---------------------------+-----------------------------------------------------------------------------+ - | |lift-cube| | |lift-cube-link| | Pick a cube and bring it to a sampled target position with the Franka robot | - +----------------+---------------------------+-----------------------------------------------------------------------------+ - | |cabi-franka| | |cabi-franka-link| | Grasp the handle of a cabinet's drawer and open it with the Franka robot | - +----------------+---------------------------+-----------------------------------------------------------------------------+ - | |cube-allegro| | |cube-allegro-link| | In-hand reorientation of a cube using Allegro hand | - +----------------+---------------------------+-----------------------------------------------------------------------------+ - | |cube-shadow| | | |cube-shadow-link| | In-hand reorientation of a cube using Shadow hand | - | | | |cube-shadow-ff-link| | | - | | | |cube-shadow-lstm-link| | | - +----------------+---------------------------+-----------------------------------------------------------------------------+ - -.. |reach-franka| image:: ../_static/tasks/manipulation/franka_reach.jpg -.. |reach-ur10| image:: ../_static/tasks/manipulation/ur10_reach.jpg -.. |lift-cube| image:: ../_static/tasks/manipulation/franka_lift.jpg -.. |cabi-franka| image:: ../_static/tasks/manipulation/franka_open_drawer.jpg -.. |cube-allegro| image:: ../_static/tasks/manipulation/allegro_cube.jpg -.. |cube-shadow| image:: ../_static/tasks/manipulation/shadow_cube.jpg - -.. |reach-franka-link| replace:: `Isaac-Reach-Franka-v0 `__ -.. |reach-ur10-link| replace:: `Isaac-Reach-UR10-v0 `__ -.. |lift-cube-link| replace:: `Isaac-Lift-Cube-Franka-v0 `__ -.. |lift-cube-ik-abs-link| replace:: `Isaac-Lift-Cube-Franka-IK-Abs-v0 `__ -.. |lift-cube-ik-rel-link| replace:: `Isaac-Lift-Cube-Franka-IK-Rel-v0 `__ -.. |cabi-franka-link| replace:: `Isaac-Open-Drawer-Franka-v0 `__ -.. |cube-allegro-link| replace:: `Isaac-Repose-Cube-Allegro-v0 `__ - -.. |cube-shadow-link| replace:: `Isaac-Shadow-Hand-Direct-v0 `__ -.. |cube-shadow-ff-link| replace:: `Isaac-Shadow-Hand-OpenAI-FF-Direct-v0 `__ -.. |cube-shadow-lstm-link| replace:: `Isaac-Shadow-Hand-OpenAI-LSTM-Direct-v0 `__ - -Locomotion ----------- - -Environments based on legged locomotion tasks. - -.. table:: - :widths: 33 37 30 - - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | World | Environment ID | Description | - +==============================+==============================================+==============================================================================+ - | |velocity-flat-anymal-b| | |velocity-flat-anymal-b-link| | Track a velocity command on flat terrain with the Anymal B robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-anymal-b| | |velocity-rough-anymal-b-link| | Track a velocity command on rough terrain with the Anymal B robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-anymal-c| | | |velocity-flat-anymal-c-link| | Track a velocity command on flat terrain with the Anymal C robot | - | | | |velocity-flat-anymal-c-direct-link| | | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-anymal-c| | | |velocity-rough-anymal-c-link| | Track a velocity command on rough terrain with the Anymal C robot | - | | | |velocity-rough-anymal-c-direct-link| | | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-anymal-d| | |velocity-flat-anymal-d-link| | Track a velocity command on flat terrain with the Anymal D robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-anymal-d| | |velocity-rough-anymal-d-link| | Track a velocity command on rough terrain with the Anymal D robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-unitree-a1| | |velocity-flat-unitree-a1-link| | Track a velocity command on flat terrain with the Unitree A1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-unitree-a1| | |velocity-rough-unitree-a1-link| | Track a velocity command on rough terrain with the Unitree A1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-unitree-go1| | |velocity-flat-unitree-go1-link| | Track a velocity command on flat terrain with the Unitree Go1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-unitree-go1| | |velocity-rough-unitree-go1-link| | Track a velocity command on rough terrain with the Unitree Go1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-unitree-go2| | |velocity-flat-unitree-go2-link| | Track a velocity command on flat terrain with the Unitree Go2 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-unitree-go2| | |velocity-rough-unitree-go2-link| | Track a velocity command on rough terrain with the Unitree Go2 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-spot| | |velocity-flat-spot-link| | Track a velocity command on flat terrain with the Boston Dynamics Spot robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-h1| | |velocity-flat-h1-link| | Track a velocity command on flat terrain with the Unitree H1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-h1| | |velocity-rough-h1-link| | Track a velocity command on rough terrain with the Unitree H1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-flat-g1| | |velocity-flat-g1-link| | Track a velocity command on flat terrain with the Unitree G1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - | |velocity-rough-g1| | |velocity-rough-g1-link| | Track a velocity command on rough terrain with the Unitree G1 robot | - +------------------------------+----------------------------------------------+------------------------------------------------------------------------------+ - -.. |velocity-flat-anymal-b-link| replace:: `Isaac-Velocity-Flat-Anymal-B-v0 `__ -.. |velocity-rough-anymal-b-link| replace:: `Isaac-Velocity-Rough-Anymal-B-v0 `__ - -.. |velocity-flat-anymal-c-link| replace:: `Isaac-Velocity-Flat-Anymal-C-v0 `__ -.. |velocity-rough-anymal-c-link| replace:: `Isaac-Velocity-Rough-Anymal-C-v0 `__ - -.. |velocity-flat-anymal-c-direct-link| replace:: `Isaac-Velocity-Flat-Anymal-C-Direct-v0 `__ -.. |velocity-rough-anymal-c-direct-link| replace:: `Isaac-Velocity-Rough-Anymal-C-Direct-v0 `__ - -.. |velocity-flat-anymal-d-link| replace:: `Isaac-Velocity-Flat-Anymal-D-v0 `__ -.. |velocity-rough-anymal-d-link| replace:: `Isaac-Velocity-Rough-Anymal-D-v0 `__ - -.. |velocity-flat-unitree-a1-link| replace:: `Isaac-Velocity-Flat-Unitree-A1-v0 `__ -.. |velocity-rough-unitree-a1-link| replace:: `Isaac-Velocity-Rough-Unitree-A1-v0 `__ - -.. |velocity-flat-unitree-go1-link| replace:: `Isaac-Velocity-Flat-Unitree-Go1-v0 `__ -.. |velocity-rough-unitree-go1-link| replace:: `Isaac-Velocity-Rough-Unitree-Go1-v0 `__ - -.. |velocity-flat-unitree-go2-link| replace:: `Isaac-Velocity-Flat-Unitree-Go2-v0 `__ -.. |velocity-rough-unitree-go2-link| replace:: `Isaac-Velocity-Rough-Unitree-Go2-v0 `__ - -.. |velocity-flat-spot-link| replace:: `Isaac-Velocity-Flat-Spot-v0 `__ - -.. |velocity-flat-h1-link| replace:: `Isaac-Velocity-Flat-H1-v0 `__ -.. |velocity-rough-h1-link| replace:: `Isaac-Velocity-Rough-H1-v0 `__ - -.. |velocity-flat-g1-link| replace:: `Isaac-Velocity-Flat-G1-v0 `__ -.. |velocity-rough-g1-link| replace:: `Isaac-Velocity-Rough-G1-v0 `__ - - -.. |velocity-flat-anymal-b| image:: ../_static/tasks/locomotion/anymal_b_flat.jpg -.. |velocity-rough-anymal-b| image:: ../_static/tasks/locomotion/anymal_b_rough.jpg -.. |velocity-flat-anymal-c| image:: ../_static/tasks/locomotion/anymal_c_flat.jpg -.. |velocity-rough-anymal-c| image:: ../_static/tasks/locomotion/anymal_c_rough.jpg -.. |velocity-flat-anymal-d| image:: ../_static/tasks/locomotion/anymal_d_flat.jpg -.. |velocity-rough-anymal-d| image:: ../_static/tasks/locomotion/anymal_d_rough.jpg -.. |velocity-flat-unitree-a1| image:: ../_static/tasks/locomotion/a1_flat.jpg -.. |velocity-rough-unitree-a1| image:: ../_static/tasks/locomotion/a1_rough.jpg -.. |velocity-flat-unitree-go1| image:: ../_static/tasks/locomotion/go1_flat.jpg -.. |velocity-rough-unitree-go1| image:: ../_static/tasks/locomotion/go1_rough.jpg -.. |velocity-flat-unitree-go2| image:: ../_static/tasks/locomotion/go2_flat.jpg -.. |velocity-rough-unitree-go2| image:: ../_static/tasks/locomotion/go2_rough.jpg -.. |velocity-flat-spot| image:: ../_static/tasks/locomotion/spot_flat.jpg -.. |velocity-flat-h1| image:: ../_static/tasks/locomotion/h1_flat.jpg -.. |velocity-rough-h1| image:: ../_static/tasks/locomotion/h1_rough.jpg -.. |velocity-flat-g1| image:: ../_static/tasks/locomotion/g1_flat.jpg -.. |velocity-rough-g1| image:: ../_static/tasks/locomotion/g1_rough.jpg - -Navigation ----------- - -.. table:: - :widths: 33 37 30 - - +----------------+---------------------+-----------------------------------------------------------------------------+ - | World | Environment ID | Description | - +================+=====================+=============================================================================+ - | |anymal_c_nav| | |anymal_c_nav-link| | Navigate towards a target x-y position and heading with the ANYmal C robot. | - +----------------+---------------------+-----------------------------------------------------------------------------+ - -.. |anymal_c_nav-link| replace:: `Isaac-Navigation-Flat-Anymal-C-v0 `__ - -.. |anymal_c_nav| image:: ../_static/tasks/navigation/anymal_c_nav.jpg - - -Others ------- - -.. table:: - :widths: 33 37 30 - - +----------------+---------------------+-----------------------------------------------------------------------------+ - | World | Environment ID | Description | - +================+=====================+=============================================================================+ - | |quadcopter| | |quadcopter-link| | Fly and hover the Crazyflie copter at a goal point by applying thrust. | - +----------------+---------------------+-----------------------------------------------------------------------------+ - -.. |quadcopter-link| replace:: `Isaac-Quadcopter-Direct-v0 `__ - - -.. |quadcopter| image:: ../_static/tasks/others/quadcopter.jpg diff --git a/docs/_build/html/_sources/source/features/motion_generators.rst b/docs/_build/html/_sources/source/features/motion_generators.rst deleted file mode 100644 index e4e09f2f17..0000000000 --- a/docs/_build/html/_sources/source/features/motion_generators.rst +++ /dev/null @@ -1,229 +0,0 @@ -Motion Generators -================= - -Robotic tasks are typically defined in task-space in terms of desired -end-effector trajectory, while control actions are executed in the -joint-space. This naturally leads to *joint-space* and *task-space* -(operational-space) control methods. However, successful execution of -interaction tasks using motion control often requires an accurate model -of both the robot manipulator as well as its environment. While a -sufficiently precise manipulator's model might be known, detailed -description of environment is hard to obtain :cite:p:`siciliano2009force`. -Planning errors caused by this mismatch can be overcome by introducing a -*compliant* behavior during interaction. - -While compliance is achievable passively through robot's structure (such -as elastic actuators, soft robot arms), we are more interested in -controller designs that focus on active interaction control. These are -broadly categorized into: - -1. **impedance control:** indirect control method where motion deviations - caused during interaction relates to contact force as a mass-spring-damper - system with adjustable parameters (stiffness and damping). A specialized case - of this is *stiffness* control where only the static relationship between - position error and contact force is considered. - -2. **hybrid force/motion control:** active control method which controls motion - and force along unconstrained and constrained task directions respectively. - Among the various schemes for hybrid motion control, the provided implementation - is based on inverse dynamics control in the operational space :cite:p:`khatib1987osc`. - -.. note:: - - To provide an even broader set of motion generators, we welcome contributions from the - community. If you are interested, please open an issue to start a discussion! - - -Joint-space controllers ------------------------ - -Torque control -~~~~~~~~~~~~~~ - -Action dimensions: ``"n"`` (number of joints) - -In torque control mode, the input actions are directly set as feed-forward -joint torque commands, i.e. at every time-step, - -.. math:: - - \tau = \tau_{des} - -Thus, this control mode is achievable by setting the command type for the actuator group, via -the :class:`ActuatorControlCfg` class, to ``"t_abs"``. - - -Velocity control -~~~~~~~~~~~~~~~~ - -Action dimensions: ``"n"`` (number of joints) - -In velocity control mode, a proportional control law is required to reduce the error between the -current and desired joint velocities. Based on input actions, the joint torques commands are computed as: - -.. math:: - - \tau = k_d (\dot{q}_{des} - \dot{q}) - -where :math:`k_d` are the gains parsed from configuration. - -This control mode is achievable by setting the command type for the actuator group, via -the :class:`ActuatorControlCfg` class, to ``"v_abs"`` or ``"v_rel"``. - -.. attention:: - - While performing velocity control, in many cases, gravity compensation is required to ensure better - tracking of the command. In this case, we suggest disabling gravity for the links in the articulation - in simulation. - -Position control with fixed impedance -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Action dimensions: ``"n"`` (number of joints) - -In position control mode, a proportional-damping (PD) control law is employed to track the desired joint -positions and ensuring the articulation remains still at the desired location (i.e., desired joint velocities -are zero). Based on the input actions, the joint torque commands are computed as: - -.. math:: - - \tau = k_p (q_{des} - q) - k_d \dot{q} - -where :math:`k_p` and :math:`k_d` are the gains parsed from configuration. - -In its simplest above form, the control mode is achievable by setting the command type for the actuator group, -via the :class:`ActuatorControlCfg` class, to ``"p_abs"`` or ``"p_rel"``. - -However, a more complete formulation which considers the dynamics of the articulation would be: - -.. math:: - - \tau = M \left( k_p (q_{des} - q) - k_d \dot{q} \right) + g - -where :math:`M` is the joint-space inertia matrix of size :math:`n \times n`, and :math:`g` is the joint-space -gravity vector. This implementation is available through the :class:`JointImpedanceController` class by setting the -impedance mode to ``"fixed"``. The gains :math:`k_p` are parsed from the input configuration and :math:`k_d` -are computed while considering the system as a decoupled point-mass oscillator, i.e., - -.. math:: - - k_d = 2 \sqrt{k_p} \times D - -where :math:`D` is the damping ratio of the system. Critical damping is achieved for :math:`D = 1`, overcritical -damping for :math:`D > 1` and undercritical damping for :math:`D < 1`. - -Additionally, it is possible to disable the inertial or gravity compensation in the controller by setting the -flags :attr:`inertial_compensation` and :attr:`gravity_compensation` in the configuration to :obj:`False`, -respectively. - -Position control with variable stiffness -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Action dimensions: ``"2n"`` (number of joints) - -In stiffness control, the same formulation as above is employed, however, the gains :math:`k_p` are part of -the input commands. This implementation is available through the :class:`JointImpedanceController` class by -setting the impedance mode to ``"variable_kp"``. - -Position control with variable impedance -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Action dimensions: ``"3n"`` (number of joints) - -In impedance control, the same formulation as above is employed, however, both :math:`k_p` and :math:`k_d` -are part of the input commands. This implementation is available through the :class:`JointImpedanceController` -class by setting the impedance mode to ``"variable"``. - -Task-space controllers ----------------------- - -Differential inverse kinematics (IK) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Action dimensions: ``"3"`` (relative/absolute position), ``"6"`` (relative pose), or ``"7"`` (absolute pose) - -Inverse kinematics converts the task-space tracking error to joint-space error. In its most typical implementation, -the pose error in the task-sace, :math:`\Delta \chi_e = (\Delta p_e, \Delta \phi_e)`, is computed as the cartesian -distance between the desired and current task-space positions, and the shortest distance in :math:`\mathbb{SO}(3)` -between the desired and current task-space orientations. - -Using the geometric Jacobian :math:`J_{eO} \in \mathbb{R}^{6 \times n}`, that relates task-space velocity to joint-space velocities, -we design the control law to obtain the desired joint positions as: - -.. math:: - - q_{des} = q + \eta J_{eO}^{-} \Delta \chi_e - -where :math:`\eta` is a scaling parameter and :math:`J_{eO}^{-}` is the pseudo-inverse of the Jacobian. - -It is possible to compute the pseudo-inverse of the Jacobian using different formulations: - -* Moore-Penrose pseduo-inverse: :math:`A^{-} = A^T(AA^T)^{-1}`. -* Levenberg-Marquardt pseduo-inverse (damped least-squares): :math:`A^{-} = A^T (AA^T + \lambda \mathbb{I})^{-1}`. -* Tanspose pseudo-inverse: :math:`A^{-} = A^T`. -* Adaptive singular-vale decomposition (SVD) pseduo-inverse from :cite:t:`buss2004ik`. - -These implementations are available through the :class:`DifferentialInverseKinematics` class. - -Impedance controller -~~~~~~~~~~~~~~~~~~~~ - - -It uses task-space pose error and Jacobian to compute join torques through mass-spring-damper system -with a) fixed stiffness, b) variable stiffness (stiffness control), -and c) variable stiffness and damping (impedance control). - -Operational-space controller -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to task-space impedance -control but uses the Equation of Motion (EoM) for computing the -task-space force - -Closed-loop proportional force controller -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It uses a proportional term -to track the desired wrench command with respect to current wrench at -the end-effector. - -Hybrid force-motion controller -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It combines closed-loop force control -and operational-space motion control to compute the desired wrench at -the end-effector. It uses selection matrices that define the -unconstrainted and constrained task directions. - - -Reactive planners ------------------ - -Typical task-space controllers do not account for motion constraints -such as joint limits, self-collision and environment collision. Instead -they rely on high-level planners (such as RRT) to handle these -non-Euclidean constraints and give joint/task-space way-points to the -controller. However, these methods are often conservative and have -undesirable deceleration when close to an object. More recently, -different approaches combine the constraints directly into an -optimization problem, thereby providing a holistic solution for motion -generation and control. - -We currently support the following planners: - -- **RMPFlow (lula):** An acceleration-based policy that composes various Reimannian Motion Policies (RMPs) to - solve a hierarchy of tasks :cite:p:`cheng2021rmpflow`. It is capable of performing dynamic collision - avoidance while navigating the end-effector to a target. - -- **MPC (OCS2):** A receding horizon control policy based on sequential linear-quadratic (SLQ) programming. - It formulates various constraints into a single optimization problem via soft-penalties and uses automatic - differentiation to compute derivatives of the system dynamics, constraints and costs. Currently, we support - the MPC formulation for end-effector trajectory tracking in fixed-arm and mobile manipulators. The formulation - considers a kinematic system model with joint limits and self-collision avoidance :cite:p:`mittal2021articulated`. - - -.. warning:: - - We wrap around the python bindings for these reactive planners to perform a batched computing of - robot actions. However, their current implementations are CPU-based which may cause certain - slowdown for learning. diff --git a/docs/_build/html/_sources/source/features/multi_gpu.rst b/docs/_build/html/_sources/source/features/multi_gpu.rst deleted file mode 100644 index c5d1775173..0000000000 --- a/docs/_build/html/_sources/source/features/multi_gpu.rst +++ /dev/null @@ -1,116 +0,0 @@ -Multi-GPU and Multi-Node Training -================================= - -.. currentmodule:: omni.isaac.lab - -Isaac Lab supports multi-GPU and multi-node reinforcement learning. Currently, this feature is only -available for RL-Games and skrl libraries workflows. We are working on extending this feature to -other workflows. - -.. attention:: - - Multi-GPU and multi-node training is only supported on Linux. Windows support is not available at this time. - This is due to limitations of the NCCL library on Windows. - - -Multi-GPU Training ------------------- - -For complex reinforcement learning environments, it may be desirable to scale up training across multiple GPUs. -This is possible in Isaac Lab through the use of the -`PyTorch distributed `_ framework. - -The :meth:`torch.distributed` API is used to launch multiple processes of training, where the number of -processes must be equal to or less than the number of GPUs available. Each process runs on -a dedicated GPU and launches its own instance of Isaac Sim and the Isaac Lab environment. -Each process collects its own rollouts during the training process and has its own copy of the policy -network. During training, gradients are aggregated across the processes and broadcasted back to the process -at the end of the epoch. - -.. image:: ../_static/multi-gpu-rl/a3c-light.svg - :class: only-light - :align: center - :alt: Multi-GPU training paradigm - :width: 80% - -.. image:: ../_static/multi-gpu-rl/a3c-dark.svg - :class: only-dark - :align: center - :width: 80% - :alt: Multi-GPU training paradigm - -To train with multiple GPUs, use the following command, where ``--proc_per_node`` represents the number of available GPUs: - -.. tab-set:: - :sync-group: rl-train - - .. tab-item:: rl_games - :sync: rl_games - - .. code-block:: shell - - python -m torch.distributed.run --nnodes=1 --nproc_per_node=2 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed - - .. tab-item:: skrl - :sync: skrl - - .. code-block:: shell - - python -m torch.distributed.run --nnodes=1 --nproc_per_node=2 source/standalone/workflows/skrl/train.py --task=Isaac-Cartpole-v0 --headless --distributed - - -Multi-Node Training -------------------- - -To scale up training beyond multiple GPUs on a single machine, it is also possible to train across multiple nodes. -To train across multiple nodes/machines, it is required to launch an individual process on each node. - -For the master node, use the following command, where ``--proc_per_node`` represents the number of available GPUs, and -``--nnodes`` represents the number of nodes: - -.. tab-set:: - :sync-group: rl-train - - .. tab-item:: rl_games - :sync: rl_games - - .. code-block:: shell - - python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=0 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=localhost:5555 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed - - .. tab-item:: skrl - :sync: skrl - - .. code-block:: shell - - python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=0 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=localhost:5555 source/standalone/workflows/skrl/train.py --task=Isaac-Cartpole-v0 --headless --distributed - -Note that the port (``5555``) can be replaced with any other available port. - -For non-master nodes, use the following command, replacing ``--node_rank`` with the index of each machine: - -.. tab-set:: - :sync-group: rl-train - - .. tab-item:: rl_games - :sync: rl_games - - .. code-block:: shell - - python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=1 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=ip_of_master_machine:5555 source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --distributed - - .. tab-item:: skrl - :sync: skrl - - .. code-block:: shell - - python -m torch.distributed.run --nproc_per_node=2 --nnodes=2 --node_rank=1 --rdzv_id=123 --rdzv_backend=c10d --rdzv_endpoint=ip_of_master_machine:5555 source/standalone/workflows/skrl/train.py --task=Isaac-Cartpole-v0 --headless --distributed - -For more details on multi-node training with PyTorch, please visit the -`PyTorch documentation `_. - -.. note:: - - As mentioned in the PyTorch documentation, "multi-node training is bottlenecked by inter-node communication - latencies". When this latency is high, it is possible multi-node training will perform worse than running on - a single node instance. diff --git a/docs/_build/html/_sources/source/features/task_workflows.rst b/docs/_build/html/_sources/source/features/task_workflows.rst deleted file mode 100644 index 3bb0b0310b..0000000000 --- a/docs/_build/html/_sources/source/features/task_workflows.rst +++ /dev/null @@ -1,134 +0,0 @@ -.. _feature-workflows: - - -Task Design Workflows -===================== - -.. currentmodule:: omni.isaac.lab - -Environments define the interface between the agent and the simulation. In the simplest case, the environment provides -the agent with the current observations and executes the actions provided by the agent. In a Markov Decision Process -(MDP) formulation, the environment can also provide additional information such as the current reward, done flag, and -information about the current episode. - -While the environment interface is simple to understand, its implementation can vary significantly depending on the -complexity of the task. In the context of reinforcement learning (RL), the environment implementation can be broken down -into several components, such as the reward function, observation function, termination function, and reset function. -Each of these components can be implemented in different ways depending on the complexity of the task and the desired -level of modularity. - -We provide two different workflows for designing environments with the framework: - -* **Manager-based**: The environment is decomposed into individual components (or managers) that handle different - aspects of the environment (such as computing observations, applying actions, and applying randomization). The - user defines configuration classes for each component and the environment is responsible for coordinating the - managers and calling their functions. -* **Direct**: The user defines a single class that implements the entire environment directly without the need for - separate managers. This class is responsible for computing observations, applying actions, and computing rewards. - -Both workflows have their own advantages and disadvantages. The manager-based workflow is more modular and allows -different components of the environment to be swapped out easily. This is useful when prototyping the environment -and experimenting with different configurations. On the other hand, the direct workflow is more efficient and allows -for more fine-grained control over the environment logic. This is useful when optimizing the environment for performance -or when implementing complex logic that is difficult to decompose into separate components. - - -Manager-Based Environments --------------------------- - -A majority of environment implementations follow a similar structure. The environment processes the input actions, -steps through the simulation, computes observations and reward signals, applies randomization, and resets the terminated -environments. Motivated by this, the environment can be decomposed into individual components that handle each of these tasks. -For example, the observation manager is responsible for computing the observations, the reward manager is responsible for -computing the rewards, and the termination manager is responsible for computing the termination signal. This approach -is known as the manager-based environment design in the framework. - -Manager-based environments promote modular implementations of tasks by decomposing the task into individual -components that are managed by separate classes. Each component of the task, such as rewards, observations, -termination can all be specified as individual configuration classes that are then passed to the corresponding -manager classes. The manager is then responsible for parsing the configurations and processing the contents specified -in its configuration. - -The coordination between the different managers is orchestrated by the class :class:`envs.ManagerBasedRLEnv`. -It takes in a task configuration class instance (:class:`envs.ManagerBasedRLEnvCfg`) that contains the configurations -for each of the components of the task. Based on the configurations, the scene is set up and the task is initialized. -Afterwards, while stepping through the environment, all the managers are called sequentially to perform the necessary -operations. - -For their own tasks, we expect the user to mainly define the task configuration class and use the existing -:class:`envs.ManagerBasedRLEnv` class for the task implementation. The task configuration class should inherit from -the base class :class:`envs.ManagerBasedRLEnvCfg` and contain variables assigned to various configuration classes -for each component (such as the ``ObservationCfg`` and ``RewardCfg``). - -.. dropdown:: Example for defining the reward function for the Cartpole task using the manager-style - :icon: plus - - The following class is a part of the Cartpole environment configuration class. The :class:`RewardsCfg` class - defines individual terms that compose the reward function. Each reward term is defined by its function - implementation, weight and additional parameters to be passed to the function. Users can define multiple - reward terms and their weights to be used in the reward function. - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :pyobject: RewardsCfg - - -Through this approach, it is possible to easily vary the implementations of the task by switching some components -while leaving the remaining of the code intact. This flexibility is desirable when prototyping the environment and -experimenting with different configurations. It also allows for easy collaborating with others on implementing an -environment, since contributors may choose to use different combinations of configurations for their own task -specifications. - -.. seealso:: - - We provide a more detailed tutorial for setting up an environment using the manager-based workflow at - :ref:`tutorial-create-manager-rl-env`. - - -Direct Environments -------------------- - -The direct-style environment aligns more closely with traditional implementations of environments, -where a single script directly implements the reward function, observation function, resets, and all the other components -of the environment. This approach does not require the manager classes. Instead, users are provided the complete freedom -to implement their task through the APIs from the base class :class:`envs.DirectRLEnv`. For users migrating from the `IsaacGymEnvs`_ -and `OmniIsaacGymEnvs`_ framework, this workflow may be more familiar. - -When defining an environment with the direct-style implementation, we expect the user define a single class that -implements the entire environment. The task class should inherit from the base :class:`envs.DirectRLEnv` class and should -have its corresponding configuration class that inherits from :class:`envs.DirectRLEnvCfg`. The task class is responsible -for setting up the scene, processing the actions, computing the rewards, observations, resets, and termination signals. - -.. dropdown:: Example for defining the reward function for the Cartpole task using the direct-style - :icon: plus - - The following function is a part of the Cartpole environment class and is responsible for computing the rewards. - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._get_rewards - :dedent: 4 - - It calls the :meth:`compute_rewards` function which is Torch JIT compiled for performance benefits. - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: compute_rewards - -This approach provides more transparency in the implementations of the environments, as logic is defined within the task -class instead of abstracted with the use of managers. This may be beneficial when implementing complex logic that is -difficult to decompose into separate components. Additionally, the direct-style implementation may bring more performance -benefits for the environment, as it allows implementing large chunks of logic with optimized frameworks such as -`PyTorch JIT`_ or `Warp`_. This may be valuable when scaling up training tremendously which requires optimizing individual -operations in the environment. - -.. seealso:: - - We provide a more detailed tutorial for setting up a RL environment using the direct workflow at - :ref:`tutorial-create-direct-rl-env`. - - -.. _IsaacGymEnvs: https://github.com/isaac-sim/IsaacGymEnvs -.. _OmniIsaacGymEnvs: https://github.com/isaac-sim/OmniIsaacGymEnvs -.. _Pytorch JIT: https://pytorch.org/docs/stable/jit.html -.. _Warp: https://github.com/NVIDIA/warp diff --git a/docs/_build/html/_sources/source/features/tiled_rendering.rst b/docs/_build/html/_sources/source/features/tiled_rendering.rst deleted file mode 100644 index ac6285159a..0000000000 --- a/docs/_build/html/_sources/source/features/tiled_rendering.rst +++ /dev/null @@ -1,82 +0,0 @@ -Tiled Rendering and Recording -============================= - -.. currentmodule:: omni.isaac.lab - - -Tiled Rendering ---------------- - -.. note:: - - This feature is only available from Isaac Sim version 4.0.0. - - Tiled rendering requires heavy memory resources. We recommend running at most 256 cameras in the scene. - -Tiled rendering APIs provide a vectorized interface for collecting data from camera sensors. -This is useful for reinforcement learning environments requiring vision in the loop. -Tiled rendering works by concatenating camera outputs from multiple cameras and rendering -one single large image instead of multiple smaller images that would have been produced -by each individual camera. This reduces the amount of time required for rendering and -provides a more efficient API for working with vision data. - -Isaac Lab provides tiled rendering APIs for RGB and depth data through the :class:`~sensors.TiledCamera` -class. Configurations for the tiled rendering APIs can be defined through the :class:`~sensors.TiledCameraCfg` -class, specifying parameters such as the regex expression for all camera paths, the transform -for the cameras, the desired data type, the type of cameras to add to the scene, and the camera -resolution. - -.. code-block:: python - - tiled_camera: TiledCameraCfg = TiledCameraCfg( - prim_path="/World/envs/env_.*/Camera", - offset=TiledCameraCfg.OffsetCfg(pos=(-7.0, 0.0, 3.0), rot=(0.9945, 0.0, 0.1045, 0.0), convention="world"), - data_types=["rgb"], - spawn=sim_utils.PinholeCameraCfg( - focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0) - ), - width=80, - height=80, - ) - -To access the tiled rendering interface, a :class:`~sensors.TiledCamera` object can be created and used -to retrieve data from the cameras. - -.. code-block:: python - - tiled_camera = TiledCamera(cfg.tiled_camera) - data_type = "rgb" - data = tiled_camera.data.output[data_type] - -The returned data will be transformed into the shape (num_cameras, height, width, num_channels), which -can be used directly as observation for reinforcement learning. - -When working with rendering, make sure to add the ``--enable_cameras`` argument when launching the -environment. For example: - -.. code-block:: shell - - python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-RGB-Camera-Direct-v0 --headless --enable_cameras - - -Recording during training -------------------------- - -Isaac Lab supports recording video clips during training using the `gymnasium.wrappers.RecordVideo `_ class. - -This feature can be enabled by using the following command line arguments with the training script: - -* ``--video`` - enables video recording during training -* ``--video_length`` - length of each recorded video (in steps) -* ``--video_interval`` - interval between each video recording (in steps) - -Make sure to also add the ``--enable_cameras`` argument when running headless. -Note that enabling recording is equivalent to enabling rendering during training, which will slow down both startup and runtime performance. - -Example usage: - -.. code-block:: shell - - python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 --headless --enable_cameras --video --video_length 100 --video_interval 500 - -Recorded videos will be saved in the same directory as the training checkpoints, under ``IsaacLab/logs////videos``. diff --git a/docs/_build/html/_sources/source/how-to/add_own_library.rst b/docs/_build/html/_sources/source/how-to/add_own_library.rst deleted file mode 100644 index 144ad2c074..0000000000 --- a/docs/_build/html/_sources/source/how-to/add_own_library.rst +++ /dev/null @@ -1,91 +0,0 @@ -Adding your own learning library -================================ - -Isaac Lab comes pre-integrated with a number of libraries (such as RSL-RL, RL-Games, SKRL, Stable Baselines, etc.). -However, you may want to integrate your own library with Isaac Lab or use a different version of the libraries than -the one installed by Isaac Lab. This is possible as long as the library is available as Python package that supports -the Python version used by the underlying simulator. For instance, if you are using Isaac Sim 2023.1.1, you need -to ensure that the library is available for Python 3.10. - -Using a different version of a library --------------------------------------- - -If you want to use a different version of a library than the one installed by Isaac Lab, you can install the library -by building it from source or using a different version of the library available on PyPI. - -For instance, if you want to use your own modified version of the `rsl-rl`_ library, you can follow these steps: - -1. Follow the instructions for installing Isaac Lab. This will install the default version of the ``rsl-rl`` library. -2. Clone the ``rsl-rl`` library from the GitHub repository: - - .. code-block:: bash - - git clone git@github.com:leggedrobotics/rsl_rl.git - - -3. Install the library in your Python environment: - - .. code-block:: bash - - # Assuming you are in the root directory of the Isaac Lab repository - cd IsaacLab - - # Note: If you are using a virtual environment, make sure to activate it before running the following command - ./isaaclab.sh -p -m pip install -e /path/to/rsl_rl - -In this case, the ``rsl-rl`` library will be installed in the Python environment used by Isaac Lab. You can now use the -``rsl-rl`` library in your experiments. To check the library version and other details, you can use the following -command: - -.. code-block:: bash - - ./isaaclab.sh -p -m pip show rsl-rl - -This should now show the location of the ``rsl-rl`` library as the directory where you cloned the library. -For instance, if you cloned the library to ``/home/user/git/rsl_rl``, the output of the above command should be: - -.. code-block:: bash - - Name: rsl_rl - Version: 2.0.2 - Summary: Fast and simple RL algorithms implemented in pytorch - Home-page: https://github.com/leggedrobotics/rsl_rl - Author: ETH Zurich, NVIDIA CORPORATION - Author-email: - License: BSD-3 - Location: /home/user/git/rsl_rl - Requires: torch, torchvision, numpy, GitPython, onnx - Required-by: - - -Integrating a new library -------------------------- - -Adding a new library to Isaac Lab is similar to using a different version of a library. You can install the library -in your Python environment and use it in your experiments. However, if you want to integrate the library with -Isaac Lab, you can will first need to make a wrapper for the library, as explained in -:ref:`how-to-env-wrappers`. - -The following steps can be followed to integrate a new library with Isaac Lab: - -1. Add your library as an extra-dependency in the ``setup.py`` for the extension ``omni.isaac.lab_tasks``. - This will ensure that the library is installed when you install Isaac Lab or it will complain if the library is not - installed or available. -2. Install your library in the Python environment used by Isaac Lab. You can do this by following the steps mentioned - in the previous section. -3. Create a wrapper for the library. You can check the module :mod:`omni.isaac.lab_tasks.utils.wrappers` - for examples of wrappers for different libraries. You can create a new wrapper for your library and add it to the - module. You can also create a new module for the wrapper if you prefer. -4. Create workflow scripts for your library to train and evaluate agents. You can check the existing workflow scripts - in the ``source/standalone/workflows`` directory for examples. You can create new workflow - scripts for your library and add them to the directory. - -Optionally, you can also add some tests and documentation for the wrapper. This will help ensure that the wrapper -works as expected and can guide users on how to use the wrapper. - -* Add some tests to ensure that the wrapper works as expected and remains compatible with the library. - These tests can be added to the ``source/extensions/omni.isaac.lab_tasks/test/wrappers`` directory. -* Add some documentation for the wrapper. You can add the API documentation to the - ``docs/source/api/lab_tasks/omni.isaac.lab_tasks.utils.wrappers.rst`` file. - -.. _rsl-rl: https://github.com/leggedrobotics/rsl_rl diff --git a/docs/_build/html/_sources/source/how-to/draw_markers.rst b/docs/_build/html/_sources/source/how-to/draw_markers.rst deleted file mode 100644 index 80f872d253..0000000000 --- a/docs/_build/html/_sources/source/how-to/draw_markers.rst +++ /dev/null @@ -1,75 +0,0 @@ -Creating Visualization Markers -============================== - -.. currentmodule:: omni.isaac.lab - -Visualization markers are useful to debug the state of the environment. They can be used to visualize -the frames, commands, and other information in the simulation. - -While Isaac Sim provides its own :mod:`omni.isaac.debug_draw` extension, it is limited to rendering only -points, lines and splines. For cases, where you need to render more complex shapes, you can use the -:class:`markers.VisualizationMarkers` class. - -This guide is accompanied by a sample script ``markers.py`` in the ``IsaacLab/source/standalone/demos`` directory. - -.. dropdown:: Code for markers.py - :icon: code - - .. literalinclude:: ../../../source/standalone/demos/markers.py - :language: python - :emphasize-lines: 45-90, 106-107, 136-142 - :linenos: - - - -Configuring the markers ------------------------ - -The :class:`~markers.VisualizationMarkersCfg` class provides a simple interface to configure -different types of markers. It takes in the following parameters: - -- :attr:`~markers.VisualizationMarkersCfg.prim_path`: The corresponding prim path for the marker class. -- :attr:`~markers.VisualizationMarkersCfg.markers`: A dictionary specifying the different marker prototypes - handled by the class. The key is the name of the marker prototype and the value is its spawn configuration. - -.. note:: - - In case the marker prototype specifies a configuration with physics properties, these are removed. - This is because the markers are not meant to be simulated. - -Here we show all the different types of markers that can be configured. These range from simple shapes like -cones and spheres to more complex geometries like a frame or arrows. The marker prototypes can also be -configured from USD files. - -.. literalinclude:: ../../../source/standalone/demos/markers.py - :language: python - :lines: 45-90 - :dedent: - - -Drawing the markers -------------------- - -To draw the markers, we call the :class:`~markers.VisualizationMarkers.visualize` method. This method takes in -as arguments the pose of the markers and the corresponding marker prototypes to draw. - -.. literalinclude:: ../../../source/standalone/demos/markers.py - :language: python - :lines: 136-142 - :dedent: - - -Executing the Script --------------------- - -To run the accompanying script, execute the following command: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/demos/markers.py - -The simulation should start, and you can observe the different types of markers arranged in a grid pattern. -The markers will rotating around their respective axes. Additionally every few rotations, they will -roll forward on the grid. - -To stop the simulation, close the window, or use ``Ctrl+C`` in the terminal. diff --git a/docs/_build/html/_sources/source/how-to/import_new_asset.rst b/docs/_build/html/_sources/source/how-to/import_new_asset.rst deleted file mode 100644 index e6dbc2d2cf..0000000000 --- a/docs/_build/html/_sources/source/how-to/import_new_asset.rst +++ /dev/null @@ -1,176 +0,0 @@ -Importing a New Asset -===================== - -.. currentmodule:: omni.isaac.lab - -NVIDIA Omniverse relies on the Universal Scene Description (USD) file format to -import and export assets. USD is an open source file format developed by Pixar -Animation Studios. It is a scene description format optimized for large-scale, -complex data sets. While this format is widely used in the film and animation -industry, it is less common in the robotics community. - -To this end, NVIDIA has developed various importers that allow you to import -assets from other file formats into USD. These importers are available as -extensions to Omniverse Kit: - -* **URDF Importer** - Import assets from URDF files. -* **MJCF Importer** - Import assets from MJCF files. -* **Asset Importer** - Import assets from various file formats, including - OBJ, FBX, STL, and glTF. - -The recommended workflow from NVIDIA is to use the above importers to convert -the asset into its USD representation. Once the asset is in USD format, you can -use the Omniverse Kit to edit the asset and export it to other file formats. - -An important note to use assets for large-scale simulation is to ensure that they -are in `instanceable`_ format. This allows the asset to be efficiently loaded -into memory and used multiple times in a scene. Otherwise, the asset will be -loaded into memory multiple times, which can cause performance issues. -For more details on instanceable assets, please check the Isaac Sim `documentation`_. - - -Using URDF Importer -------------------- - -Isaac Sim includes the URDF and MJCF importers by default. These importers support the -option to import assets as instanceable assets. By selecting this option, the -importer will create two USD files: one for all the mesh data and one for -all the non-mesh data (e.g. joints, rigid bodies, etc.). The prims in the mesh data file are -referenced in the non-mesh data file. This allows the mesh data (which is often bulky) to be -loaded into memory only once and used multiple times in a scene. - -For using these importers from the GUI, please check the documentation for `MJCF importer`_ and -`URDF importer`_ respectively. - -For using the URDF importers from Python scripts, we include a utility tool called ``convert_urdf.py``. -Internally, this script creates an instance of :class:`~sim.converters.UrdfConverterCfg` which -is then passed to the :class:`~sim.converters.UrdfConverter` class. The configuration class specifies -the default values for the importer. The important settings are: - -* :attr:`~sim.converters.UrdfConverterCfg.fix_base` - Whether to fix the base of the robot. - This depends on whether you have a floating-base or fixed-base robot. -* :attr:`~sim.converters.UrdfConverterCfg.make_instanceable` - Whether to create instanceable assets. - Usually, this should be set to ``True``. -* :attr:`~sim.converters.UrdfConverterCfg.merge_fixed_joints` - Whether to merge the fixed joints. - Usually, this should be set to ``True`` to reduce the asset complexity. -* :attr:`~sim.converters.UrdfConverterCfg.default_drive_type` - The drive-type for the joints. - We recommend this to always be ``"none"``. This allows changing the drive configuration using the - actuator models. -* :attr:`~sim.converters.UrdfConverterCfg.default_drive_stiffness` - The drive stiffness for the joints. - We recommend this to always be ``0.0``. This allows changing the drive configuration using the - actuator models. -* :attr:`~sim.converters.UrdfConverterCfg.default_drive_damping` - The drive damping for the joints. - Similar to the stiffness, we recommend this to always be ``0.0``. - -Example Usage -~~~~~~~~~~~~~ - -In this example, we use the pre-processed URDF file of the ANYmal-D robot. To check the -pre-process URDF, please check the file the `anymal.urdf`_. The main difference between the -pre-processed URDF and the original URDF are: - -* We removed the ```` tag from the URDF. This tag is not supported by the URDF importer. -* We removed the ```` tag from the URDF. This tag is not supported by the URDF importer. -* We removed various collision bodies from the URDF to reduce the complexity of the asset. -* We changed all the joint's damping and friction parameters to ``0.0``. This ensures that we can perform - effort-control on the joints without PhysX adding additional damping. -* We added the ```` tag to fixed joints. This ensures that the importer does - not merge these fixed joints. - -The following shows the steps to clone the repository and run the converter: - -.. code-block:: bash - - # create a directory to clone - mkdir ~/git && cd ~/git - # clone a repository with URDF files - git clone git@github.com:isaac-orbit/anymal_d_simple_description.git - - # go to top of the repository - cd IsaacLab - # run the converter - ./isaaclab.sh -p source/standalone/tools/convert_urdf.py \ - ~/git/anymal_d_simple_description/urdf/anymal.urdf \ - source/extensions/omni.isaac.lab_assets/data/Robots/ANYbotics/anymal_d.usd \ - --merge-joints \ - --make-instanceable - - -Executing the above script will create two USD files inside the -``source/extensions/omni.isaac.lab_assets/data/Robots/ANYbotics/`` directory: - -* ``anymal_d.usd`` - This is the main asset file. It contains all the non-mesh data. -* ``Props/instanceable_assets.usd`` - This is the mesh data file. - -.. note:: - - Since Isaac Sim 2023.1.1, the URDF importer behavior has changed and it stores the mesh data inside the - main asset file even if the ``--make-instanceable`` flag is set. This means that the - ``Props/instanceable_assets.usd`` file is created but not used anymore. - -You can press play on the opened window to see the asset in the scene. The asset should "collapse" -if everything is working correctly. If it blows up, then it might be that you have self-collisions -present in the URDF. - -To run the script headless, you can add the ``--headless`` flag. This will not open the GUI and -exit the script after the conversion is complete. - - -Using Mesh Importer -------------------- - -Omniverse Kit includes the mesh converter tool that uses the ASSIMP library to import assets -from various mesh formats (e.g. OBJ, FBX, STL, glTF, etc.). The asset converter tool is available -as an extension to Omniverse Kit. Please check the `asset converter`_ documentation for more details. -However, unlike Isaac Sim's URDF and MJCF importers, the asset converter tool does not support -creating instanceable assets. This means that the asset will be loaded into memory multiple times -if it is used multiple times in a scene. - -Thus, we include a utility tool called ``convert_mesh.py`` that uses the asset converter tool to -import the asset and then converts it into an instanceable asset. Internally, this script creates -an instance of :class:`~sim.converters.MeshConverterCfg` which is then passed to the -:class:`~sim.converters.MeshConverter` class. Since the mesh file does not contain any physics -information, the configuration class accepts different physics properties (such as mass, collision -shape, etc.) as input. Please check the documentation for :class:`~sim.converters.MeshConverterCfg` -for more details. - -Example Usage -~~~~~~~~~~~~~ - -We use an OBJ file of a cube to demonstrate the usage of the mesh converter. The following shows -the steps to clone the repository and run the converter: - -.. code-block:: bash - - # create a directory to clone - mkdir ~/git && cd ~/git - # clone a repository with URDF files - git clone git@github.com:NVIDIA-Omniverse/IsaacGymEnvs.git - - # go to top of the repository - cd IsaacLab - # run the converter - ./isaaclab.sh -p source/standalone/tools/convert_mesh.py \ - ~/git/IsaacGymEnvs/assets/trifinger/objects/meshes/cube_multicolor.obj \ - source/extensions/omni.isaac.lab_assets/data/Props/CubeMultiColor/cube_multicolor.usd \ - --make-instanceable \ - --collision-approximation convexDecomposition \ - --mass 1.0 - -Similar to the URDF converter, executing the above script will create two USD files inside the -``source/extensions/omni.isaac.lab_assets/data/Props/CubeMultiColor/`` directory. Additionally, -if you press play on the opened window, you should see the asset fall down under the influence -of gravity. - -* If you do not set the ``--mass`` flag, then no rigid body properties will be added to the asset. - It will be imported as a static asset. -* If you also do not set the ``--collision-approximation`` flag, then the asset will not have any collider - properties as well and will be imported as a visual asset. - - -.. _instanceable: https://openusd.org/dev/api/_usd__page__scenegraph_instancing.html -.. _documentation: https://docs.omniverse.nvidia.com/isaacsim/latest/isaac_lab_tutorials/tutorial_instanceable_assets.html -.. _MJCF importer: https://docs.omniverse.nvidia.com/isaacsim/latest/advanced_tutorials/tutorial_advanced_import_mjcf.html -.. _URDF importer: https://docs.omniverse.nvidia.com/isaacsim/latest/advanced_tutorials/tutorial_advanced_import_urdf.html -.. _anymal.urdf: https://github.com/isaac-orbit/anymal_d_simple_description/blob/master/urdf/anymal.urdf -.. _asset converter: https://docs.omniverse.nvidia.com/extensions/latest/ext_asset-converter.html diff --git a/docs/_build/html/_sources/source/how-to/index.rst b/docs/_build/html/_sources/source/how-to/index.rst deleted file mode 100644 index 599a3a3b14..0000000000 --- a/docs/_build/html/_sources/source/how-to/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -How-to Guides -============= - -This section includes guides that help you use Isaac Lab. These are intended for users who -have already worked through the tutorials and are looking for more information on how to -use Isaac Lab. If you are new to Isaac Lab, we recommend you start with the tutorials. - -.. note:: - - This section is a work in progress. If you have a question that is not answered here, - please open an issue on our `GitHub page `_. - -.. toctree:: - :maxdepth: 1 - - import_new_asset - write_articulation_cfg - make_fixed_prim - save_camera_output - draw_markers - wrap_rl_env - add_own_library - master_omniverse - record_animation diff --git a/docs/_build/html/_sources/source/how-to/make_fixed_prim.rst b/docs/_build/html/_sources/source/how-to/make_fixed_prim.rst deleted file mode 100644 index 1847c4805b..0000000000 --- a/docs/_build/html/_sources/source/how-to/make_fixed_prim.rst +++ /dev/null @@ -1,179 +0,0 @@ -Making a physics prim fixed in the simulation -============================================= - -.. currentmodule:: omni.isaac.lab - -When a USD prim has physics schemas applied on it, it is affected by physics simulation. -This means that the prim can move, rotate, and collide with other prims in the simulation world. -However, there are cases where it is desirable to make certain prims static in the simulation world, -i.e. the prim should still participate in collisions but its position and orientation should not change. - -The following sections describe how to spawn a prim with physics schemas and make it static in the simulation world. - -Static colliders ----------------- - -Static colliders are prims that are not affected by physics but can collide with other prims in the simulation world. -These don't have any rigid body properties applied on them. However, this also means that they can't be accessed -using the physics tensor API (i.e., through the :class:`assets.RigidObject` class). - -For instance, to spawn a cone static in the simulation world, the following code can be used: - -.. code-block:: python - - import omni.isaac.lab.sim as sim_utils - - cone_spawn_cfg = sim_utils.ConeCfg( - radius=0.15, - height=0.5, - collision_props=sim_utils.CollisionPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)), - ) - cone_spawn_cfg.func( - "/World/Cone", cone_spawn_cfg, translation=(0.0, 0.0, 2.0), orientation=(0.5, 0.0, 0.5, 0.0) - ) - - -Rigid object ------------- - -Rigid objects (i.e. object only has a single body) can be made static by setting the parameter -:attr:`sim.schemas.RigidBodyPropertiesCfg.kinematic_enabled` as True. This will make the object -kinematic and it will not be affected by physics. - -For instance, to spawn a cone static in the simulation world but with rigid body schema on it, -the following code can be used: - -.. code-block:: python - - import omni.isaac.lab.sim as sim_utils - - cone_spawn_cfg = sim_utils.ConeCfg( - radius=0.15, - height=0.5, - rigid_props=sim_utils.RigidBodyPropertiesCfg(kinematic_enabled=True), - mass_props=sim_utils.MassPropertiesCfg(mass=1.0), - collision_props=sim_utils.CollisionPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)), - ) - cone_spawn_cfg.func( - "/World/Cone", cone_spawn_cfg, translation=(0.0, 0.0, 2.0), orientation=(0.5, 0.0, 0.5, 0.0) - ) - - -Articulation ------------- - -Fixing the root of an articulation requires having a fixed joint to the root rigid body link of the articulation. -This can be achieved by setting the parameter :attr:`sim.schemas.ArticulationRootPropertiesCfg.fix_root_link` -as True. Based on the value of this parameter, the following cases are possible: - -* If set to :obj:`None`, the root link is not modified. -* If the articulation already has a fixed root link, this flag will enable or disable the fixed joint. -* If the articulation does not have a fixed root link, this flag will create a fixed joint between the world - frame and the root link. The joint is created with the name "FixedJoint" under the root link. - -For instance, to spawn an ANYmal robot and make it static in the simulation world, the following code can be used: - -.. code-block:: python - - import omni.isaac.lab.sim as sim_utils - from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR - - anymal_spawn_cfg = sim_utils.UsdFileCfg( - usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd", - activate_contact_sensors=True, - rigid_props=sim_utils.RigidBodyPropertiesCfg( - disable_gravity=False, - retain_accelerations=False, - linear_damping=0.0, - angular_damping=0.0, - max_linear_velocity=1000.0, - max_angular_velocity=1000.0, - max_depenetration_velocity=1.0, - ), - articulation_props=sim_utils.ArticulationRootPropertiesCfg( - enabled_self_collisions=True, - solver_position_iteration_count=4, - solver_velocity_iteration_count=0, - fix_root_link=True, - ), - ) - anymal_spawn_cfg.func( - "/World/ANYmal", anymal_spawn_cfg, translation=(0.0, 0.0, 0.8), orientation=(1.0, 0.0, 0.0, 0.0) - ) - - -This will create a fixed joint between the world frame and the root link of the ANYmal robot -at the prim path ``"/World/ANYmal/base/FixedJoint"`` since the root link is at the path ``"/World/ANYmal/base"``. - - -Further notes -------------- - -Given the flexibility of USD asset designing the following possible scenarios are usually encountered: - -1. **Articulation root schema on the rigid body prim without a fixed joint**: - - This is the most common and recommended scenario for floating-base articulations. The root prim - has both the rigid body and the articulation root properties. In this case, the articulation root - is parsed as a floating-base with the root prim of the articulation ``Link0Xform``. - - .. code-block:: text - - ArticulationXform - └── Link0Xform (RigidBody and ArticulationRoot schema) - -2. **Articulation root schema on the parent prim with a fixed joint**: - - This is the expected arrangement for fixed-base articulations. The root prim has only the rigid body - properties and the articulation root properties are applied to its parent prim. In this case, the - articulation root is parsed as a fixed-base with the root prim of the articulation ``Link0Xform``. - - .. code-block:: text - - ArticulationXform (ArticulationRoot schema) - └── Link0Xform (RigidBody schema) - └── FixedJoint (connecting the world frame and Link0Xform) - -3. **Articulation root schema on the parent prim without a fixed joint**: - - This is a scenario where the root prim has only the rigid body properties and the articulation root properties - are applied to its parent prim. However, the fixed joint is not created between the world frame and the root link. - In this case, the articulation is parsed as a floating-base system. However, the PhysX parser uses its own - heuristic (such as alphabetical order) to determine the root prim of the articulation. It may select the root prim - at ``Link0Xform`` or choose another prim as the root prim. - - .. code-block:: text - - ArticulationXform (ArticulationRoot schema) - └── Link0Xform (RigidBody schema) - -4. **Articulation root schema on the rigid body prim with a fixed joint**: - - While this is a valid scenario, it is not recommended as it may lead to unexpected behavior. In this case, - the articulation is still parsed as a floating-base system. However, the fixed joint, created between the - world frame and the root link, is considered as a part of the maximal coordinate tree. This is different from - PhysX considering the articulation as a fixed-base system. Hence, the simulation may not behave as expected. - - .. code-block:: text - - ArticulationXform - └── Link0Xform (RigidBody and ArticulationRoot schema) - └── FixedJoint (connecting the world frame and Link0Xform) - -For floating base articulations, the root prim usually has both the rigid body and the articulation -root properties. However, directly connecting this prim to the world frame will cause the simulation -to consider the fixed joint as a part of the maximal coordinate tree. This is different from PhysX -considering the articulation as a fixed-base system. - -Internally, when the parameter :attr:`sim.schemas.ArticulationRootPropertiesCfg.fix_root_link` is set to True -and the articulation is detected as a floating-base system, the fixed joint is created between the world frame -the root rigid body link of the articulation. However, to make the PhysX parser consider the articulation as a -fixed-base system, the articulation root properties are removed from the root rigid body prim and applied to -its parent prim instead. - -.. note:: - - In future release of Isaac Sim, an explicit flag will be added to the articulation root schema from PhysX - to toggle between fixed-base and floating-base systems. This will resolve the need of the above workaround. diff --git a/docs/_build/html/_sources/source/how-to/master_omniverse.rst b/docs/_build/html/_sources/source/how-to/master_omniverse.rst deleted file mode 100644 index 80385f1b89..0000000000 --- a/docs/_build/html/_sources/source/how-to/master_omniverse.rst +++ /dev/null @@ -1,124 +0,0 @@ -Mastering Omniverse for Robotics -================================ - -NVIDIA Omniverse offers a large suite of tools for 3D content workflows. -There are three main components (relevant to robotics) in Omniverse: - -- **USD Composer**: This is based on a novel file format (Universal Scene - Description) from the animation (originally Pixar) community that is - used in Omniverse -- **PhysX SDK**: This is the main physics engine behind Omniverse that - leverages GPU-based parallelization for massive scenes -- **RTX-enabled Renderer**: This uses ray-tracing kernels in NVIDIA RTX - GPUs for real-time physically-based rendering - -Of these, the first two require a deeper understanding to start working -with Omniverse and its constituent applications (Isaac Sim and Isaac Lab). - -The main things to learn: - -- How to use the Composer GUI efficiently? -- What are USD prims and schemas? -- How do you compose a USD scene? -- What is the difference between references and payloads in USD? -- What is meant by scene-graph instancing? -- How to apply PhysX schemas on prims? What all schemas are possible? -- How to write basic operations in USD for creating prims and modifying - their attributes? - - -Part 1: Using USD Composer --------------------------- - -While several `video -tutorials `__ and -`documentation `__ exist -out there on NVIDIA Omniverse, going through all of them would take an -extensive amount of time and effort. Thus, we have curated these -resources to guide you through using Omniverse, specifically for -robotics. - -Introduction to Omniverse and USD - -- `What is NVIDIA Omniverse? `__ -- `What is the USD File Type? \| Getting Started in NVIDIA Omniverse `__ -- `What Makes USD Unique in NVIDIA Omniverse `__ - -Using Omniverse USD Composer - -- `Introduction to Omniverse USD Composer `__ -- `Navigation Basics in Omniverse USD Composer `__ -- `Lighting Basics in NVIDIA Omniverse USD Composer `__ -- `Rendering Overview in NVIDIA Omniverse USD Composer `__ - -Materials and MDL - -- `Five Things to Know About Materials in NVIDIA Omniverse `__ -- `How to apply materials? `__ - -Omniverse Physics and PhysX SDK - -- `Basics - Setting Up Physics and Toolbar Overview `__ -- `Basics - Demos Overview `__ -- `Rigid Bodies - Mass Editing `__ -- `Materials - Friction Restitution and Defaults `__ -- `Overview of Simulation Ready Assets Physics in Omniverse `__ - -Importing assets - -- `Omniverse Create - Importing FBX Files \| NVIDIA Omniverse Tutorials `__ -- `Omniverse Asset Importer `__ -- `Isaac Sim URDF impoter `__ - - -Part 2: Scripting in Omniverse ------------------------------- - -The above links mainly introduced how to use the USD Composer and its -functionalities through UI operations. However, often developers -need to write scripts to perform operations. This is especially true -when you want to automate certain tasks or create custom applications -that use Omniverse as a backend. This section will introduce you to -scripting in Omniverse. - -USD is the main file format Omniverse operates with. So naturally, the -APIs (from OpenUSD) for modifying USD are at the core of Omniverse. -Most of the APIs are in C++ and Python bindings are provided for them. -Thus, to script in Omniverse, you need to understand the USD APIs. - -.. note:: - - While Isaac Sim and Isaac Lab try to "relieve" users from understanding - the core USD concepts and APIs, understanding these basics still - help a lot once you start diving inside the codebase and modifying - it for your own application. - -Before diving into USD scripting, it is good to get acquainted with the -terminologies used in USD. We recommend the following `introduction to -USD basics `__ by -Houdini, which is a 3D animation software. -Make sure to go through the following sections: - -- `Quick example `__ -- `Attributes and primvars `__ -- `Composition `__ -- `Schemas `__ -- `Instances `__ - and `Scene-graph Instancing `__ - -As a test of understanding, make sure you can answer the following: - -- What are prims? What is meant by a prim path in a stage? -- How are attributes related to prims? -- How are schemas related to prims? -- What is the difference between attributes and schemas? -- What is asset instancing? - -Part 3: More Resources ----------------------- - -- `Omniverse Glossary of Terms `__ -- `Omniverse Code Samples `__ -- `PhysX Collider Compatibility `__ -- `PhysX Limitations `__ -- `PhysX Documentation `__. diff --git a/docs/_build/html/_sources/source/how-to/record_animation.rst b/docs/_build/html/_sources/source/how-to/record_animation.rst deleted file mode 100644 index 9801bf2b4a..0000000000 --- a/docs/_build/html/_sources/source/how-to/record_animation.rst +++ /dev/null @@ -1,78 +0,0 @@ -Recording Animations of Simulations -=================================== - -.. currentmodule:: omni.isaac.lab - -Omniverse includes tools to record animations of physics simulations. The `Stage Recorder`_ extension -listens to all the motion and USD property changes within a USD stage and records them to a USD file. -This file contains the time samples of the changes, which can be played back to render the animation. - -The timeSampled USD file only contains the changes to the stage. It uses the same hierarchy as the original -stage at the time of recording. This allows adding the animation to the original stage, or to a different -stage with the same hierarchy. The timeSampled file can be directly added as a sublayer to the original stage -to play back the animation. - -.. note:: - - Omniverse only supports playing animation or playing physics on a USD prim at the same time. If you want to - play back the animation of a USD prim, you need to disable the physics simulation on the prim. - - -In Isaac Lab, we directly use the `Stage Recorder`_ extension to record the animation of the physics simulation. -This is available as a feature in the :class:`~omni.isaac.lab.envs.ui.BaseEnvWindow` class. -However, to record the animation of a simulation, you need to disable `Fabric`_ to allow reading and writing -all the changes (such as motion and USD properties) to the USD stage. - - -Stage Recorder Settings -~~~~~~~~~~~~~~~~~~~~~~~ - -Isaac Lab integration of the `Stage Recorder`_ extension assumes certain default settings. If you want to change the -settings, you can directly use the `Stage Recorder`_ extension in the Omniverse Create application. - -.. dropdown:: Settings used in base_env_window.py - :icon: code - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/base_env_window.py - :language: python - :linenos: - :pyobject: BaseEnvWindow._toggle_recording_animation_fn - - -Example Usage -~~~~~~~~~~~~~ - -In all environment standalone scripts, Fabric can be disabled by passing the ``--disable_fabric`` flag to the script. -Here we run the state-machine example and record the animation of the simulation. - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/environments/state_machine/lift_cube_sm.py --num_envs 8 --cpu --disable_fabric - - -On running the script, the Isaac Lab UI window opens with the button "Record Animation" in the toolbar. -Clicking this button starts recording the animation of the simulation. On clicking the button again, the -recording stops. The recorded animation and the original stage (with all physics disabled) are saved -to the ``recordings`` folder in the current working directory. The files are stored in the ``usd`` format: - -- ``Stage.usd``: The original stage with all physics disabled -- ``TimeSample_tk001.usd``: The timeSampled file containing the recorded animation - -You can open Omniverse Isaac Sim application to play back the animation. There are many ways to launch -the application (such as from terminal or `Omniverse Launcher`_). Here we use the terminal to open the -application and play the animation. - -.. code-block:: bash - - ./isaaclab.sh -s # Opens Isaac Sim application through _isaac_sim/isaac-sim.sh - -On a new stage, add the ``Stage.usd`` as a sublayer and then add the ``TimeSample_tk001.usd`` as a sublayer. -You can do this by dragging and dropping the files from the file explorer to the stage. Please check out -the `tutorial on layering in Omniverse`_ for more details. - -You can then play the animation by pressing the play button. - -.. _Stage Recorder: https://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html -.. _Fabric: https://docs.omniverse.nvidia.com/kit/docs/usdrt/latest/docs/usd_fabric_usdrt.html -.. _Omniverse Launcher: https://docs.omniverse.nvidia.com/launcher/latest/index.html -.. _tutorial on layering in Omniverse: https://www.youtube.com/watch?v=LTwmNkSDh-c&ab_channel=NVIDIAOmniverse diff --git a/docs/_build/html/_sources/source/how-to/save_camera_output.rst b/docs/_build/html/_sources/source/how-to/save_camera_output.rst deleted file mode 100644 index 94a834492a..0000000000 --- a/docs/_build/html/_sources/source/how-to/save_camera_output.rst +++ /dev/null @@ -1,102 +0,0 @@ -.. _how-to-save-images-and-3d-reprojection: - - -Saving rendered images and 3D re-projection -=========================================== - -.. currentmodule:: omni.isaac.lab - -This guide accompanied with the ``run_usd_camera.py`` script in the ``IsaacLab/source/standalone/tutorials/04_sensors`` -directory. - -.. dropdown:: Code for run_usd_camera.py - :icon: code - - .. literalinclude:: ../../../source/standalone/tutorials/04_sensors/run_usd_camera.py - :language: python - :emphasize-lines: 171-179, 229-247, 251-264 - :linenos: - - -Saving using Replicator Basic Writer ------------------------------------- - -To save camera outputs, we use the basic write class from Omniverse Replicator. This class allows us to save the -images in a numpy format. For more information on the basic writer, please check the -`documentation `_. - -.. literalinclude:: ../../../source/standalone/tutorials/04_sensors/run_usd_camera.py - :language: python - :start-at: rep_writer = rep.BasicWriter( - :end-before: # Camera positions, targets, orientations - -While stepping the simulator, the images can be saved to the defined folder. Since the BasicWriter only supports -saving data using NumPy format, we first need to convert the PyTorch sensors to NumPy arrays before packing -them in a dictionary. - -.. literalinclude:: ../../../source/standalone/tutorials/04_sensors/run_usd_camera.py - :language: python - :start-at: # Save images from camera at camera_index - :end-at: single_cam_info = camera.data.info[camera_index] - -After this step, we can save the images using the BasicWriter. - -.. literalinclude:: ../../../source/standalone/tutorials/04_sensors/run_usd_camera.py - :language: python - :start-at: # Pack data back into replicator format to save them using its writer - :end-at: rep_writer.write(rep_output) - - -Projection into 3D Space ------------------------- - -We include utilities to project the depth image into 3D Space. The re-projection operations are done using -PyTorch operations which allows faster computation. - -.. code-block:: python - - from omni.isaac.lab.utils.math import transform_points, unproject_depth - - # Pointcloud in world frame - points_3d_cam = unproject_depth( - camera.data.output["distance_to_image_plane"], camera.data.intrinsic_matrices - ) - - points_3d_world = transform_points(points_3d_cam, camera.data.pos_w, camera.data.quat_w_ros) - -Alternately, we can use the :meth:`omni.isaac.lab.sensors.camera.utils.create_pointcloud_from_depth` function -to create a point cloud from the depth image and transform it to the world frame. - -.. literalinclude:: ../../../source/standalone/tutorials/04_sensors/run_usd_camera.py - :language: python - :start-at: # Derive pointcloud from camera at camera_index - :end-before: # In the first few steps, things are still being instanced and Camera.data - -The resulting point cloud can be visualized using the :mod:`omni.isaac.debug_draw` extension from Isaac Sim. -This makes it easy to visualize the point cloud in the 3D space. - -.. literalinclude:: ../../../source/standalone/tutorials/04_sensors/run_usd_camera.py - :language: python - :start-at: # In the first few steps, things are still being instanced and Camera.data - :end-at: pc_markers.visualize(translations=pointcloud) - - -Executing the script --------------------- - -To run the accompanying script, execute the following command: - -.. code-block:: bash - - # Usage with saving and drawing - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --save --draw - - # Usage with saving only in headless mode - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py --save --headless --enable_cameras - - -The simulation should start, and you can observe different objects falling down. An output folder will be created -in the ``IsaacLab/source/standalone/tutorials/04_sensors`` directory, where the images will be saved. Additionally, -you should see the point cloud in the 3D space drawn on the viewport. - -To stop the simulation, close the window, press the ``STOP`` button in the UI, or use ``Ctrl+C`` in the terminal. diff --git a/docs/_build/html/_sources/source/how-to/wrap_rl_env.rst b/docs/_build/html/_sources/source/how-to/wrap_rl_env.rst deleted file mode 100644 index 764dbe5a9f..0000000000 --- a/docs/_build/html/_sources/source/how-to/wrap_rl_env.rst +++ /dev/null @@ -1,166 +0,0 @@ -.. _how-to-env-wrappers: - - -Wrapping environments -===================== - -.. currentmodule:: omni.isaac.lab - -Environment wrappers are a way to modify the behavior of an environment without modifying the environment itself. -This can be used to apply functions to modify observations or rewards, record videos, enforce time limits, etc. -A detailed description of the API is available in the :class:`gymnasium.Wrapper` class. - -At present, all RL environments inheriting from the :class:`~envs.ManagerBasedRLEnv` class -are compatible with :class:`gymnasium.Wrapper`, since the base class implements the :class:`gymnasium.Env` interface. -In order to wrap an environment, you need to first initialize the base environment. After that, you can -wrap it with as many wrappers as you want by calling ``env = wrapper(env, *args, **kwargs)`` repeatedly. - -For example, here is how you would wrap an environment to enforce that reset is called before step or render: - -.. code-block:: python - - """Launch Isaac Sim Simulator first.""" - - - from omni.isaac.lab.app import AppLauncher - - # launch omniverse app in headless mode - app_launcher = AppLauncher(headless=True) - simulation_app = app_launcher.app - - """Rest everything follows.""" - - import gymnasium as gym - - import omni.isaac.lab_tasks # noqa: F401 - from omni.isaac.lab_tasks.utils import load_cfg_from_registry - - # create base environment - cfg = load_cfg_from_registry("Isaac-Reach-Franka-v0", "env_cfg_entry_point") - env = gym.make("Isaac-Reach-Franka-v0", cfg=cfg) - # wrap environment to enforce that reset is called before step - env = gym.wrappers.OrderEnforcing(env) - - -Wrapper for recording videos ----------------------------- - -The :class:`gymnasium.wrappers.RecordVideo` wrapper can be used to record videos of the environment. -The wrapper takes a ``video_dir`` argument, which specifies where to save the videos. The videos are saved in -`mp4 `__ format at specified intervals for specified -number of environment steps or episodes. - -To use the wrapper, you need to first install ``ffmpeg``. On Ubuntu, you can install it by running: - -.. code-block:: bash - - sudo apt-get install ffmpeg - -.. attention:: - - By default, when running an environment in headless mode, the Omniverse viewport is disabled. This is done to - improve performance by avoiding unnecessary rendering. - - We notice the following performance in different rendering modes with the ``Isaac-Reach-Franka-v0`` environment - using an RTX 3090 GPU: - - * No GUI execution without off-screen rendering enabled: ~65,000 FPS - * No GUI execution with off-screen enabled: ~57,000 FPS - * GUI execution with full rendering: ~13,000 FPS - - -The viewport camera used for rendering is the default camera in the scene called ``"/OmniverseKit_Persp"``. -The camera's pose and image resolution can be configured through the -:class:`~envs.ViewerCfg` class. - - -.. dropdown:: Default parameters of the ViewerCfg class: - :icon: code - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab/omni/isaac/lab/envs/common.py - :language: python - :pyobject: ViewerCfg - - -After adjusting the parameters, you can record videos by wrapping the environment with the -:class:`gymnasium.wrappers.RecordVideo` wrapper and enabling the off-screen rendering -flag. Additionally, you need to specify the render mode of the environment as ``"rgb_array"``. - -As an example, the following code records a video of the ``Isaac-Reach-Franka-v0`` environment -for 200 steps, and saves it in the ``videos`` folder at a step interval of 1500 steps. - -.. code:: python - - """Launch Isaac Sim Simulator first.""" - - - from omni.isaac.lab.app import AppLauncher - - # launch omniverse app in headless mode with off-screen rendering - app_launcher = AppLauncher(headless=True, enable_cameras=True) - simulation_app = app_launcher.app - - """Rest everything follows.""" - - import gymnasium as gym - - # adjust camera resolution and pose - env_cfg.viewer.resolution = (640, 480) - env_cfg.viewer.eye = (1.0, 1.0, 1.0) - env_cfg.viewer.lookat = (0.0, 0.0, 0.0) - # create isaac-env instance - # set render mode to rgb_array to obtain images on render calls - env = gym.make(task_name, cfg=env_cfg, render_mode="rgb_array") - # wrap for video recording - video_kwargs = { - "video_folder": "videos", - "step_trigger": lambda step: step % 1500 == 0, - "video_length": 200, - } - env = gym.wrappers.RecordVideo(env, **video_kwargs) - - -Wrapper for learning frameworks -------------------------------- - -Every learning framework has its own API for interacting with environments. For example, the -`Stable-Baselines3`_ library uses the `gym.Env `_ -interface to interact with environments. However, libraries like `RL-Games`_, `RSL-RL`_ or `SKRL`_ -use their own API for interfacing with a learning environments. Since there is no one-size-fits-all -solution, we do not base the :class:`~envs.ManagerBasedRLEnv` class on any particular learning framework's -environment definition. Instead, we implement wrappers to make it compatible with the learning -framework's environment definition. - -As an example of how to use the RL task environment with Stable-Baselines3: - -.. code:: python - - from omni.isaac.lab_tasks.utils.wrappers.sb3 import Sb3VecEnvWrapper - - # create isaac-env instance - env = gym.make(task_name, cfg=env_cfg) - # wrap around environment for stable baselines - env = Sb3VecEnvWrapper(env) - - -.. caution:: - - Wrapping the environment with the respective learning framework's wrapper should happen in the end, - i.e. after all other wrappers have been applied. This is because the learning framework's wrapper - modifies the interpretation of environment's APIs which may no longer be compatible with :class:`gymnasium.Env`. - - -Adding new wrappers -------------------- - -All new wrappers should be added to the :mod:`omni.isaac.lab_tasks.utils.wrappers` module. -They should check that the underlying environment is an instance of :class:`omni.isaac.lab.envs.ManagerBasedRLEnv` -before applying the wrapper. This can be done by using the :func:`unwrapped` property. - -We include a set of wrappers in this module that can be used as a reference to implement your own wrappers. -If you implement a new wrapper, please consider contributing it to the framework by opening a pull request. - -.. _Stable-Baselines3: https://stable-baselines3.readthedocs.io/en/master/ -.. _SKRL: https://skrl.readthedocs.io -.. _RL-Games: https://github.com/Denys88/rl_games -.. _RSL-RL: https://github.com/leggedrobotics/rsl_rl diff --git a/docs/_build/html/_sources/source/how-to/write_articulation_cfg.rst b/docs/_build/html/_sources/source/how-to/write_articulation_cfg.rst deleted file mode 100644 index 4c06286479..0000000000 --- a/docs/_build/html/_sources/source/how-to/write_articulation_cfg.rst +++ /dev/null @@ -1,116 +0,0 @@ -.. _how-to-write-articulation-config: - - -Writing an Asset Configuration -============================== - -.. currentmodule:: omni.isaac.lab - -This guide walks through the process of creating an :class:`~assets.ArticulationCfg`. -The :class:`~assets.ArticulationCfg` is a configuration object that defines the -properties of an :class:`~assets.Articulation` in Isaac Lab. - -.. note:: - - While we only cover the creation of an :class:`~assets.ArticulationCfg` in this guide, - the process is similar for creating any other asset configuration object. - -We will use the Cartpole example to demonstrate how to create an :class:`~assets.ArticulationCfg`. -The Cartpole is a simple robot that consists of a cart with a pole attached to it. The cart -is free to move along a rail, and the pole is free to rotate about the cart. - -.. dropdown:: Code for Cartpole configuration - :icon: code - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py - :language: python - :linenos: - - -Defining the spawn configuration --------------------------------- - -As explained in :ref:`tutorial-spawn-prims` tutorials, the spawn configuration defines -the properties of the assets to be spawned. This spawning may happen procedurally, or -through an existing asset file (e.g. USD or URDF). In this example, we will spawn the -Cartpole from a USD file. - -When spawning an asset from a USD file, we define its :class:`~sim.spawners.from_files.UsdFileCfg`. -This configuration object takes in the following parameters: - -* :class:`~sim.spawners.from_files.UsdFileCfg.usd_path`: The USD file path to spawn from -* :class:`~sim.spawners.from_files.UsdFileCfg.rigid_props`: The properties of the articulation's root -* :class:`~sim.spawners.from_files.UsdFileCfg.articulation_props`: The properties of all the articulation's links - -The last two parameters are optional. If not specified, they are kept at their default values in the USD file. - -.. literalinclude:: ../../../source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py - :language: python - :lines: 19-35 - :dedent: - -To import articulation from a URDF file instead of a USD file, you can replace the -:class:`~sim.spawners.from_files.UsdFileCfg` with a :class:`~sim.spawners.from_files.UrdfFileCfg`. -For more details, please check the API documentation. - - -Defining the initial state --------------------------- - -Every asset requires defining their initial or *default* state in the simulation through its configuration. -This configuration is stored into the asset's default state buffers that can be accessed when the asset's -state needs to be reset. - -.. note:: - The initial state of an asset is defined w.r.t. its local environment frame. This then needs to - be transformed into the global simulation frame when resetting the asset's state. For more - details, please check the :ref:`tutorial-interact-articulation` tutorial. - - -For an articulation, the :class:`~assets.ArticulationCfg.InitialStateCfg` object defines the -initial state of the root of the articulation and the initial state of all its joints. In this -example, we will spawn the Cartpole at the origin of the XY plane at a Z height of 2.0 meters. -Meanwhile, the joint positions and velocities are set to 0.0. - -.. literalinclude:: ../../../source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py - :language: python - :lines: 36-38 - :dedent: - -Defining the actuator configuration ------------------------------------ - -Actuators are a crucial component of an articulation. Through this configuration, it is possible -to define the type of actuator model to use. We can use the internal actuator model provided by -the physics engine (i.e. the implicit actuator model), or use a custom actuator model which is -governed by a user-defined system of equations (i.e. the explicit actuator model). -For more details on actuators, see :ref:`feature-actuators`. - -The cartpole's articulation has two actuators, one corresponding to its each joint: -``cart_to_pole`` and ``slider_to_cart``. We use two different actuator models for these actuators as -an example. However, since they are both using the same actuator model, it is possible -to combine them into a single actuator model. - -.. dropdown:: Actuator model configuration with separate actuator models - :icon: code - - .. literalinclude:: ../../../source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets/cartpole.py - :language: python - :lines: 39-49 - :dedent: - - -.. dropdown:: Actuator model configuration with a single actuator model - :icon: code - - .. code-block:: python - - actuators={ - "all_joints": ImplicitActuatorCfg( - joint_names_expr=[".*"], - effort_limit=400.0, - velocity_limit=100.0, - stiffness={"slider_to_cart": 0.0, "cart_to_pole": 0.0}, - damping={"slider_to_cart": 10.0, "cart_to_pole": 0.0}, - ), - }, diff --git a/docs/_build/html/_sources/source/migration/migrating_from_isaacgymenvs.rst b/docs/_build/html/_sources/source/migration/migrating_from_isaacgymenvs.rst deleted file mode 100644 index eb8a1505b3..0000000000 --- a/docs/_build/html/_sources/source/migration/migrating_from_isaacgymenvs.rst +++ /dev/null @@ -1,922 +0,0 @@ -.. _migrating-from-isaacgymenvs: - -From IsaacGymEnvs -================= - -.. currentmodule:: omni.isaac.lab - - -`IsaacGymEnvs`_ was a reinforcement learning framework designed for the `Isaac Gym Preview Release`_. -As both IsaacGymEnvs and the Isaac Gym Preview Release are now deprecated, the following guide walks through -the key differences between IsaacGymEnvs and Isaac Lab, as well as differences in APIs between Isaac Gym Preview -Release and Isaac Sim. - - -Task Config Setup -~~~~~~~~~~~~~~~~~ - -In IsaacGymEnvs, task config files were defined in ``.yaml`` format. With Isaac Lab, configs are now specified using -a specialized Python class :class:`~omni.isaac.lab.utils.configclass`. The :class:`~omni.isaac.lab.utils.configclass` -module provides a wrapper on top of Python's ``dataclasses`` module. Each environment should specify its own config -class annotated by ``@configclass`` that inherits from :class:`~envs.DirectRLEnvCfg`, which can include simulation -parameters, environment scene parameters, robot parameters, and task-specific parameters. - -Below is an example skeleton of a task config class: - -.. code-block:: python - - from omni.isaac.lab.envs import DirectRLEnvCfg - from omni.isaac.lab.scene import InteractiveSceneCfg - from omni.isaac.lab.sim import SimulationCfg - - @configclass - class MyEnvCfg(DirectRLEnvCfg): - # simulation - sim: SimulationCfg = SimulationCfg() - # robot - robot_cfg: ArticulationCfg = ArticulationCfg() - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg() - # env - decimation = 2 - episode_length_s = 5.0 - num_actions = 1 - num_observations = 4 - num_states = 0 - # task-specific parameters - ... - -Simulation Config ------------------ - -Simulation related parameters are defined as part of the :class:`~omni.isaac.lab.sim.SimulationCfg` class, -which is a :class:`~omni.isaac.lab.utils.configclass` module that holds simulation parameters such as ``dt``, -``device``, and ``gravity``. Each task config must have a variable named ``sim`` defined that holds the type -:class:`~omni.isaac.lab.sim.SimulationCfg`. - -In Isaac Lab, the use of ``substeps`` has been replaced -by a combination of the simulation ``dt`` and the ``decimation`` parameters. For example, in IsaacGymEnvs, having -``dt=1/60`` and ``substeps=2`` is equivalent to taking 2 simulation steps with ``dt=1/120``, but running the task step -at ``1/60`` seconds. The ``decimation`` parameter is a task parameter that controls the number of simulation steps to -take for each task (or RL) step, replacing the ``controlFrequencyInv`` parameter in IsaacGymEnvs. -Thus, the same setup in Isaac Lab will become ``dt=1/120`` and ``decimation=2``. - -In Isaac Sim, physx simulation parameters such as ``num_position_iterations``, ``num_velocity_iterations``, -``contact_offset``, ``rest_offset``, ``bounce_threshold_velocity``, ``max_depenetration_velocity`` can all -be specified on a per-actor basis. These parameters have been moved from the physx simulation config -to each individual articulation and rigid body config. - -When running simulation on the GPU, buffers in PhysX require pre-allocation for computing and storing -information such as contacts, collisions and aggregate pairs. These buffers may need to be adjusted -depending on the complexity of the environment, the number of expected contacts and collisions, -and the number of actors in the environment. The :class:`~omni.isaac.lab.sim.PhysxCfg` class provides access for -setting the GPU buffer dimensions. - -+--------------------------------------------------------------+-------------------------------------------------------------------+ -| | | -|.. code-block:: yaml |.. code-block:: python | -| | | -| # IsaacGymEnvs | # IsaacLab | -| sim: | sim: SimulationCfg = SimulationCfg( | -| dt: 0.0166 # 1/60 s | dt=1 / 120, | -| substeps: 2 | # decimation will be set in the task config | -| up_axis: "z" | # up axis will always be Z in isaac sim | -| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | use_gpu_pipeline=True, | -| gravity: [0.0, 0.0, -9.81] | gravity=(0.0, 0.0, -9.81), | -| physx: | physx: PhysxCfg = PhysxCfg( | -| num_threads: ${....num_threads} | # num_threads is no longer needed | -| solver_type: ${....solver_type} | solver_type=1, | -| use_gpu: ${contains:"cuda",${....sim_device}} | use_gpu=True, | -| num_position_iterations: 4 | max_position_iteration_count=4, | -| num_velocity_iterations: 0 | max_velocity_iteration_count=0, | -| contact_offset: 0.02 | # moved to actor config | -| rest_offset: 0.001 | # moved to actor config | -| bounce_threshold_velocity: 0.2 | bounce_threshold_velocity=0.2, | -| max_depenetration_velocity: 100.0 | # moved to actor config | -| default_buffer_size_multiplier: 2.0 | # default_buffer_size_multiplier is no longer needed | -| max_gpu_contact_pairs: 1048576 # 1024*1024 | gpu_max_rigid_contact_count=2**23 | -| num_subscenes: ${....num_subscenes} | # num_subscenes is no longer needed | -| contact_collection: 0 | # contact_collection is no longer needed | -| | )) | -+--------------------------------------------------------------+-------------------------------------------------------------------+ - -Scene Config ------------- - -The :class:`~omni.isaac.lab.scene.InteractiveSceneCfg` class can be used to specify parameters related to the scene, -such as the number of environments and the spacing between environments. Each task config must have a variable named -``scene`` defined that holds the type :class:`~omni.isaac.lab.scene.InteractiveSceneCfg`. - -+--------------------------------------------------------------+-------------------------------------------------------------------+ -| | | -|.. code-block:: yaml |.. code-block:: python | -| | | -| # IsaacGymEnvs | # IsaacLab | -| env: | scene: InteractiveSceneCfg = InteractiveSceneCfg( | -| numEnvs: ${resolve_default:512,${...num_envs}} | num_envs=512, | -| envSpacing: 4.0 | env_spacing=4.0) | -+--------------------------------------------------------------+-------------------------------------------------------------------+ - -Task Config ------------ - -Each environment should specify its own config class that holds task specific parameters, such as the dimensions of the -observation and action buffers. Reward term scaling parameters can also be specified in the config class. - -The following parameters must be set for each environment config: - -.. code-block:: python - - decimation = 2 - episode_length_s = 5.0 - num_actions = 1 - num_observations = 4 - num_states = 0 - -Note that the maximum episode length parameter (now ``episode_length_s``) is in seconds instead of steps as it was -in IsaacGymEnvs. To convert between step count to seconds, use the equation: -``episode_length_s = dt * decimation * num_steps`` - - -RL Config Setup -~~~~~~~~~~~~~~~ - -RL config files for the rl_games library can continue to be defined in ``.yaml`` files in Isaac Lab. -Most of the content of the config file can be copied directly from IsaacGymEnvs. -Note that in Isaac Lab, we do not use hydra to resolve relative paths in config files. -Please replace any relative paths such as ``${....device}`` with the actual values of the parameters. - -Additionally, the observation and action clip ranges have been moved to the RL config file. -For any ``clipObservations`` and ``clipActions`` parameters that were defined in the IsaacGymEnvs task config file, -they should be moved to the RL config file in Isaac Lab. - -+--------------------------+----------------------------+ -| | | -| IsaacGymEnvs Task Config | Isaac Lab RL Config | -+--------------------------+----------------------------+ -|.. code-block:: yaml |.. code-block:: yaml | -| | | -| # IsaacGymEnvs | # IsaacLab | -| env: | params: | -| clipObservations: 5.0 | env: | -| clipActions: 1.0 | clip_observations: 5.0 | -| | clip_actions: 1.0 | -+--------------------------+----------------------------+ - -Environment Creation -~~~~~~~~~~~~~~~~~~~~ - -In IsaacGymEnvs, environment creation generally included four components: creating the sim object with ``create_sim()``, -creating the ground plane, importing the assets from MJCF or URDF files, and finally creating the environments -by looping through each environment and adding actors into the environments. - -Isaac Lab no longer requires calling the ``create_sim()`` method to retrieve the sim object. Instead, the simulation -context is retrieved automatically by the framework. It is also no longer required to use the ``sim`` as an -argument for the simulation APIs. - -In replacement of ``create_sim()``, tasks can implement the ``_setup_scene()`` method in Isaac Lab. -This method can be used for adding actors into the scene, adding ground plane, cloning the actors, and -adding any other optional objects into the scene, such as lights. - -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def create_sim(self): | def _setup_scene(self): | -| # set the up axis to be z-up | self.cartpole = Articulation(self.cfg.robot_cfg) | -| self.up_axis = self.cfg["sim"]["up_axis"] | # add ground plane | -| | spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg() | -| self.sim = super().create_sim(self.device_id, self.graphics_device_id, | # clone, filter, and replicate | -| self.physics_engine, self.sim_params) | self.scene.clone_environments(copy_from_source=False) | -| self._create_ground_plane() | self.scene.filter_collisions(global_prim_paths=[]) | -| self._create_envs(self.num_envs, self.cfg["env"]['envSpacing'], | # add articultion to scene | -| int(np.sqrt(self.num_envs))) | self.scene.articulations["cartpole"] = self.cartpole | -| | # add lights | -| | light_cfg = sim_utils.DomeLightCfg(intensity=2000.0) | -| | light_cfg.func("/World/Light", light_cfg) | -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ - - -Ground Plane ------------- - -In Isaac Lab, most of the environment creation process has been simplified into configs with the :class:`~omni.isaac.lab.utils.configclass` module. - -The ground plane can be defined using the :class:`~terrains.TerrainImporterCfg` class. - -.. code-block:: python - - from omni.isaac.lab.terrains import TerrainImporterCfg - - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="multiply", - restitution_combine_mode="multiply", - static_friction=1.0, - dynamic_friction=1.0, - restitution=0.0, - ), - ) - -The terrain can then be added to the scene in ``_setup_scene(self)`` by referencing the ``TerrainImporterCfg`` object: - -.. code-block::python - - def _setup_scene(self): - ... - self.cfg.terrain.num_envs = self.scene.cfg.num_envs - self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing - self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) - - -Actors ------- - -Isaac Lab and Isaac Sim both use the `USD (Universal Scene Description) `_ -library for describing the scene. Assets defined in MJCF and URDF formats can be imported to USD using importer -tools described in the `Importing a New Asset <../how-to/import_new_asset.html>`_ tutorial. - -Each Articulation and Rigid Body actor can also have its own config class. The -:class:`~omni.isaac.lab.assets.ArticulationCfg` class can be used to define parameters for articulation actors, -including file path, simulation parameters, actuator properties, and initial states. - -.. code-block::python - - from omni.isaac.lab.actuators import ImplicitActuatorCfg - from omni.isaac.lab.assets import ArticulationCfg - - CARTPOLE_CFG = ArticulationCfg( - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Classic/Cartpole/cartpole.usd", - rigid_props=sim_utils.RigidBodyPropertiesCfg( - rigid_body_enabled=True, - max_linear_velocity=1000.0, - max_angular_velocity=1000.0, - max_depenetration_velocity=100.0, - enable_gyroscopic_forces=True, - ), - articulation_props=sim_utils.ArticulationRootPropertiesCfg( - enabled_self_collisions=False, - solver_position_iteration_count=4, - solver_velocity_iteration_count=0, - sleep_threshold=0.005, - stabilization_threshold=0.001, - ), - ), - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} - ), - actuators={ - "cart_actuator": ImplicitActuatorCfg( - joint_names_expr=["slider_to_cart"], - effort_limit=400.0, - velocity_limit=100.0, - stiffness=0.0, - damping=10.0, - ), - "pole_actuator": ImplicitActuatorCfg( - joint_names_expr=["cart_to_pole"], effort_limit=400.0, velocity_limit=100.0, stiffness=0.0, damping=0.0 - ), - }, - ) - -Within the :class:`~assets.ArticulationCfg`, the ``spawn`` attribute can be used to add the robot to the scene by -specifying the path to the robot file. In addition, :class:`~omni.isaac.lab.sim.schemas.RigidBodyPropertiesCfg` can -be used to specify simulation properties for the rigid bodies in the articulation. -Similarly, the :class:`~omni.isaac.lab.sim.schemas.ArticulationRootPropertiesCfg` class can be used to specify -simulation properties for the articulation. Joint properties are now specified as part of the ``actuators`` -dictionary using :class:`~actuators.ImplicitActuatorCfg`. Joints with the same properties can be grouped into -regex expressions or provided as a list of names or expressions. - -Actors are added to the scene by simply calling ``self.cartpole = Articulation(self.cfg.robot_cfg)``, -where ``self.cfg.robot_cfg`` is an :class:`~assets.ArticulationCfg` object. Once initialized, they should also -be added to the :class:`~scene.InteractiveScene` by calling ``self.scene.articulations["cartpole"] = self.cartpole`` -so that the :class:`~scene.InteractiveScene` can traverse through actors in the scene for writing values to the -simulation and resetting. - -Simulation Parameters for Actors -"""""""""""""""""""""""""""""""" - -Some simulation parameters related to Rigid Bodies and Articulations may have different -default values between Isaac Gym Preview Release and Isaac Sim. -It may be helpful to double check the USD assets to ensure that the default values are -applicable for the asset. - -For instance, the following parameters in the ``RigidBodyAPI`` could be different -between Isaac Gym Preview Release and Isaac Sim: - -.. list-table:: - :widths: 50 50 50 - :header-rows: 1 - - * - RigidBodyAPI Parameter - - Default Value in Isaac Sim - - Default Value in Isaac Gym Preview Release - * - Linear Damping - - 0.00 - - 0.00 - * - Angular Damping - - 0.05 - - 0.0 - * - Max Linear Velocity - - inf - - 1000 - * - Max Angular Velocity - - 5729.58008 (degree/s) - - 64.0 (rad/s) - * - Max Contact Impulse - - inf - - 1e32 - -Articulation parameters for the ``JointAPI`` and ``DriveAPI`` could be altered as well. Note -that the Isaac Sim UI assumes the unit of angle to be degrees. It is particularly -worth noting that the ``Damping`` and ``Stiffness`` parameters in the ``DriveAPI`` have the unit -of ``1/deg`` in the Isaac Sim UI but ``1/rad`` in Isaac Gym Preview Release. - -.. list-table:: - :widths: 50 50 50 - :header-rows: 1 - - * - Joint Parameter - - Default Value in Isaac Sim - - Default Value in Isaac Gym Preview Releases - * - Maximum Joint Velocity - - 1000000.0 (deg) - - 100.0 (rad) - - -Cloner ------- - -Isaac Sim introduced a concept of ``Cloner``, which is a class designed for replication during the scene creation process. -In IsaacGymEnvs, scenes had to be created by looping through the number of environments. -Within each iteration, actors were added to each environment and their handles had to be cached. -Isaac Lab eliminates the need for looping through the environments by using the ``Cloner`` APIs. -The scene creation process is as follow: - -#. Construct a single environment (what the scene would look like if number of environments = 1) -#. Call ``clone_environments()`` to replicate the single environment -#. Call ``filter_collisions()`` to filter out collision between environments (if required) - - -.. code-block:: python - - # construct a single environment with the Cartpole robot - self.cartpole = Articulation(self.cfg.robot_cfg) - # clone the environment - self.scene.clone_environments(copy_from_source=False) - # filter collisions - self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path]) - - -Accessing States from Simulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -APIs for accessing physics states in Isaac Lab require the creation of an :class:`~assets.Articulation` or :class:`~assets.RigidObject` -object. Multiple objects can be initialized for different articulations or rigid bodies in the scene by defining -corresponding :class:`~assets.ArticulationCfg` or :class:`~assets.RigidObjectCfg` config as outlined in the section above. -This approach eliminates the need of retrieving body handles to slice states for specific bodies in the scene. - - -.. code-block:: python - - self._robot = Articulation(self.cfg.robot) - self._cabinet = Articulation(self.cfg.cabinet) - self._object = RigidObject(self.cfg.object_cfg) - - -We have also removed ``acquire`` and ``refresh`` APIs in Isaac Lab. Physics states can be directly applied or retrieved -using APIs defined for the articulations and rigid objects. - -APIs provided in Isaac Lab no longer require explicit wrapping and un-wrapping of underlying buffers. -APIs can now work with tensors directly for reading and writing data. - -+------------------------------------------------------------------+-----------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+-----------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| dof_state_tensor = self.gym.acquire_dof_state_tensor(self.sim) | self.joint_pos = self._robot.data.joint_pos | -| self.dof_state = gymtorch.wrap_tensor(dof_state_tensor) | self.joint_vel = self._robot.data.joint_vel | -| self.gym.refresh_dof_state_tensor(self.sim) | | -+------------------------------------------------------------------+-----------------------------------------------------------------+ - -Note some naming differences between APIs in Isaac Gym Preview Release and Isaac Lab. Most ``dof`` related APIs have been -named to ``joint`` in Isaac Lab. -APIs in Isaac Lab also no longer follow the explicit ``_tensors`` or ``_tensor_indexed`` suffixes in naming. -Indexed versions of APIs now happen implicitly through the optional ``indices`` parameter. - -Most APIs in Isaac Lab also provide -the option to specify an ``indices`` parameter, which can be used when reading or writing data for a subset -of environments. Note that when setting states with the ``indices`` parameter, the shape of the states buffer -should match with the dimension of the ``indices`` list. - -+---------------------------------------------------------------------------+---------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+---------------------------------------------------------------------------+---------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| env_ids_int32 = env_ids.to(dtype=torch.int32) | self._robot.write_joint_state_to_sim(joint_pos, joint_vel, | -| self.gym.set_dof_state_tensor_indexed(self.sim, | joint_ids, env_ids) | -| gymtorch.unwrap_tensor(self.dof_state), | | -| gymtorch.unwrap_tensor(env_ids_int32), len(env_ids_int32)) | | -+---------------------------------------------------------------------------+---------------------------------------------------------------+ - -Quaternion Convention ---------------------- - -Isaac Lab and Isaac Sim both adopt ``wxyz`` as the quaternion convention. However, the quaternion -convention used in Isaac Gym Preview Release was ``xyzw``. -Remember to switch all quaternions to use the ``xyzw`` convention when working indexing rotation data. -Similarly, please ensure all quaternions are in ``wxyz`` before passing them to Isaac Lab APIs. - - -Articulation Joint Order ------------------------- - -Physics simulation in Isaac Sim and Isaac Lab assumes a breadth-first -ordering for the joints in a given kinematic tree. -However, Isaac Gym Preview Release assumed a depth-first ordering for joints in the kinematic tree. -This means that indexing joints based on their ordering may be different in IsaacGymEnvs and Isaac Lab. - -In Isaac Lab, the list of joint names can be retrieved with ``Articulation.data.joint_names``, which will -also correspond to the ordering of the joints in the Articulation. - - -Creating a New Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each environment in Isaac Lab should be in its own directory following this structure: - -.. code-block:: none - - my_environment/ - - agents/ - - __init__.py - - rl_games_ppo_cfg.py - - __init__.py - my_env.py - -* ``my_environment`` is the root directory of the task. -* ``my_environment/agents`` is the directory containing all RL config files for the task. Isaac Lab supports multiple RL libraries that can each have its own individual config file. -* ``my_environment/__init__.py`` is the main file that registers the environment with the Gymnasium interface. This allows the training and inferencing scripts to find the task by its name. The content of this file should be as follow: - -.. code-block:: python - - import gymnasium as gym - - from . import agents - from .cartpole_env import CartpoleEnv, CartpoleEnvCfg - - ## - # Register Gym environments. - ## - - gym.register( - id="Isaac-Cartpole-Direct-v0", - entry_point="omni.isaac.lab_tasks.direct_workflow.cartpole:CartpoleEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": CartpoleEnvCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml" - }, - ) - -* ``my_environment/my_env.py`` is the main python script that implements the task logic and task config class for the environment. - - -Task Logic -~~~~~~~~~~ - -In Isaac Lab, the ``post_physics_step`` function has been moved to the framework in the base class. -Tasks are not required to implement this method, but can choose to override it if a different workflow is desired. - -By default, Isaac Lab follows the following flow in logic: - -+----------------------------------+----------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+----------------------------------+----------------------------------+ -|.. code-block:: none |.. code-block:: none | -| | | -| pre_physics_step | pre_physics_step | -| |-- apply_action | |-- _pre_physics_step(action)| -| | |-- _apply_action() | -| | | -| post_physics_step | post_physics_step | -| |-- reset_idx() | |-- _get_dones() | -| |-- compute_observation() | |-- _get_rewards() | -| |-- compute_reward() | |-- _reset_idx() | -| | |-- _get_observations() | -+----------------------------------+----------------------------------+ - -In Isaac Lab, we also separate the ``pre_physics_step`` API for processing actions from the policy with -the ``apply_action`` API, which sets the actions into the simulation. This provides more flexibility in controlling -when actions should be written to simulation when ``decimation`` is used. -``pre_physics_step`` will be called once per step before stepping simulation. -``apply_actions`` will be called ``decimation`` number of times for each RL step, once before each simulation step call. - -With this approach, resets are performed based on actions from the current step instead of the previous step. -Observations will also be computed with the correct states after resets. - -We have also performed some renamings of APIs: - -* ``create_sim(self)`` --> ``_setup_scene(self)`` -* ``pre_physics_step(self, actions)`` --> ``_pre_physics_step(self, actions)`` and ``_apply_action(self)`` -* ``reset_idx(self, env_ids)`` --> ``_reset_idx(self, env_ids)`` -* ``compute_observations(self)`` --> ``_get_observations(self)`` - ``_get_observations()`` should now return a dictionary ``{"policy": obs}`` -* ``compute_reward(self)`` --> ``_get_rewards(self)`` - ``_get_rewards()`` should now return the reward buffer -* ``post_physics_step(self)`` --> moved to the base class -* In addition, Isaac Lab requires the implementation of ``_is_done(self)``, which should return two buffers: the ``reset`` buffer and the ``time_out`` buffer. - - -Putting It All Together -~~~~~~~~~~~~~~~~~~~~~~~ - -The Cartpole environment is shown here in completion to fully show the comparison between the IsaacGymEnvs implementation and the Isaac Lab implementation. - -Task Config ------------ - -+--------------------------------------------------------+---------------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+--------------------------------------------------------+---------------------------------------------------------------------+ -|.. code-block:: yaml |.. code-block:: python | -| | | -| # used to create the object | @configclass | -| name: Cartpole | class CartpoleEnvCfg(DirectRLEnvCfg): | -| | | -| physics_engine: ${..physics_engine} | # simulation | -| | sim: SimulationCfg = SimulationCfg(dt=1 / 120) | -| # if given, will override the device setting in gym. | # robot | -| env: | robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace( | -| numEnvs: ${resolve_default:512,${...num_envs}} | prim_path="/World/envs/env_.*/Robot") | -| envSpacing: 4.0 | cart_dof_name = "slider_to_cart" | -| resetDist: 3.0 | pole_dof_name = "cart_to_pole" | -| maxEffort: 400.0 | # scene | -| | scene: InteractiveSceneCfg = InteractiveSceneCfg( | -| clipObservations: 5.0 | num_envs=4096, env_spacing=4.0, replicate_physics=True) | -| clipActions: 1.0 | # env | -| | decimation = 2 | -| asset: | episode_length_s = 5.0 | -| assetRoot: "../../assets" | action_scale = 100.0 # [N] | -| assetFileName: "urdf/cartpole.urdf" | num_actions = 1 | -| | num_observations = 4 | -| enableCameraSensors: False | num_states = 0 | -| | # reset | -| sim: | max_cart_pos = 3.0 | -| dt: 0.0166 # 1/60 s | initial_pole_angle_range = [-0.25, 0.25] | -| substeps: 2 | # reward scales | -| up_axis: "z" | rew_scale_alive = 1.0 | -| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | rew_scale_terminated = -2.0 | -| gravity: [0.0, 0.0, -9.81] | rew_scale_pole_pos = -1.0 | -| physx: | rew_scale_cart_vel = -0.01 | -| num_threads: ${....num_threads} | rew_scale_pole_vel = -0.005 | -| solver_type: ${....solver_type} | | -| use_gpu: ${contains:"cuda",${....sim_device}} | | -| num_position_iterations: 4 | | -| num_velocity_iterations: 0 | | -| contact_offset: 0.02 | | -| rest_offset: 0.001 | | -| bounce_threshold_velocity: 0.2 | | -| max_depenetration_velocity: 100.0 | | -| default_buffer_size_multiplier: 2.0 | | -| max_gpu_contact_pairs: 1048576 # 1024*1024 | | -| num_subscenes: ${....num_subscenes} | | -| contact_collection: 0 | | -+--------------------------------------------------------+---------------------------------------------------------------------+ - - - -Task Setup ----------- - -Isaac Lab no longer requires pre-initialization of buffers through the ``acquire_*`` APIs that were used in IsaacGymEnvs. -It is also no longer necessary to ``wrap`` and ``unwrap`` tensors. - -+-------------------------------------------------------------------------+-------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+-------------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| class Cartpole(VecTask): | class CartpoleEnv(DirectRLEnv): | -| | cfg: CartpoleEnvCfg | -| def __init__(self, cfg, rl_device, sim_device, graphics_device_id, | def __init__(self, cfg: CartpoleEnvCfg, | -| headless, virtual_screen_capture, force_render): | render_mode: str | None = None, **kwargs): | -| self.cfg = cfg | | -| | super().__init__(cfg, render_mode, **kwargs) | -| self.reset_dist = self.cfg["env"]["resetDist"] | | -| | self._cart_dof_idx, _ = self.cartpole.find_joints( | -| self.max_push_effort = self.cfg["env"]["maxEffort"] | self.cfg.cart_dof_name) | -| self.max_episode_length = 500 | self._pole_dof_idx, _ = self.cartpole.find_joints( | -| | self.cfg.pole_dof_name) | -| self.cfg["env"]["numObservations"] = 4 | self.action_scale = self.cfg.action_scale | -| self.cfg["env"]["numActions"] = 1 | | -| | self.joint_pos = self.cartpole.data.joint_pos | -| super().__init__(config=self.cfg, | self.joint_vel = self.cartpole.data.joint_vel | -| rl_device=rl_device, sim_device=sim_device, | | -| graphics_device_id=graphics_device_id, headless=headless, | | -| virtual_screen_capture=virtual_screen_capture, | | -| force_render=force_render) | | -| | | -| dof_state_tensor = self.gym.acquire_dof_state_tensor(self.sim) | | -| self.dof_state = gymtorch.wrap_tensor(dof_state_tensor) | | -| self.dof_pos = self.dof_state.view( | | -| self.num_envs, self.num_dof, 2)[..., 0] | | -| self.dof_vel = self.dof_state.view( | | -| self.num_envs, self.num_dof, 2)[..., 1] | | -+-------------------------------------------------------------------------+-------------------------------------------------------------+ - - - -Scene Setup ------------ - -Scene setup is now done through the ``Cloner`` API and by specifying actor attributes in config objects. -This eliminates the need to loop through the number of environments to set up the environments and avoids -the need to set simulation parameters for actors in the task implementation. - -+------------------------------------------------------------------------+---------------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------------+---------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def create_sim(self): | def _setup_scene(self): | -| # set the up axis to be z-up given that assets are y-up by default | self.cartpole = Articulation(self.cfg.robot_cfg) | -| self.up_axis = self.cfg["sim"]["up_axis"] | # add ground plane | -| | spawn_ground_plane(prim_path="/World/ground", | -| self.sim = super().create_sim(self.device_id, | cfg=GroundPlaneCfg()) | -| self.graphics_device_id, self.physics_engine, | # clone, filter, and replicate | -| self.sim_params) | self.scene.clone_environments( | -| self._create_ground_plane() | copy_from_source=False) | -| self._create_envs(self.num_envs, | self.scene.filter_collisions( | -| self.cfg["env"]['envSpacing'], | global_prim_paths=[]) | -| int(np.sqrt(self.num_envs))) | # add articultion to scene | -| | self.scene.articulations["cartpole"] = self.cartpole | -| def _create_ground_plane(self): | # add lights | -| plane_params = gymapi.PlaneParams() | light_cfg = sim_utils.DomeLightCfg( | -| # set the normal force to be z dimension | intensity=2000.0, color=(0.75, 0.75, 0.75)) | -| plane_params.normal = (gymapi.Vec3(0.0, 0.0, 1.0) | light_cfg.func("/World/Light", light_cfg) | -| if self.up_axis == 'z' | | -| else gymapi.Vec3(0.0, 1.0, 0.0)) | CARTPOLE_CFG = ArticulationCfg( | -| self.gym.add_ground(self.sim, plane_params) | spawn=sim_utils.UsdFileCfg( | -| | usd_path=f"{ISAACLAB_NUCLEUS_DIR}/.../cartpole.usd", | -| def _create_envs(self, num_envs, spacing, num_per_row): | rigid_props=sim_utils.RigidBodyPropertiesCfg( | -| # define plane on which environments are initialized | rigid_body_enabled=True, | -| lower = (gymapi.Vec3(0.5 * -spacing, -spacing, 0.0) | max_linear_velocity=1000.0, | -| if self.up_axis == 'z' | max_angular_velocity=1000.0, | -| else gymapi.Vec3(0.5 * -spacing, 0.0, -spacing)) | max_depenetration_velocity=100.0, | -| upper = gymapi.Vec3(0.5 * spacing, spacing, spacing) | enable_gyroscopic_forces=True, | -| | ), | -| asset_root = os.path.join(os.path.dirname( | articulation_props=sim_utils.ArticulationRootPropertiesCfg( | -| os.path.abspath(__file__)), "../../assets") | enabled_self_collisions=False, | -| asset_file = "urdf/cartpole.urdf" | solver_position_iteration_count=4, | -| | solver_velocity_iteration_count=0, | -| if "asset" in self.cfg["env"]: | sleep_threshold=0.005, | -| asset_root = os.path.join(os.path.dirname( | stabilization_threshold=0.001, | -| os.path.abspath(__file__)), | ), | -| self.cfg["env"]["asset"].get("assetRoot", asset_root)) | ), | -| asset_file = self.cfg["env"]["asset"].get( | init_state=ArticulationCfg.InitialStateCfg( | -| "assetFileName", asset_file) | pos=(0.0, 0.0, 2.0), | -| | joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} | -| asset_path = os.path.join(asset_root, asset_file) | ), | -| asset_root = os.path.dirname(asset_path) | actuators={ | -| asset_file = os.path.basename(asset_path) | "cart_actuator": ImplicitActuatorCfg( | -| | joint_names_expr=["slider_to_cart"], | -| asset_options = gymapi.AssetOptions() | effort_limit=400.0, | -| asset_options.fix_base_link = True | velocity_limit=100.0, | -| cartpole_asset = self.gym.load_asset(self.sim, | stiffness=0.0, | -| asset_root, asset_file, asset_options) | damping=10.0, | -| self.num_dof = self.gym.get_asset_dof_count( | ), | -| cartpole_asset) | "pole_actuator": ImplicitActuatorCfg( | -| | joint_names_expr=["cart_to_pole"], effort_limit=400.0, | -| pose = gymapi.Transform() | velocity_limit=100.0, stiffness=0.0, damping=0.0 | -| if self.up_axis == 'z': | ), | -| pose.p.z = 2.0 | }, | -| pose.r = gymapi.Quat(0.0, 0.0, 0.0, 1.0) | ) | -| else: | | -| pose.p.y = 2.0 | | -| pose.r = gymapi.Quat( | | -| -np.sqrt(2)/2, 0.0, 0.0, np.sqrt(2)/2) | | -| | | -| self.cartpole_handles = [] | | -| self.envs = [] | | -| for i in range(self.num_envs): | | -| # create env instance | | -| env_ptr = self.gym.create_env( | | -| self.sim, lower, upper, num_per_row | | -| ) | | -| cartpole_handle = self.gym.create_actor( | | -| env_ptr, cartpole_asset, pose, | | -| "cartpole", i, 1, 0) | | -| | | -| dof_props = self.gym.get_actor_dof_properties( | | -| env_ptr, cartpole_handle) | | -| dof_props['driveMode'][0] = gymapi.DOF_MODE_EFFORT | | -| dof_props['driveMode'][1] = gymapi.DOF_MODE_NONE | | -| dof_props['stiffness'][:] = 0.0 | | -| dof_props['damping'][:] = 0.0 | | -| self.gym.set_actor_dof_properties(env_ptr, c | | -| artpole_handle, dof_props) | | -| | | -| self.envs.append(env_ptr) | | -| self.cartpole_handles.append(cartpole_handle) | | -+------------------------------------------------------------------------+---------------------------------------------------------------------+ - - -Pre and Post Physics Step -------------------------- - -In IsaacGymEnvs, due to limitations of the GPU APIs, observations had stale data when environments had to perform resets. -This restriction has been eliminated in Isaac Lab, and thus, tasks follow the correct workflow of applying actions, stepping simulation, -collecting states, computing dones, calculating rewards, performing resets, and finally computing observations. -This workflow is done automatically by the framework such that a ``post_physics_step`` API is not required in the task. -However, individual tasks can override the ``step()`` API to control the workflow. - -+------------------------------------------------------------------+-------------------------------------------------------------+ -| IsaacGymEnvs | IsaacLab | -+------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def pre_physics_step(self, actions): | def _pre_physics_step(self, actions: torch.Tensor) -> None: | -| actions_tensor = torch.zeros( | self.actions = self.action_scale * actions | -| self.num_envs * self.num_dof, | | -| device=self.device, dtype=torch.float) | def _apply_action(self) -> None: | -| actions_tensor[::self.num_dof] = actions.to( | self.cartpole.set_joint_effort_target( | -| self.device).squeeze() * self.max_push_effort | self.actions, joint_ids=self._cart_dof_idx) | -| forces = gymtorch.unwrap_tensor(actions_tensor) | | -| self.gym.set_dof_actuation_force_tensor( | | -| self.sim, forces) | | -| | | -| def post_physics_step(self): | | -| self.progress_buf += 1 | | -| | | -| env_ids = self.reset_buf.nonzero( | | -| as_tuple=False).squeeze(-1) | | -| if len(env_ids) > 0: | | -| self.reset_idx(env_ids) | | -| | | -| self.compute_observations() | | -| self.compute_reward() | | -+------------------------------------------------------------------+-------------------------------------------------------------+ - - -Dones and Resets ----------------- - -In Isaac Lab, ``dones`` are computed in the ``_get_dones()`` method and should return two variables: ``resets`` and ``time_out``. -Tracking of the ``progress_buf`` has been moved to the base class and is now automatically incremented and reset by the framework. -The ``progress_buf`` variable has also been renamed to ``episode_length_buf``. - -+-----------------------------------------------------------------------+---------------------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+-----------------------------------------------------------------------+---------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def reset_idx(self, env_ids): | def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: | -| positions = 0.2 * (torch.rand((len(env_ids), self.num_dof), | self.joint_pos = self.cartpole.data.joint_pos | -| device=self.device) - 0.5) | self.joint_vel = self.cartpole.data.joint_vel | -| velocities = 0.5 * (torch.rand((len(env_ids), self.num_dof), | | -| device=self.device) - 0.5) | time_out = self.episode_length_buf >= self.max_episode_length - 1 | -| | out_of_bounds = torch.any(torch.abs( | -| self.dof_pos[env_ids, :] = positions[:] | self.joint_pos[:, self._pole_dof_idx] > self.cfg.max_cart_pos), | -| self.dof_vel[env_ids, :] = velocities[:] | dim=1) | -| | out_of_bounds = out_of_bounds | torch.any( | -| env_ids_int32 = env_ids.to(dtype=torch.int32) | torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, | -| self.gym.set_dof_state_tensor_indexed(self.sim, | dim=1) | -| gymtorch.unwrap_tensor(self.dof_state), | return out_of_bounds, time_out | -| gymtorch.unwrap_tensor(env_ids_int32), len(env_ids_int32)) | | -| self.reset_buf[env_ids] = 0 | def _reset_idx(self, env_ids: Sequence[int] | None): | -| self.progress_buf[env_ids] = 0 | if env_ids is None: | -| | env_ids = self.cartpole._ALL_INDICES | -| | super()._reset_idx(env_ids) | -| | | -| | joint_pos = self.cartpole.data.default_joint_pos[env_ids] | -| | joint_pos[:, self._pole_dof_idx] += sample_uniform( | -| | self.cfg.initial_pole_angle_range[0] * math.pi, | -| | self.cfg.initial_pole_angle_range[1] * math.pi, | -| | joint_pos[:, self._pole_dof_idx].shape, | -| | joint_pos.device, | -| | ) | -| | joint_vel = self.cartpole.data.default_joint_vel[env_ids] | -| | | -| | default_root_state = self.cartpole.data.default_root_state[env_ids] | -| | default_root_state[:, :3] += self.scene.env_origins[env_ids] | -| | | -| | self.joint_pos[env_ids] = joint_pos | -| | | -| | self.cartpole.write_root_pose_to_sim( | -| | default_root_state[:, :7], env_ids) | -| | self.cartpole.write_root_velocity_to_sim( | -| | default_root_state[:, 7:], env_ids) | -| | self.cartpole.write_joint_state_to_sim( | -| | joint_pos, joint_vel, None, env_ids) | -+-----------------------------------------------------------------------+---------------------------------------------------------------------------+ - - -Observations ------------- - -In Isaac Lab, the ``_get_observations()`` API should now return a dictionary containing the ``policy`` key with the observation -buffer as the value. -For asymmetric policies, the dictionary should also include a ``critic`` key that holds the state buffer. - -+--------------------------------------------------------------------------+---------------------------------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+--------------------------------------------------------------------------+---------------------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def compute_observations(self, env_ids=None): | def _get_observations(self) -> dict: | -| if env_ids is None: | obs = torch.cat( | -| env_ids = np.arange(self.num_envs) | ( | -| | self.joint_pos[:, self._pole_dof_idx[0]], | -| self.gym.refresh_dof_state_tensor(self.sim) | self.joint_vel[:, self._pole_dof_idx[0]], | -| | self.joint_pos[:, self._cart_dof_idx[0]], | -| self.obs_buf[env_ids, 0] = self.dof_pos[env_ids, 0] | self.joint_vel[:, self._cart_dof_idx[0]], | -| self.obs_buf[env_ids, 1] = self.dof_vel[env_ids, 0] | ), | -| self.obs_buf[env_ids, 2] = self.dof_pos[env_ids, 1] | dim=-1, | -| self.obs_buf[env_ids, 3] = self.dof_vel[env_ids, 1] | ) | -| | observations = {"policy": obs} | -| return self.obs_buf | return observations | -+--------------------------------------------------------------------------+---------------------------------------------------------------------------------------+ - - -Rewards -------- - -In Isaac Lab, the reward method ``_get_rewards`` should return the reward buffer as a return value. -Similar to IsaacGymEnvs, computations in the reward function can also be performed using pytorch jit -by adding the ``@torch.jit.script`` annotation. - -+--------------------------------------------------------------------------+----------------------------------------------------------------------------------------+ -| IsaacGymEnvs | Isaac Lab | -+--------------------------------------------------------------------------+----------------------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def compute_reward(self): | def _get_rewards(self) -> torch.Tensor: | -| # retrieve environment observations from buffer | total_reward = compute_rewards( | -| pole_angle = self.obs_buf[:, 2] | self.cfg.rew_scale_alive, | -| pole_vel = self.obs_buf[:, 3] | self.cfg.rew_scale_terminated, | -| cart_vel = self.obs_buf[:, 1] | self.cfg.rew_scale_pole_pos, | -| cart_pos = self.obs_buf[:, 0] | self.cfg.rew_scale_cart_vel, | -| | self.cfg.rew_scale_pole_vel, | -| self.rew_buf[:], self.reset_buf[:] = compute_cartpole_reward( | self.joint_pos[:, self._pole_dof_idx[0]], | -| pole_angle, pole_vel, cart_vel, cart_pos, | self.joint_vel[:, self._pole_dof_idx[0]], | -| self.reset_dist, self.reset_buf, | self.joint_pos[:, self._cart_dof_idx[0]], | -| self.progress_buf, self.max_episode_length | self.joint_vel[:, self._cart_dof_idx[0]], | -| ) | self.reset_terminated, | -| | ) | -| @torch.jit.script | return total_reward | -| def compute_cartpole_reward(pole_angle, pole_vel, | | -| cart_vel, cart_pos, | @torch.jit.script | -| reset_dist, reset_buf, | def compute_rewards( | -| progress_buf, max_episode_length): | rew_scale_alive: float, | -| | rew_scale_terminated: float, | -| reward = (1.0 - pole_angle * pole_angle - | rew_scale_pole_pos: float, | -| 0.01 * torch.abs(cart_vel) - | rew_scale_cart_vel: float, | -| 0.005 * torch.abs(pole_vel)) | rew_scale_pole_vel: float, | -| | pole_pos: torch.Tensor, | -| # adjust reward for reset agents | pole_vel: torch.Tensor, | -| reward = torch.where(torch.abs(cart_pos) > reset_dist, | cart_pos: torch.Tensor, | -| torch.ones_like(reward) * -2.0, reward) | cart_vel: torch.Tensor, | -| reward = torch.where(torch.abs(pole_angle) > np.pi / 2, | reset_terminated: torch.Tensor, | -| torch.ones_like(reward) * -2.0, reward) | ): | -| | rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) | -| reset = torch.where(torch.abs(cart_pos) > reset_dist, | rew_termination = rew_scale_terminated * reset_terminated.float() | -| torch.ones_like(reset_buf), reset_buf) | rew_pole_pos = rew_scale_pole_pos * torch.sum( | -| reset = torch.where(torch.abs(pole_angle) > np.pi / 2, | torch.square(pole_pos), dim=-1) | -| torch.ones_like(reset_buf), reset_buf) | rew_cart_vel = rew_scale_cart_vel * torch.sum( | -| reset = torch.where(progress_buf >= max_episode_length - 1, | torch.abs(cart_vel), dim=-1) | -| torch.ones_like(reset_buf), reset) | rew_pole_vel = rew_scale_pole_vel * torch.sum( | -| | torch.abs(pole_vel), dim=-1) | -| | total_reward = (rew_alive + rew_termination | -| | + rew_pole_pos + rew_cart_vel + rew_pole_vel) | -| | return total_reward | -+--------------------------------------------------------------------------+----------------------------------------------------------------------------------------+ - - - -Launching Training -~~~~~~~~~~~~~~~~~~ - -To launch a training in Isaac Lab, use the command: - -.. code-block:: bash - - python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 --headless - -Launching Inferencing -~~~~~~~~~~~~~~~~~~~~~ - -To launch inferencing in Isaac Lab, use the command: - -.. code-block:: bash - - python source/standalone/workflows/rl_games/play.py --task=Isaac-Cartpole-Direct-v0 --num_envs=25 --checkpoint= - - -.. _IsaacGymEnvs: https://github.com/isaac-sim/IsaacGymEnvs -.. _Isaac Gym Preview Release: https://developer.nvidia.com/isaac-gym diff --git a/docs/_build/html/_sources/source/migration/migrating_from_omniisaacgymenvs.rst b/docs/_build/html/_sources/source/migration/migrating_from_omniisaacgymenvs.rst deleted file mode 100644 index ba32330f19..0000000000 --- a/docs/_build/html/_sources/source/migration/migrating_from_omniisaacgymenvs.rst +++ /dev/null @@ -1,994 +0,0 @@ -.. _migrating-from-omniisaacgymenvs: - -From OmniIsaacGymEnvs -===================== - -.. currentmodule:: omni.isaac.lab - - -`OmniIsaacGymEnvs`_ was a reinforcement learning framework using the Isaac Sim platform. -Features from OmniIsaacGymEnvs have been integrated into the Isaac Lab framework. -We have updated OmniIsaacGymEnvs to Isaac Sim version 4.0.0 to support the migration process -to Isaac Lab. Moving forward, OmniIsaacGymEnvs will be deprecated and future development -will continue in Isaac Lab. - - -Task Config Setup -~~~~~~~~~~~~~~~~~ - -In OmniIsaacGymEnvs, task config files were defined in ``.yaml`` format. With Isaac Lab, configs are now specified -using a specialized Python class :class:`~omni.isaac.lab.utils.configclass`. The -:class:`~omni.isaac.lab.utils.configclass` module provides a wrapper on top of Python's ``dataclasses`` module. -Each environment should specify its own config class annotated by ``@configclass`` that inherits from the -:class:`~envs.DirectRLEnvCfg` class, which can include simulation parameters, environment scene parameters, -robot parameters, and task-specific parameters. - -Below is an example skeleton of a task config class: - -.. code-block:: python - - from omni.isaac.lab.envs import DirectRLEnvCfg - from omni.isaac.lab.scene import InteractiveSceneCfg - from omni.isaac.lab.sim import SimulationCfg - - @configclass - class MyEnvCfg(DirectRLEnvCfg): - # simulation - sim: SimulationCfg = SimulationCfg() - # robot - robot_cfg: ArticulationCfg = ArticulationCfg() - # scene - scene: InteractiveSceneCfg = InteractiveSceneCfg() - # env - decimation = 2 - episode_length_s = 5.0 - num_actions = 1 - num_observations = 4 - num_states = 0 - # task-specific parameters - ... - -Simulation Config ------------------ - -Simulation related parameters are defined as part of the :class:`~omni.isaac.lab.sim.SimulationCfg` class, -which is a :class:`~omni.isaac.lab.utils.configclass` module that holds simulation parameters such as ``dt``, -``device``, and ``gravity``. Each task config must have a variable named ``sim`` defined that holds the type -:class:`~omni.isaac.lab.sim.SimulationCfg`. - -Simulation parameters for articulations and rigid bodies such as ``num_position_iterations``, ``num_velocity_iterations``, -``contact_offset``, ``rest_offset``, ``bounce_threshold_velocity``, ``max_depenetration_velocity`` can all -be specified on a per-actor basis in the config class for each individual articulation and rigid body. - -When running simulation on the GPU, buffers in PhysX require pre-allocation for computing and storing -information such as contacts, collisions and aggregate pairs. These buffers may need to be adjusted -depending on the complexity of the environment, the number of expected contacts and collisions, -and the number of actors in the environment. The :class:`~omni.isaac.lab.sim.PhysxCfg` class provides access -for setting the GPU buffer dimensions. - -+--------------------------------------------------------------+-------------------------------------------------------------------+ -| | | -|.. code-block:: yaml |.. code-block:: python | -| | | -| # OmniIsaacGymEnvs | # IsaacLab | -| sim: | sim: SimulationCfg = SimulationCfg( | -| dt: 0.0083 # 1/120 s | dt=1 / 120, | -| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | use_gpu_pipeline=True, | -| use_fabric: True | use_fabric=True, | -| enable_scene_query_support: False | enable_scene_query_support=False, | -| disable_contact_processing: False | disable_contact_processing=False, | -| gravity: [0.0, 0.0, -9.81] | gravity=(0.0, 0.0, -9.81), | -| | | -| default_physics_material: | physics_material=RigidBodyMaterialCfg( | -| static_friction: 1.0 | static_friction=1.0, | -| dynamic_friction: 1.0 | dynamic_friction=1.0, | -| restitution: 0.0 | restitution=0.0 | -| | ) | -| physx: | physx: PhysxCfg = PhysxCfg( | -| worker_thread_count: ${....num_threads} | # worker_thread_count is no longer needed | -| solver_type: ${....solver_type} | solver_type=1, | -| use_gpu: ${contains:"cuda",${....sim_device}} | use_gpu=True, | -| solver_position_iteration_count: 4 | max_position_iteration_count=4, | -| solver_velocity_iteration_count: 0 | max_velocity_iteration_count=0, | -| contact_offset: 0.02 | # moved to actor config | -| rest_offset: 0.001 | # moved to actor config | -| bounce_threshold_velocity: 0.2 | bounce_threshold_velocity=0.2, | -| friction_offset_threshold: 0.04 | friction_offset_threshold=0.04, | -| friction_correlation_distance: 0.025 | friction_correlation_distance=0.025, | -| enable_sleeping: True | # enable_sleeping is no longer needed | -| enable_stabilization: True | enable_stabilization=True, | -| max_depenetration_velocity: 100.0 | # moved to RigidBodyPropertiesCfg | -| | | -| gpu_max_rigid_contact_count: 524288 | gpu_max_rigid_contact_count=2**23, | -| gpu_max_rigid_patch_count: 81920 | gpu_max_rigid_patch_count=5 * 2**15, | -| gpu_found_lost_pairs_capacity: 1024 | gpu_found_lost_pairs_capacity=2**21, | -| gpu_found_lost_aggregate_pairs_capacity: 262144 | gpu_found_lost_aggregate_pairs_capacity=2**25, | -| gpu_total_aggregate_pairs_capacity: 1024 | gpu_total_aggregate_pairs_capacity=2**21, | -| gpu_heap_capacity: 67108864 | gpu_heap_capacity=2**26, | -| gpu_temp_buffer_capacity: 16777216 | gpu_temp_buffer_capacity=2**24, | -| gpu_max_num_partitions: 8 | gpu_max_num_partitions=8, | -| gpu_max_soft_body_contacts: 1048576 | gpu_max_soft_body_contacts=2**20, | -| gpu_max_particle_contacts: 1048576 | gpu_max_particle_contacts=2**20, | -| | ) | -| | ) | -+--------------------------------------------------------------+-------------------------------------------------------------------+ - -Parameters such as ``add_ground_plane`` and ``add_distant_light`` are now part of the task logic when creating the scene. -``enable_cameras`` is now a command line argument ``--enable_cameras`` that can be passed directly to the training script. - - -Scene Config ------------- - -The :class:`~omni.isaac.lab.scene.InteractiveSceneCfg` class can be used to specify parameters related to the scene, -such as the number of environments and the spacing between environments. Each task config must have a variable named -``scene`` defined that holds the type :class:`~omni.isaac.lab.scene.InteractiveSceneCfg`. - -+--------------------------------------------------------------+-------------------------------------------------------------------+ -| | | -|.. code-block:: yaml |.. code-block:: python | -| | | -| # OmniIsaacGymEnvs | # IsaacLab | -| env: | scene: InteractiveSceneCfg = InteractiveSceneCfg( | -| numEnvs: ${resolve_default:512,${...num_envs}} | num_envs=512, | -| envSpacing: 4.0 | env_spacing=4.0) | -+--------------------------------------------------------------+-------------------------------------------------------------------+ - -Task Config ------------ - -Each environment should specify its own config class that holds task specific parameters, such as the dimensions of the -observation and action buffers. Reward term scaling parameters can also be specified in the config class. - -In Isaac Lab, the ``controlFrequencyInv`` parameter has been renamed to ``decimation``, -which must be specified as a parameter in the config class. - -In addition, the maximum episode length parameter (now ``episode_length_s``) is in seconds instead of steps as it was -in OmniIsaacGymEnvs. To convert between step count to seconds, use the equation: -``episode_length_s = dt * decimation * num_steps``. - -The following parameters must be set for each environment config: - -.. code-block:: python - - decimation = 2 - episode_length_s = 5.0 - num_actions = 1 - num_observations = 4 - num_states = 0 - - -RL Config Setup -~~~~~~~~~~~~~~~ - -RL config files for the rl_games library can continue to be defined in ``.yaml`` files in Isaac Lab. -Most of the content of the config file can be copied directly from OmniIsaacGymEnvs. -Note that in Isaac Lab, we do not use hydra to resolve relative paths in config files. -Please replace any relative paths such as ``${....device}`` with the actual values of the parameters. - -Additionally, the observation and action clip ranges have been moved to the RL config file. -For any ``clipObservations`` and ``clipActions`` parameters that were defined in the IsaacGymEnvs task config file, -they should be moved to the RL config file in Isaac Lab. - -+--------------------------+----------------------------+ -| | | -| IsaacGymEnvs Task Config | Isaac Lab RL Config | -+--------------------------+----------------------------+ -|.. code-block:: yaml |.. code-block:: yaml | -| | | -| # OmniIsaacGymEnvs | # IsaacLab | -| env: | params: | -| clipObservations: 5.0 | env: | -| clipActions: 1.0 | clip_observations: 5.0 | -| | clip_actions: 1.0 | -+--------------------------+----------------------------+ - -Environment Creation -~~~~~~~~~~~~~~~~~~~~ - -In OmniIsaacGymEnvs, environment creation generally happened in the ``set_up_scene()`` API, -which involved creating the initial environment, cloning the environment, filtering collisions, -adding the ground plane and lights, and creating the ``View`` classes for the actors. - -Similar functionality is performed in Isaac Lab in the ``_setup_scene()`` API. -The main difference is that the base class ``_setup_scene()`` no longer performs operations for -cloning the environment and adding ground plane and lights. Instead, these operations -should now be implemented in individual tasks' ``_setup_scene`` implementations to provide more -flexibility around the scene setup process. - -Also note that by defining an ``Articulation`` or ``RigidObject`` object, the actors will be -added to the scene by parsing the ``spawn`` parameter in the actor config and a ``View`` class -will automatically be created for the actor. This avoids the need to separately define an -``ArticulationView`` or ``RigidPrimView`` object for the actors. - - -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def set_up_scene(self, scene) -> None: | def _setup_scene(self): | -| self.get_cartpole() | self.cartpole = Articulation(self.cfg.robot_cfg) | -| super().set_up_scene(scene) | # add ground plane | -| | spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg() | -| self._cartpoles = ArticulationView( | # clone, filter, and replicate | -| prim_paths_expr="/World/envs/.*/Cartpole", | self.scene.clone_environments(copy_from_source=False) | -| name="cartpole_view", reset_xform_properties=False | self.scene.filter_collisions(global_prim_paths=[]) | -| ) | # add articultion to scene | -| scene.add(self._cartpoles) | self.scene.articulations["cartpole"] = self.cartpole | -| | # add lights | -| | light_cfg = sim_utils.DomeLightCfg(intensity=2000.0) | -| | light_cfg.func("/World/Light", light_cfg) | -+------------------------------------------------------------------------------+------------------------------------------------------------------------+ - - -Ground Plane ------------- - -In addition to the above example, more sophisticated ground planes can be defined using the :class:`~terrains.TerrainImporterCfg` class. - -.. code-block:: python - - from omni.isaac.lab.terrains import TerrainImporterCfg - - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - collision_group=-1, - physics_material=sim_utils.RigidBodyMaterialCfg( - friction_combine_mode="multiply", - restitution_combine_mode="multiply", - static_friction=1.0, - dynamic_friction=1.0, - restitution=0.0, - ), - ) - -The terrain can then be added to the scene in ``_setup_scene(self)`` by referencing the ``TerrainImporterCfg`` object: - -.. code-block::python - - def _setup_scene(self): - ... - self.cfg.terrain.num_envs = self.scene.cfg.num_envs - self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing - self._terrain = self.cfg.terrain.class_type(self.cfg.terrain) - - -Actors ------- - -In Isaac Lab, each Articulation and Rigid Body actor can have its own config class. The -:class:`~omni.isaac.lab.assets.ArticulationCfg` class can be used to define parameters for articulation actors, -including file path, simulation parameters, actuator properties, and initial states. - -.. code-block::python - - from omni.isaac.lab.actuators import ImplicitActuatorCfg - from omni.isaac.lab.assets import ArticulationCfg - - CARTPOLE_CFG = ArticulationCfg( - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/Classic/Cartpole/cartpole.usd", - rigid_props=sim_utils.RigidBodyPropertiesCfg( - rigid_body_enabled=True, - max_linear_velocity=1000.0, - max_angular_velocity=1000.0, - max_depenetration_velocity=100.0, - enable_gyroscopic_forces=True, - ), - articulation_props=sim_utils.ArticulationRootPropertiesCfg( - enabled_self_collisions=False, - solver_position_iteration_count=4, - solver_velocity_iteration_count=0, - sleep_threshold=0.005, - stabilization_threshold=0.001, - ), - ), - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} - ), - actuators={ - "cart_actuator": ImplicitActuatorCfg( - joint_names_expr=["slider_to_cart"], - effort_limit=400.0, - velocity_limit=100.0, - stiffness=0.0, - damping=10.0, - ), - "pole_actuator": ImplicitActuatorCfg( - joint_names_expr=["cart_to_pole"], effort_limit=400.0, velocity_limit=100.0, stiffness=0.0, damping=0.0 - ), - }, - ) - -Within the :class:`~assets.ArticulationCfg`, the ``spawn`` attribute can be used to add the robot to the scene -by specifying the path to the robot file. In addition, the :class:`~omni.isaac.lab.sim.schemas.RigidBodyPropertiesCfg` -class can be used to specify simulation properties for the rigid bodies in the articulation. Similarly, the -:class:`~omni.isaac.lab.sim.schemas.ArticulationRootPropertiesCfg` class can be used to specify simulation properties -for the articulation. The joint properties are now specified as part of the ``actuators`` dictionary using -:class:`~actuators.ImplicitActuatorCfg`. Joints with the same properties can be grouped into regex expressions or -provided as a list of names or expressions. - -Actors are added to the scene by simply calling ``self.cartpole = Articulation(self.cfg.robot_cfg)``, where -``self.cfg.robot_cfg`` is an :class:`~assets.ArticulationCfg` object. Once initialized, they should also be added -to the :class:`~scene.InteractiveScene` by calling ``self.scene.articulations["cartpole"] = self.cartpole`` so that -the :class:`~scene.InteractiveScene` can traverse through actors in the scene for writing values to the simulation -and resetting. - - -Accessing States from Simulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -APIs for accessing physics states in Isaac Lab require the creation of an :class:`~assets.Articulation` or -:class:`~assets.RigidObject` object. Multiple objects can be initialized for different articulations or rigid bodies -in the scene by defining corresponding :class:`~assets.ArticulationCfg` or :class:`~assets.RigidObjectCfg` config, -as outlined in the section above. This replaces the previously used :class:`~omni.isaac.core.articulations.ArticulationView` -and :class:`omni.isaac.core.prims.RigidPrimView` classes used in OmniIsaacGymEnvs. - -However, functionality between the classes are similar: - -+------------------------------------------------------------------+-----------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+-----------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| dof_pos = self._cartpoles.get_joint_positions(clone=False) | self.joint_pos = self._robot.data.joint_pos | -| dof_vel = self._cartpoles.get_joint_velocities(clone=False) | self.joint_vel = self._robot.data.joint_vel | -+------------------------------------------------------------------+-----------------------------------------------------------------+ - -In Isaac Lab, :class:`~assets.Articulation` and :class:`~assets.RigidObject` classes both have a ``data`` class. -The data classes (:class:`~assets.ArticulationData` and :class:`~assets.RigidObjectData`) contain -buffers that hold the states for the articulation and rigid objects and provide -a more performant way of retrieving states from the actors. - -Apart from some renamings of APIs, setting states for actors can also be performed similarly between OmniIsaacGymEnvs and Isaac Lab. - -+---------------------------------------------------------------------------+---------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+---------------------------------------------------------------------------+---------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| indices = env_ids.to(dtype=torch.int32) | self._robot.write_joint_state_to_sim(joint_pos, joint_vel, | -| self._cartpoles.set_joint_positions(dof_pos, indices=indices) | joint_ids, env_ids) | -| self._cartpoles.set_joint_velocities(dof_vel, indices=indices) | | -+---------------------------------------------------------------------------+---------------------------------------------------------------+ - -In Isaac Lab, ``root_pose`` and ``root_velocity`` have been combined into single buffers and no longer split between -``root_position``, ``root_orientation``, ``root_linear_velocity`` and ``root_angular_velocity``. - -.. code-block::python - - self.cartpole.write_root_pose_to_sim(default_root_state[:, :7], env_ids) - self.cartpole.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids) - - -Creating a New Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each environment in Isaac Lab should be in its own directory following this structure: - -.. code-block:: none - - my_environment/ - - agents/ - - __init__.py - - rl_games_ppo_cfg.py - - __init__.py - my_env.py - -* ``my_environment`` is the root directory of the task. -* ``my_environment/agents`` is the directory containing all RL config files for the task. Isaac Lab supports multiple - RL libraries that can each have its own individual config file. -* ``my_environment/__init__.py`` is the main file that registers the environment with the Gymnasium interface. - This allows the training and inferencing scripts to find the task by its name. - The content of this file should be as follow: - - .. code-block:: python - - import gymnasium as gym - - from . import agents - from .cartpole_env import CartpoleEnv, CartpoleEnvCfg - - ## - # Register Gym environments. - ## - - gym.register( - id="Isaac-Cartpole-Direct-v0", - entry_point="omni.isaac.lab_tasks.direct_workflow.cartpole:CartpoleEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": CartpoleEnvCfg, - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml" - }, - ) - -* ``my_environment/my_env.py`` is the main python script that implements the task logic and task config class for - the environment. - - -Task Logic -~~~~~~~~~~ - -The ``post_reset`` API in OmniIsaacGymEnvs is no longer required in Isaac Lab. Everything that was previously -done in ``post_reset`` can be done in the ``__init__`` method after executing the base class's -``__init__``. At this point, simulation has already started. - -In OmniIsaacGymEnvs, due to limitations of the GPU APIs, resets could not be performed based on states of the current -step. Instead, resets have to be performed at the beginning of the next time step. -This restriction has been eliminated in Isaac Lab, and thus, tasks follow the correct workflow of applying actions, -stepping simulation, collecting states, computing dones, calculating rewards, performing resets, and finally computing -observations. This workflow is done automatically by the framework such that a ``post_physics_step`` API is not -required in the task. However, individual tasks can override the ``step()`` API to control the workflow. - -In Isaac Lab, we also separate the ``pre_physics_step`` API for processing actions from the policy with -the ``apply_action`` API, which sets the actions into the simulation. This provides more flexibility in controlling -when actions should be written to simulation when ``decimation`` is used. -The ``pre_physics_step`` method will be called once per step before stepping simulation. -The ``apply_actions`` method will be called ``decimation`` number of times for each RL step, -once before each simulation step call. - -The ordering of the calls are as follow: - -+----------------------------------+----------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+----------------------------------+----------------------------------+ -|.. code-block:: none |.. code-block:: none | -| | | -| pre_physics_step | pre_physics_step | -| |-- reset_idx() | |-- _pre_physics_step(action)| -| |-- apply_action | |-- _apply_action() | -| | | -| post_physics_step | post_physics_step | -| |-- get_observations() | |-- _get_dones() | -| |-- calculate_metrics() | |-- _get_rewards() | -| |-- is_done() | |-- _reset_idx() | -| | |-- _get_observations() | -+----------------------------------+----------------------------------+ - -With this approach, resets are performed based on actions from the current step instead of the previous step. -Observations will also be computed with the correct states after resets. - -We have also performed some renamings of APIs: - -* ``set_up_scene(self, scene)`` --> ``_setup_scene(self)`` -* ``post_reset(self)`` --> ``__init__(...)`` -* ``pre_physics_step(self, actions)`` --> ``_pre_physics_step(self, actions)`` and ``_apply_action(self)`` -* ``reset_idx(self, env_ids)`` --> ``_reset_idx(self, env_ids)`` -* ``get_observations(self)`` --> ``_get_observations(self)`` - ``_get_observations()`` should now return a dictionary ``{"policy": obs}`` -* ``calculate_metrics(self)`` --> ``_get_rewards(self)`` - ``_get_rewards()`` should now return the reward buffer -* ``is_done(self)`` --> ``_get_dones(self)`` - ``_get_dones()`` should now return 2 buffers: ``reset`` and ``time_out`` buffers - - - -Putting It All Together -~~~~~~~~~~~~~~~~~~~~~~~ - -The Cartpole environment is shown here in completion to fully show the comparison between the OmniIsaacGymEnvs -implementation and the Isaac Lab implementation. - -Task Config ------------ - -Task config in Isaac Lab can be split into the main task configuration class and individual config objects for the actors. - -+-----------------------------------------------------------------+-----------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-----------------------------------------------------------------+-----------------------------------------------------------------+ -|.. code-block:: yaml |.. code-block:: python | -| | | -| # used to create the object | @configclass | -| | class CartpoleEnvCfg(DirectRLEnvCfg): | -| name: Cartpole | | -| | # simulation | -| physics_engine: ${..physics_engine} | sim: SimulationCfg = SimulationCfg(dt=1 / 120) | -| | # robot | -| # if given, will override the device setting in gym. | robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace( | -| env: | prim_path="/World/envs/env_.*/Robot") | -| | cart_dof_name = "slider_to_cart" | -| numEnvs: ${resolve_default:512,${...num_envs}} | pole_dof_name = "cart_to_pole" | -| envSpacing: 4.0 | # scene | -| resetDist: 3.0 | scene: InteractiveSceneCfg = InteractiveSceneCfg( | -| maxEffort: 400.0 | num_envs=4096, env_spacing=4.0, replicate_physics=True) | -| | # env | -| clipObservations: 5.0 | decimation = 2 | -| clipActions: 1.0 | episode_length_s = 5.0 | -| controlFrequencyInv: 2 # 60 Hz | action_scale = 100.0 # [N] | -| | num_actions = 1 | -| sim: | num_observations = 4 | -| | num_states = 0 | -| dt: 0.0083 # 1/120 s | # reset | -| use_gpu_pipeline: ${eq:${...pipeline},"gpu"} | max_cart_pos = 3.0 | -| gravity: [0.0, 0.0, -9.81] | initial_pole_angle_range = [-0.25, 0.25] | -| add_ground_plane: True | # reward scales | -| add_distant_light: False | rew_scale_alive = 1.0 | -| use_fabric: True | rew_scale_terminated = -2.0 | -| enable_scene_query_support: False | rew_scale_pole_pos = -1.0 | -| disable_contact_processing: False | rew_scale_cart_vel = -0.01 | -| | rew_scale_pole_vel = -0.005 | -| enable_cameras: False | | -| | | -| default_physics_material: | CARTPOLE_CFG = ArticulationCfg( | -| static_friction: 1.0 | spawn=sim_utils.UsdFileCfg( | -| dynamic_friction: 1.0 | usd_path=f"{ISAACLAB_NUCLEUS_DIR}/.../cartpole.usd", | -| restitution: 0.0 | rigid_props=sim_utils.RigidBodyPropertiesCfg( | -| | rigid_body_enabled=True, | -| physx: | max_linear_velocity=1000.0, | -| worker_thread_count: ${....num_threads} | max_angular_velocity=1000.0, | -| solver_type: ${....solver_type} | max_depenetration_velocity=100.0, | -| use_gpu: ${eq:${....sim_device},"gpu"} # set to False to... | enable_gyroscopic_forces=True, | -| solver_position_iteration_count: 4 | ), | -| solver_velocity_iteration_count: 0 | articulation_props=sim_utils.ArticulationRootPropertiesCfg( | -| contact_offset: 0.02 | enabled_self_collisions=False, | -| rest_offset: 0.001 | solver_position_iteration_count=4, | -| bounce_threshold_velocity: 0.2 | solver_velocity_iteration_count=0, | -| friction_offset_threshold: 0.04 | sleep_threshold=0.005, | -| friction_correlation_distance: 0.025 | stabilization_threshold=0.001, | -| enable_sleeping: True | ), | -| enable_stabilization: True | ), | -| max_depenetration_velocity: 100.0 | init_state=ArticulationCfg.InitialStateCfg( | -| | pos=(0.0, 0.0, 2.0), | -| # GPU buffers | joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0} | -| gpu_max_rigid_contact_count: 524288 | ), | -| gpu_max_rigid_patch_count: 81920 | actuators={ | -| gpu_found_lost_pairs_capacity: 1024 | "cart_actuator": ImplicitActuatorCfg( | -| gpu_found_lost_aggregate_pairs_capacity: 262144 | joint_names_expr=["slider_to_cart"], | -| gpu_total_aggregate_pairs_capacity: 1024 | effort_limit=400.0, | -| gpu_max_soft_body_contacts: 1048576 | velocity_limit=100.0, | -| gpu_max_particle_contacts: 1048576 | stiffness=0.0, | -| gpu_heap_capacity: 67108864 | damping=10.0, | -| gpu_temp_buffer_capacity: 16777216 | ), | -| gpu_max_num_partitions: 8 | "pole_actuator": ImplicitActuatorCfg( | -| | joint_names_expr=["cart_to_pole"], effort_limit=400.0, | -| Cartpole: | velocity_limit=100.0, stiffness=0.0, damping=0.0 | -| override_usd_defaults: False | ), | -| enable_self_collisions: False | }, | -| enable_gyroscopic_forces: True | ) | -| solver_position_iteration_count: 4 | | -| solver_velocity_iteration_count: 0 | | -| sleep_threshold: 0.005 | | -| stabilization_threshold: 0.001 | | -| density: -1 | | -| max_depenetration_velocity: 100.0 | | -| contact_offset: 0.02 | | -| rest_offset: 0.001 | | -+-----------------------------------------------------------------+-----------------------------------------------------------------+ - - - -Task Setup ----------- - -The ``post_reset`` API in OmniIsaacGymEnvs is no longer required in Isaac Lab. -Everything that was previously done in ``post_reset`` can be done in the ``__init__`` method after -executing the base class's ``__init__``. At this point, simulation has already started. - -+-------------------------------------------------------------------------+-------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-------------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| class CartpoleTask(RLTask): | class CartpoleEnv(DirectRLEnv): | -| | cfg: CartpoleEnvCfg | -| def __init__(self, name, sim_config, env, offset=None) -> None: | def __init__(self, cfg: CartpoleEnvCfg, | -| | render_mode: str | None = None, **kwargs): | -| self.update_config(sim_config) | super().__init__(cfg, render_mode, **kwargs) | -| self._max_episode_length = 500 | | -| | | -| self._num_observations = 4 | self._cart_dof_idx, _ = self.cartpole.find_joints( | -| self._num_actions = 1 | self.cfg.cart_dof_name) | -| | self._pole_dof_idx, _ = self.cartpole.find_joints( | -| RLTask.__init__(self, name, env) | self.cfg.pole_dof_name) | -| | self.action_scale=self.cfg.action_scale | -| def update_config(self, sim_config): | | -| self._sim_config = sim_config | self.joint_pos = self.cartpole.data.joint_pos | -| self._cfg = sim_config.config | self.joint_vel = self.cartpole.data.joint_vel | -| self._task_cfg = sim_config. | | -| task_config | | -| | | -| self._num_envs = self._task_cfg["env"]["numEnvs"] | | -| self._env_spacing = self._task_cfg["env"]["envSpacing"] | | -| self._cartpole_positions = torch.tensor([0.0, 0.0, 2.0]) | | -| | | -| self._reset_dist = self._task_cfg["env"]["resetDist"] | | -| self._max_push_effort = self._task_cfg["env"]["maxEffort"] | | -| | | -| | | -| def post_reset(self): | | -| self._cart_dof_idx = self._cartpoles.get_dof_index( | | -| "cartJoint") | | -| self._pole_dof_idx = self._cartpoles.get_dof_index( | | -| "poleJoint") | | -| # randomize all envs | | -| indices = torch.arange( | | -| self._cartpoles.count, dtype=torch.int64, | | -| device=self._device) | | -| self.reset_idx(indices) | | -+-------------------------------------------------------------------------+-------------------------------------------------------------+ - - - -Scene Setup ------------ - -The ``set_up_scene`` method in OmniIsaacGymEnvs has been replaced by the ``_setup_scene`` API in the task class in -Isaac Lab. Additionally, scene cloning and collision filtering have been provided as APIs for the task class to -call when necessary. Similarly, adding ground plane and lights should also be taken care of in the task class. -Adding actors to the scene has been replaced by ``self.scene.articulations["cartpole"] = self.cartpole``. - -+-----------------------------------------------------------+----------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-----------------------------------------------------------+----------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def set_up_scene(self, scene) -> None: | def _setup_scene(self): | -| | self.cartpole = Articulation(self.cfg.robot_cfg) | -| self.get_cartpole() | # add ground plane | -| super().set_up_scene(scene) | spawn_ground_plane(prim_path="/World/ground", | -| self._cartpoles = ArticulationView( | cfg=GroundPlaneCfg()) | -| prim_paths_expr="/World/envs/.*/Cartpole", | # clone, filter, and replicate | -| name="cartpole_view", | self.scene.clone_environments( | -| reset_xform_properties=False | copy_from_source=False) | -| ) | self.scene.filter_collisions( | -| scene.add(self._cartpoles) | global_prim_paths=[]) | -| return | # add articultion to scene | -| | self.scene.articulations["cartpole"] = self.cartpole | -| def get_cartpole(self): | | -| cartpole = Cartpole( | # add lights | -| prim_path=self.default_zero_env_path+"/Cartpole", | light_cfg = sim_utils.DomeLightCfg( | -| name="Cartpole", | intensity=2000.0, color=(0.75, 0.75, 0.75)) | -| translation=self._cartpole_positions | light_cfg.func("/World/Light", light_cfg) | -| ) | | -| # applies articulation settings from the | | -| # task configuration yaml file | | -| self._sim_config.apply_articulation_settings( | | -| "Cartpole", get_prim_at_path(cartpole.prim_path), | | -| self._sim_config.parse_actor_config("Cartpole") | | -| ) | | -+-----------------------------------------------------------+----------------------------------------------------------+ - - -Pre-Physics Step ----------------- - -Note that resets are no longer performed in the ``pre_physics_step`` API. In addition, the separation of the -``_pre_physics_step`` and ``_apply_action`` methods allow for more flexibility in processing the action buffer -and setting actions into simulation. - -+------------------------------------------------------------------+-------------------------------------------------------------+ -| OmniIsaacGymEnvs | IsaacLab | -+------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def pre_physics_step(self, actions) -> None: | def _pre_physics_step(self, | -| if not self.world.is_playing(): | actions: torch.Tensor) -> None: | -| return | self.actions = self.action_scale * actions | -| | | -| reset_env_ids = self.reset_buf.nonzero( | def _apply_action(self) -> None: | -| as_tuple=False).squeeze(-1) | self.cartpole.set_joint_effort_target( | -| if len(reset_env_ids) > 0: | self.actions, joint_ids=self._cart_dof_idx) | -| self.reset_idx(reset_env_ids) | | -| | | -| actions = actions.to(self._device) | | -| | | -| forces = torch.zeros((self._cartpoles.count, | | -| self._cartpoles.num_dof), | | -| dtype=torch.float32, device=self._device) | | -| forces[:, self._cart_dof_idx] = | | -| self._max_push_effort * actions[:, 0] | | -| | | -| indices = torch.arange(self._cartpoles.count, | | -| dtype=torch.int32, device=self._device) | | -| self._cartpoles.set_joint_efforts( | | -| forces, indices=indices) | | -+------------------------------------------------------------------+-------------------------------------------------------------+ - - -Dones and Resets ----------------- - -In Isaac Lab, the ``dones`` are computed in the ``_get_dones()`` method and should return two variables: ``resets`` and -``time_out``. The ``_reset_idx()`` method is also called after stepping simulation instead of before, as it was done in -OmniIsaacGymEnvs. The ``progress_buf`` tensor has been renamed to ``episode_length_buf`` in Isaac Lab and the -bookkeeping is now done automatically by the framework. Task implementations no longer need to increment or -reset the ``episode_length_buf`` buffer. - -+------------------------------------------------------------------+--------------------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+--------------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def is_done(self) -> None: | def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]: | -| resets = torch.where( | self.joint_pos = self.cartpole.data.joint_pos | -| torch.abs(self.cart_pos) > self._reset_dist, 1, 0) | self.joint_vel = self.cartpole.data.joint_vel | -| resets = torch.where( | | -| torch.abs(self.pole_pos) > math.pi / 2, 1, resets) | time_out = self.episode_length_buf >= self.max_episode_length - 1 | -| resets = torch.where( | out_of_bounds = torch.any(torch.abs( | -| self.progress_buf >= self._max_episode_length, 1, resets) | self.joint_pos[:, self._pole_dof_idx] > self.cfg.max_cart_pos), | -| self.reset_buf[:] = resets | dim=1) | -| | out_of_bounds = out_of_bounds | torch.any( | -| | torch.abs(self.joint_pos[:, self._pole_dof_idx]) > math.pi / 2, | -| | dim=1) | -| | return out_of_bounds, time_out | -| | | -| def reset_idx(self, env_ids): | def _reset_idx(self, env_ids: Sequence[int] | None): | -| num_resets = len(env_ids) | if env_ids is None: | -| | env_ids = self.cartpole._ALL_INDICES | -| # randomize DOF positions | super()._reset_idx(env_ids) | -| dof_pos = torch.zeros((num_resets, self._cartpoles.num_dof), | | -| device=self._device) | joint_pos = self.cartpole.data.default_joint_pos[env_ids] | -| dof_pos[:, self._cart_dof_idx] = 1.0 * ( | joint_pos[:, self._pole_dof_idx] += sample_uniform( | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | self.cfg.initial_pole_angle_range[0] * math.pi, | -| dof_pos[:, self._pole_dof_idx] = 0.125 * math.pi * ( | self.cfg.initial_pole_angle_range[1] * math.pi, | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | joint_pos[:, self._pole_dof_idx].shape, | -| | joint_pos.device, | -| # randomize DOF velocities | ) | -| dof_vel = torch.zeros((num_resets, self._cartpoles.num_dof), | joint_vel = self.cartpole.data.default_joint_vel[env_ids] | -| device=self._device) | | -| dof_vel[:, self._cart_dof_idx] = 0.5 * ( | default_root_state = self.cartpole.data.default_root_state[env_ids] | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | default_root_state[:, :3] += self.scene.env_origins[env_ids] | -| dof_vel[:, self._pole_dof_idx] = 0.25 * math.pi * ( | | -| 1.0 - 2.0 * torch.rand(num_resets, device=self._device)) | self.joint_pos[env_ids] = joint_pos | -| | self.joint_vel[env_ids] = joint_vel | -| # apply resets | | -| indices = env_ids.to(dtype=torch.int32) | self.cartpole.write_root_pose_to_sim( | -| self._cartpoles.set_joint_positions(dof_pos, indices=indices) | default_root_state[:, :7], env_ids) | -| self._cartpoles.set_joint_velocities(dof_vel, indices=indices) | self.cartpole.write_root_velocity_to_sim( | -| | default_root_state[:, 7:], env_ids) | -| # bookkeeping | self.cartpole.write_joint_state_to_sim( | -| self.reset_buf[env_ids] = 0 | joint_pos, joint_vel, None, env_ids) | -| self.progress_buf[env_ids] = 0 | | -| | | -| | | -+------------------------------------------------------------------+--------------------------------------------------------------------------+ - - -Rewards -------- - -In Isaac Lab, rewards are implemented in the ``_get_rewards`` API and should return the reward buffer instead of assigning -it directly to ``self.rew_buf``. Computation in the reward function can also be performed using pytorch jit -through defining functions with the ``@torch.jit.script`` annotation. - -+-------------------------------------------------------+-----------------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+-------------------------------------------------------+-----------------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| def calculate_metrics(self) -> None: | def _get_rewards(self) -> torch.Tensor: | -| reward = (1.0 - self.pole_pos * self.pole_pos | total_reward = compute_rewards( | -| - 0.01 * torch.abs(self.cart_vel) - 0.005 | self.cfg.rew_scale_alive, | -| * torch.abs(self.pole_vel)) | self.cfg.rew_scale_terminated, | -| reward = torch.where( | self.cfg.rew_scale_pole_pos, | -| torch.abs(self.cart_pos) > self._reset_dist, | self.cfg.rew_scale_cart_vel, | -| torch.ones_like(reward) * -2.0, reward) | self.cfg.rew_scale_pole_vel, | -| reward = torch.where( | self.joint_pos[:, self._pole_dof_idx[0]], | -| torch.abs(self.pole_pos) > np.pi / 2, | self.joint_vel[:, self._pole_dof_idx[0]], | -| torch.ones_like(reward) * -2.0, reward) | self.joint_pos[:, self._cart_dof_idx[0]], | -| | self.joint_vel[:, self._cart_dof_idx[0]], | -| self.rew_buf[:] = reward | self.reset_terminated, | -| | ) | -| | return total_reward | -| | | -| | @torch.jit.script | -| | def compute_rewards( | -| | rew_scale_alive: float, | -| | rew_scale_terminated: float, | -| | rew_scale_pole_pos: float, | -| | rew_scale_cart_vel: float, | -| | rew_scale_pole_vel: float, | -| | pole_pos: torch.Tensor, | -| | pole_vel: torch.Tensor, | -| | cart_pos: torch.Tensor, | -| | cart_vel: torch.Tensor, | -| | reset_terminated: torch.Tensor, | -| | ): | -| | rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) | -| | rew_termination = rew_scale_terminated * reset_terminated.float() | -| | rew_pole_pos = rew_scale_pole_pos * torch.sum( | -| | torch.square(pole_pos), dim=-1) | -| | rew_cart_vel = rew_scale_cart_vel * torch.sum( | -| | torch.abs(cart_vel), dim=-1) | -| | rew_pole_vel = rew_scale_pole_vel * torch.sum( | -| | torch.abs(pole_vel), dim=-1) | -| | total_reward = (rew_alive + rew_termination | -| | + rew_pole_pos + rew_cart_vel + rew_pole_vel) | -| | return total_reward | -+-------------------------------------------------------+-----------------------------------------------------------------------+ - - -Observations ------------- - -In Isaac Lab, the ``_get_observations()`` API must return a dictionary with the key ``policy`` that has the observation buffer as the value. -When working with asymmetric actor-critic states, the states for the critic should have the key ``critic`` and be returned -with the observation buffer in the same dictionary. - -+------------------------------------------------------------------+-------------------------------------------------------------+ -| OmniIsaacGymEnvs | Isaac Lab | -+------------------------------------------------------------------+-------------------------------------------------------------+ -|.. code-block:: python |.. code-block:: | -| | | -| def get_observations(self) -> dict: | def _get_observations(self) -> dict: | -| dof_pos = self._cartpoles.get_joint_positions(clone=False) | obs = torch.cat( | -| dof_vel = self._cartpoles.get_joint_velocities(clone=False) | ( | -| | self.joint_pos[:, self._pole_dof_idx[0]], | -| self.cart_pos = dof_pos[:, self._cart_dof_idx] | self.joint_vel[:, self._pole_dof_idx[0]], | -| self.cart_vel = dof_vel[:, self._cart_dof_idx] | self.joint_pos[:, self._cart_dof_idx[0]], | -| self.pole_pos = dof_pos[:, self._pole_dof_idx] | self.joint_vel[:, self._cart_dof_idx[0]], | -| self.pole_vel = dof_vel[:, self._pole_dof_idx] | ), | -| self.obs_buf[:, 0] = self.cart_pos | dim=-1, | -| self.obs_buf[:, 1] = self.cart_vel | ) | -| self.obs_buf[:, 2] = self.pole_pos | observations = {"policy": obs} | -| self.obs_buf[:, 3] = self.pole_vel | return observations | -| | | -| observations = {self._cartpoles.name: | | -| {"obs_buf": self.obs_buf}} | | -| return observations | | -+------------------------------------------------------------------+-------------------------------------------------------------+ - - -Domain Randomization -~~~~~~~~~~~~~~~~~~~~ - -In OmniIsaacGymEnvs, domain randomization was specified through the task ``.yaml`` config file. -In Isaac Lab, the domain randomization configuration uses the :class:`~omni.isaac.lab.utils.configclass` module -to specify a configuration class consisting of :class:`~managers.EventTermCfg` variables. - -Below is an example of a configuration class for domain randomization: - -.. code-block:: python - - @configclass - class EventCfg: - robot_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names=".*"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (1.0, 1.0), - "restitution_range": (1.0, 1.0), - "num_buckets": 250, - }, - ) - robot_joint_stiffness_and_damping = EventTerm( - func=mdp.randomize_actuator_gains, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), - "stiffness_distribution_params": (0.75, 1.5), - "damping_distribution_params": (0.3, 3.0), - "operation": "scale", - "distribution": "log_uniform", - }, - ) - reset_gravity = EventTerm( - func=mdp.randomize_physics_scene_gravity, - mode="interval", - is_global_time=True, - interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) - params={ - "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), - "operation": "add", - "distribution": "gaussian", - }, - ) - -Each ``EventTerm`` object is of the :class:`~managers.EventTermCfg` class and takes in a ``func`` parameter -for specifying the function to call during randomization, a ``mode`` parameter, which can be ``startup``, -``reset`` or ``interval``. THe ``params`` dictionary should provide the necessary arguments to the -function that is specified in the ``func`` parameter. -Functions specified as ``func`` for the ``EventTerm`` can be found in the :class:`~envs.mdp.events` module. - -Note that as part of the ``"asset_cfg": SceneEntityCfg("robot", body_names=".*")`` parameter, the name of -the actor ``"robot"`` is provided, along with the body or joint names specified as a regex expression, -which will be the actors and bodies/joints that will have randomization applied. - -One difference with OmniIsaacGymEnvs is that ``interval`` randomization is now specified as seconds instead of -steps. When ``mode="interval"``, the ``interval_range_s`` parameter must also be provided, which specifies -the range of seconds for which randomization should be applied. This range will then be randomized to -determine a specific time in seconds when the next randomization will occur for the term. -To convert between steps to seconds, use the equation ``time_s = num_steps * (decimation * dt)``. - -Similar to OmniIsaacGymEnvs, randomization APIs are available for randomizing articulation properties, -such as joint stiffness and damping, joint limits, rigid body materials, fixed tendon properties, -as well as rigid body properties, such as mass and rigid body materials. Randomization of the -physics scene gravity is also supported. Note that randomization of scale is current not supported -in Isaac Lab. To randomize scale, please set up the scene in a way where each environment holds the actor -at a different scale. - -Once the ``configclass`` for the randomization terms have been set up, the class must be added -to the base config class for the task and be assigned to the variable ``events``. - -.. code-block:: python - - @configclass - class MyTaskConfig: - events: EventCfg = EventCfg() - - -Action and Observation Noise ----------------------------- - -Actions and observation noise can also be added using the :class:`~utils.configclass` module. -Action and observation noise configs must be added to the main task config using the -``action_noise_model`` and ``observation_noise_model`` variables: - -.. code-block:: python - - @configclass - class MyTaskConfig: - # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), - ) - # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), - ) - - -:class:`~.utils.noise.NoiseModelWithAdditiveBiasCfg` can be used to sample both uncorrelated noise -per step as well as correlated noise that is re-sampled at reset time. -The ``noise_cfg`` term specifies the Gaussian distribution that will be sampled at each -step for all environments. This noise will be added to the corresponding actions and -observations buffers at every step. -The ``bias_noise_cfg`` term specifies the Gaussian distribution for the correlated noise -that will be sampled at reset time for the environments being reset. The same noise -will be applied each step for the remaining of the episode for the environments and -resampled at the next reset. - -This replaces the following setup in OmniIsaacGymEnvs: - -.. code-block:: yaml - - domain_randomization: - randomize: True - randomization_params: - observations: - on_reset: - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0, .0001] - on_interval: - frequency_interval: 1 - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0, .002] - actions: - on_reset: - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0, 0.015] - on_interval: - frequency_interval: 1 - operation: "additive" - distribution: "gaussian" - distribution_parameters: [0., 0.05] - - -Launching Training -~~~~~~~~~~~~~~~~~~ - -To launch a training in Isaac Lab, use the command: - -.. code-block:: bash - - python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 --headless - -Launching Inferencing -~~~~~~~~~~~~~~~~~~~~~ - -To launch inferencing in Isaac Lab, use the command: - -.. code-block:: bash - - python source/standalone/workflows/rl_games/play.py --task=Isaac-Cartpole-Direct-v0 --num_envs=25 --checkpoint= - - -.. _`OmniIsaacGymEnvs`: https://github.com/isaac-sim/OmniIsaacGymEnvs diff --git a/docs/_build/html/_sources/source/migration/migrating_from_orbit.rst b/docs/_build/html/_sources/source/migration/migrating_from_orbit.rst deleted file mode 100644 index e4cf685fa1..0000000000 --- a/docs/_build/html/_sources/source/migration/migrating_from_orbit.rst +++ /dev/null @@ -1,131 +0,0 @@ -.. _migrating-from-orbit: - -From Orbit -========== - -.. currentmodule:: omni.isaac.lab - -Since `Orbit`_ was used as basis for Isaac Lab, migrating from Orbit to Isaac Lab is straightforward. -The following sections describe the changes that need to be made to your code to migrate from Orbit to Isaac Lab. - -Renaming of the launch script -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The script ``orbit.sh`` has been renamed to ``isaaclab.sh``. - - -Updates to extensions -~~~~~~~~~~~~~~~~~~~~~ - -The extensions ``omni.isaac.orbit``, ``omni.isaac.orbit_tasks``, and ``omni.isaac.orbit_assets`` have been renamed -to ``omni.isaac.lab``, ``omni.isaac.lab_tasks``, and ``omni.isaac.lab_assets``, respectively. Thus, -the new folder structure looks like this: - -- ``source/extensions/omni.isaac.lab/omni/isaac/lab`` -- ``source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks`` -- ``source/extensions/omni.isaac.lab_assets/omni/isaac/lab_assets`` - -The high level imports have to be updated as well: - -+-------------------------------------+-----------------------------------+ -| Orbit | Isaac Lab | -+=====================================+===================================+ -| ``from omni.isaac.orbit...`` | ``from omni.isaac.lab...`` | -+-------------------------------------+-----------------------------------+ -| ``from omni.isaac.orbit_tasks...`` | ``from omni.isaac.lab_tasks...`` | -+-------------------------------------+-----------------------------------+ -| ``from omni.isaac.orbit_assets...`` | ``from omni.isaac.lab_assets...`` | -+-------------------------------------+-----------------------------------+ - - -Updates to class names -~~~~~~~~~~~~~~~~~~~~~~ - -In Isaac Lab, we introduced the concept of task design workflows (see :ref:`feature-workflows`). The Orbit code is using -the manager-based workflow and the environment specific class names have been updated to reflect this change: - -+------------------------+---------------------------------------------------------+ -| Orbit | Isaac Lab | -+========================+=========================================================+ -| ``BaseEnv`` | :class:`omni.isaac.lab.envs.ManagerBasedEnv` | -+------------------------+---------------------------------------------------------+ -| ``BaseEnvCfg`` | :class:`omni.isaac.lab.envs.ManagerBasedEnvCfg` | -+------------------------+---------------------------------------------------------+ -| ``RLTaskEnv`` | :class:`omni.isaac.lab.envs.ManagerBasedRLEnv` | -+------------------------+---------------------------------------------------------+ -| ``RLTaskEnvCfg`` | :class:`omni.isaac.lab.envs.ManagerBasedRLEnvCfg` | -+------------------------+---------------------------------------------------------+ -| ``RLTaskEnvWindow`` | :class:`omni.isaac.lab.envs.ui.ManagerBasedRLEnvWindow` | -+------------------------+---------------------------------------------------------+ - - -Updates to the tasks folder structure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To support the manager-based and direct workflows, we have added two folders in the tasks extension: - -- ``source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based`` -- ``source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct`` - -The tasks from Orbit can now be found under the ``manager_based`` folder. -This change must also be reflected in the imports for your tasks. For example, - -.. code-block:: python - - from omni.isaac.orbit_tasks.locomotion.velocity.velocity_env_cfg ... - -should now be: - -.. code-block:: python - - from omni.isaac.lab_tasks.manager_based.locomotion.velocity.velocity_env_cfg ... - - -Other Breaking changes -~~~~~~~~~~~~~~~~~~~~~~ - -Offscreen rendering -------------------- - -The input argument ``--offscreen_render`` given to :class:`omni.isaac.lab.app.AppLauncher` and the environment variable -``OFFSCREEN_RENDER`` have been renamed to ``--enable_cameras`` and ``ENABLE_CAMERAS`` respectively. - - -Event term distribution configuration -------------------------------------- - -Some of the event functions in `events.py `_ -accepted a ``distribution`` parameter and a ``range`` to sample from. In an effort to support arbitrary distributions, -we have renamed the input argument ``AAA_range`` to ``AAA_distribution_params`` for these functions. -Therefore, event term configurations whose functions have a ``distribution`` argument should be updated. For example, - -.. code-block:: python - :emphasize-lines: 6 - - add_base_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names="base"), - "mass_range": (-5.0, 5.0), - "operation": "add", - }, - ) - -should now be: - -.. code-block:: python - :emphasize-lines: 6 - - add_base_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names="base"), - "mass_distribution_params": (-5.0, 5.0), - "operation": "add", - }, - ) - - -.. _Orbit: https://isaac-orbit.github.io/ diff --git a/docs/_build/html/_sources/source/refs/bibliography.rst b/docs/_build/html/_sources/source/refs/bibliography.rst deleted file mode 100644 index 0d21440d01..0000000000 --- a/docs/_build/html/_sources/source/refs/bibliography.rst +++ /dev/null @@ -1,4 +0,0 @@ -Bibliography -============ - -.. bibliography:: diff --git a/docs/_build/html/_sources/source/refs/changelog.rst b/docs/_build/html/_sources/source/refs/changelog.rst deleted file mode 100644 index 0538e7c43c..0000000000 --- a/docs/_build/html/_sources/source/refs/changelog.rst +++ /dev/null @@ -1,38 +0,0 @@ -Extensions Changelog -==================== - -All notable changes to this project are documented in this file. The format is based on -`Keep a Changelog `__ and this project adheres to -`Semantic Versioning `__. For a broader information -about the changes in the framework, please refer to the -`release notes `__. - -Each extension has its own changelog. The changelog for each extension is located in the -``docs`` directory of the extension. The changelog for each extension is also included in -this changelog to make it easier to find the changelog for a specific extension. - -omni.isaac.lab --------------- - -Extension containing the core framework of Isaac Lab. - -.. include:: ../../../source/extensions/omni.isaac.lab/docs/CHANGELOG.rst - :start-line: 3 - - -omni.isaac.lab_assets ---------------------- - -Extension for configurations of various assets and sensors for Isaac Lab. - -.. include:: ../../../source/extensions/omni.isaac.lab_assets/docs/CHANGELOG.rst - :start-line: 3 - - -omni.isaac.lab_tasks --------------------- - -Extension containing the environments built using Isaac Lab. - -.. include:: ../../../source/extensions/omni.isaac.lab_tasks/docs/CHANGELOG.rst - :start-line: 3 diff --git a/docs/_build/html/_sources/source/refs/contributing.rst b/docs/_build/html/_sources/source/refs/contributing.rst deleted file mode 100644 index 4fff39f8d2..0000000000 --- a/docs/_build/html/_sources/source/refs/contributing.rst +++ /dev/null @@ -1,270 +0,0 @@ -Contribution Guidelines -======================= - -We wholeheartedly welcome contributions to the project to make the framework more mature -and useful for everyone. These may happen in forms of: - -* Bug reports: Please report any bugs you find in the `issue tracker `__. -* Feature requests: Please suggest new features you would like to see in the `discussions `__. -* Code contributions: Please submit a `pull request `__. - - * Bug fixes - * New features - * Documentation improvements - * Tutorials and tutorial improvements - - -.. attention:: - - We prefer GitHub `discussions `_ for discussing ideas, - asking questions, conversations and requests for new features. - - Please use the - `issue tracker `_ only to track executable pieces of work - with a definite scope and a clear deliverable. These can be fixing bugs, new features, or general updates. - - -Contributing Code ------------------ - -We use `GitHub `__ for code hosting. Please -follow the following steps to contribute code: - -1. Create an issue in the `issue tracker `__ to discuss - the changes or additions you would like to make. This helps us to avoid duplicate work and to make - sure that the changes are aligned with the roadmap of the project. -2. Fork the repository. -3. Create a new branch for your changes. -4. Make your changes and commit them. -5. Push your changes to your fork. -6. Submit a pull request to the `main branch `__. -7. Ensure all the checks on the pull request template are performed. - -After sending a pull request, the maintainers will review your code and provide feedback. - -Please ensure that your code is well-formatted, documented and passes all the tests. - -.. tip:: - - It is important to keep the pull request as small as possible. This makes it easier for the - maintainers to review your code. If you are making multiple changes, please send multiple pull requests. - Large pull requests are difficult to review and may take a long time to merge. - - -Coding Style ------------- - -We follow the `Google Style -Guides `__ for the -codebase. For Python code, the PEP guidelines are followed. Most -important ones are `PEP-8 `__ -for code comments and layout, -`PEP-484 `__ and -`PEP-585 `__ for -type-hinting. - -For documentation, we adopt the `Google Style Guide `__ -for docstrings. We use `Sphinx `__ for generating the documentation. -Please make sure that your code is well-documented and follows the guidelines. - -Circular Imports -^^^^^^^^^^^^^^^^ - -Circular imports happen when two modules import each other, which is a common issue in Python. -You can prevent circular imports by adhering to the best practices outlined in this -`StackOverflow post `__. - -In general, it is essential to avoid circular imports as they can lead to unpredictable behavior. - -However, in our codebase, we encounter circular imports at a sub-package level. This situation arises -due to our specific code structure. We organize classes or functions and their corresponding configuration -objects into separate files. This separation enhances code readability and maintainability. Nevertheless, -it can result in circular imports because, in many configuration objects, we specify classes or functions -as default values using the attributes ``class_type`` and ``func`` respectively. - -To address circular imports, we leverage the `typing.TYPE_CHECKING -`_ variable. This special variable is -evaluated only during type-checking, allowing us to import classes or functions in the configuration objects -without triggering circular imports. - -It is important to note that this is the sole instance within our codebase where circular imports are used -and are acceptable. In all other scenarios, we adhere to best practices and recommend that you do the same. - -Type-hinting -^^^^^^^^^^^^ - -To make the code more readable, we use `type hints `__ for -all the functions and classes. This helps in understanding the code and makes it easier to maintain. Following -this practice also helps in catching bugs early with static type checkers like `mypy `__. - -To avoid duplication of efforts, we do not specify type hints for the arguments and return values in the docstrings. -However, if your function or class is not self-explanatory, please add a docstring with the type hints. - -Tools -^^^^^ - -We use the following tools for maintaining code quality: - -* `pre-commit `__: Runs a list of formatters and linters over the codebase. -* `black `__: The uncompromising code formatter. -* `flake8 `__: A wrapper around PyFlakes, pycodestyle and - McCabe complexity checker. - -Please check `here `__ for instructions -to set these up. To run over the entire repository, please execute the -following command in the terminal: - -.. code:: bash - - ./isaaclab.sh --format # or "./isaaclab.sh -f" - -Contributing Documentation --------------------------- - -Contributing to the documentation is as easy as contributing to the codebase. All the source files -for the documentation are located in the ``IsaacLab/docs`` directory. The documentation is written in -`reStructuredText `__ format. - -We use `Sphinx `__ with the -`Book Theme `__ -for maintaining the documentation. - -Sending a pull request for the documentation is the same as sending a pull request for the codebase. -Please follow the steps mentioned in the `Contributing Code`_ section. - -.. caution:: - - To build the documentation, we recommend creating a `virtual environment `__ - to install the dependencies. This can also be a `conda environment `__. - - -To build the documentation, run the following command in the terminal which installs the required python packages and -builds the documentation using the ``docs/Makefile``: - -.. code:: bash - - ./isaaclab.sh --docs # or "./isaaclab.sh -d" - -The documentation is generated in the ``docs/_build`` directory. To view the documentation, open -the ``index.html`` file in the ``html`` directory. This can be done by running the following command -in the terminal: - -.. code:: bash - - xdg-open docs/_build/html/index.html - -.. hint:: - - The ``xdg-open`` command is used to open the ``index.html`` file in the default browser. If you are - using a different operating system, you can use the appropriate command to open the file in the browser. - - -To do a clean build, run the following command in the terminal: - -.. code:: bash - - rm -rf docs/_build && ./isaaclab.sh --docs - - -Contributing assets -------------------- - -Currently, we host the assets for the extensions on `NVIDIA Nucleus Server `__. -Nucleus is a cloud-based storage service that allows users to store and share large files. It is -integrated with the `NVIDIA Omniverse Platform `__. - -Since all assets are hosted on Nucleus, we do not need to include them in the repository. However, -we need to include the links to the assets in the documentation. - -The included assets are part of the `Isaac Sim Content `__. -To use this content, you need to download the files to a Nucleus server or create an **Isaac** Mount on -a Nucleus server. - -Please check the `Isaac Sim documentation `__ -for more information on how to download the assets. - -.. attention:: - - We are currently working on a better way to contribute assets. We will update this section once we - have a solution. In the meantime, please follow the steps mentioned below. - -To host your own assets, the current solution is: - -1. Create a separate repository for the assets and add it over there -2. Make sure the assets are licensed for use and distribution -3. Include images of the assets in the README file of the repository -4. Send a pull request with a link to the repository - -We will then verify the assets, its licensing, and include the assets into the Nucleus server for hosting. -In case you have any questions, please feel free to reach out to us through e-mail or by opening an issue -in the repository. - - -Maintaining a changelog ------------------------ - -Each extension maintains a changelog in the ``CHANGELOG.rst`` file in the ``docs`` directory. The -file is written in `reStructuredText `__ format. It -contains a curated, chronologically ordered list of notable changes for each version of the extension. - -The goal of this changelog is to help users and contributors see precisely what notable changes have -been made between each release (or version) of the extension. This is a *MUST* for every extension. - -For updating the changelog, please follow the following guidelines: - -* Each version should have a section with the version number and the release date. -* The version number is updated according to `Semantic Versioning `__. The - release date is the date on which the version is released. -* Each version is divided into subsections based on the type of changes made. - - * ``Added``: For new features. - * ``Changed``: For changes in existing functionality. - * ``Deprecated``: For soon-to-be removed features. - * ``Removed``: For now removed features. - * ``Fixed``: For any bug fixes. - -* Each change is described in its corresponding sub-section with a bullet point. -* The bullet points are written in the past tense and in imperative mode. - -For example, the following is a sample changelog: - -.. code:: rst - - Changelog - --------- - - 0.1.0 (2021-02-01) - ~~~~~~~~~~~~~~~~~~ - - Added - ^^^^^ - - * Added a new feature. - - Changed - ^^^^^^^ - - * Changed an existing feature. - - Deprecated - ^^^^^^^^^^ - - * Deprecated an existing feature. - - Removed - ^^^^^^^ - - * Removed an existing feature. - - Fixed - ^^^^^ - - * Fixed a bug. - - 0.0.1 (2021-01-01) - ~~~~~~~~~~~~~~~~~~ - - Added - ^^^^^ - - * Added a new feature. diff --git a/docs/_build/html/_sources/source/refs/issues.rst b/docs/_build/html/_sources/source/refs/issues.rst deleted file mode 100644 index 4a760e6d85..0000000000 --- a/docs/_build/html/_sources/source/refs/issues.rst +++ /dev/null @@ -1,117 +0,0 @@ -Known Issues -============ - -.. attention:: - - Please also refer to the `Omniverse Isaac Sim documentation`_ for known issues and workarounds. - -Stale values after resetting the environment --------------------------------------------- - -When resetting the environment, some of the data fields of assets and sensors are not updated. -These include the poses of links in a kinematic chain, the camera images, the contact sensor readings, -and the lidar point clouds. This is a known issue which has to do with the way the PhysX and -rendering engines work in Omniverse. - -Many physics engines do a simulation step as a two-level call: ``forward()`` and ``simulate()``, -where the kinematic and dynamic states are updated, respectively. Unfortunately, PhysX has only a -single ``step()`` call where the two operations are combined. Due to computations through GPU -kernels, it is not so straightforward for them to split these operations. Thus, at the moment, -it is not possible to set the root and/or joint states and do a forward call to update the -kinematic states of links. This affects both initialization as well as episodic resets. - -Similarly for RTX rendering related sensors (such as cameras), the sensor data is not updated -immediately after setting the state of the sensor. The rendering engine update is bundled with -the simulator's ``step()`` call which only gets called when the simulation is stepped forward. -This means that the sensor data is not updated immediately after a reset and it will hold -outdated values. - -While the above is erroneous, there is currently no direct workaround for it. From our experience in -using IsaacGym, the reset values affect the agent learning critically depending on how frequently -the environment terminates. Eventually if the agent is learning successfully, this number drops -and does not affect the performance that critically. - -We have made a feature request to the respective Omniverse teams to have complete control -over stepping different parts of the simulation app. However, at this point, there is no set -timeline for this feature request. - - -Non-determinism in physics simulation -------------------------------------- - -Due to GPU work scheduling, there's a possibility that runtime changes to simulation parameters -may alter the order in which operations take place. This occurs because environment updates can -happen while the GPU is occupied with other tasks. Due to the inherent nature of floating-point -numeric storage, any modification to the execution ordering can result in minor changes in the -least significant bits of output data. These changes may lead to divergent execution over the -course of simulating thousands of environments and simulation frames. - -An illustrative example of this issue is observed with the runtime domain randomization of object's -physics materials. This process can introduce both determinancy and simulation issues when executed -on the GPU due to the way these parameters are passed from the CPU to the GPU in the lower-level APIs. -Consequently, it is strongly advised to perform this operation only at setup time, before the -environment stepping commences. - -For more information, please refer to the `PhysX Determinism documentation`_. - -In addition, due to floating point precision, states across different environments in the simulation -may be non-deterministic when the same set of actions are applied to the same initial -states. This occurs as environments are placed further apart from the world origin at (0, 0, 0). -As actors get placed at different origins in the world, floating point errors may build up -and result in slight variance in results even when starting from the same initial states. One -possible workaround for this issue is to place all actors/environments at the world origin -at (0, 0, 0) and filter out collisions between the environments. Note that this may induce -a performance degradation of around 15-50%, depending on the complexity of actors and -environment. - - -Blank initial frames from the camera ------------------------------------- - -When using the :class:`omni.isaac.lab.sensors.Camera` sensor in standalone scripts, the first few frames -may be blank. This is a known issue with the simulator where it needs a few steps to load the material -textures properly and fill up the render targets. - -A hack to work around this is to add the following after initializing the camera sensor and setting -its pose: - -.. code-block:: python - - from omni.isaac.lab.sim import SimulationContext - - sim = SimulationContext.instance() - - # note: the number of steps might vary depending on how complicated the scene is. - for _ in range(12): - sim.render() - - -Using instanceable assets for markers -------------------------------------- - -When using `instanceable assets`_ for markers, the markers do not work properly, since Omniverse does not support -instanceable assets when using the :class:`UsdGeom.PointInstancer` schema. This is a known issue and will hopefully -be fixed in a future release. - -If you use an instanceable assets for markers, the marker class removes all the physics properties of the asset. -This is then replicated across other references of the same asset since physics properties of instanceable assets -are stored in the instanceable asset's USD file and not in its stage reference's USD file. - -.. _instanceable assets: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_gym_instanceable_assets.html -.. _Omniverse Isaac Sim documentation: https://docs.omniverse.nvidia.com/isaacsim/latest/known_issues.html -.. _PhysX Determinism documentation: https://nvidia-omniverse.github.io/PhysX/physx/5.3.1/docs/BestPractices.html#determinism - - -Exiting the process -------------------- - -When exiting a process with ``Ctrl+C``, occasionally the below error may appear: - -.. code-block:: bash - - [Error] [omni.physx.plugin] Subscribtion cannot be changed during the event call. - -This is due to the termination occurring in the middle of a physics event call and -should not affect the functionality of Isaac Lab. It is safe to ignore the error -message and continue with terminating the process. On Windows systems, please use -``Ctrl+Break`` or ``Ctrl+fn+B`` to terminate the process. diff --git a/docs/_build/html/_sources/source/refs/license.rst b/docs/_build/html/_sources/source/refs/license.rst deleted file mode 100644 index 4619970952..0000000000 --- a/docs/_build/html/_sources/source/refs/license.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. _license: - -License -======== - -NVIDIA Isaac Sim is available freely under `individual license -`_. For more information -about its license terms, please check `here `_. -The license files for all its dependencies and included assets are available in its -`documentation `_. - - -The Isaac Lab framework is open-sourced under the -`BSD-3-Clause license `_. - - -.. code-block:: text - - Copyright (c) 2022-2024, The Isaac Lab Project Developers. - All rights reserved. - - SPDX-License-Identifier: BSD-3-Clause - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/_build/html/_sources/source/refs/migration.rst b/docs/_build/html/_sources/source/refs/migration.rst deleted file mode 100644 index 513af9b136..0000000000 --- a/docs/_build/html/_sources/source/refs/migration.rst +++ /dev/null @@ -1,90 +0,0 @@ -Migration Guide (Isaac Sim) -=========================== - -Moving from Isaac Sim 2022.2.1 to 2023.1.0 and later brings in a number of changes to the -APIs and the way the application is built. This document outlines the changes -and how to migrate your code to the new APIs. Many of these changes attribute to -the underlying Omniverse Kit upgrade from 104.2 to 105.1. The new upgrade brings -the following notable changes: - -* Update to USD 22.11 -* Upgrading the Python from 3.7 to 3.10 - - -Renaming of PhysX Flatcache to PhysX Fabric -------------------------------------------- - -The PhysX Flatcache has been renamed to PhysX Fabric. The new name is more -descriptive of the functionality and is consistent with the naming convention -used by Omniverse called `Fabric`_. Consequently, the Python module name has -also been changed from :mod:`omni.physxflatcache` to :mod:`omni.physxfabric`. - -Following this, on the Isaac Sim side, various renaming have occurred: - -* The parameter passed to :class:`SimulationContext` constructor via the keyword :obj:`sim_params` - now expects the key ``use_fabric`` instead of ``use_flatcache``. -* The Python attribute :attr:`SimulationContext.get_physics_context().use_flatcache` is now - :attr:`SimulationContext.get_physics_context().use_fabric`. -* The Python function :meth:`SimulationContext.get_physics_context().enable_flatcache` is now - :meth:`SimulationContext.get_physics_context().enable_fabric`. - - -Renaming of the URDF and MJCF Importers ---------------------------------------- - -Starting from Isaac Sim 2023.1, the URDF and MJCF importers have been renamed to be more consistent -with the other asset importers in Omniverse. The importers are now available on NVIDIA-Omniverse GitHub -as open source projects. - -Due to the extension name change, the Python module names have also been changed: - -* URDF Importer: :mod:`omni.importer.urdf` (previously :mod:`omni.isaac.urdf`) -* MJCF Importer: :mod:`omni.importer.mjcf` (previously :mod:`omni.isaac.mjcf`) - - -Deprecation of :class:`UsdLux.Light` API ----------------------------------------- - -As highlighted in the release notes of `USD 22.11`_, the ``UsdLux.Light`` API has -been deprecated in favor of the new ``UsdLuxLightAPI`` API. In the new API the attributes -are prefixed with ``inputs:``. For example, the ``intensity`` attribute is now available as -``inputs:intensity``. - -The following example shows how to create a sphere light using the old API and -the new API. - -.. dropdown:: Code for Isaac Sim 2022.2.1 and below - :icon: code - - .. code-block:: python - - import omni.isaac.core.utils.prims as prim_utils - - prim_utils.create_prim( - "/World/Light/GreySphere", - "SphereLight", - translation=(4.5, 3.5, 10.0), - attributes={"radius": 2.5, "intensity": 600.0, "color": (0.75, 0.75, 0.75)}, - ) - -.. dropdown:: Code for Isaac Sim 2023.1.0 and above - :icon: code - - .. code-block:: python - - import omni.isaac.core.utils.prims as prim_utils - - prim_utils.create_prim( - "/World/Light/WhiteSphere", - "SphereLight", - translation=(-4.5, 3.5, 10.0), - attributes={ - "inputs:radius": 2.5, - "inputs:intensity": 600.0, - "inputs:color": (1.0, 1.0, 1.0) - }, - ) - - -.. _Fabric: https://docs.omniverse.nvidia.com/kit/docs/usdrt/latest/docs/usd_fabric_usdrt.html -.. _`USD 22.11`: https://github.com/PixarAnimationStudios/OpenUSD/blob/release/CHANGELOG.md diff --git a/docs/_build/html/_sources/source/refs/troubleshooting.rst b/docs/_build/html/_sources/source/refs/troubleshooting.rst deleted file mode 100644 index 09830e8812..0000000000 --- a/docs/_build/html/_sources/source/refs/troubleshooting.rst +++ /dev/null @@ -1,244 +0,0 @@ -Tricks and Troubleshooting -========================== - -.. note:: - - The following lists some of the common tricks and troubleshooting methods that we use in our common workflows. - Please also check the `troubleshooting page on Omniverse - `__ for more - assistance. - - -Checking the internal logs from the simulator ---------------------------------------------- - -When running the simulator from a standalone script, it logs warnings and errors to the terminal. At the same time, -it also logs internal messages to a file. These are useful for debugging and understanding the internal state of the -simulator. Depending on your system, the log file can be found in the locations listed -`here `_. - -To obtain the exact location of the log file, you need to check the first few lines of the terminal output when -you run the standalone script. The log file location is printed at the start of the terminal output. For example: - -.. code:: bash - - [INFO] Using python from: /home/${USER}/git/IsaacLab/_isaac_sim/python.sh - ... - Passing the following args to the base kit application: [] - Loading user config located at: '.../data/Kit/Isaac-Sim/2023.1/user.config.json' - [Info] [carb] Logging to file: '.../logs/Kit/Isaac-Sim/2023.1/kit_20240328_183346.log' - - -In the above example, the log file is located at ``.../logs/Kit/Isaac-Sim/2023.1/kit_20240328_183346.log``, -``...`` is the path to the user's log directory. The log file is named ``kit_20240328_183346.log`` - -You can open this file to check the internal logs from the simulator. Also when reporting issues, please include -this log file to help us debug the issue. - - -Using CPU Scaling Governor for performance ------------------------------------------- - -By default on many systems, the CPU frequency governor is set to -“powersave” mode, which sets the CPU to lowest static frequency. To -increase the maximum performance, we recommend setting the CPU frequency -governor to “performance” mode. For more details, please check the the -link -`here `__. - -.. warning:: - We advice not to set the governor to “performance” mode on a system with poor - cooling (such as laptops), since it may cause the system to overheat. - -- To view existing ``scaling_governor`` value per CPU: - - .. code:: bash - - cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - -- To change the governor to “performance” mode for each CPU: - - .. code:: bash - - echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - - -Observing long load times at the start of the simulation --------------------------------------------------------- - -The first time you run the simulator, it will take a long time to load up. This is because the -simulator is compiling shaders and loading assets. Subsequent runs should be faster to start up, -but may still take some time. - -Please note that once the Isaac Sim app loads, the environment creation time may scale linearly with -the number of environments. Please expect a longer load time if running with thousands of -environments or if each environment contains a larger number of assets. We are continually working -on improving the time needed for this. - -When an instance of Isaac Sim is already running, launching another Isaac Sim instance in a different -process may appear to hang at startup for the first time. Please be patient and give it some time as -the second process will take longer to start up due to slower shader compilation. - - -Receiving a “PhysX error” when running simulation on GPU --------------------------------------------------------- - -When using the GPU pipeline, the buffers used for the physics simulation are allocated on the GPU only -once at the start of the simulation. This means that they do not grow dynamically as the number of -collisions or objects in the scene changes. If the number of collisions or objects in the scene -exceeds the size of the buffers, the simulation will fail with an error such as the following: - -.. code:: bash - - PhysX error: the application need to increase the PxgDynamicsMemoryConfig::foundLostPairsCapacity - parameter to 3072, otherwise the simulation will miss interactions - -In this case, you need to increase the size of the buffers passed to the -:class:`~omni.isaac.lab.sim.SimulationContext` class. The size of the buffers can be increased by setting -the :attr:`~omni.isaac.lab.sim.PhysxCfg.gpu_found_lost_pairs_capacity` parameter in the -:class:`~omni.isaac.lab.sim.PhysxCfg` class. For example, to increase the size of the buffers to -4096, you can use the following code: - -.. code:: python - - import omni.isaac.lab.sim as sim_utils - - sim_cfg = sim_utils.SimulationConfig() - sim_cfg.physx.gpu_found_lost_pairs_capacity = 4096 - sim = SimulationContext(sim_params=sim_cfg) - -Please see the documentation for :class:`~omni.isaac.lab.sim.SimulationCfg` for more details -on the parameters that can be used to configure the simulation. - - -Preventing memory leaks in the simulator ----------------------------------------- - -Memory leaks in the Isaac Sim simulator can occur when C++ callbacks are registered with Python objects. -This happens when callback functions within classes maintain references to the Python objects they are -associated with. As a result, Python's garbage collection is unable to reclaim memory associated with -these objects, preventing the corresponding C++ objects from being destroyed. Over time, this can lead -to memory leaks and increased resource usage. - -To prevent memory leaks in the Isaac Sim simulator, it is essential to use weak references when registering -callbacks with the simulator. This ensures that Python objects can be garbage collected when they are no -longer needed, thereby avoiding memory leaks. The `weakref `_ -module from the Python standard library can be employed for this purpose. - - -For example, consider a class with a callback function ``on_event_callback`` that needs to be registered -with the simulator. If you use a strong reference to the ``MyClass`` object when passing the callback, -the reference count of the ``MyClass`` object will be incremented. This prevents the ``MyClass`` object -from being garbage collected when it is no longer needed, i.e., the ``__del__`` destructor will not be -called. - -.. code:: python - - import omni.kit - - class MyClass: - def __init__(self): - app_interface = omni.kit.app.get_app_interface() - self._handle = app_interface.get_post_update_event_stream().create_subscription_to_pop( - self.on_event_callback - ) - - def __del__(self): - self._handle.unsubscribe() - self._handle = None - - def on_event_callback(self, event): - # do something with the message - - -To fix this issue, it's crucial to employ weak references when registering the callback. While this approach -adds some verbosity to the code, it ensures that the ``MyClass`` object can be garbage collected when no longer -in use. Here's the modified code: - -.. code:: python - - import omni.kit - import weakref - - class MyClass: - def __init__(self): - app_interface = omni.kit.app.get_app_interface() - self._handle = app_interface.get_post_update_event_stream().create_subscription_to_pop( - lambda event, obj=weakref.proxy(self): obj.on_event_callback(event) - ) - - def __del__(self): - self._handle.unsubscribe() - self._handle = None - - def on_event_callback(self, event): - # do something with the message - - -In this revised code, the weak reference ``weakref.proxy(self)`` is used when registering the callback, -allowing the ``MyClass`` object to be properly garbage collected. - -By following this pattern, you can prevent memory leaks and maintain a more efficient and stable simulation. - - -Understanding the error logs from crashes ------------------------------------------ - -Many times the simulator crashes due to a bug in the implementation. -This swamps the terminal with exceptions, some of which are coming from -the python interpreter calling ``__del__()`` destructor of the -simulation application. These typically look like the following: - -.. code:: bash - - ... - - [INFO]: Completed setting up the environment... - - Traceback (most recent call last): - File "source/standalone/workflows/robomimic/collect_demonstrations.py", line 166, in - main() - File "source/standalone/workflows/robomimic/collect_demonstrations.py", line 126, in main - actions = pre_process_actions(delta_pose, gripper_command) - File "source/standalone/workflows/robomimic/collect_demonstrations.py", line 57, in pre_process_actions - return torch.concat([delta_pose, gripper_vel], dim=1) - TypeError: expected Tensor as element 1 in argument 0, but got int - Exception ignored in: ._Registry.__del__ at 0x7f94ac097f80> - Traceback (most recent call last): - File "../IsaacLab/_isaac_sim/kit/extscore/omni.kit.viewport.registry/omni/kit/viewport/registry/registry.py", line 103, in __del__ - File "../IsaacLab/_isaac_sim/kit/extscore/omni.kit.viewport.registry/omni/kit/viewport/registry/registry.py", line 98, in destroy - TypeError: 'NoneType' object is not callable - Exception ignored in: ._Registry.__del__ at 0x7f94ac097f80> - Traceback (most recent call last): - File "../IsaacLab/_isaac_sim/kit/extscore/omni.kit.viewport.registry/omni/kit/viewport/registry/registry.py", line 103, in __del__ - File "../IsaacLab/_isaac_sim/kit/extscore/omni.kit.viewport.registry/omni/kit/viewport/registry/registry.py", line 98, in destroy - TypeError: 'NoneType' object is not callable - Exception ignored in: - Traceback (most recent call last): - File "../IsaacLab/_isaac_sim/kit/kernel/py/omni/kit/app/_impl/__init__.py", line 114, in __del__ - AttributeError: 'NoneType' object has no attribute 'get_settings' - Exception ignored in: - Traceback (most recent call last): - File "../IsaacLab/_isaac_sim/extscache/omni.kit.viewport.menubar.lighting-104.0.7/omni/kit/viewport/menubar/lighting/actions.py", line 345, in __del__ - File "../IsaacLab/_isaac_sim/extscache/omni.kit.viewport.menubar.lighting-104.0.7/omni/kit/viewport/menubar/lighting/actions.py", line 350, in destroy - TypeError: 'NoneType' object is not callable - 2022-12-02 15:41:54 [18,514ms] [Warning] [carb.audio.context] 1 contexts were leaked - ../IsaacLab/_isaac_sim/python.sh: line 41: 414372 Segmentation fault (core dumped) $python_exe "$@" $args - There was an error running python - -This is a known error with running standalone scripts with the Isaac Sim -simulator. Please scroll above the exceptions thrown with -``registry`` to see the actual error log. - -In the above case, the actual error is: - -.. code:: bash - - Traceback (most recent call last): - File "source/standalone/workflows/robomimic/tools/collect_demonstrations.py", line 166, in - main() - File "source/standalone/workflows/robomimic/tools/collect_demonstrations.py", line 126, in main - actions = pre_process_actions(delta_pose, gripper_command) - File "source/standalone/workflows/robomimic/tools/collect_demonstrations.py", line 57, in pre_process_actions - return torch.concat([delta_pose, gripper_vel], dim=1) - TypeError: expected Tensor as element 1 in argument 0, but got int diff --git a/docs/_build/html/_sources/source/setup/developer.rst b/docs/_build/html/_sources/source/setup/developer.rst deleted file mode 100644 index f03115e4b9..0000000000 --- a/docs/_build/html/_sources/source/setup/developer.rst +++ /dev/null @@ -1,270 +0,0 @@ -Developer's Guide -================= - -For development, we suggest using `Microsoft Visual Studio Code -(VSCode) `__. This is also suggested by -NVIDIA Omniverse and there exists tutorials on how to `debug Omniverse -extensions `__ -using VSCode. - - -Setting up Visual Studio Code ------------------------------ - -The following is only applicable for Isaac Sim installed via the Omniverse Launcher. -The ``Isaac Lab`` repository includes the VSCode settings to easily allow setting -up your development environment. These are included in the ``.vscode`` directory -and include the following files: - -.. code-block:: bash - - .vscode - ├── tools - │   ├── launch.template.json - │   ├── settings.template.json - │   └── setup_vscode.py - ├── extensions.json - ├── launch.json # <- this is generated by setup_vscode.py - ├── settings.json # <- this is generated by setup_vscode.py - └── tasks.json - - -To setup the IDE, please follow these instructions: - -1. Open the ``Isaac Lab`` directory on Visual Studio Code IDE -2. Run VSCode `Tasks `__, by - pressing ``Ctrl+Shift+P``, selecting ``Tasks: Run Task`` and running the - ``setup_python_env`` in the drop down menu. - - .. image:: ../_static/vscode_tasks.png - :width: 600px - :align: center - :alt: VSCode Tasks - -If everything executes correctly, it should create the following files: - -* ``.vscode/launch.json``: Contains the launch configurations for debugging python code. -* ``.vscode/settings.json``: Contains the settings for the python interpreter and the python environment. - -For more information on VSCode support for Omniverse, please refer to the -following links: - -* `Isaac Sim VSCode support `__ -* `Debugging with VSCode `__ - - -Configuring the python interpreter ----------------------------------- - -In the provided configuration, we set the default python interpreter to use the -python executable provided by Omniverse. This is specified in the -``.vscode/settings.json`` file: - -.. code-block:: json - - { - "python.defaultInterpreterPath": "${workspaceFolder}/_isaac_sim/python.sh", - } - -If you want to use a different python interpreter (for instance, from your conda environment), -you need to change the python interpreter used by selecting and activating the python interpreter -of your choice in the bottom left corner of VSCode, or opening the command palette (``Ctrl+Shift+P``) -and selecting ``Python: Select Interpreter``. - -For more information on how to set python interpreter for VSCode, please -refer to the `VSCode documentation `_. - -Repository organization ------------------------ - -The ``Isaac Lab`` repository is structured as follows: - -.. code-block:: bash - - IsaacLab - ├── .vscode - ├── .flake8 - ├── LICENSE - ├── isaaclab.sh - ├── pyproject.toml - ├── README.md - ├── docs - ├── source - │   ├── extensions - │   │   ├── omni.isaac.lab - │   │   ├── omni.isaac.lab_assets - │   │   └── omni.isaac.lab_tasks - │   ├── standalone - │   │   ├── demos - │   │   ├── environments - │   │   ├── tools - │   │   ├── tutorials - │   │   └── workflows - └── VERSION - -The ``source`` directory contains the source code for all ``Isaac Lab`` *extensions* -and *standalone applications*. The two are the different development workflows -supported in `Isaac Sim `__. -These are described in the following sections. - -Extensions -~~~~~~~~~~ - -Extensions are the recommended way to develop applications in Isaac Sim. They are -modularized packages that formulate the Omniverse ecosystem. Each extension -provides a set of functionalities that can be used by other extensions or -standalone applications. A folder is recognized as an extension if it contains -an ``extension.toml`` file in the ``config`` directory. More information on extensions can be found in the -`Omniverse documentation `__. - -Isaac Lab in itself provides extensions for robot learning. These are written into the -``source/extensions`` directory. Each extension is written as a python package and -follows the following structure: - -.. code:: bash - - - ├── config - │   └── extension.toml - ├── docs - │   ├── CHANGELOG.md - │   └── README.md - ├── - │ ├── __init__.py - │ ├── .... - │ └── scripts - ├── setup.py - └── tests - -The ``config/extension.toml`` file contains the metadata of the extension. This -includes the name, version, description, dependencies, etc. This information is used -by Omniverse to load the extension. The ``docs`` directory contains the documentation -for the extension with more detailed information about the extension and a CHANGELOG -file that contains the changes made to the extension in each version. - -The ```` directory contains the main python package for the extension. -It may also contains the ``scripts`` directory for keeping python-based applications -that are loaded into Omniverse when then extension is enabled using the -`Extension Manager `__. - -More specifically, when an extension is enabled, the python module specified in the -``config/extension.toml`` file is loaded and scripts that contains children of the -:class:`omni.ext.IExt` class are executed. - -.. code:: python - - import omni.ext - - class MyExt(omni.ext.IExt): - """My extension application.""" - - def on_startup(self, ext_id): - """Called when the extension is loaded.""" - pass - - def on_shutdown(self): - """Called when the extension is unloaded. - - It releases all references to the extension and cleans up any resources. - """ - pass - -While loading extensions into Omniverse happens automatically, using the python package -in standalone applications requires additional steps. To simplify the build process and -avoiding the need to understand the `premake `__ -build system used by Omniverse, we directly use the `setuptools `__ -python package to build the python module provided by the extensions. This is done by the -``setup.py`` file in the extension directory. - -.. note:: - - The ``setup.py`` file is not required for extensions that are only loaded into Omniverse - using the `Extension Manager `__. - -Lastly, the ``tests`` directory contains the unit tests for the extension. These are written -using the `unittest `__ framework. It is -important to note that Omniverse also provides a similar -`testing framework `__. -However, it requires going through the build process and does not support testing of the python module in -standalone applications. - -Extension Dependency Management -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Certain extensions may have dependencies which need to be installed before the extension can be run. -While Python dependencies can be expressed via the ``INSTALL_REQUIRES`` array in ``setup.py``, we need -a separate installation pipeline to handle non-Python dependencies. We have therefore created -an additional setup procedure, ``python tools/install_deps.py {dep_type} {extensions_dir}``, which scans the ``extension.toml`` -file of the directories under the ``{extensions_dir}`` (such as ``${ISAACLAB_PATH}/source/extensions``) for ``apt`` and ``rosdep`` dependencies. - -This example ``extension.toml`` has both ``apt_deps`` and ``ros_ws`` specified, so both -``apt`` and ``rosdep`` packages will be installed if ``python tools/install_deps.py all ${ISAACLAB_PATH}/source/extensions`` -is passed: - -.. code-block:: toml - - [isaaclab_settings] - apt_deps = ["example_package"] - ros_ws = "path/from/extension_root/to/ros_ws" - -From the ``apt_deps`` in the above example, the package ``example_package`` would be installed via ``apt``. -From the ``ros_ws``, a ``rosdep install --from-paths {ros_ws}/src --ignore-src`` command will be called. -This will install all the `ROS package.xml dependencies `__ -in the directory structure below. Currently the ROS distro is assumed to be ``humble``. - -``apt`` deps are automatically installed this way during the build process of the ``Dockerfile.base``, -and ``rosdep`` deps during the build process of ``Dockerfile.ros2``. - - -Standalone applications -~~~~~~~~~~~~~~~~~~~~~~~ - -In a typical Omniverse workflow, the simulator is launched first, after which the extensions are -enabled that load the python module and run the python application. While this is a recommended -workflow, it is not always possible to use this workflow. For example, for robot learning, it is -essential to have complete control over simulation stepping and all the other functionalities -instead of asynchronously waiting for the simulator to step. In such cases, it is necessary to -write a standalone application that launches the simulator using :class:`~omni.isaac.lab.app.AppLauncher` -and allows complete control over the simulation through the :class:`~omni.isaac.lab.sim.SimulationContext` -class. - -.. code:: python - - """Launch Isaac Sim Simulator first.""" - - from omni.isaac.lab.app import AppLauncher - - # launch omniverse app - app_launcher = AppLauncher(headless=False) - simulation_app = app_launcher.app - - - """Rest everything follows.""" - - from omni.isaac.lab.sim import SimulationContext - - if __name__ == "__main__": - # get simulation context - simulation_context = SimulationContext() - # reset and play simulation - simulation_context.reset() - # step simulation - simulation_context.step() - # stop simulation - simulation_context.stop() - - # close the simulation - simulation_app.close() - - -The ``source/standalone`` directory contains various standalone applications designed using the extensions -provided by ``Isaac Lab``. These applications are written in python and are structured as follows: - -* **demos**: Contains various demo applications that showcase the core framework ``omni.isaac.lab``. -* **environments**: Contains applications for running environments defined in ``omni.isaac.lab_tasks`` with different agents. - These include a random policy, zero-action policy, teleoperation or scripted state machines. -* **tools**: Contains applications for using the tools provided by the framework. These include converting assets, generating - datasets, etc. -* **tutorials**: Contains step-by-step tutorials for using the APIs provided by the framework. -* **workflows**: Contains applications for using environments with various learning-based frameworks. These include different - reinforcement learning or imitation learning libraries. diff --git a/docs/_build/html/_sources/source/setup/faq.rst b/docs/_build/html/_sources/source/setup/faq.rst deleted file mode 100644 index f088854cf9..0000000000 --- a/docs/_build/html/_sources/source/setup/faq.rst +++ /dev/null @@ -1,87 +0,0 @@ -Frequently Asked Questions -========================== - -Where does Isaac Lab fit in the Isaac ecosystem? ------------------------------------------------- - -Over the years, NVIDIA has developed a number of tools for robotics and AI. These tools leverage -the power of GPUs to accelerate the simulation both in terms of speed and realism. They show great -promise in the field of simulation technology and are being used by many researchers and companies -worldwide. - -`Isaac Gym`_ :cite:`makoviychuk2021isaac` provides a high performance GPU-based physics simulation -for robot learning. It is built on top of `PhysX`_ which supports GPU-accelerated simulation of rigid bodies -and a Python API to directly access physics simulation data. Through an end-to-end GPU pipeline, it is possible -to achieve high frame rates compared to CPU-based physics engines. The tool has been used successfully in a -number of research projects, including legged locomotion :cite:`rudin2022learning` :cite:`rudin2022advanced`, -in-hand manipulation :cite:`handa2022dextreme` :cite:`allshire2022transferring`, and industrial assembly -:cite:`narang2022factory`. - -Despite the success of Isaac Gym, it is not designed to be a general purpose simulator for -robotics. For example, it does not include interaction between deformable and rigid objects, high-fidelity -rendering, and support for ROS. The tool has been primarily designed as a preview release to showcase the -capabilities of the underlying physics engine. With the release of `Isaac Sim`_, NVIDIA is building -a general purpose simulator for robotics and has integrated the functionalities of Isaac Gym into -Isaac Sim. - -`Isaac Sim`_ is a robot simulation toolkit built on top of Omniverse, which is a general purpose platform -that aims to unite complex 3D workflows. Isaac Sim leverages the latest advances in graphics and -physics simulation to provide a high-fidelity simulation environment for robotics. It supports -ROS/ROS2, various sensor simulation, tools for domain randomization and synthetic data creation. -Tiled rendering support in Isaac Sim allows for vectorized rendering across environments, along with -support for running in the cloud using `Isaac Automator`_. -Overall, it is a powerful tool for roboticists and is a huge step forward in the field of robotics -simulation. - -With the release of above two tools, NVIDIA also released an open-sourced set of environments called -`IsaacGymEnvs`_ and `OmniIsaacGymEnvs`_, that have been built on top of Isaac Gym and Isaac Sim respectively. -These environments have been designed to display the capabilities of the underlying simulators and provide -a starting point to understand what is possible with the simulators for robot learning. These environments -can be used for benchmarking but are not designed for developing and testing custom environments and algorithms. -This is where Isaac Lab comes in. - -Isaac Lab is built on top of Isaac Sim to provide a unified and flexible framework -for robot learning that exploits latest simulation technologies. It is designed to be modular and extensible, -and aims to simplify common workflows in robotics research (such as RL, learning from demonstrations, and -motion planning). While it includes some pre-built environments, sensors, and tasks, its main goal is to -provide an open-sourced, unified, and easy-to-use interface for developing and testing custom environments -and robot learning algorithms. It not only inherits the capabilities of Isaac Sim, but also adds a number -of new features that pertain to robot learning research. For example, including actuator dynamics in the -simulation, procedural terrain generation, and support to collect data from human demonstrations. - -Isaac Lab replaces the previous `IsaacGymEnvs`_, `OmniIsaacGymEnvs`_ and `Orbit`_ frameworks and will -be the single robot learning framework for Isaac Sim. Previously released frameworks are deprecated -and we encourage users to follow our `migration guides`_ to transition over to Isaac Lab. - - -Why should I use Isaac Lab? ---------------------------- - -Since Isaac Sim remains closed-sourced, it is difficult for users to contribute to the simulator and build a -common framework for research. On its current path, we see the community using the simulator will simply -develop their own frameworks that will result in scattered efforts with a lot of duplication of work. -This has happened in the past with other simulators, and we believe that it is not the best way to move -forward as a community. - -Isaac Lab provides an open-sourced platform for the community to drive progress with consolidated efforts -toward designing benchmarks and robot learning systems as a joint initiative. This allows us to reuse -existing components and algorithms, and to build on top of each other's work. Doing so not only saves -time and effort, but also allows us to focus on the more important aspects of research. Our hope with -Isaac Lab is that it becomes the de-facto platform for robot learning research and an environment *zoo* -that leverages Isaac Sim. As the framework matures, we foresee it benefitting hugely from the latest -simulation developments (as part of internal developments at NVIDIA and collaborating partners) -and research in robotics. - -We are already working with labs in universities and research institutions to integrate their work into Isaac Lab -and hope that others in the community will join us too in this effort. If you are interested in contributing -to Isaac Lab, please reach out to us. - - -.. _PhysX: https://developer.nvidia.com/physx-sdk -.. _Isaac Sim: https://developer.nvidia.com/isaac-sim -.. _Isaac Gym: https://developer.nvidia.com/isaac-gym -.. _IsaacGymEnvs: https://github.com/isaac-sim/IsaacGymEnvs -.. _OmniIsaacGymEnvs: https://github.com/isaac-sim/OmniIsaacGymEnvs -.. _Orbit: https://isaac-orbit.github.io/ -.. _Isaac Automator: https://github.com/isaac-sim/IsaacAutomator -.. _migration guides: ../migration/index.html diff --git a/docs/_build/html/_sources/source/setup/installation/binaries_installation.rst b/docs/_build/html/_sources/source/setup/installation/binaries_installation.rst deleted file mode 100644 index 7607758bd2..0000000000 --- a/docs/_build/html/_sources/source/setup/installation/binaries_installation.rst +++ /dev/null @@ -1,257 +0,0 @@ -Installation using Isaac Sim Binaries -===================================== - - -Installing Isaac Sim --------------------- - -Downloading pre-built binaries -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Please follow the Isaac Sim -`documentation `__ -to install the latest Isaac Sim release. - -To check the minimum system requirements,refer to the documentation -`here `__. - -.. note:: - We have tested Isaac Lab with Isaac Sim 4.0 release on Ubuntu - 20.04LTS with NVIDIA driver 525.147. - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - On Linux systems, by default, Isaac Sim is installed in the directory - ``${HOME}/.local/share/ov/pkg/isaac_sim-*``, with ``*`` corresponding to the Isaac Sim version. - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - On Windows systems, by default,Isaac Sim is installed in the directory - ``C:\Users\user\AppData\Local\ov\pkg\isaac_sim-*``, with ``*`` corresponding to the Isaac Sim version. - - -Installing Isaac Lab --------------------- - -Cloning Isaac Lab -~~~~~~~~~~~~~~~~~ - -.. note:: - - We recommend making a `fork `_ of the Isaac Lab repository to contribute - to the project but this is not mandatory to use the framework. If you - make a fork, please replace ``isaac-sim`` with your username - in the following instructions. - -Clone the Isaac Lab repository into your workspace: - -.. tab-set:: - - .. tab-item:: SSH - - .. code:: bash - - git clone git@github.com:isaac-sim/IsaacLab.git - - .. tab-item:: HTTPS - - .. code:: bash - - git clone https://github.com/isaac-sim/IsaacLab.git - - -.. note:: - We provide a helper executable `isaaclab.sh `_ that provides - utilities to manage extensions: - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: text - - ./isaaclab.sh --help - - usage: isaaclab.sh [-h] [-i] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab. - - optional arguments: - -h, --help Display the help content. - -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl-games, rsl-rl, sb3, skrl) as extra dependencies. Default is 'all'. - -f, --format Run pre-commit to format the code and check lints. - -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). - -s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim. - -t, --test Run all python unittest tests. - -o, --docker Run the docker container helper script (docker/container.sh). - -v, --vscode Generate the VSCode settings file from template. - -d, --docs Build the documentation from source using sphinx. - -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: text - - isaaclab.bat --help - - usage: isaaclab.bat [-h] [-i] [-f] [-p] [-s] [-v] [-d] [-c] -- Utility to manage Isaac Lab. - - optional arguments: - -h, --help Display the help content. - -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl-games, rsl-rl, sb3, skrl) as extra dependencies. Default is 'all'. - -f, --format Run pre-commit to format the code and check lints. - -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). - -s, --sim Run the simulator executable (isaac-sim.bat) provided by Isaac Sim. - -t, --test Run all python unittest tests. - -v, --vscode Generate the VSCode settings file from template. - -d, --docs Build the documentation from source using sphinx. - -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. - - -Creating the Isaac Sim Symbolic Link -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Set up a symbolic link between the installed Isaac Sim root folder -and ``_isaac_sim`` in the Isaac Lab directory. This makes it convenient -to index the python modules and look for extensions shipped with Isaac Sim. - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - # enter the cloned repository - cd IsaacLab - # create a symbolic link - ln -s path_to_isaac_sim _isaac_sim - # For example: ln -s /home/nvidia/.local/share/ov/pkg/isaac-sim-4.0.0 _isaac_sim - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - :: enter the cloned repository - cd IsaacLab - :: create a symbolic link - requires launching Command Prompt with Administrator access - mklink /D _isaac_sim path_to_isaac_sim - :: For example: mklink /D _isaac_sim C:/Users/nvidia/AppData/Local/ov/pkg/isaac-sim-4.0.0 - - -Setting up the conda environment (optional) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. attention:: - This step is optional. If you are using the bundled python with Isaac Sim, you can skip this step. - -The executable ``isaaclab.sh`` automatically fetches the python bundled with Isaac -Sim, using ``./isaaclab.sh -p`` command (unless inside a virtual environment). This executable -behaves like a python executable, and can be used to run any python script or -module with the simulator. For more information, please refer to the -`documentation `__. - -Although using a virtual environment is optional, we recommend using ``conda``. To install -``conda``, please follow the instructions `here `__. -In case you want to use ``conda`` to create a virtual environment, you can -use the following command: - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - # Option 1: Default name for conda environment is 'isaaclab' - ./isaaclab.sh --conda # or "./isaaclab.sh -c" - # Option 2: Custom name for conda environment - ./isaaclab.sh --conda my_env # or "./isaaclab.sh -c my_env" - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - :: Option 1: Default name for conda environment is 'isaaclab' - isaaclab.bat --conda :: or "isaaclab.bat -c" - :: Option 2: Custom name for conda environment - isaaclab.bat --conda my_env :: or "isaaclab.bat -c my_env" - - -If you are using ``conda`` to create a virtual environment, make sure to -activate the environment before running any scripts. For example: - -.. code:: bash - - conda activate isaaclab # or "conda activate my_env" - -Once you are in the virtual environment, you do not need to use ``./isaaclab.sh -p`` / ``isaaclab.bat -p`` -to run python scripts. You can use the default python executable in your environment -by running ``python`` or ``python3``. However, for the rest of the documentation, -we will assume that you are using ``./isaaclab.sh -p`` / ``isaaclab.bat -p`` to run python scripts. This command -is equivalent to running ``python`` or ``python3`` in your virtual environment. - - -Installation -~~~~~~~~~~~~ - -- Install dependencies using ``apt`` (on Linux only): - - .. code:: bash - - # these dependency are needed by robomimic which is not available on Windows - sudo apt install cmake build-essential - -- Run the install command that iterates over all the extensions in ``source/extensions`` directory and installs them - using pip (with ``--editable`` flag): - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - ./isaaclab.sh --install # or "./isaaclab.sh -i" - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - isaaclab.bat --install :: or "isaaclab.bat -i" - -.. note:: - - By default, the above will install all the learning frameworks. If you want to install only a specific framework, you can - pass the name of the framework as an argument. For example, to install only the ``rl_games`` framework, you can run - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - ./isaaclab.sh --install rl_games # or "./isaaclab.sh -i rl_games" - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - isaaclab.bat --install rl_games :: or "isaaclab.bat -i rl_games" - - The valid options are ``rl_games``, ``rsl_rl``, ``sb3``, ``skrl``, ``robomimic``, ``none``. diff --git a/docs/_build/html/_sources/source/setup/installation/cloud_installation.rst b/docs/_build/html/_sources/source/setup/installation/cloud_installation.rst deleted file mode 100644 index 00703d2290..0000000000 --- a/docs/_build/html/_sources/source/setup/installation/cloud_installation.rst +++ /dev/null @@ -1,155 +0,0 @@ -Running Isaac Lab in the Cloud -============================== - -Isaac Lab can be run in various cloud infrastructures with the use of `Isaac Automator `__. -Isaac Automator allows for quick deployment of Isaac Sim and Isaac Lab onto the public clouds (AWS, GCP, Azure, and Alibaba Cloud are currently supported). - -The result is a fully configured remote desktop cloud workstation, which can be used for development and testing of Isaac Lab within minutes and on a budget. Isaac Automator supports variety of GPU instances and stop-start functionality to save on cloud costs and a variety of tools to aid the workflow (like uploading and downloading data, autorun, deployment management, etc). - - -Installing Isaac Automator --------------------------- - -For the most update-to-date and complete installation instructions, please refer to `Isaac Automator `__. - -To use Isaac Automator, first clone the repo: - -.. code-block:: bash - - git clone https://github.com/isaac-sim/IsaacAutomator.git - -Isaac Automator requires having ``docker`` pre-installed on the system. - -* To install Docker, please follow the instructions for your operating system on the `Docker website`_. -* Follow the post-installation steps for Docker on the `post-installation steps`_ page. These steps allow you to run - Docker without using ``sudo``. - -Isaac Automator also requires obtaining a NGC API key. - -* Get access to the `Isaac Sim container`_ by joining the NVIDIA Developer Program credentials. -* Generate your `NGC API key`_ to access locked container images from NVIDIA GPU Cloud (NGC). - - * This step requires you to create an NGC account if you do not already have one. - * Once you have your generated API key, you need to log in to NGC - from the terminal. - - .. code:: bash - - docker login nvcr.io - - * For the username, enter ``$oauthtoken`` exactly as shown. It is a special username that is used to - authenticate with NGC. - - .. code:: text - - Username: $oauthtoken - Password: - - -Running Isaac Automator ------------------------ - -To run Isaac Automator, first build the Isaac Automator container: - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code-block:: bash - - ./build - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code-block:: batch - - docker build --platform linux/x86_64 -t isa . - -Next, enter the automator container: - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code-block:: bash - - ./run - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code-block:: batch - - docker run --platform linux/x86_64 -it --rm -v .:/app isa bash - -Next, run the deployed script for your preferred cloud: - -.. code-block:: bash - - # AWS - ./deploy-aws - # Azure - ./deploy-azure - # GCP - ./deploy-gcp - # Alibaba Cloud - ./deploy-alicloud - -Follow the prompts for entering information regarding the environment setup and credentials. -Once successful, instructions for connecting to the cloud instance will be available in the terminal. -Connections can be made using SSH, noVCN, or NoMachine. - -For details on the credentials and setup required for each cloud, please visit the -`Isaac Automator `__ -page for more instructions. - - -Running Isaac Lab on the Cloud ------------------------------- - -Once connected to the cloud instance, the desktop will have an icon showing ``isaaclab.sh``. -Launch the ``isaaclab.sh`` executable, which will open a new Terminal. Within the terminal, -Isaac Lab commands can be executed in the same way as running locally. - -For example: - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code-block:: bash - - ./isaaclab.sh -p source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code-block:: batch - - ./isaaclab.bat -p source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-v0 - - -Destroying a Development -------------------------- - -To save costs, deployments can be destroyed when not being used. -This can be done from within the Automator container, which can be entered with command ``./run``. - -To destroy a deployment, run: - -.. code:: bash - - ./destroy - - -.. _`Docker website`: https://docs.docker.com/desktop/install/linux-install/ -.. _`post-installation steps`: https://docs.docker.com/engine/install/linux-postinstall/ -.. _`Isaac Sim container`: https://catalog.ngc.nvidia.com/orgs/nvidia/containers/isaac-sim -.. _`NGC API key`: https://docs.nvidia.com/ngc/gpu-cloud/ngc-user-guide/index.html#generating-api-key diff --git a/docs/_build/html/_sources/source/setup/installation/index.rst b/docs/_build/html/_sources/source/setup/installation/index.rst deleted file mode 100644 index 074fe38598..0000000000 --- a/docs/_build/html/_sources/source/setup/installation/index.rst +++ /dev/null @@ -1,45 +0,0 @@ -Installation Guide -=================== - -.. image:: https://img.shields.io/badge/IsaacSim-4.0-silver.svg - :target: https://developer.nvidia.com/isaac-sim - :alt: IsaacSim 4.0 - -.. image:: https://img.shields.io/badge/python-3.10-blue.svg - :target: https://www.python.org/downloads/release/python-31013/ - :alt: Python 3.10 - -.. image:: https://img.shields.io/badge/platform-linux--64-orange.svg - :target: https://releases.ubuntu.com/20.04/ - :alt: Ubuntu 20.04 - -.. image:: https://img.shields.io/badge/platform-windows--64-orange.svg - :target: https://www.microsoft.com/en-ca/windows/windows-11 - :alt: Windows 11 - -.. caution:: - - We have dropped support for Isaac Sim versions 2023.1.0 and below. We recommend using the latest - Isaac Sim 4.0 release to benefit from the latest features and improvements. - - For more information, please refer to the - `Isaac Sim release notes `__. - -.. note:: - - We recommend system requirements with at least 32GB RAM and 16GB VRAM for Isaac Lab. - For the full list of system requirements for Isaac Sim, please refer to the - `Isaac Sim system requirements `_. - -As an experimental feature in Isaac Sim 4.0 release, Isaac Sim can also be installed through pip. -This simplifies the installation -process by avoiding the need to download the Omniverse Launcher and installing Isaac Sim through -the launcher. Therefore, there are two ways to install Isaac Lab: - -.. toctree:: - :maxdepth: 2 - - Installation using Isaac Sim pip (experimental) - binaries_installation - verifying_installation - cloud_installation diff --git a/docs/_build/html/_sources/source/setup/installation/pip_installation.rst b/docs/_build/html/_sources/source/setup/installation/pip_installation.rst deleted file mode 100644 index 0c58047253..0000000000 --- a/docs/_build/html/_sources/source/setup/installation/pip_installation.rst +++ /dev/null @@ -1,212 +0,0 @@ -Installation using Isaac Sim pip -================================ - - -Installing Isaac Sim --------------------- - -.. note:: - - Installing Isaac Sim from pip is currently an experimental feature. - If errors occur, please report them to the - `Isaac Sim Forums `_ - and install Isaac Sim from pre-built binaries. - -.. note:: - - Installing Isaac Sim with pip requires GLIBC 2.34+ version compatibility. - To check the GLIBC version on your system, use command ``ldd --version``. - - -- To use the pip installation approach for Isaac Sim, we recommend first creating a virtual environment. - Ensure that the python version of the virtual environment is **Python 3.10**. - - .. tab-set:: - - .. tab-item:: conda environment - - .. code-block:: bash - - conda create -n isaaclab python=3.10 - conda activate isaaclab - - .. tab-item:: venv environment - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code-block:: bash - - # create a conda environment named isaaclab with python3.10 - python3.10 -m venv isaaclab - # activate the conda environment - source isaaclab/bin/activate - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code-block:: batch - - # create a virtual environment named isaaclab with python3.10 - python3.10 -m venv isaaclab - # activate the virtual environment - isaaclab\Scripts\activate - - -- Next, install a CUDA-enabled PyTorch 2.2.2 build based on the CUDA version available on your system. - - .. tab-set:: - - .. tab-item:: CUDA 11 - - .. code-block:: bash - - pip install torch==2.2.2 --index-url https://download.pytorch.org/whl/cu118 - - .. tab-item:: CUDA 12 - - .. code-block:: bash - - pip install torch==2.2.2 --index-url https://download.pytorch.org/whl/cu121 - - -- Then, install the Isaac Sim packages necessary for running Isaac Lab: - - .. code-block:: bash - - pip install isaacsim-rl isaacsim-replicator isaacsim-extscache-physics isaacsim-extscache-kit-sdk isaacsim-extscache-kit isaacsim-app --extra-index-url https://pypi.nvidia.com - - -Installing Isaac Lab --------------------- - -Cloning Isaac Lab -~~~~~~~~~~~~~~~~~ - -.. note:: - - We recommend making a `fork `_ of the Isaac Lab repository to contribute - to the project but this is not mandatory to use the framework. If you - make a fork, please replace ``isaac-sim`` with your username - in the following instructions. - -Clone the Isaac Lab repository into your workspace: - -.. tab-set:: - - .. tab-item:: SSH - - .. code:: bash - - git clone git@github.com:isaac-sim/IsaacLab.git - - .. tab-item:: HTTPS - - .. code:: bash - - git clone https://github.com/isaac-sim/IsaacLab.git - - -.. note:: - We provide a helper executable `isaaclab.sh `_ that provides - utilities to manage extensions: - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: text - - ./isaaclab.sh --help - - usage: isaaclab.sh [-h] [-i] [-f] [-p] [-s] [-t] [-o] [-v] [-d] [-c] -- Utility to manage Isaac Lab. - - optional arguments: - -h, --help Display the help content. - -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl_games, rsl_rl, sb3, skrl) as extra dependencies. Default is 'all'. - -f, --format Run pre-commit to format the code and check lints. - -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). - -s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim. - -t, --test Run all python unittest tests. - -o, --docker Run the docker container helper script (docker/container.sh). - -v, --vscode Generate the VSCode settings file from template. - -d, --docs Build the documentation from source using sphinx. - -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: text - - isaaclab.bat --help - - usage: isaaclab.bat [-h] [-i] [-f] [-p] [-s] [-v] [-d] [-c] -- Utility to manage Isaac Lab. - - optional arguments: - -h, --help Display the help content. - -i, --install [LIB] Install the extensions inside Isaac Lab and learning frameworks (rl_games, rsl_rl, sb3, skrl) as extra dependencies. Default is 'all'. - -f, --format Run pre-commit to format the code and check lints. - -p, --python Run the python executable provided by Isaac Sim or virtual environment (if active). - -s, --sim Run the simulator executable (isaac-sim.bat) provided by Isaac Sim. - -t, --test Run all python unittest tests. - -v, --vscode Generate the VSCode settings file from template. - -d, --docs Build the documentation from source using sphinx. - -c, --conda [NAME] Create the conda environment for Isaac Lab. Default name is 'isaaclab'. - -Installation -~~~~~~~~~~~~ - -- Install dependencies using ``apt`` (on Ubuntu): - - .. code:: bash - - sudo apt install cmake build-essential - -- Run the install command that iterates over all the extensions in ``source/extensions`` directory and installs them - using pip (with ``--editable`` flag): - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - ./isaaclab.sh --install # or "./isaaclab.sh -i" - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: bash - - isaaclab.bat --install :: or "isaaclab.bat -i" - -.. note:: - - By default, this will install all the learning frameworks. If you want to install only a specific framework, you can - pass the name of the framework as an argument. For example, to install only the ``rl_games`` framework, you can run - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - ./isaaclab.sh --install rl_games # or "./isaaclab.sh -i rl_games" - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: bash - - isaaclab.bat --install rl_games :: or "isaaclab.bat -i rl_games" - - The valid options are ``rl_games``, ``rsl_rl``, ``sb3``, ``skrl``, ``robomimic``, ``none``. diff --git a/docs/_build/html/_sources/source/setup/installation/verifying_installation.rst b/docs/_build/html/_sources/source/setup/installation/verifying_installation.rst deleted file mode 100644 index 9a1730ddf5..0000000000 --- a/docs/_build/html/_sources/source/setup/installation/verifying_installation.rst +++ /dev/null @@ -1,206 +0,0 @@ -Verifying the Installation -========================== - - -Verifying the Isaac Sim installation ------------------------------------- - -Isaac Sim installed from pip -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Make sure that your virtual environment is activated (if applicable) - - -- Check that the simulator runs as expected: - - .. code:: bash - - # note: you can pass the argument "--help" to see all arguments possible. - isaacsim - - By default, this will launch an empty mini Kit window. - -- To run with a specific experience file, run: - - .. code:: bash - - # experience files can be absolute path, or relative path searched in isaacsim/apps or omni/apps - isaacsim omni.isaac.sim.python.kit - - -.. attention:: - - When running Isaac Sim for the first time, all dependent extensions will be pulled from the registry. - This process can take upwards of 10 minutes and is required on the first run of each experience file. - Once the extensions are pulled, consecutive runs using the same experience file will use the cached extensions. - - In addition, the first run will prompt users to accept the Nvidia Omniverse License Agreement. - To accept the EULA, reply ``Yes`` when prompted with the below message: - - .. code:: bash - - By installing or using Isaac Sim, I agree to the terms of NVIDIA OMNIVERSE LICENSE AGREEMENT (EULA) - in https://docs.omniverse.nvidia.com/isaacsim/latest/common/NVIDIA_Omniverse_License_Agreement.html - - Do you accept the EULA? (Yes/No): Yes - - -If the simulator does not run or crashes while following the above -instructions, it means that something is incorrectly configured. To -debug and troubleshoot, please check Isaac Sim -`documentation `__ -and the -`forums `__. - - -Isaac Sim installed from binaries -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To avoid the overhead of finding and locating the Isaac Sim installation -directory every time, we recommend exporting the following environment -variables to your terminal for the remaining of the installation instructions: - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - # Isaac Sim root directory - export ISAACSIM_PATH="${HOME}/.local/share/ov/pkg/isaac-sim-4.0.0" - # Isaac Sim python executable - export ISAACSIM_PYTHON_EXE="${ISAACSIM_PATH}/python.sh" - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - :: Isaac Sim root directory - set ISAACSIM_PATH="C:\Users\user\AppData\Local\ov\pkg\isaac-sim-4.0.0" - :: Isaac Sim python executable - set ISAACSIM_PYTHON_EXE="%ISAACSIM_PATH%\python.bat" - - -For more information on common paths, please check the Isaac Sim -`documentation `__. - - -- Check that the simulator runs as expected: - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - # note: you can pass the argument "--help" to see all arguments possible. - ${ISAACSIM_PATH}/isaac-sim.sh - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - :: note: you can pass the argument "--help" to see all arguments possible. - %ISAACSIM_PATH%\isaac-sim.bat - - -- Check that the simulator runs from a standalone python script: - - .. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - # checks that python path is set correctly - ${ISAACSIM_PYTHON_EXE} -c "print('Isaac Sim configuration is now complete.')" - # checks that Isaac Sim can be launched from python - ${ISAACSIM_PYTHON_EXE} ${ISAACSIM_PATH}/standalone_examples/api/omni.isaac.core/add_cubes.py - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - :: checks that python path is set correctly - %ISAACSIM_PYTHON_EXE% -c "print('Isaac Sim configuration is now complete.')" - :: checks that Isaac Sim can be launched from python - %ISAACSIM_PYTHON_EXE% %ISAACSIM_PATH%\standalone_examples\api\omni.isaac.core\add_cubes.py - - -.. caution:: - - If you have been using a previous version of Isaac Sim, you need to run the following command for the *first* - time after installation to remove all the old user data and cached variables: - - .. tab-set:: - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - - .. code:: bash - - ${ISAACSIM_PATH}/isaac-sim.sh --reset-user - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - - .. code:: batch - - %ISAACSIM_PATH%\isaac-sim.bat --reset-user - - -If the simulator does not run or crashes while following the above -instructions, it means that something is incorrectly configured. To -debug and troubleshoot, please check Isaac Sim -`documentation `__ -and the -`forums `__. - - -Verifying the Isaac Lab installation ------------------------------------- - -To verify that the installation was successful, run the following command from the -top of the repository: - -.. tab-set:: - :sync-group: os - - .. tab-item:: :icon:`fa-brands fa-linux` Linux - :sync: linux - - .. code:: bash - - # Option 1: Using the isaaclab.sh executable - # note: this works for both the bundled python and the virtual environment - ./isaaclab.sh -p source/standalone/tutorials/00_sim/create_empty.py - - # Option 2: Using python in your virtual environment - python source/standalone/tutorials/00_sim/create_empty.py - - .. tab-item:: :icon:`fa-brands fa-windows` Windows - :sync: windows - - .. code:: batch - - :: Option 1: Using the isaaclab.bat executable - :: note: this works for both the bundled python and the virtual environment - isaaclab.bat -p source\standalone\tutorials\00_sim\create_empty.py - - :: Option 2: Using python in your virtual environment - python source\standalone\tutorials\00_sim\create_empty.py - - -The above command should launch the simulator and display a window with a black -ground plane. You can exit the script by pressing ``Ctrl+C`` on your terminal. -On Windows machines, please terminate the process from Command Prompt using -``Ctrl+Break`` or ``Ctrl+fn+B``. - -If you see this, then the installation was successful! |:tada:| diff --git a/docs/_build/html/_sources/source/setup/sample.rst b/docs/_build/html/_sources/source/setup/sample.rst deleted file mode 100644 index 5bc0e0893a..0000000000 --- a/docs/_build/html/_sources/source/setup/sample.rst +++ /dev/null @@ -1,249 +0,0 @@ -Running Existing Scripts -======================== - -Showroom --------- - -The main core interface extension in Isaac Lab ``omni.isaac.lab`` provides -the main modules for actuators, objects, robots and sensors. We provide -a list of demo scripts and tutorials. These showcase how to use the provided -interfaces within a code in a minimal way. - -A few quick showroom scripts to run and checkout: - -- Spawn different quadrupeds and make robots stand using position commands: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/demos/quadrupeds.py - -- Spawn different arms and apply random joint position commands: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/demos/arms.py - -- Spawn different hands and command them to open and close: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/demos/hands.py - -- Spawn procedurally generated terrains with different configurations: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/demos/procedural_terrain.py - -- Spawn multiple markers that are useful for visualizations: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/demos/markers.py - -Workflows ---------- - -With Isaac Lab, we also provide a suite of benchmark environments included -in the ``omni.isaac.lab_tasks`` extension. We use the OpenAI Gym registry -to register these environments. For each environment, we provide a default -configuration file that defines the scene, observations, rewards and action spaces. - -The list of environments available registered with OpenAI Gym can be found by running: - -.. code:: bash - - ./isaaclab.sh -p source/standalone/environments/list_envs.py - - -Basic agents -~~~~~~~~~~~~ - -These include basic agents that output zero or random agents. They are -useful to ensure that the environments are configured correctly. - -- Zero-action agent on the Cart-pole example - - .. code:: bash - - ./isaaclab.sh -p source/standalone/environments/zero_agent.py --task Isaac-Cartpole-v0 --num_envs 32 - -- Random-action agent on the Cart-pole example: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/environments/random_agent.py --task Isaac-Cartpole-v0 --num_envs 32 - - -State machine -~~~~~~~~~~~~~ - -We include examples on hand-crafted state machines for the environments. These -help in understanding the environment and how to use the provided interfaces. -The state machines are written in `warp `__ which -allows efficient execution for large number of environments using CUDA kernels. - -.. code:: bash - - ./isaaclab.sh -p source/standalone/environments/state_machine/lift_cube_sm.py --num_envs 32 - - -Teleoperation -~~~~~~~~~~~~~ - -We provide interfaces for providing commands in SE(2) and SE(3) space -for robot control. In case of SE(2) teleoperation, the returned command -is the linear x-y velocity and yaw rate, while in SE(3), the returned -command is a 6-D vector representing the change in pose. - -To play inverse kinematics (IK) control with a keyboard device: - -.. code:: bash - - ./isaaclab.sh -p source/standalone/environments/teleoperation/teleop_se3_agent.py --task Isaac-Lift-Cube-Franka-IK-Rel-v0 --num_envs 1 --device keyboard - -The script prints the teleoperation events configured. For keyboard, -these are as follows: - -.. code:: text - - Keyboard Controller for SE(3): Se3Keyboard - Reset all commands: L - Toggle gripper (open/close): K - Move arm along x-axis: W/S - Move arm along y-axis: A/D - Move arm along z-axis: Q/E - Rotate arm along x-axis: Z/X - Rotate arm along y-axis: T/G - Rotate arm along z-axis: C/V - -Imitation Learning -~~~~~~~~~~~~~~~~~~ - -Using the teleoperation devices, it is also possible to collect data for -learning from demonstrations (LfD). For this, we support the learning -framework `Robomimic `__ (Linux only) and allow saving -data in -`HDF5 `__ -format. - -1. Collect demonstrations with teleoperation for the environment - ``Isaac-Lift-Cube-Franka-IK-Rel-v0``: - - .. code:: bash - - # step a: collect data with keyboard - ./isaaclab.sh -p source/standalone/workflows/robomimic/collect_demonstrations.py --task Isaac-Lift-Cube-Franka-IK-Rel-v0 --num_envs 1 --num_demos 10 --device keyboard - # step b: inspect the collected dataset - ./isaaclab.sh -p source/standalone/workflows/robomimic/tools/inspect_demonstrations.py logs/robomimic/Isaac-Lift-Cube-Franka-IK-Rel-v0/hdf_dataset.hdf5 - -2. Split the dataset into train and validation set: - - .. code:: bash - - # install the dependencies - sudo apt install cmake build-essential - # install python module (for robomimic) - ./isaaclab.sh -i robomimic - # split data - ./isaaclab.sh -p source/standalone//workflows/robomimic/tools/split_train_val.py logs/robomimic/Isaac-Lift-Cube-Franka-IK-Rel-v0/hdf_dataset.hdf5 --ratio 0.2 - -3. Train a BC agent for ``Isaac-Lift-Cube-Franka-IK-Rel-v0`` with - `Robomimic `__: - - .. code:: bash - - ./isaaclab.sh -p source/standalone/workflows/robomimic/train.py --task Isaac-Lift-Cube-Franka-IK-Rel-v0 --algo bc --dataset logs/robomimic/Isaac-Lift-Cube-Franka-IK-Rel-v0/hdf_dataset.hdf5 - -4. Play the learned model to visualize results: - - .. code:: bash - - ./isaaclab.sh -p source/standalone//workflows/robomimic/play.py --task Isaac-Lift-Cube-Franka-IK-Rel-v0 --checkpoint /PATH/TO/model.pth - -Reinforcement Learning -~~~~~~~~~~~~~~~~~~~~~~ - -We provide wrappers to different reinforcement libraries. These wrappers convert the data -from the environments into the respective libraries function argument and return types. - -- Training an agent with - `Stable-Baselines3 `__ - on ``Isaac-Cartpole-v0``: - - .. code:: bash - - # install python module (for stable-baselines3) - ./isaaclab.sh -i sb3 - # run script for training - # note: we enable cpu flag since SB3 doesn't optimize for GPU anyway - ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --headless --cpu - # run script for playing with 32 environments - ./isaaclab.sh -p source/standalone/workflows/sb3/play.py --task Isaac-Cartpole-v0 --num_envs 32 --checkpoint /PATH/TO/model.zip - -- Training an agent with - `SKRL `__ on ``Isaac-Reach-Franka-v0``: - - .. tab-set:: - - .. tab-item:: PyTorch - - .. code:: bash - - # install python module (for skrl) - ./isaaclab.sh -i skrl - # run script for training - ./isaaclab.sh -p source/standalone/workflows/skrl/train.py --task Isaac-Reach-Franka-v0 --headless - # run script for playing with 32 environments - ./isaaclab.sh -p source/standalone/workflows/skrl/play.py --task Isaac-Reach-Franka-v0 --num_envs 32 --checkpoint /PATH/TO/model.pt - - .. tab-item:: JAX - - .. code:: bash - - # install python module (for skrl) - ./isaaclab.sh -i skrl - # install skrl dependencies for JAX. Visit https://skrl.readthedocs.io/en/latest/intro/installation.html for more details - ./isaaclab.sh -p -m pip install skrl["jax"] - # run script for training - ./isaaclab.sh -p source/standalone/workflows/skrl/train.py --task Isaac-Reach-Franka-v0 --headless --ml_framework jax - # run script for playing with 32 environments - ./isaaclab.sh -p source/standalone/workflows/skrl/play.py --task Isaac-Reach-Franka-v0 --num_envs 32 --ml_framework jax --checkpoint /PATH/TO/model.pt - -- Training an agent with - `RL-Games `__ on ``Isaac-Ant-v0``: - - .. code:: bash - - # install python module (for rl-games) - ./isaaclab.sh -i rl_games - # run script for training - ./isaaclab.sh -p source/standalone/workflows/rl_games/train.py --task Isaac-Ant-v0 --headless - # run script for playing with 32 environments - ./isaaclab.sh -p source/standalone/workflows/rl_games/play.py --task Isaac-Ant-v0 --num_envs 32 --checkpoint /PATH/TO/model.pth - -- Training an agent with - `RSL-RL `__ on ``Isaac-Reach-Franka-v0``: - - .. code:: bash - - # install python module (for rsl-rl) - ./isaaclab.sh -i rsl_rl - # run script for training - ./isaaclab.sh -p source/standalone/workflows/rsl_rl/train.py --task Isaac-Reach-Franka-v0 --headless - # run script for playing with 32 environments - ./isaaclab.sh -p source/standalone/workflows/rsl_rl/play.py --task Isaac-Reach-Franka-v0 --num_envs 32 --load_run run_folder_name --checkpoint model.pt - -All the scripts above log the training progress to `Tensorboard`_ in the ``logs`` directory in the root of -the repository. The logs directory follows the pattern ``logs///``, where ```` -is the name of the learning framework, ```` is the task name, and ```` is the timestamp at -which the training script was executed. - -To view the logs, run: - -.. code:: bash - - # execute from the root directory of the repository - ./isaaclab.sh -p -m tensorboard.main --logdir=logs - -.. _Tensorboard: https://www.tensorflow.org/tensorboard diff --git a/docs/_build/html/_sources/source/setup/template.rst b/docs/_build/html/_sources/source/setup/template.rst deleted file mode 100644 index defaa2b78c..0000000000 --- a/docs/_build/html/_sources/source/setup/template.rst +++ /dev/null @@ -1,25 +0,0 @@ -Building your Own Project -========================= - -Traditionally, building new projects that utilize Isaac Lab's features required creating your own -extensions within the Isaac Lab repository. However, this approach can obscure project visibility and -complicate updates from one version of Isaac Lab to another. To circumvent these challenges, we now -provide a pre-configured and customizable `extension template `_ -for creating projects in an isolated environment. - -This template serves three distinct use cases: - -* **Project Template**: Provides essential access to Isaac Sim and Isaac Lab's features, making it ideal for projects - that require a standalone environment. -* **Python Package**: Facilitates integration with Isaac Sim's native or virtual Python environment, allowing for - the creation of Python packages that can be shared and reused across multiple projects. -* **Omniverse Extension**: Supports direct integration into Omniverse extension workflow. - -.. note:: - - We recommend using the extension template for new projects, as it provides a more streamlined and - efficient workflow. Additionally it ensures that your project remains up-to-date with the latest - features and improvements in Isaac Lab. - - -To get started, please follow the instructions in the `extension template repository `_. diff --git a/docs/_build/html/_sources/source/setup/translation.rst b/docs/_build/html/_sources/source/setup/translation.rst deleted file mode 100644 index 662d1fa273..0000000000 --- a/docs/_build/html/_sources/source/setup/translation.rst +++ /dev/null @@ -1,27 +0,0 @@ -About Translation -========================= - -原版英文文档网站: `https://isaac-sim.github.io/IsaacLab `__ - -中文翻译文档网站: `https://docs.robotsfan.com/isaaclab `__ - -译者: `github@fan-ziqi `__ - -翻译将在 `fan-ziqi/IsaacLab `__ 里更新。 - -如果你对此翻译有疑问,请联系 fanziqi614@gmail.com 或者在上述仓库开启一个issue。 - -如果您想参与翻译和校对工作,请阅读下述注意事项: - -* 校对之前请在沟通群内声明自己将要校对的文件,以免重复工作。 -* 拉取上述仓库并切换到 ``main`` 分支,并时刻确保你的本地分支与上游同步。 -* 修改 ``docs/locale/zh_CN/LC_MESSAGES`` 中将要校对的 ``*.po`` 文件,然后开启pull-request。 -* 如果上游文档有更新,使用正则表达式 ``(?`__ -* **2024-07-04** 使用Github-Action实现监测官方英文文档变化并自动触发增量翻译。自动构建HTML,上传到gh-pages-zhcn分支,并触发webhook自动同步到阿里云境内服务器,保证国内用户的访问速度。 By `fan-ziqi `__ -* **2024-07-03** 使用ChatGPT-3.5-API实现批量翻译脚本,对文档进行粗略翻译,但未翻译 ``API`` 和 ``CHANGELOG`` 。 By `fan-ziqi `__ diff --git a/docs/_build/html/_sources/source/tutorials/00_sim/create_empty.rst b/docs/_build/html/_sources/source/tutorials/00_sim/create_empty.rst deleted file mode 100644 index f290f02987..0000000000 --- a/docs/_build/html/_sources/source/tutorials/00_sim/create_empty.rst +++ /dev/null @@ -1,163 +0,0 @@ -Creating an empty scene -======================= - -.. currentmodule:: omni.isaac.lab - -This tutorial shows how to launch and control Isaac Sim simulator from a standalone Python script. It sets up an -empty scene in Isaac Lab and introduces the two main classes used in the framework, :class:`app.AppLauncher` and -:class:`sim.SimulationContext`. - -Please review `Isaac Sim Interface`_ and `Isaac Sim Workflows`_ prior to beginning this tutorial to get -an initial understanding of working with the simulator. - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``create_empty.py`` script in the ``source/standalone/tutorials/00_sim`` directory. - -.. dropdown:: Code for create_empty.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/00_sim/create_empty.py - :language: python - :emphasize-lines: 18-30,34,40-44,46-47,51-54,60-61 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -Launching the simulator ------------------------ - -The first step when working with standalone Python scripts is to launch the simulation application. -This is necessary to do at the start since various dependency modules of Isaac Sim are only available -after the simulation app is running. - -This can be done by importing the :class:`app.AppLauncher` class. This utility class wraps around -:class:`omni.isaac.kit.SimulationApp` class to launch the simulator. It provides mechanisms to -configure the simulator using command-line arguments and environment variables. - -For this tutorial, we mainly look at adding the command-line options to a user-defined -:class:`argparse.ArgumentParser`. This is done by passing the parser instance to the -:meth:`app.AppLauncher.add_app_launcher_args` method, which appends different parameters -to it. These include launching the app headless, configuring different Livestream options, -and enabling off-screen rendering. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/create_empty.py - :language: python - :start-at: import argparse - :end-at: simulation_app = app_launcher.app - -Importing python modules ------------------------- - -Once the simulation app is running, it is possible to import different Python modules from -Isaac Sim and other libraries. Here we import the following module: - -* :mod:`omni.isaac.lab.sim`: A sub-package in Isaac Lab for all the core simulator-related operations. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/create_empty.py - :language: python - :start-at: from omni.isaac.lab.sim import SimulationCfg, SimulationContext - :end-at: from omni.isaac.lab.sim import SimulationCfg, SimulationContext - - -Configuring the simulation context ----------------------------------- - -When launching the simulator from a standalone script, the user has complete control over playing, -pausing and stepping the simulator. All these operations are handled through the **simulation -context**. It takes care of various timeline events and also configures the `physics scene`_ for -simulation. - -In Isaac Lab, the :class:`sim.SimulationContext` class inherits from Isaac Sim's -:class:`omni.isaac.core.simulation_context.SimulationContext` to allow configuring the simulation -through Python's ``dataclass`` object and handle certain intricacies of the simulation stepping. - -For this tutorial, we set the physics and rendering time step to 0.01 seconds. This is done -by passing these quantities to the :class:`sim.SimulationCfg`, which is then used to create an -instance of the simulation context. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/create_empty.py - :language: python - :start-at: # Initialize the simulation context - :end-at: sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - - -Following the creation of the simulation context, we have only configured the physics acting on the -simulated scene. This includes the device to use for simulation, the gravity vector, and other advanced -solver parameters. There are now two main steps remaining to run the simulation: - -1. Designing the simulation scene: Adding sensors, robots and other simulated objects -2. Running the simulation loop: Stepping the simulator, and setting and getting data from the simulator - -In this tutorial, we look at Step 2 first for an empty scene to focus on the simulation control first. -In the following tutorials, we will look into Step 1 and working with simulation handles for interacting -with the simulator. - -Running the simulation ----------------------- - -The first thing, after setting up the simulation scene, is to call the :meth:`sim.SimulationContext.reset` -method. This method plays the timeline and initializes the physics handles in the simulator. It must always -be called the first time before stepping the simulator. Otherwise, the simulation handles are not initialized -properly. - -.. note:: - - :meth:`sim.SimulationContext.reset` is different from :meth:`sim.SimulationContext.play` method as the latter - only plays the timeline and does not initializes the physics handles. - -After playing the simulation timeline, we set up a simple simulation loop where the simulator is stepped repeatedly -while the simulation app is running. The method :meth:`sim.SimulationContext.step` takes in as argument :attr:`render`, -which dictates whether the step includes updating the rendering-related events or not. By default, this flag is -set to True. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/create_empty.py - :language: python - :start-at: # Play the simulator - :end-at: sim.step() - -Exiting the simulation ----------------------- - -Lastly, the simulation application is stopped and its window is closed by calling -:meth:`omni.isaac.kit.SimulationApp.close` method. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/create_empty.py - :language: python - :start-at: # close sim app - :end-at: simulation_app.close() - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Now that we have gone through the code, let's run the script and see the result: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/00_sim/create_empty.py - - -The simulation should be playing, and the stage should be rendering. To stop the simulation, -you can either close the window, or press ``Ctrl+C`` in the terminal. - -Passing ``--help`` to the above script will show the different command-line arguments added -earlier by the :class:`app.AppLauncher` class. To run the script headless, you can execute the -following: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/00_sim/create_empty.py --headless - - -Now that we have a basic understanding of how to run a simulation, let's move on to the -following tutorial where we will learn how to add assets to the stage. - -.. _`Isaac Sim Interface`: https://docs.omniverse.nvidia.com/isaacsim/latest/introductory_tutorials/tutorial_intro_interface.html#isaac-sim-app-tutorial-intro-interface -.. _`Isaac Sim Workflows`: https://docs.omniverse.nvidia.com/isaacsim/latest/introductory_tutorials/tutorial_intro_workflows.html -.. _carb: https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/index.html -.. _`physics scene`: https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_physics.html#physics-scene diff --git a/docs/_build/html/_sources/source/tutorials/00_sim/index.rst b/docs/_build/html/_sources/source/tutorials/00_sim/index.rst deleted file mode 100644 index 02e09493e9..0000000000 --- a/docs/_build/html/_sources/source/tutorials/00_sim/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Setting up a Simple Simulation -============================== - -These tutorials show you how to launch the simulation with different settings and spawn objects in the -simulated scene. They cover the following APIs: :class:`~omni.isaac.lab.app.AppLauncher`, -:class:`~omni.isaac.lab.sim.SimulationContext`, and :class:`~omni.isaac.lab.sim.spawners`. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - create_empty - spawn_prims - launch_app diff --git a/docs/_build/html/_sources/source/tutorials/00_sim/launch_app.rst b/docs/_build/html/_sources/source/tutorials/00_sim/launch_app.rst deleted file mode 100644 index b6aab2dd82..0000000000 --- a/docs/_build/html/_sources/source/tutorials/00_sim/launch_app.rst +++ /dev/null @@ -1,174 +0,0 @@ -Deep-dive into AppLauncher -========================== - -.. currentmodule:: omni.isaac.lab - -In this tutorial, we will dive into the :class:`app.AppLauncher` class to configure the simulator using -CLI arguments and environment variables (envars). Particularly, we will demonstrate how to use -:class:`~app.AppLauncher` to enable livestreaming and configure the :class:`omni.isaac.kit.SimulationApp` -instance it wraps, while also allowing user-provided options. - -The :class:`~app.AppLauncher` is a wrapper for :class:`~omni.isaac.kit.SimulationApp` to simplify -its configuration. The :class:`~omni.isaac.kit.SimulationApp` has many extensions that must be -loaded to enable different capabilities, and some of these extensions are order- and inter-dependent. -Additionally, there are startup options such as ``headless`` which must be set at instantiation time, -and which have an implied relationship with some extensions, e.g. the livestreaming extensions. -The :class:`~app.AppLauncher` presents an interface that can handle these extensions and startup -options in a portable manner across a variety of use cases. To achieve this, we offer CLI and envar -flags which can be merged with user-defined CLI args, while passing forward arguments intended -for :class:`~omni.isaac.kit.SimulationApp`. - - -The Code --------- - -The tutorial corresponds to the ``launch_app.py`` script in the -``source/standalone/tutorials/00_sim`` directory. - -.. dropdown:: Code for launch_app.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/00_sim/launch_app.py - :language: python - :emphasize-lines: 18-40 - :linenos: - -The Code Explained ------------------- - -Adding arguments to the argparser -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:class:`~app.AppLauncher` is designed to be compatible with custom CLI args that users need for -their own scripts, while still providing a portable CLI interface. - -In this tutorial, a standard :class:`argparse.ArgumentParser` is instantiated and given the -script-specific ``--size`` argument, as well as the arguments ``--height`` and ``--width``. -The latter are ingested by :class:`~omni.isaac.kit.SimulationApp`. - -The argument ``--size`` is not used by :class:`~app.AppLauncher`, but will merge seamlessly -with the :class:`~app.AppLauncher` interface. In-script arguments can be merged with the -:class:`~app.AppLauncher` interface via the :meth:`~app.AppLauncher.add_app_launcher_args` method, -which will return a modified :class:`~argparse.ArgumentParser` with the :class:`~app.AppLauncher` -arguments appended. This can then be processed into an :class:`argparse.Namespace` using the -standard :meth:`argparse.ArgumentParser.parse_args` method and passed directly to -:class:`~app.AppLauncher` for instantiation. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/launch_app.py - :language: python - :start-at: import argparse - :end-at: simulation_app = app_launcher.app - -The above only illustrates only one of several ways of passing arguments to :class:`~app.AppLauncher`. -Please consult its documentation page to see further options. - -Understanding the output of --help -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -While executing the script, we can pass the ``--help`` argument and see the combined outputs of the -custom arguments and those from :class:`~app.AppLauncher`. - -.. code-block:: console - - ./isaaclab.sh -p source/standalone/tutorials/00_sim/launch_app.py --help - - [INFO] Using python from: /isaac-sim/python.sh - [INFO][AppLauncher]: The argument 'width' will be used to configure the SimulationApp. - [INFO][AppLauncher]: The argument 'height' will be used to configure the SimulationApp. - usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--headless] [--livestream {0,1,2}] - [--enable_cameras] [--verbose] [--experience EXPERIENCE] - - Tutorial on running IsaacSim via the AppLauncher. - - options: - -h, --help show this help message and exit - --size SIZE Side-length of cuboid - --width WIDTH Width of the viewport and generated images. Defaults to 1280 - --height HEIGHT Height of the viewport and generated images. Defaults to 720 - - app_launcher arguments: - --headless Force display off at all times. - --livestream {0,1,2} - Force enable livestreaming. Mapping corresponds to that for the "LIVESTREAM" environment variable. - --enable_cameras Enable cameras when running without a GUI. - --verbose Enable verbose terminal logging from the SimulationApp. - --experience EXPERIENCE - The experience file to load when launching the SimulationApp. - - * If an empty string is provided, the experience file is determined based on the headless flag. - * If a relative path is provided, it is resolved relative to the `apps` folder in Isaac Sim and - Isaac Lab (in that order). - -This readout details the ``--size``, ``--height``, and ``--width`` arguments defined in the script directly, -as well as the :class:`~app.AppLauncher` arguments. - -The ``[INFO]`` messages preceding the help output also reads out which of these arguments are going -to be interpreted as arguments to the :class:`~omni.isaac.kit.SimulationApp` instance which the -:class:`~app.AppLauncher` class wraps. In this case, it is ``--height`` and ``--width``. These -are classified as such because they match the name and type of an argument which can be processed -by :class:`~omni.isaac.kit.SimulationApp`. Please refer to the `specification`_ for such arguments -for more examples. - -Using environment variables -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -As noted in the help message, the :class:`~app.AppLauncher` arguments (``--livestream``, ``--headless``) -have corresponding environment variables (envar) as well. These are detailed in :mod:`omni.isaac.lab.app` -documentation. Providing any of these arguments through CLI is equivalent to running the script in a shell -environment where the corresponding envar is set. - -The support for :class:`~app.AppLauncher` envars are simply a convenience to provide session-persistent -configurations, and can be set in the user's ``${HOME}/.bashrc`` for persistent settings between sessions. -In the case where these arguments are provided from the CLI, they will override their corresponding envar, -as we will demonstrate later in this tutorial. - -These arguments can be used with any script that starts the simulation using :class:`~app.AppLauncher`, -with one exception, ``--enable_cameras``. This setting sets the rendering pipeline to use the -offscreen renderer. However, this setting is only compatible with the :class:`omni.isaac.lab.sim.SimulationContext`. -It will not work with Isaac Sim's :class:`omni.isaac.core.simulation_context.SimulationContext` class. -For more information on this flag, please see the :class:`~app.AppLauncher` API documentation. - - -The Code Execution ------------------- - -We will now run the example script: - -.. code-block:: console - - LIVESTREAM=1 ./isaaclab.sh -p source/standalone/tutorials/00_sim/launch_app.py --size 0.5 - -This will spawn a 0.5m\ :sup:`3` volume cuboid in the simulation. No GUI will appear, equivalent -to if we had passed the ``--headless`` flag because headlessness is implied by our ``LIVESTREAM`` -envar. If a visualization is desired, we could get one via Isaac's `Native Livestreaming`_. Streaming -is currently the only supported method of visualization from within the container. The -process can be killed by pressing ``Ctrl+C`` in the launching terminal. - - -Now, let's look at how :class:`~app.AppLauncher` handles conflicting commands: - -.. code-block:: console - - export LIVESTREAM=0 - ./isaaclab.sh -p source/standalone/tutorials/00_sim/launch_app.py --size 0.5 --livestream 1 - -This will cause the same behavior as in the previous run, because although we have set ``LIVESTREAM=0`` -in our envars, CLI args such as ``--livestream`` take precedence in determining behavior. The process can -be killed by pressing ``Ctrl+C`` in the launching terminal. - -Finally, we will examine passing arguments to :class:`~omni.isaac.kit.SimulationApp` through -:class:`~app.AppLauncher`: - -.. code-block:: console - - export LIVESTREAM=1 - ./isaaclab.sh -p source/standalone/tutorials/00_sim/launch_app.py --size 0.5 --width 1920 --height 1080 - -This will cause the same behavior as before, but now the viewport will be rendered at 1920x1080p resolution. -This can be useful when we want to gather high-resolution video, or we can specify a lower resolution if we -want our simulation to be more performant. The process can be killed by pressing ``Ctrl+C`` in the launching -terminal. - - -.. _specification: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.kit/docs/index.html#omni.isaac.kit.SimulationApp.DEFAULT_LAUNCHER_CONFIG -.. _Native Livestreaming: https://docs.omniverse.nvidia.com/isaacsim/latest/installation/manual_livestream_clients.html#omniverse-streaming-client diff --git a/docs/_build/html/_sources/source/tutorials/00_sim/spawn_prims.rst b/docs/_build/html/_sources/source/tutorials/00_sim/spawn_prims.rst deleted file mode 100644 index e7e81616cd..0000000000 --- a/docs/_build/html/_sources/source/tutorials/00_sim/spawn_prims.rst +++ /dev/null @@ -1,178 +0,0 @@ -.. _tutorial-spawn-prims: - - -Spawning prims into the scene -============================= - -.. currentmodule:: omni.isaac.lab - -This tutorial explores how to spawn various objects (or prims) into the scene in Isaac Lab from Python. -It builds upon the previous tutorial on running the simulator from a standalone script and -demonstrates how to spawn a ground plane, lights, primitive shapes, and meshes from USD files. - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``spawn_prims.py`` script in the ``source/standalone/tutorials/00_sim`` directory. -Let's take a look at the Python script: - -.. dropdown:: Code for spawn_prims.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :emphasize-lines: 40-79, 91-92 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -Scene designing in Omniverse is built around a software system and file format called USD (Universal Scene Description). -It allows describing 3D scenes in a hierarchical manner, similar to a file system. Since USD is a comprehensive framework, -we recommend reading the `USD documentation`_ to learn more about it. - -For completeness, we introduce the must know concepts of USD in this tutorial. - -* **Primitives (Prims)**: These are the basic building blocks of a USD scene. They can be thought of as nodes in a scene - graph. Each node can be a mesh, a light, a camera, or a transform. It can also be a group of other prims under it. -* **Attributes**: These are the properties of a prim. They can be thought of as key-value pairs. For example, a prim can - have an attribute called ``color`` with a value of ``red``. -* **Relationships**: These are the connections between prims. They can be thought of as pointers to other prims. For - example, a mesh prim can have a relationship to a material prim for shading. - -A collection of these prims, with their attributes and relationships, is called a **USD stage**. It can be thought of -as a container for all prims in a scene. When we say we are designing a scene, we are actually designing a USD stage. - -While working with direct USD APIs provides a lot of flexibility, it can be cumbersome to learn and use. To make it -easier to design scenes, Isaac Lab builds on top of the USD APIs to provide a configuration-driven interface to spawn prims -into a scene. These are included in the :mod:`sim.spawners` module. - -When spawning prims into the scene, each prim requires a configuration class instance that defines the prim's attributes -and relationships (through material and shading information). The configuration class is then passed to its respective -function where the prim name and transformation are specified. The function then spawns the prim into the scene. - -At a high-level, this is how it works: - -.. code-block:: python - - # Create a configuration class instance - cfg = MyPrimCfg() - prim_path = "/path/to/prim" - - # Spawn the prim into the scene using the corresponding spawner function - spawn_my_prim(prim_path, cfg, translation=[0, 0, 0], orientation=[1, 0, 0, 0], scale=[1, 1, 1]) - # OR - # Use the spawner function directly from the configuration class - cfg.func(prim_path, cfg, translation=[0, 0, 0], orientation=[1, 0, 0, 0], scale=[1, 1, 1]) - - -In this tutorial, we demonstrate the spawning of various different prims into the scene. For more -information on the available spawners, please refer to the :mod:`sim.spawners` module in Isaac Lab. - -.. attention:: - - All the scene designing must happen before the simulation starts. Once the simulation starts, we recommend keeping - the scene frozen and only altering the properties of the prim. This is particularly important for GPU simulation - as adding new prims during simulation may alter the physics simulation buffers on GPU and lead to unexpected - behaviors. - - -Spawning a ground plane ------------------------ - -The :class:`~sim.spawners.from_files.GroundPlaneCfg` configures a grid-like ground plane with -modifiable properties such as its appearance and size. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :start-at: # Ground-plane - :end-at: cfg_ground.func("/World/defaultGroundPlane", cfg_ground) - - -Spawning lights ---------------- - -It is possible to spawn `different light prims`_ into the stage. These include distant lights, sphere lights, disk -lights, and cylinder lights. In this tutorial, we spawn a distant light which is a light that is infinitely far away -from the scene and shines in a single direction. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :start-at: # spawn distant light - :end-at: cfg_light_distant.func("/World/lightDistant", cfg_light_distant, translation=(1, 0, 10)) - - -Spawning primitive shapes -------------------------- - -Before spawning primitive shapes, we introduce the concept of a transform prim or Xform. A transform prim is a prim that -contains only transformation properties. It is used to group other prims under it and to transform them as a group. -Here we make an Xform prim to group all the primitive shapes under it. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :start-at: # create a new xform prim for all objects to be spawned under - :end-at: prim_utils.create_prim("/World/Objects", "Xform") - -Next, we spawn a cone using the :class:`~sim.spawners.shapes.ConeCfg` class. It is possible to specify -the radius, height, physics properties, and material properties of the cone. By default, the physics and material -properties are disabled. - -The first two cones we spawn ``Cone1`` and ``Cone2`` are visual elements and do not have physics enabled. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :start-at: # spawn a red cone - :end-at: cfg_cone.func("/World/Objects/Cone2", cfg_cone, translation=(-1.0, -1.0, 1.0)) - -For the third cone ``ConeRigid``, we add rigid body physics to it by setting the attributes for that in the configuration -class. Through these attributes, we can specify the mass, friction, and restitution of the cone. If unspecified, they -default to the default values set by USD Physics. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :start-at: # spawn a green cone with colliders and rigid body - :end-before: # spawn a usd file of a table into the scene - - -Spawning from another file --------------------------- - -Lastly, it is possible to spawn prims from other file formats such as other USD, URDF, or OBJ files. In this tutorial, -we spawn a USD file of a table into the scene. The table is a mesh prim and has a material prim associated with it. -All of this information is stored in its USD file. - -.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/spawn_prims.py - :language: python - :start-at: # spawn a usd file of a table into the scene - :end-at: cfg.func("/World/Objects/Table", cfg, translation=(0.0, 0.0, 1.05)) - -The table above is added as a reference to the scene. In layman terms, this means that the table is not actually added -to the scene, but a ``pointer`` to the table asset is added. This allows us to modify the table asset and have the changes -reflected in the scene in a non-destructive manner. For example, we can change the material of the table without -actually modifying the underlying file for the table asset directly. Only the changes are stored in the USD stage. - - -Executing the Script -~~~~~~~~~~~~~~~~~~~~ - -Similar to the tutorial before, to run the script, execute the following command: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/00_sim/spawn_prims.py - -Once the simulation starts, you should see a window with a ground plane, a light, some cones, and a table. -The green cone, which has rigid body physics enabled, should fall and collide with the table and the ground -plane. The other cones are visual elements and should not move. To stop the simulation, you can close the window, -or press ``Ctrl+C`` in the terminal. - -This tutorial provided a foundation for spawning various prims into the scene in Isaac Lab. Although simple, it -demonstrates the basic concepts of scene designing in Isaac Lab and how to use the spawners. In the coming tutorials, -we will now look at how to interact with the scene and the simulation. - - -.. _`USD documentation`: https://graphics.pixar.com/usd/docs/index.html -.. _`different light prims`: https://youtu.be/c7qyI8pZvF4?feature=shared diff --git a/docs/_build/html/_sources/source/tutorials/01_assets/index.rst b/docs/_build/html/_sources/source/tutorials/01_assets/index.rst deleted file mode 100644 index 4fd5ec69d0..0000000000 --- a/docs/_build/html/_sources/source/tutorials/01_assets/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Interacting with Assets -======================= - -Having spawned objects in the scene, these tutorials show you how to create physics handles for these -objects and interact with them. These revolve around the :class:`~omni.isaac.lab.assets.AssetBase` -class and its derivatives such as :class:`~omni.isaac.lab.assets.RigidObject` and -:class:`~omni.isaac.lab.assets.Articulation`. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - run_rigid_object - run_articulation diff --git a/docs/_build/html/_sources/source/tutorials/01_assets/run_articulation.rst b/docs/_build/html/_sources/source/tutorials/01_assets/run_articulation.rst deleted file mode 100644 index 1d1423d270..0000000000 --- a/docs/_build/html/_sources/source/tutorials/01_assets/run_articulation.rst +++ /dev/null @@ -1,141 +0,0 @@ -.. _tutorial-interact-articulation: - -Interacting with an articulation -================================ - -.. currentmodule:: omni.isaac.lab - - -This tutorial shows how to interact with an articulated robot in the simulation. It is a continuation of the -:ref:`tutorial-interact-rigid-object` tutorial, where we learned how to interact with a rigid object. -On top of setting the root state, we will see how to set the joint state and apply commands to the articulated -robot. - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``run_articulation.py`` script in the ``source/standalone/tutorials/01_assets`` -directory. - -.. dropdown:: Code for run_articulation.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_articulation.py - :language: python - :emphasize-lines: 58-69, 91-104, 108-111, 116-117 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -Designing the scene -------------------- - -Similar to the previous tutorial, we populate the scene with a ground plane and a distant light. Instead of -spawning rigid objects, we now spawn a cart-pole articulation from its USD file. The cart-pole is a simple robot -consisting of a cart and a pole attached to it. The cart is free to move along the x-axis, and the pole is free to -rotate about the cart. The USD file for the cart-pole contains the robot's geometry, joints, and other physical -properties. - -For the cart-pole, we use its pre-defined configuration object, which is an instance of the -:class:`assets.ArticulationCfg` class. This class contains information about the articulation's spawning strategy, -default initial state, actuator models for different joints, and other meta-information. A deeper-dive into how to -create this configuration object is provided in the :ref:`how-to-write-articulation-config` tutorial. - -As seen in the previous tutorial, we can spawn the articulation into the scene in a similar fashion by creating -an instance of the :class:`assets.Articulation` class by passing the configuration object to its constructor. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_articulation.py - :language: python - :start-at: # Create separate groups called "Origin1", "Origin2", "Origin3" - :end-at: cartpole = Articulation(cfg=cartpole_cfg) - - -Running the simulation loop ---------------------------- - -Continuing from the previous tutorial, we reset the simulation at regular intervals, set commands to the articulation, -step the simulation, and update the articulation's internal buffers. - -Resetting the simulation -"""""""""""""""""""""""" - -Similar to a rigid object, an articulation also has a root state. This state corresponds to the root body in the -articulation tree. On top of the root state, an articulation also has joint states. These states correspond to the -joint positions and velocities. - -To reset the articulation, we first set the root state by calling the :meth:`Articulation.write_root_state_to_sim` -method. Similarly, we set the joint states by calling the :meth:`Articulation.write_joint_state_to_sim` method. -Finally, we call the :meth:`Articulation.reset` method to reset any internal buffers and caches. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_articulation.py - :language: python - :start-at: # reset the scene entities - :end-at: robot.reset() - -Stepping the simulation -""""""""""""""""""""""" - -Applying commands to the articulation involves two steps: - -1. *Setting the joint targets*: This sets the desired joint position, velocity, or effort targets for the articulation. -2. *Writing the data to the simulation*: Based on the articulation's configuration, this step handles any - :ref:`actuation conversions ` and writes the converted values to the PhysX buffer. - -In this tutorial, we control the articulation using joint effort commands. For this to work, we need to set the -articulation's stiffness and damping parameters to zero. This is done a-priori inside the cart-pole's pre-defined -configuration object. - -At every step, we randomly sample joint efforts and set them to the articulation by calling the -:meth:`Articulation.set_joint_effort_target` method. After setting the targets, we call the -:meth:`Articulation.write_data_to_sim` method to write the data to the PhysX buffer. Finally, we step -the simulation. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_articulation.py - :language: python - :start-at: # Apply random action - :end-at: robot.write_data_to_sim() - - -Updating the state -"""""""""""""""""" - -Every articulation class contains a :class:`assets.ArticulationData` object. This stores the state of the -articulation. To update the state inside the buffer, we call the :meth:`assets.Articulation.update` method. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_articulation.py - :language: python - :start-at: # Update buffers - :end-at: robot.update(sim_dt) - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -To run the code and see the results, let's run the script from the terminal: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/01_assets/run_articulation.py - - -This command should open a stage with a ground plane, lights, and two cart-poles that are moving around randomly. -To stop the simulation, you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C`` -in the terminal. - -In this tutorial, we learned how to create and interact with a simple articulation. We saw how to set the state -of an articulation (its root and joint state) and how to apply commands to it. We also saw how to update its -buffers to read the latest state from the simulation. - -In addition to this tutorial, we also provide a few other scripts that spawn different robots.These are included -in the ``source/standalone/demos`` directory. You can run these scripts as: - -.. code-block:: bash - - # Spawn many different single-arm manipulators - ./isaaclab.sh -p source/standalone/demos/arms.py - - # Spawn many different quadrupeds - ./isaaclab.sh -p source/standalone/demos/quadrupeds.py diff --git a/docs/_build/html/_sources/source/tutorials/01_assets/run_rigid_object.rst b/docs/_build/html/_sources/source/tutorials/01_assets/run_rigid_object.rst deleted file mode 100644 index ea80c58c36..0000000000 --- a/docs/_build/html/_sources/source/tutorials/01_assets/run_rigid_object.rst +++ /dev/null @@ -1,147 +0,0 @@ -.. _tutorial-interact-rigid-object: - - -Interacting with a rigid object -=============================== - -.. currentmodule:: omni.isaac.lab - -In the previous tutorials, we learned the essential workings of the standalone script and how to -spawn different objects (or *prims*) into the simulation. This tutorial shows how to create and interact -with a rigid object. For this, we will use the :class:`assets.RigidObject` class provided in Isaac Lab. - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``run_rigid_object.py`` script in the ``source/standalone/tutorials/01_assets`` directory. - -.. dropdown:: Code for run_rigid_object.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_rigid_object.py - :language: python - :emphasize-lines: 55-74, 76-78, 98-108, 111-112, 118-119, 132-134, 139-140 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -In this script, we split the ``main`` function into two separate functions, which highlight the two main -steps of setting up any simulation in the simulator: - -1. **Design scene**: As the name suggests, this part is responsible for adding all the prims to the scene. -2. **Run simulation**: This part is responsible for stepping the simulator, interacting with the prims - in the scene, e.g., changing their poses, and applying any commands to them. - -A distinction between these two steps is necessary because the second step only happens after the first step -is complete and the simulator is reset. Once the simulator is reset (which automatically plays the simulation), -no new (physics-enabled) prims should be added to the scene as it may lead to unexpected behaviors. However, -the prims can be interacted with through their respective handles. - - -Designing the scene -------------------- - -Similar to the previous tutorial, we populate the scene with a ground plane and a light source. In addition, -we add a rigid object to the scene using the :class:`assets.RigidObject` class. This class is responsible for -spawning the prims at the input path and initializes their corresponding rigid body physics handles. - -In this tutorial, we create a conical rigid object using the spawn configuration similar to the rigid cone -in the :ref:`Spawn Objects ` tutorial. The only difference is that now we wrap -the spawning configuration into the :class:`assets.RigidObjectCfg` class. This class contains information about -the asset's spawning strategy, default initial state, and other meta-information. When this class is passed to -the :class:`assets.RigidObject` class, it spawns the object and initializes the corresponding physics handles -when the simulation is played. - -As an example on spawning the rigid object prim multiple times, we create its parent Xform prims, -``/World/Origin{i}``, that correspond to different spawn locations. When the regex expression -``/World/Origin*/Cone`` is passed to the :class:`assets.RigidObject` class, it spawns the rigid object prim at -each of the ``/World/Origin{i}`` locations. For instance, if ``/World/Origin1`` and ``/World/Origin2`` are -present in the scene, the rigid object prims are spawned at the locations ``/World/Origin1/Cone`` and -``/World/Origin2/Cone`` respectively. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_rigid_object.py - :language: python - :start-at: # Create separate groups called "Origin1", "Origin2", "Origin3" - :end-at: cone_object = RigidObject(cfg=cone_cfg) - -Since we want to interact with the rigid object, we pass this entity back to the main function. This entity -is then used to interact with the rigid object in the simulation loop. In later tutorials, we will see a more -convenient way to handle multiple scene entities using the :class:`scene.InteractiveScene` class. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_rigid_object.py - :language: python - :start-at: # return the scene information - :end-at: return scene_entities, origins - - -Running the simulation loop ---------------------------- - -We modify the simulation loop to interact with the rigid object to include three steps -- resetting the -simulation state at fixed intervals, stepping the simulation, and updating the internal buffers of the -rigid object. For the convenience of this tutorial, we extract the rigid object's entity from the scene -dictionary and store it in a variable. - -Resetting the simulation state -"""""""""""""""""""""""""""""" - -To reset the simulation state of the spawned rigid object prims, we need to set their pose and velocity. -Together they define the root state of the spawned rigid objects. It is important to note that this state -is defined in the **simulation world frame**, and not of their parent Xform prim. This is because the physics -engine only understands the world frame and not the parent Xform prim's frame. Thus, we need to transform -desired state of the rigid object prim into the world frame before setting it. - -We use the :attr:`assets.RigidObject.data.default_root_state` attribute to get the default root state of the -spawned rigid object prims. This default state can be configured from the :attr:`assets.RigidObjectCfg.init_state` -attribute, which we left as identity in this tutorial. We then randomize the translation of the root state and -set the desired state of the rigid object prim using the :meth:`assets.RigidObject.write_root_state_to_sim` method. -As the name suggests, this method writes the root state of the rigid object prim into the simulation buffer. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_rigid_object.py - :language: python - :start-at: # reset root state - :end-at: cone_object.reset() - -Stepping the simulation -""""""""""""""""""""""" - -Before stepping the simulation, we perform the :meth:`assets.RigidObject.write_data_to_sim` method. This method -writes other data, such as external forces, into the simulation buffer. In this tutorial, we do not apply any -external forces to the rigid object, so this method is not necessary. However, it is included for completeness. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_rigid_object.py - :language: python - :start-at: # apply sim data - :end-at: cone_object.write_data_to_sim() - -Updating the state -"""""""""""""""""" - -After stepping the simulation, we update the internal buffers of the rigid object prims to reflect their new state -inside the :class:`assets.RigidObject.data` attribute. This is done using the :meth:`assets.RigidObject.update` method. - -.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/run_rigid_object.py - :language: python - :start-at: # update buffers - :end-at: cone_object.update(sim_dt) - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Now that we have gone through the code, let's run the script and see the result: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/01_assets/run_rigid_object.py - - -This should open a stage with a ground plane, lights, and several green cones. The cones must be dropping from -a random height and settling on to the ground. To stop the simulation, you can either close the window, or press -the ``STOP`` button in the UI, or press ``Ctrl+C`` in the terminal - -This tutorial showed how to spawn rigid objects and wrap them in a :class:`RigidObject` class to initialize their -physics handles which allows setting and obtaining their state. In the next tutorial, we will see how to interact -with an articulated object which is a collection of rigid objects connected by joints. diff --git a/docs/_build/html/_sources/source/tutorials/02_scene/create_scene.rst b/docs/_build/html/_sources/source/tutorials/02_scene/create_scene.rst deleted file mode 100644 index e621e9d834..0000000000 --- a/docs/_build/html/_sources/source/tutorials/02_scene/create_scene.rst +++ /dev/null @@ -1,161 +0,0 @@ -.. _tutorial-interactive-scene: - -Using the Interactive Scene -=========================== - -.. currentmodule:: omni.isaac.lab - -So far in the tutorials, we manually spawned assets into the simulation and created -object instances to interact with them. However, as the complexity of the scene -increases, it becomes tedious to perform these tasks manually. In this tutorial, -we will introduce the :class:`scene.InteractiveScene` class, which provides a convenient -interface for spawning prims and managing them in the simulation. - -At a high-level, the interactive scene is a collection of scene entities. Each entity -can be either a non-interactive prim (e.g. ground plane, light source), an interactive -prim (e.g. articulation, rigid object), or a sensor (e.g. camera, lidar). The interactive -scene provides a convenient interface for spawning these entities and managing them -in the simulation. - -Compared the manual approach, it provides the following benefits: - -* Alleviates the user needing to spawn each asset separately as this is handled implicitly. -* Enables user-friendly cloning of scene prims for multiple environments. -* Collects all the scene entities into a single object, which makes them easier to manage. - -In this tutorial, we take the cartpole example from the :ref:`tutorial-interact-articulation` -tutorial and replace the ``design_scene`` function with an :class:`scene.InteractiveScene` object. -While it may seem like overkill to use the interactive scene for this simple example, it will -become more useful in the future as more assets and sensors are added to the scene. - - -The Code -~~~~~~~~ - -This tutorial corresponds to the ``create_scene.py`` script within -``source/standalone/tutorials/02_scene``. - -.. dropdown:: Code for create_scene.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/02_scene/create_scene.py - :language: python - :emphasize-lines: 50-63, 68-70, 91-92, 99-100, 105-106, 116-118 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -While the code is similar to the previous tutorial, there are a few key differences -that we will go over in detail. - -Scene configuration -------------------- - -The scene is composed of a collection of entities, each with their own configuration. -These are specified in a configuration class that inherits from :class:`scene.InteractiveSceneCfg`. -The configuration class is then passed to the :class:`scene.InteractiveScene` constructor -to create the scene. - -For the cartpole example, we specify the same scene as in the previous tutorial, but list -them now in the configuration class :class:`CartpoleSceneCfg` instead of manually spawning them. - -.. literalinclude:: ../../../../source/standalone/tutorials/02_scene/create_scene.py - :language: python - :pyobject: CartpoleSceneCfg - -The variable names in the configuration class are used as keys to access the corresponding -entity from the :class:`scene.InteractiveScene` object. For example, the cartpole can -be accessed via ``scene["cartpole"]``. However, we will get to that later. First, let's -look at how individual scene entities are configured. - -Similar to how a rigid object and articulation were configured in the previous tutorials, -the configurations are specified using a configuration class. However, there is a key -difference between the configurations for the ground plane and light source and the -configuration for the cartpole. The ground plane and light source are non-interactive -prims, while the cartpole is an interactive prim. This distinction is reflected in the -configuration classes used to specify them. The configurations for the ground plane and -light source are specified using an instance of the :class:`assets.AssetBaseCfg` class -while the cartpole is configured using an instance of the :class:`assets.ArticulationCfg`. -Anything that is not an interactive prim (i.e., neither an asset nor a sensor) is not -*handled* by the scene during simulation steps. - -Another key difference to note is in the specification of the prim paths for the -different prims: - -* Ground plane: ``/World/defaultGroundPlane`` -* Light source: ``/World/Light`` -* Cartpole: ``{ENV_REGEX_NS}/Robot`` - -As we learned earlier, Omniverse creates a graph of prims in the USD stage. The prim -paths are used to specify the location of the prim in the graph. The ground plane and -light source are specified using absolute paths, while the cartpole is specified using -a relative path. The relative path is specified using the ``ENV_REGEX_NS`` variable, -which is a special variable that is replaced with the environment name during scene creation. -Any entity that has the ``ENV_REGEX_NS`` variable in its prim path will be cloned for each -environment. This path is replaced by the scene object with ``/World/envs/env_{i}`` where -``i`` is the environment index. - -Scene instantiation -------------------- - -Unlike before where we called the ``design_scene`` function to create the scene, we now -create an instance of the :class:`scene.InteractiveScene` class and pass in the configuration -object to its constructor. While creating the configuration instance of ``CartpoleSceneCfg`` -we specify how many environment copies we want to create using the ``num_envs`` argument. -This will be used to clone the scene for each environment. - -.. literalinclude:: ../../../../source/standalone/tutorials/02_scene/create_scene.py - :language: python - :start-at: # Design scene - :end-at: scene = InteractiveScene(scene_cfg) - -Accessing scene elements ------------------------- - -Similar to how entities were accessed from a dictionary in the previous tutorials, the -scene elements can be accessed from the :class:`InteractiveScene` object using the -``[]`` operator. The operator takes in a string key and returns the corresponding -entity. The key is specified through the configuration class for each entity. For example, -the cartpole is specified using the key ``"cartpole"`` in the configuration class. - -.. literalinclude:: ../../../../source/standalone/tutorials/02_scene/create_scene.py - :language: python - :start-at: # Extract scene entities - :end-at: robot = scene["cartpole"] - -Running the simulation loop ---------------------------- - -The rest of the script looks similar to previous scripts that interfaced with :class:`assets.Articulation`, -with a few small differences in the methods called: - -* :meth:`assets.Articulation.reset` ⟶ :meth:`scene.InteractiveScene.reset` -* :meth:`assets.Articulation.write_data_to_sim` ⟶ :meth:`scene.InteractiveScene.write_data_to_sim` -* :meth:`assets.Articulation.update` ⟶ :meth:`scene.InteractiveScene.update` - -Under the hood, the methods of :class:`scene.InteractiveScene` call the corresponding -methods of the entities in the scene. - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Let's run the script to simulate 32 cartpoles in the scene. We can do this by passing -the ``--num_envs`` argument to the script. - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/02_scene/create_scene.py --num_envs 32 - -This should open a stage with 32 cartpoles swinging around randomly. You can use the -mouse to rotate the camera and the arrow keys to move around the scene. - -In this tutorial, we saw how to use :class:`scene.InteractiveScene` to create a -scene with multiple assets. We also saw how to use the ``num_envs`` argument -to clone the scene for multiple environments. - -There are many more example usages of the :class:`scene.InteractiveSceneCfg` in the tasks found -under the ``omni.isaac.lab_tasks`` extension. Please check out the source code to see -how they are used for more complex scenes. diff --git a/docs/_build/html/_sources/source/tutorials/02_scene/index.rst b/docs/_build/html/_sources/source/tutorials/02_scene/index.rst deleted file mode 100644 index d7851b6c42..0000000000 --- a/docs/_build/html/_sources/source/tutorials/02_scene/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Creating a Scene -================ - -With the basic concepts of the framework covered, the tutorials move to a more intuitive scene -interface that uses the :class:`~omni.isaac.lab.scene.InteractiveScene` class. This class -provides a higher level abstraction for creating scenes easily. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - create_scene diff --git a/docs/_build/html/_sources/source/tutorials/03_envs/create_direct_rl_env.rst b/docs/_build/html/_sources/source/tutorials/03_envs/create_direct_rl_env.rst deleted file mode 100644 index eb074c339a..0000000000 --- a/docs/_build/html/_sources/source/tutorials/03_envs/create_direct_rl_env.rst +++ /dev/null @@ -1,334 +0,0 @@ -.. _tutorial-create-direct-rl-env: - - -Creating a Direct Workflow RL Environment -========================================= - -.. currentmodule:: omni.isaac.lab - -In addition to the :class:`envs.ManagerBasedRLEnv` class, which encourages the use of configuration classes -for more modular environments, the :class:`~omni.isaac.lab.envs.DirectRLEnv` class allows for more direct control -in the scripting of environment. - -Instead of using Manager classes for defining rewards and observations, the direct workflow tasks -implement the full reward and observation functions directly in the task script. -This allows for more control in the implementation of the methods, such as using pytorch jit -features, and provides a less abstracted framework that makes it easier to find the various -pieces of code. - -In this tutorial, we will configure the cartpole environment using the direct workflow implementation to create a task -for balancing the pole upright. We will learn how to specify the task using by implementing functions -for scene creation, actions, resets, rewards and observations. - - -The Code -~~~~~~~~ - -For this tutorial, we use the cartpole environment defined in ``omni.isaac.lab_tasks.direct.cartpole`` module. - -.. dropdown:: Code for cartpole_env.py - :icon: code - - .. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -Similar to the manager-based environments, a configuration class is defined for the task to hold settings -for the simulation parameters, the scene, the actors, and the task. With the direct workflow implementation, -the :class:`envs.DirectRLEnvCfg` class is used as the base class for configurations. -Since the direct workflow implementation does not use Action and Observation managers, the task -config should define the number of actions and observations for the environment. - -.. code-block:: python - - @configclass - class CartpoleEnvCfg(DirectRLEnvCfg): - ... - num_actions = 1 - num_observations = 4 - num_states = 0 - -The config class can also be used to define task-specific attributes, such as scaling for reward terms -and thresholds for reset conditions. - -.. code-block:: python - - @configclass - class CartpoleEnvCfg(DirectRLEnvCfg): - ... - # reset - max_cart_pos = 3.0 - initial_pole_angle_range = [-0.25, 0.25] - - # reward scales - rew_scale_alive = 1.0 - rew_scale_terminated = -2.0 - rew_scale_pole_pos = -1.0 - rew_scale_cart_vel = -0.01 - rew_scale_pole_vel = -0.005 - -When creating a new environment, the code should define a new class that inherits from :class:`~omni.isaac.lab.envs.DirectRLEnv`. - -.. code-block:: python - - class CartpoleEnv(DirectRLEnv): - cfg: CartpoleEnvCfg - - def __init__(self, cfg: CartpoleEnvCfg, render_mode: str | None = None, **kwargs): - super().__init__(cfg, render_mode, **kwargs) - -The class can also hold class variables that are accessible by all functions in the class, -including functions for applying actions, computing resets, rewards, and observations. - -Scene Creation --------------- - -In contrast to manager-based environments where the scene creation is taken care of by the framework, -the direct workflow implementation provides flexibility for users to implement their own scene creation -function. This includes adding actors into the stage, cloning the environments, filtering collisions -between the environments, adding the actors into the scene, and adding any additional props to the -scene, such as ground plane and lights. These operations should be implemented in the -``_setup_scene(self)`` method. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._setup_scene - -Defining Rewards ----------------- - -Reward function should be defined in the ``_get_rewards(self)`` API, which returns the reward -buffer as a return value. Within this function, the task is free to implement the logic of -the reward function. In this example, we implement a Pytorch JIT function that computes -the various components of the reward function. - -.. code-block:: python - - def _get_rewards(self) -> torch.Tensor: - total_reward = compute_rewards( - self.cfg.rew_scale_alive, - self.cfg.rew_scale_terminated, - self.cfg.rew_scale_pole_pos, - self.cfg.rew_scale_cart_vel, - self.cfg.rew_scale_pole_vel, - self.joint_pos[:, self._pole_dof_idx[0]], - self.joint_vel[:, self._pole_dof_idx[0]], - self.joint_pos[:, self._cart_dof_idx[0]], - self.joint_vel[:, self._cart_dof_idx[0]], - self.reset_terminated, - ) - return total_reward - - @torch.jit.script - def compute_rewards( - rew_scale_alive: float, - rew_scale_terminated: float, - rew_scale_pole_pos: float, - rew_scale_cart_vel: float, - rew_scale_pole_vel: float, - pole_pos: torch.Tensor, - pole_vel: torch.Tensor, - cart_pos: torch.Tensor, - cart_vel: torch.Tensor, - reset_terminated: torch.Tensor, - ): - rew_alive = rew_scale_alive * (1.0 - reset_terminated.float()) - rew_termination = rew_scale_terminated * reset_terminated.float() - rew_pole_pos = rew_scale_pole_pos * torch.sum(torch.square(pole_pos), dim=-1) - rew_cart_vel = rew_scale_cart_vel * torch.sum(torch.abs(cart_vel), dim=-1) - rew_pole_vel = rew_scale_pole_vel * torch.sum(torch.abs(pole_vel), dim=-1) - total_reward = rew_alive + rew_termination + rew_pole_pos + rew_cart_vel + rew_pole_vel - return total_reward - - -Defining Observations ---------------------- - -The observation buffer should be computed in the ``_get_observations(self)`` function, -which constructs the observation buffer for the environment. At the end of this API, -a dictionary should be returned that contains ``policy`` as the key, and the full -observation buffer as the value. For asymmetric policies, the dictionary should also -include the key ``critic`` and the states buffer as the value. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._get_observations - -Computing Dones and Performing Resets -------------------------------------- - -Populating the ``dones`` buffer should be done in the ``_get_dones(self)`` method. -This method is free to implement logic that computes which environments would need to be reset -and which environments have reached the episode length limit. Both results should be -returned by the ``_get_dones(self)`` function, in the form of a tuple of boolean tensors. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._get_dones - -Once the indices for environments requiring reset have been computed, the ``_reset_idx(self, env_ids)`` -function performs the reset operations on those environments. Within this function, new states -for the environments requiring reset should be set directly into simulation. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._reset_idx - -Applying Actions ----------------- - -There are two APIs that are designed for working with actions. The ``_pre_physics_step(self, actions)`` takes in actions -from the policy as an argument and is called once per RL step, prior to taking any physics steps. This function can -be used to process the actions buffer from the policy and cache the data in a class variable for the environment. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._pre_physics_step - -The ``_apply_action(self)`` API is called ``decimation`` number of times for each RL step, prior to taking -each physics step. This provides more flexibility for environments where actions should be applied -for each physics step. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/cartpole_env.py - :language: python - :pyobject: CartpoleEnv._apply_action - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -To run training for the direct workflow Cartpole environment, we can use the following command: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-Direct-v0 - -All direct workflow tasks have the suffix ``-Direct`` added to the task name to differentiate the implementation style. - - -Domain Randomization -~~~~~~~~~~~~~~~~~~~~ - -In the direct workflow, domain randomization configuration uses the :class:`~omni.isaac.lab.utils.configclass` module -to specify a configuration class consisting of :class:`~managers.EventTermCfg` variables. - -Below is an example of a configuration class for domain randomization: - -.. code-block:: python - - @configclass - class EventCfg: - robot_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names=".*"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (1.0, 1.0), - "restitution_range": (1.0, 1.0), - "num_buckets": 250, - }, - ) - robot_joint_stiffness_and_damping = EventTerm( - func=mdp.randomize_actuator_gains, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), - "stiffness_distribution_params": (0.75, 1.5), - "damping_distribution_params": (0.3, 3.0), - "operation": "scale", - "distribution": "log_uniform", - }, - ) - reset_gravity = EventTerm( - func=mdp.randomize_physics_scene_gravity, - mode="interval", - is_global_time=True, - interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) - params={ - "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), - "operation": "add", - "distribution": "gaussian", - }, - ) - -Each ``EventTerm`` object is of the :class:`~managers.EventTermCfg` class and takes in a ``func`` parameter -for specifying the function to call during randomization, a ``mode`` parameter, which can be ``startup``, -``reset`` or ``interval``. THe ``params`` dictionary should provide the necessary arguments to the -function that is specified in the ``func`` parameter. -Functions specified as ``func`` for the ``EventTerm`` can be found in the :class:`~envs.mdp.events` module. - -Note that as part of the ``"asset_cfg": SceneEntityCfg("robot", body_names=".*")`` parameter, the name of -the actor ``"robot"`` is provided, along with the body or joint names specified as a regex expression, -which will be the actors and bodies/joints that will have randomization applied. - -Once the ``configclass`` for the randomization terms have been set up, the class must be added -to the base config class for the task and be assigned to the variable ``events``. - -.. code-block:: python - - @configclass - class MyTaskConfig: - events: EventCfg = EventCfg() - - -Action and Observation Noise ----------------------------- - -Actions and observation noise can also be added using the :class:`~utils.configclass` module. -Action and observation noise configs must be added to the main task config using the -``action_noise_model`` and ``observation_noise_model`` variables: - -.. code-block:: python - - @configclass - class MyTaskConfig: - - # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), - ) - - # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), - ) - - -:class:`~.utils.noise.NoiseModelWithAdditiveBiasCfg` can be used to sample both uncorrelated noise -per step as well as correlated noise that is re-sampled at reset time. - -The ``noise_cfg`` term specifies the Gaussian distribution that will be sampled at each -step for all environments. This noise will be added to the corresponding actions and -observations buffers at every step. - -The ``bias_noise_cfg`` term specifies the Gaussian distribution for the correlated noise -that will be sampled at reset time for the environments being reset. The same noise -will be applied each step for the remaining of the episode for the environments and -resampled at the next reset. - -If only per-step noise is desired, :class:`~utils.noise.GaussianNoiseCfg` can be used -to specify an additive Gaussian distribution that adds the sampled noise to the input buffer. - -.. code-block:: python - - @configclass - class MyTaskConfig: - action_noise_model: GaussianNoiseCfg = GaussianNoiseCfg(mean=0.0, std=0.05, operation="add") - - - - -In this tutorial, we learnt how to create a direct workflow task environment for reinforcement learning. We do this -by extending the base environment to include the scene setup, actions, dones, reset, reward and observaion functions. - -While it is possible to manually create an instance of :class:`~omni.isaac.lab.envs.DirectRLEnv` class for a desired task, -this is not scalable as it requires specialized scripts for each task. Thus, we exploit the -:meth:`gymnasium.make` function to create the environment with the gym interface. We will learn how to do this -in the next tutorial. diff --git a/docs/_build/html/_sources/source/tutorials/03_envs/create_manager_base_env.rst b/docs/_build/html/_sources/source/tutorials/03_envs/create_manager_base_env.rst deleted file mode 100644 index 05136b6691..0000000000 --- a/docs/_build/html/_sources/source/tutorials/03_envs/create_manager_base_env.rst +++ /dev/null @@ -1,218 +0,0 @@ -.. _tutorial-create-manager-base-env: - - -Creating a Manager-Based Base Environment -========================================= - -.. currentmodule:: omni.isaac.lab - -Environments bring together different aspects of the simulation such as -the scene, observations and actions spaces, reset events etc. to create a -coherent interface for various applications. In Isaac Lab, manager-based environments are -implemented as :class:`envs.ManagerBasedEnv` and :class:`envs.ManagerBasedRLEnv` classes. -The two classes are very similar, but :class:`envs.ManagerBasedRLEnv` is useful for -reinforcement learning tasks and contains rewards, terminations, curriculum -and command generation. The :class:`envs.ManagerBasedEnv` class is useful for -traditional robot control and doesn't contain rewards and terminations. - -In this tutorial, we will look at the base class :class:`envs.ManagerBasedEnv` and its -corresponding configuration class :class:`envs.ManagerBasedEnvCfg` for the manager-based workflow. -We will use the -cartpole environment from earlier to illustrate the different components -in creating a new :class:`envs.ManagerBasedEnv` environment. - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``create_cartpole_base_env`` script in the ``source/standalone/tutorials/03_envs`` -directory. - -.. dropdown:: Code for create_cartpole_base_env.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py - :language: python - :emphasize-lines: 47-51, 54-71, 74-108, 111-130, 135-139, 144, 148, 153-154, 160-161 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -The base class :class:`envs.ManagerBasedEnv` wraps around many intricacies of the simulation interaction -and provides a simple interface for the user to run the simulation and interact with it. It -is composed of the following components: - -* :class:`scene.InteractiveScene` - The scene that is used for the simulation. -* :class:`managers.ActionManager` - The manager that handles actions. -* :class:`managers.ObservationManager` - The manager that handles observations. -* :class:`managers.EventManager` - The manager that schedules operations (such as domain randomization) - at specified simulation events. For instance, at startup, on resets, or periodic intervals. - -By configuring these components, the user can create different variations of the same environment -with minimal effort. In this tutorial, we will go through the different components of the -:class:`envs.ManagerBasedEnv` class and how to configure them to create a new environment. - -Designing the scene -------------------- - -The first step in creating a new environment is to configure its scene. For the cartpole -environment, we will be using the scene from the previous tutorial. Thus, we omit the -scene configuration here. For more details on how to configure a scene, see -:ref:`tutorial-interactive-scene`. - -Defining actions ----------------- - -In the previous tutorial, we directly input the action to the cartpole using -the :meth:`assets.Articulation.set_joint_effort_target` method. In this tutorial, we will -use the :class:`managers.ActionManager` to handle the actions. - -The action manager can comprise of multiple :class:`managers.ActionTerm`. Each action term -is responsible for applying *control* over a specific aspect of the environment. For instance, -for robotic arm, we can have two action terms -- one for controlling the joints of the arm, -and the other for controlling the gripper. This composition allows the user to define -different control schemes for different aspects of the environment. - -In the cartpole environment, we want to control the force applied to the cart to balance the pole. -Thus, we will create an action term that controls the force applied to the cart. - -.. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py - :language: python - :pyobject: ActionsCfg - -Defining observations ---------------------- - -While the scene defines the state of the environment, the observations define the states -that are observable by the agent. These observations are used by the agent to make decisions -on what actions to take. In Isaac Lab, the observations are computed by the -:class:`managers.ObservationManager` class. - -Similar to the action manager, the observation manager can comprise of multiple observation terms. -These are further grouped into observation groups which are used to define different observation -spaces for the environment. For instance, for hierarchical control, we may want to define -two observation groups -- one for the low level controller and the other for the high level -controller. It is assumed that all the observation terms in a group have the same dimensions. - -For this tutorial, we will only define one observation group named ``"policy"``. While not completely -prescriptive, this group is a necessary requirement for various wrappers in Isaac Lab. -We define a group by inheriting from the :class:`managers.ObservationGroupCfg` class. This class -collects different observation terms and help define common properties for the group, such -as enabling noise corruption or concatenating the observations into a single tensor. - -The individual terms are defined by inheriting from the :class:`managers.ObservationTermCfg` class. -This class takes in the :attr:`managers.ObservationTermCfg.func` that specifies the function or -callable class that computes the observation for that term. It includes other parameters for -defining the noise model, clipping, scaling, etc. However, we leave these parameters to their -default values for this tutorial. - -.. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py - :language: python - :pyobject: ObservationsCfg - -Defining events ---------------- - -At this point, we have defined the scene, actions and observations for the cartpole environment. -The general idea for all these components is to define the configuration classes and then -pass them to the corresponding managers. The event manager is no different. - -The :class:`managers.EventManager` class is responsible for events corresponding to changes -in the simulation state. This includes resetting (or randomizing) the scene, randomizing physical -properties (such as mass, friction, etc.), and varying visual properties (such as colors, textures, etc.). -Each of these are specified through the :class:`managers.EventTermCfg` class, which -takes in the :attr:`managers.EventTermCfg.func` that specifies the function or callable -class that performs the event. - -Additionally, it expects the **mode** of the event. The mode specifies when the event term should be applied. -It is possible to specify your own mode. For this, you'll need to adapt the :class:`~envs.ManagerBasedEnv` class. -However, out of the box, Isaac Lab provides three commonly used modes: - -* ``"startup"`` - Event that takes place only once at environment startup. -* ``"reset"`` - Event that occurs on environment termination and reset. -* ``"interval"`` - Event that are executed at a given interval, i.e., periodically after a certain number of steps. - -For this example, we define events that randomize the pole's mass on startup. This is done only once since this -operation is expensive and we don't want to do it on every reset. We also create an event to randomize the initial -joint state of the cartpole and the pole at every reset. - -.. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py - :language: python - :pyobject: EventCfg - -Tying it all together ---------------------- - -Having defined the scene and manager configurations, we can now define the environment configuration -through the :class:`envs.ManagerBasedEnvCfg` class. This class takes in the scene, action, observation and -event configurations. - -In addition to these, it also takes in the :attr:`envs.ManagerBasedEnvCfg.sim` which defines the simulation -parameters such as the timestep, gravity, etc. This is initialized to the default values, but can -be modified as needed. We recommend doing so by defining the :meth:`__post_init__` method in the -:class:`envs.ManagerBasedEnvCfg` class, which is called after the configuration is initialized. - -.. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py - :language: python - :pyobject: CartpoleEnvCfg - -Running the simulation ----------------------- - -Lastly, we revisit the simulation execution loop. This is now much simpler since we have -abstracted away most of the details into the environment configuration. We only need to -call the :meth:`envs.ManagerBasedEnv.reset` method to reset the environment and :meth:`envs.ManagerBasedEnv.step` -method to step the environment. Both these functions return the observation and an info dictionary -which may contain additional information provided by the environment. These can be used by an -agent for decision-making. - -The :class:`envs.ManagerBasedEnv` class does not have any notion of terminations since that concept is -specific for episodic tasks. Thus, the user is responsible for defining the termination condition -for the environment. In this tutorial, we reset the simulation at regular intervals. - -.. literalinclude:: ../../../../source/standalone/tutorials/03_envs/create_cartpole_base_env.py - :language: python - :pyobject: main - -An important thing to note above is that the entire simulation loop is wrapped inside the -:meth:`torch.inference_mode` context manager. This is because the environment uses PyTorch -operations under-the-hood and we want to ensure that the simulation is not slowed down by -the overhead of PyTorch's autograd engine and gradients are not computed for the simulation -operations. - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -To run the base environment made in this tutorial, you can use the following command: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/03_envs/create_cartpole_base_env.py --num_envs 32 - - -This should open a stage with a ground plane, light source, and cartpoles. The simulation should be -playing with random actions on the cartpole. Additionally, it opens a UI window on the bottom -right corner of the screen named ``"Isaac Lab"``. This window contains different UI elements that -can be used for debugging and visualization. - -To stop the simulation, you can either close the window, or press ``Ctrl+C`` in the terminal where you -started the simulation. - -In this tutorial, we learned about the different managers that help define a base environment. We -include more examples of defining the base environment in the ``source/standalone/tutorials/03_envs`` -directory. For completeness, they can be run using the following commands: - -.. code-block:: bash - - # Floating cube environment with custom action term for PD control - ./isaaclab.sh -p source/standalone/tutorials/03_envs/create_cube_base_env.py --num_envs 32 - - # Quadrupedal locomotion environment with a policy that interacts with the environment - ./isaaclab.sh -p source/standalone/tutorials/03_envs/create_quadruped_base_env.py --num_envs 32 - - -In the following tutorial, we will look at the :class:`envs.ManagerBasedRLEnv` class and how to use it -to create a Markovian Decision Process (MDP). diff --git a/docs/_build/html/_sources/source/tutorials/03_envs/create_manager_rl_env.rst b/docs/_build/html/_sources/source/tutorials/03_envs/create_manager_rl_env.rst deleted file mode 100644 index 01732e2cb6..0000000000 --- a/docs/_build/html/_sources/source/tutorials/03_envs/create_manager_rl_env.rst +++ /dev/null @@ -1,194 +0,0 @@ -.. _tutorial-create-manager-rl-env: - - -Creating a Manager-Based RL Environment -======================================= - -.. currentmodule:: omni.isaac.lab - -Having learnt how to create a base environment in :ref:`tutorial-create-manager-base-env`, we will now look at how to create a manager-based -task environment for reinforcement learning. - -The base environment is designed as an sense-act environment where the agent can send commands to the environment -and receive observations from the environment. This minimal interface is sufficient for many applications such as -traditional motion planning and controls. However, many applications require a task-specification which often -serves as the learning objective for the agent. For instance, in a navigation task, the agent may be required to -reach a goal location. To this end, we use the :class:`envs.ManagerBasedRLEnv` class which extends the base environment -to include a task specification. - -Similar to other components in Isaac Lab, instead of directly modifying the base class :class:`envs.ManagerBasedRLEnv`, we -encourage users to simply implement a configuration :class:`envs.ManagerBasedRLEnvCfg` for their task environment. -This practice allows us to separate the task specification from the environment implementation, making it easier -to reuse components of the same environment for different tasks. - -In this tutorial, we will configure the cartpole environment using the :class:`envs.ManagerBasedRLEnvCfg` to create a manager-based task -for balancing the pole upright. We will learn how to specify the task using reward terms, termination criteria, -curriculum and commands. - - -The Code -~~~~~~~~ - -For this tutorial, we use the cartpole environment defined in ``omni.isaac.lab_tasks.manager_based.classic.cartpole`` module. - -.. dropdown:: Code for cartpole_env_cfg.py - :icon: code - - .. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :emphasize-lines: 63-68, 124-149, 152-162, 165-169, 187-192 - :linenos: - -The script for running the environment ``run_cartpole_rl_env.py`` is present in the -``isaaclab/source/standalone/tutorials/03_envs`` directory. The script is similar to the -``cartpole_base_env.py`` script in the previous tutorial, except that it uses the -:class:`envs.ManagerBasedRLEnv` instead of the :class:`envs.ManagerBasedEnv`. - -.. dropdown:: Code for run_cartpole_rl_env.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/03_envs/run_cartpole_rl_env.py - :language: python - :emphasize-lines: 38-42, 56-57 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -We already went through parts of the above in the :ref:`tutorial-create-manager-base-env` tutorial to learn -about how to specify the scene, observations, actions and events. Thus, in this tutorial, we -will focus only on the RL components of the environment. - -In Isaac Lab, we provide various implementations of different terms in the :mod:`envs.mdp` module. We will use -some of these terms in this tutorial, but users are free to define their own terms as well. These -are usually placed in their task-specific sub-package -(for instance, in :mod:`omni.isaac.lab_tasks.manager_based.classic.cartpole.mdp`). - - -Defining rewards ----------------- - -The :class:`managers.RewardManager` is used to compute the reward terms for the agent. Similar to the other -managers, its terms are configured using the :class:`managers.RewardTermCfg` class. The -:class:`managers.RewardTermCfg` class specifies the function or callable class that computes the reward -as well as the weighting associated with it. It also takes in dictionary of arguments, ``"params"`` -that are passed to the reward function when it is called. - -For the cartpole task, we will use the following reward terms: - -* **Alive Reward**: Encourage the agent to stay alive for as long as possible. -* **Terminating Reward**: Similarly penalize the agent for terminating. -* **Pole Angle Reward**: Encourage the agent to keep the pole at the desired upright position. -* **Cart Velocity Reward**: Encourage the agent to keep the cart velocity as small as possible. -* **Pole Velocity Reward**: Encourage the agent to keep the pole velocity as small as possible. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :pyobject: RewardsCfg - -Defining termination criteria ------------------------------ - -Most learning tasks happen over a finite number of steps that we call an episode. For instance, in the cartpole -task, we want the agent to balance the pole for as long as possible. However, if the agent reaches an unstable -or unsafe state, we want to terminate the episode. On the other hand, if the agent is able to balance the pole -for a long time, we want to terminate the episode and start a new one so that the agent can learn to balance the -pole from a different starting configuration. - -The :class:`managers.TerminationsCfg` configures what constitutes for an episode to terminate. In this example, -we want the task to terminate when either of the following conditions is met: - -* **Episode Length** The episode length is greater than the defined max_episode_length -* **Cart out of bounds** The cart goes outside of the bounds [-3, 3] - -The flag :attr:`managers.TerminationsCfg.time_out` specifies whether the term is a time-out (truncation) term -or terminated term. These are used to indicate the two types of terminations as described in `Gymnasium's documentation -`_. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :pyobject: TerminationsCfg - -Defining commands ------------------ - -For various goal-conditioned tasks, it is useful to specify the goals or commands for the agent. These are -handled through the :class:`managers.CommandManager`. The command manager handles resampling and updating the -commands at each step. It can also be used to provide the commands as an observation to the agent. - -For this simple task, we do not use any commands. This is specified by using a command term with the -:class:`envs.mdp.NullCommandCfg` configuration. However, you can see an example of command definitions in the -locomotion or manipulation tasks. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :pyobject: CommandsCfg - -Defining curriculum -------------------- - -Often times when training a learning agent, it helps to start with a simple task and gradually increase the -tasks's difficulty as the agent training progresses. This is the idea behind curriculum learning. In Isaac Lab, -we provide a :class:`managers.CurriculumManager` class that can be used to define a curriculum for your environment. - -In this tutorial we don't implement a curriculum for simplicity, but you can see an example of a -curriculum definition in the other locomotion or manipulation tasks. -We use a simple pass-through curriculum to define a curriculum manager that does not modify the environment. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :pyobject: CurriculumCfg - -Tying it all together ---------------------- - -With all the above components defined, we can now create the :class:`ManagerBasedRLEnvCfg` configuration for the -cartpole environment. This is similar to the :class:`ManagerBasedEnvCfg` defined in :ref:`tutorial-create-manager-base-env`, -only with the added RL components explained in the above sections. - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/cartpole_env_cfg.py - :language: python - :pyobject: CartpoleEnvCfg - -Running the simulation loop ---------------------------- - -Coming back to the ``run_cartpole_rl_env.py`` script, the simulation loop is similar to the previous tutorial. -The only difference is that we create an instance of :class:`envs.ManagerBasedRLEnv` instead of the -:class:`envs.ManagerBasedEnv`. Consequently, now the :meth:`envs.ManagerBasedRLEnv.step` method returns additional signals -such as the reward and termination status. The information dictionary also maintains logging of quantities -such as the reward contribution from individual terms, the termination status of each term, the episode length etc. - -.. literalinclude:: ../../../../source/standalone/tutorials/03_envs/run_cartpole_rl_env.py - :language: python - :pyobject: main - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Similar to the previous tutorial, we can run the environment by executing the ``run_cartpole_rl_env.py`` script. - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/03_envs/run_cartpole_rl_env.py --num_envs 32 - - -This should open a similar simulation as in the previous tutorial. However, this time, the environment -returns more signals that specify the reward and termination status. Additionally, the individual -environments reset themselves when they terminate based on the termination criteria specified in the -configuration. - -To stop the simulation, you can either close the window, or press ``Ctrl+C`` in the terminal -where you started the simulation. - -In this tutorial, we learnt how to create a task environment for reinforcement learning. We do this -by extending the base environment to include the rewards, terminations, commands and curriculum terms. -We also learnt how to use the :class:`envs.ManagerBasedRLEnv` class to run the environment and receive various -signals from it. - -While it is possible to manually create an instance of :class:`envs.ManagerBasedRLEnv` class for a desired task, -this is not scalable as it requires specialized scripts for each task. Thus, we exploit the -:meth:`gymnasium.make` function to create the environment with the gym interface. We will learn how to do this -in the next tutorial. diff --git a/docs/_build/html/_sources/source/tutorials/03_envs/index.rst b/docs/_build/html/_sources/source/tutorials/03_envs/index.rst deleted file mode 100644 index f08148dab3..0000000000 --- a/docs/_build/html/_sources/source/tutorials/03_envs/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -Designing an Environment -======================== - -The following tutorials introduce the concept of manager-based environments: :class:`~omni.isaac.lab.envs.ManagerBasedEnv` -and its derivative :class:`~omni.isaac.lab.envs.ManagerBasedRLEnv`, as well as the direct workflow base class -:class:`~omni.isaac.lab.envs.DirectRLEnv`. These environments bring-in together -different aspects of the framework to create a simulation environment for agent interaction. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - create_manager_base_env - create_manager_rl_env - create_direct_rl_env - register_rl_env_gym - run_rl_training diff --git a/docs/_build/html/_sources/source/tutorials/03_envs/register_rl_env_gym.rst b/docs/_build/html/_sources/source/tutorials/03_envs/register_rl_env_gym.rst deleted file mode 100644 index bbf21cc46b..0000000000 --- a/docs/_build/html/_sources/source/tutorials/03_envs/register_rl_env_gym.rst +++ /dev/null @@ -1,162 +0,0 @@ -Registering an Environment -========================== - -.. currentmodule:: omni.isaac.lab - -In the previous tutorial, we learned how to create a custom cartpole environment. We manually -created an instance of the environment by importing the environment class and its configuration -class. - -.. dropdown:: Environment creation in the previous tutorial - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/03_envs/run_cartpole_rl_env.py - :language: python - :start-at: # create environment configuration - :end-at: env = ManagerBasedRLEnv(cfg=env_cfg) - -While straightforward, this approach is not scalable as we have a large suite of environments. -In this tutorial, we will show how to use the :meth:`gymnasium.register` method to register -environments with the ``gymnasium`` registry. This allows us to create the environment through -the :meth:`gymnasium.make` function. - - -.. dropdown:: Environment creation in this tutorial - :icon: code - - .. literalinclude:: ../../../../source/standalone/environments/random_agent.py - :language: python - :lines: 36-47 - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``random_agent.py`` script in the ``source/standalone/environments`` directory. - -.. dropdown:: Code for random_agent.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/environments/random_agent.py - :language: python - :emphasize-lines: 36-37, 42-47 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -The :class:`envs.ManagerBasedRLEnv` class inherits from the :class:`gymnasium.Env` class to follow -a standard interface. However, unlike the traditional Gym environments, the :class:`envs.ManagerBasedRLEnv` -implements a *vectorized* environment. This means that multiple environment instances -are running simultaneously in the same process, and all the data is returned in a batched -fashion. - -Similarly, the :class:`envs.DirectRLEnv` class also inherits from the :class:`gymnasium.Env` class -for the direct workflow. - -Using the gym registry ----------------------- - -To register an environment, we use the :meth:`gymnasium.register` method. This method takes -in the environment name, the entry point to the environment class, and the entry point to the -environment configuration class. - -.. note:: - The :mod:`gymnasium` registry is a global registry. Hence, it is important to ensure that the - environment names are unique. Otherwise, the registry will throw an error when registering - the environment. - -Manager-Based Environments -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -For manager-based environments, the following shows the registration -call for the cartpole environment in the ``omni.isaac.lab_tasks.manager_based.classic.cartpole`` sub-package: - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/classic/cartpole/__init__.py - :language: python - :lines: 10- - :emphasize-lines: 4, 11, 12, 15 - -The ``id`` argument is the name of the environment. As a convention, we name all the environments -with the prefix ``Isaac-`` to make it easier to search for them in the registry. The name of the -environment is typically followed by the name of the task, and then the name of the robot. -For instance, for legged locomotion with ANYmal C on flat terrain, the environment is called -``Isaac-Velocity-Flat-Anymal-C-v0``. The version number ``v`` is typically used to specify different -variations of the same environment. Otherwise, the names of the environments can become too long -and difficult to read. - -The ``entry_point`` argument is the entry point to the environment class. The entry point is a string -of the form ``:``. In the case of the cartpole environment, the entry point is -``omni.isaac.lab.envs:ManagerBasedRLEnv``. The entry point is used to import the environment class -when creating the environment instance. - -The ``env_cfg_entry_point`` argument specifies the default configuration for the environment. The default -configuration is loaded using the :meth:`omni.isaac.lab_tasks.utils.parse_env_cfg` function. -It is then passed to the :meth:`gymnasium.make` function to create the environment instance. -The configuration entry point can be both a YAML file or a python configuration class. - -Direct Environments -^^^^^^^^^^^^^^^^^^^ - -For direct-based environments, the environment registration follows a similar pattern. Instead of -registering the environment's entry point as the :class:`~omni.isaac.lab.envs.ManagerBasedRLEnv` class, -we register the environment's entry point as the implementation class of the environment. -Additionally, we add the suffix ``-Direct`` to the environment name to differentiate it from the -manager-based environments. - -As an example, the following shows the registration call for the cartpole environment in the -``omni.isaac.lab_tasks.direct.cartpole`` sub-package: - -.. literalinclude:: ../../../../source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/direct/cartpole/__init__.py - :language: python - :lines: 10-31 - :emphasize-lines: 5, 12, 13, 16 - - -Creating the environment ------------------------- - -To inform the ``gym`` registry with all the environments provided by the ``omni.isaac.lab_tasks`` -extension, we must import the module at the start of the script. This will execute the ``__init__.py`` -file which iterates over all the sub-packages and registers their respective environments. - -.. literalinclude:: ../../../../source/standalone/environments/random_agent.py - :language: python - :start-at: import omni.isaac.lab_tasks # noqa: F401 - :end-at: import omni.isaac.lab_tasks # noqa: F401 - -In this tutorial, the task name is read from the command line. The task name is used to parse -the default configuration as well as to create the environment instance. In addition, other -parsed command line arguments such as the number of environments, the simulation device, -and whether to render, are used to override the default configuration. - -.. literalinclude:: ../../../../source/standalone/environments/random_agent.py - :language: python - :start-at: # create environment configuration - :end-at: env = gym.make(args_cli.task, cfg=env_cfg) - -Once creating the environment, the rest of the execution follows the standard resetting and stepping. - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Now that we have gone through the code, let's run the script and see the result: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/environments/random_agent.py --task Isaac-Cartpole-v0 --num_envs 32 - - -This should open a stage with everything similar to the :ref:`tutorial-create-manager-rl-env` tutorial. -To stop the simulation, you can either close the window, or press ``Ctrl+C`` in the terminal. - -In addition, you can also change the simulation device from GPU to CPU by adding the ``--cpu`` flag: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/environments/random_agent.py --task Isaac-Cartpole-v0 --num_envs 32 --cpu - -With the ``--cpu`` flag, the simulation will run on the CPU. This is useful for debugging the simulation. -However, the simulation will run much slower than on the GPU. diff --git a/docs/_build/html/_sources/source/tutorials/03_envs/run_rl_training.rst b/docs/_build/html/_sources/source/tutorials/03_envs/run_rl_training.rst deleted file mode 100644 index ce99d99413..0000000000 --- a/docs/_build/html/_sources/source/tutorials/03_envs/run_rl_training.rst +++ /dev/null @@ -1,154 +0,0 @@ -Training with an RL Agent -========================= - -.. currentmodule:: omni.isaac.lab - -In the previous tutorials, we covered how to define an RL task environment, register -it into the ``gym`` registry, and interact with it using a random agent. We now move -on to the next step: training an RL agent to solve the task. - -Although the :class:`envs.ManagerBasedRLEnv` conforms to the :class:`gymnasium.Env` interface, -it is not exactly a ``gym`` environment. The input and outputs of the environment are -not numpy arrays, but rather based on torch tensors with the first dimension being the -number of environment instances. - -Additionally, most RL libraries expect their own variation of an environment interface. -For example, `Stable-Baselines3`_ expects the environment to conform to its -`VecEnv API`_ which expects a list of numpy arrays instead of a single tensor. Similarly, -`RSL-RL`_, `RL-Games`_ and `SKRL`_ expect a different interface. Since there is no one-size-fits-all -solution, we do not base the :class:`envs.ManagerBasedRLEnv` on any particular learning library. -Instead, we implement wrappers to convert the environment into the expected interface. -These are specified in the :mod:`omni.isaac.lab_tasks.utils.wrappers` module. - -In this tutorial, we will use `Stable-Baselines3`_ to train an RL agent to solve the -cartpole balancing task. - -.. caution:: - - Wrapping the environment with the respective learning framework's wrapper should happen in the end, - i.e. after all other wrappers have been applied. This is because the learning framework's wrapper - modifies the interpretation of environment's APIs which may no longer be compatible with :class:`gymnasium.Env`. - -The Code --------- - -For this tutorial, we use the training script from `Stable-Baselines3`_ workflow in the -``source/standalone/workflows/sb3`` directory. - -.. dropdown:: Code for train.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/workflows/sb3/train.py - :language: python - :emphasize-lines: 57, 66, 68-70, 81, 90-98, 100, 105-113, 115-116, 121-126, 133-136 - :linenos: - -The Code Explained ------------------- - -.. currentmodule:: omni.isaac.lab_tasks.utils - -Most of the code above is boilerplate code to create logging directories, saving the parsed configurations, -and setting up different Stable-Baselines3 components. For this tutorial, the important part is creating -the environment and wrapping it with the Stable-Baselines3 wrapper. - -There are three wrappers used in the code above: - -1. :class:`gymnasium.wrappers.RecordVideo`: This wrapper records a video of the environment - and saves it to the specified directory. This is useful for visualizing the agent's behavior - during training. -2. :class:`wrappers.sb3.Sb3VecEnvWrapper`: This wrapper converts the environment - into a Stable-Baselines3 compatible environment. -3. `stable_baselines3.common.vec_env.VecNormalize`_: This wrapper normalizes the - environment's observations and rewards. - -Each of these wrappers wrap around the previous wrapper by following ``env = wrapper(env, *args, **kwargs)`` -repeatedly. The final environment is then used to train the agent. For more information on how these -wrappers work, please refer to the :ref:`how-to-env-wrappers` documentation. - -The Code Execution ------------------- - -We train a PPO agent from Stable-Baselines3 to solve the cartpole balancing task. - -Training the agent -~~~~~~~~~~~~~~~~~~ - -There are three main ways to train the agent. Each of them has their own advantages and disadvantages. -It is up to you to decide which one you prefer based on your use case. - -Headless execution -"""""""""""""""""" - -If the ``--headless`` flag is set, the simulation is not rendered during training. This is useful -when training on a remote server or when you do not want to see the simulation. Typically, it speeds -up the training process since only physics simulation step is performed. - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --num_envs 64 --headless - - -Headless execution with off-screen render -""""""""""""""""""""""""""""""""""""""""" - -Since the above command does not render the simulation, it is not possible to visualize the agent's -behavior during training. To visualize the agent's behavior, we pass the ``--enable_cameras`` which -enables off-screen rendering. Additionally, we pass the flag ``--video`` which records a video of the -agent's behavior during training. - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --num_envs 64 --headless --enable_cameras --video - -The videos are saved to the ``logs/sb3/Isaac-Cartpole-v0//videos`` directory. You can open these videos -using any video player. - -Interactive execution -""""""""""""""""""""" - -.. currentmodule:: omni.isaac.lab - -While the above two methods are useful for training the agent, they don't allow you to interact with the -simulation to see what is happening. In this case, you can ignore the ``--headless`` flag and run the -training script as follows: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/workflows/sb3/train.py --task Isaac-Cartpole-v0 --num_envs 64 - -This will open the Isaac Sim window and you can see the agent training in the environment. However, this -will slow down the training process since the simulation is rendered on the screen. As a workaround, you -can switch between different render modes in the ``"Isaac Lab"`` window that is docked on the bottom-right -corner of the screen. To learn more about these render modes, please check the -:class:`sim.SimulationContext.RenderMode` class. - -Viewing the logs -~~~~~~~~~~~~~~~~ - -On a separate terminal, you can monitor the training progress by executing the following command: - -.. code:: bash - - # execute from the root directory of the repository - ./isaaclab.sh -p -m tensorboard.main --logdir logs/sb3/Isaac-Cartpole-v0 - -Playing the trained agent -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Once the training is complete, you can visualize the trained agent by executing the following command: - -.. code:: bash - - # execute from the root directory of the repository - ./isaaclab.sh -p source/standalone/workflows/sb3/play.py --task Isaac-Cartpole-v0 --num_envs 32 --use_last_checkpoint - -The above command will load the latest checkpoint from the ``logs/sb3/Isaac-Cartpole-v0`` -directory. You can also specify a specific checkpoint by passing the ``--checkpoint`` flag. - -.. _Stable-Baselines3: https://stable-baselines3.readthedocs.io/en/master/ -.. _VecEnv API: https://stable-baselines3.readthedocs.io/en/master/guide/vec_envs.html#vecenv-api-vs-gym-api -.. _`stable_baselines3.common.vec_env.VecNormalize`: https://stable-baselines3.readthedocs.io/en/master/guide/vec_envs.html#vecnormalize -.. _RL-Games: https://github.com/Denys88/rl_games -.. _RSL-RL: https://github.com/leggedrobotics/rsl_rl -.. _SKRL: https://skrl.readthedocs.io diff --git a/docs/_build/html/_sources/source/tutorials/04_sensors/add_sensors_on_robot.rst b/docs/_build/html/_sources/source/tutorials/04_sensors/add_sensors_on_robot.rst deleted file mode 100644 index 3cd7178abf..0000000000 --- a/docs/_build/html/_sources/source/tutorials/04_sensors/add_sensors_on_robot.rst +++ /dev/null @@ -1,204 +0,0 @@ -.. _tutorial-add-sensors-on-robot: - -Adding sensors on a robot -========================= - -.. currentmodule:: omni.isaac.lab - - -While the asset classes allow us to create and simulate the physical embodiment of the robot, -sensors help in obtaining information about the environment. They typically update at a lower -frequency than the simulation and are useful for obtaining different proprioceptive and -exteroceptive information. For example, a camera sensor can be used to obtain the visual -information of the environment, and a contact sensor can be used to obtain the contact -information of the robot with the environment. - -In this tutorial, we will see how to add different sensors to a robot. We will use the -ANYmal-C robot for this tutorial. The ANYmal-C robot is a quadrupedal robot with 12 degrees -of freedom. It has 4 legs, each with 3 degrees of freedom. The robot has the following -sensors: - -- A camera sensor on the head of the robot which provides RGB-D images -- A height scanner sensor that provides terrain height information -- Contact sensors on the feet of the robot that provide contact information - -We continue this tutorial from the previous tutorial on :ref:`tutorial-interactive-scene`, -where we learned about the :class:`scene.InteractiveScene` class. - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``add_sensors_on_robot.py`` script in the -``source/standalone/tutorials/04_sensors`` directory. - -.. dropdown:: Code for add_sensors_on_robot.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/04_sensors/add_sensors_on_robot.py - :language: python - :emphasize-lines: 72-95, 143-153, 167-168 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -Similar to the previous tutorials, where we added assets to the scene, the sensors are also added -to the scene using the scene configuration. All sensors inherit from the :class:`sensors.SensorBase` class -and are configured through their respective config classes. Each sensor instance can define its own -update period, which is the frequency at which the sensor is updated. The update period is specified -in seconds through the :attr:`sensors.SensorBaseCfg.update_period` attribute. - -Depending on the specified path and the sensor type, the sensors are attached to the prims in the scene. -They may have an associated prim that is created in the scene or they may be attached to an existing prim. -For instance, the camera sensor has a corresponding prim that is created in the scene, whereas for the -contact sensor, the activating the contact reporting is a property on a rigid body prim. - -In the following, we introduce the different sensors we use in this tutorial and how they are configured. -For more description about them, please check the :mod:`sensors` module. - -Camera sensor -------------- - -A camera is defined using the :class:`sensors.CameraCfg`. It is based on the USD Camera sensor and -the different data types are captured using Omniverse Replicator API. Since it has a corresponding prim -in the scene, the prims are created in the scene at the specified prim path. - -The configuration of the camera sensor includes the following parameters: - -* :attr:`~sensors.CameraCfg.spawn`: The type of USD camera to create. This can be either - :class:`~sim.spawners.sensors.PinholeCameraCfg` or :class:`~sim.spawners.sensors.FisheyeCameraCfg`. -* :attr:`~sensors.CameraCfg.offset`: The offset of the camera sensor from the parent prim. -* :attr:`~sensors.CameraCfg.data_types`: The data types to capture. This can be ``rgb``, - ``distance_to_image_plane``, ``normals``, or other types supported by the USD Camera sensor. - -To attach an RGB-D camera sensor to the head of the robot, we specify an offset relative to the base -frame of the robot. The offset is specified as a translation and rotation relative to the base frame, -and the :attr:`~sensors.CameraCfg.OffsetCfg.convention` in which the offset is specified. - -In the following, we show the configuration of the camera sensor used in this tutorial. We set the -update period to 0.1s, which means that the camera sensor is updated at 10Hz. The prim path expression is -set to ``{ENV_REGEX_NS}/Robot/base/front_cam`` where the ``{ENV_REGEX_NS}`` is the environment namespace, -``"Robot"`` is the name of the robot, ``"base"`` is the name of the prim to which the camera is attached, -and ``"front_cam"`` is the name of the prim associated with the camera sensor. - -.. literalinclude:: ../../../../source/standalone/tutorials/04_sensors/add_sensors_on_robot.py - :language: python - :start-at: camera = CameraCfg( - :end-before: height_scanner = RayCasterCfg( - -Height scanner --------------- - -The height-scanner is implemented as a virtual sensor using the NVIDIA Warp ray-casting kernels. -Through the :class:`sensors.RayCasterCfg`, we can specify the pattern of rays to cast and the -meshes against which to cast the rays. Since they are virtual sensors, there is no corresponding -prim created in the scene for them. Instead they are attached to a prim in the scene, which is -used to specify the location of the sensor. - -For this tutorial, the ray-cast based height scanner is attached to the base frame of the robot. -The pattern of rays is specified using the :attr:`~sensors.RayCasterCfg.pattern` attribute. For -a uniform grid pattern, we specify the pattern using :class:`~sensors.patterns.GridPatternCfg`. -Since we only care about the height information, we do not need to consider the roll and pitch -of the robot. Hence, we set the :attr:`~sensors.RayCasterCfg.attach_yaw_only` to true. - -For the height-scanner, you can visualize the points where the rays hit the mesh. This is done -by setting the :attr:`~sensors.SensorBaseCfg.debug_vis` attribute to true. - -The entire configuration of the height-scanner is as follows: - -.. literalinclude:: ../../../../source/standalone/tutorials/04_sensors/add_sensors_on_robot.py - :language: python - :start-at: height_scanner = RayCasterCfg( - :end-before: contact_forces = ContactSensorCfg( - -Contact sensor --------------- - -Contact sensors wrap around the PhysX contact reporting API to obtain the contact information of the robot -with the environment. Since it relies of PhysX, the contact sensor expects the contact reporting API -to be enabled on the rigid bodies of the robot. This can be done by setting the -:attr:`~sim.spawners.RigidObjectSpawnerCfg.activate_contact_sensors` to true in the asset configuration. - -Through the :class:`sensors.ContactSensorCfg`, it is possible to specify the prims for which we want to -obtain the contact information. Additional flags can be set to obtain more information about -the contact, such as the contact air time, contact forces between filtered prims, etc. - -In this tutorial, we attach the contact sensor to the feet of the robot. The feet of the robot are -named ``"LF_FOOT"``, ``"RF_FOOT"``, ``"LH_FOOT"``, and ``"RF_FOOT"``. We pass a Regex expression -``".*_FOOT"`` to simplify the prim path specification. This Regex expression matches all prims that -end with ``"_FOOT"``. - -We set the update period to 0 to update the sensor at the same frequency as the simulation. Additionally, -for contact sensors, we can specify the history length of the contact information to store. For this -tutorial, we set the history length to 6, which means that the contact information for the last 6 -simulation steps is stored. - -The entire configuration of the contact sensor is as follows: - -.. literalinclude:: ../../../../source/standalone/tutorials/04_sensors/add_sensors_on_robot.py - :language: python - :start-at: contact_forces = ContactSensorCfg( - :lines: 1-3 - -Running the simulation loop ---------------------------- - -Similar to when using assets, the buffers and physics handles for the sensors are initialized only -when the simulation is played, i.e., it is important to call ``sim.reset()`` after creating the scene. - -.. literalinclude:: ../../../../source/standalone/tutorials/04_sensors/add_sensors_on_robot.py - :language: python - :start-at: # Play the simulator - :end-at: sim.reset() - -Besides that, the simulation loop is similar to the previous tutorials. The sensors are updated as part -of the scene update and they internally handle the updating of their buffers based on their update -periods. - -The data from the sensors can be accessed through their ``data`` attribute. As an example, we show how -to access the data for the different sensors created in this tutorial: - -.. literalinclude:: ../../../../source/standalone/tutorials/04_sensors/add_sensors_on_robot.py - :language: python - :start-at: # print information from the sensors - :end-at: print("Received max contact force of: ", torch.max(scene["contact_forces"].data.net_forces_w).item()) - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Now that we have gone through the code, let's run the script and see the result: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/add_sensors_on_robot.py --num_envs 2 - - -This command should open a stage with a ground plane, lights, and two quadrupedal robots. -Around the robots, you should see red spheres that indicate the points where the rays hit the mesh. -Additionally, you can switch the viewport to the camera view to see the RGB image captured by the -camera sensor. Please check `here `_ for more information -on how to switch the viewport to the camera view. - -To stop the simulation, you can either close the window, or press ``Ctrl+C`` in the terminal. - -While in this tutorial, we went over creating and using different sensors, there are many more sensors -available in the :mod:`sensors` module. We include minimal examples of using these sensors in the -``source/standalone/tutorials/04_sensors`` directory. For completeness, these scripts can be run using the -following commands: - -.. code-block:: bash - - # Frame Transformer - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_frame_transformer.py - - # Ray Caster - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_ray_caster.py - - # Ray Caster Camera - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_ray_caster_camera.py - - # USD Camera - ./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py diff --git a/docs/_build/html/_sources/source/tutorials/04_sensors/index.rst b/docs/_build/html/_sources/source/tutorials/04_sensors/index.rst deleted file mode 100644 index 8c506f5dec..0000000000 --- a/docs/_build/html/_sources/source/tutorials/04_sensors/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Integrating Sensors -=================== - -The following tutorial shows you how to integrate sensors into the simulation environment. The -tutorials introduce the :class:`~omni.isaac.lab.sensors.SensorBase` class and its derivatives -such as :class:`~omni.isaac.lab.sensors.Camera` and :class:`~omni.isaac.lab.sensors.RayCaster`. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - add_sensors_on_robot diff --git a/docs/_build/html/_sources/source/tutorials/05_controllers/index.rst b/docs/_build/html/_sources/source/tutorials/05_controllers/index.rst deleted file mode 100644 index 3da810ce51..0000000000 --- a/docs/_build/html/_sources/source/tutorials/05_controllers/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -Using Motion Generators -======================= - -While the robots in the simulation environment can be controlled at the joint-level, the following -tutorials show you how to use motion generators to control the robots at the task-level. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - run_diff_ik diff --git a/docs/_build/html/_sources/source/tutorials/05_controllers/run_diff_ik.rst b/docs/_build/html/_sources/source/tutorials/05_controllers/run_diff_ik.rst deleted file mode 100644 index 7aee394e2e..0000000000 --- a/docs/_build/html/_sources/source/tutorials/05_controllers/run_diff_ik.rst +++ /dev/null @@ -1,154 +0,0 @@ -Using a task-space controller -============================= - -.. currentmodule:: omni.isaac.lab - -In the previous tutorials, we have joint-space controllers to control the robot. However, in many -cases, it is more intuitive to control the robot using a task-space controller. For example, if we -want to teleoperate the robot, it is easier to specify the desired end-effector pose rather than -the desired joint positions. - -In this tutorial, we will learn how to use a task-space controller to control the robot. -We will use the :class:`controllers.DifferentialIKController` class to track a desired -end-effector pose command. - - -The Code -~~~~~~~~ - -The tutorial corresponds to the ``run_diff_ik.py`` script in the -``source/standalone/tutorials/05_controllers`` directory. - - -.. dropdown:: Code for run_diff_ik.py - :icon: code - - .. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/run_diff_ik.py - :language: python - :emphasize-lines: 98-100, 121-136, 155-157, 161-171 - :linenos: - - -The Code Explained -~~~~~~~~~~~~~~~~~~ - -While using any task-space controller, it is important to ensure that the provided -quantities are in the correct frames. When parallelizing environment instances, they are -all existing in the same unique simulation world frame. However, typically, we want each -environment itself to have its own local frame. This is accessible through the -:attr:`scene.InteractiveScene.env_origins` attribute. - -In our APIs, we use the following notation for frames: - -- The simulation world frame (denoted as ``w``), which is the frame of the entire simulation. -- The local environment frame (denoted as ``e``), which is the frame of the local environment. -- The robot's base frame (denoted as ``b``), which is the frame of the robot's base link. - -Since the asset instances are not "aware" of the local environment frame, they return -their states in the simulation world frame. Thus, we need to convert the obtained -quantities to the local environment frame. This is done by subtracting the local environment -origin from the obtained quantities. - - -Creating an IK controller -------------------------- - -The :class:`~controllers.DifferentialIKController` class computes the desired joint -positions for a robot to reach a desired end-effector pose. The included implementation -performs the computation in a batched format and uses PyTorch operations. It supports -different types of inverse kinematics solvers, including the damped least-squares method -and the pseudo-inverse method. These solvers can be specified using the -:attr:`~controllers.DifferentialIKControllerCfg.ik_method` argument. -Additionally, the controller can handle commands as both relative and absolute poses. - -In this tutorial, we will use the damped least-squares method to compute the desired -joint positions. Additionally, since we want to track desired end-effector poses, we -will use the absolute pose command mode. - -.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/run_diff_ik.py - :language: python - :start-at: # Create controller - :end-at: diff_ik_controller = DifferentialIKController(diff_ik_cfg, num_envs=scene.num_envs, device=sim.device) - -Obtaining the robot's joint and body indices --------------------------------------------- - -The IK controller implementation is a computation-only class. Thus, it expects the -user to provide the necessary information about the robot. This includes the robot's -joint positions, current end-effector pose, and the Jacobian matrix. - -While the attribute :attr:`assets.ArticulationData.joint_pos` provides the joint positions, -we only want the joint positions of the robot's arm, and not the gripper. Similarly, while -the attribute :attr:`assets.ArticulationData.body_state_w` provides the state of all the -robot's bodies, we only want the state of the robot's end-effector. Thus, we need to -index into these arrays to obtain the desired quantities. - -For this, the articulation class provides the methods :meth:`~assets.Articulation.find_joints` -and :meth:`~assets.Articulation.find_bodies`. These methods take in the names of the joints -and bodies and return their corresponding indices. - -While you may directly use these methods to obtain the indices, we recommend using the -:attr:`~managers.SceneEntityCfg` class to resolve the indices. This class is used in various -places in the APIs to extract certain information from a scene entity. Internally, it -calls the above methods to obtain the indices. However, it also performs some additional -checks to ensure that the provided names are valid. Thus, it is a safer option to use -this class. - -.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/run_diff_ik.py - :language: python - :start-at: # Specify robot-specific parameters - :end-before: # Define simulation stepping - - -Computing robot command ------------------------ - -The IK controller separates the operation of setting the desired command and -computing the desired joint positions. This is done to allow for the user to -run the IK controller at a different frequency than the robot's control frequency. - -The :meth:`~controllers.DifferentialIKController.set_command` method takes in -the desired end-effector pose as a single batched array. The pose is specified in -the robot's base frame. - -.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/run_diff_ik.py - :language: python - :start-at: # reset controller - :end-at: diff_ik_controller.set_command(ik_commands) - -We can then compute the desired joint positions using the -:meth:`~controllers.DifferentialIKController.compute` method. -The method takes in the current end-effector pose (in base frame), Jacobian, and -current joint positions. We read the Jacobian matrix from the robot's data, which uses -its value computed from the physics engine. - - -.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/run_diff_ik.py - :language: python - :start-at: # obtain quantities from simulation - :end-at: joint_pos_des = diff_ik_controller.compute(ee_pos_b, ee_quat_b, jacobian, joint_pos) - -The computed joint position targets can then be applied on the robot, as done in the -previous tutorials. - -.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/run_diff_ik.py - :language: python - :start-at: # apply actions - :end-at: scene.write_data_to_sim() - - -The Code Execution -~~~~~~~~~~~~~~~~~~ - -Now that we have gone through the code, let's run the script and see the result: - -.. code-block:: bash - - ./isaaclab.sh -p source/standalone/tutorials/05_controllers/run_diff_ik.py --robot franka_panda --num_envs 128 - -The script will start a simulation with 128 robots. The robots will be controlled using the IK controller. -The current and desired end-effector poses should be displayed using frame markers. When the robot reaches -the desired pose, the command should cycle through to the next pose specified in the script. - -To stop the simulation, you can either close the window, or press the ``STOP`` button in the UI, or -press ``Ctrl+C`` in the terminal. diff --git a/docs/_build/html/_sources/source/tutorials/index.rst b/docs/_build/html/_sources/source/tutorials/index.rst deleted file mode 100644 index 34f445f712..0000000000 --- a/docs/_build/html/_sources/source/tutorials/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -Tutorials -========= - -Welcome to the Isaac Lab tutorials! These tutorials provide a step-by-step guide to help you understand -and use various features of the framework. All the tutorials are written as Python scripts. You can -find the source code for each tutorial in the ``source/standalone/tutorials`` directory of the Isaac Lab -repository. - -.. note:: - - We would love to extend the tutorials to cover more topics and use cases, so please let us know if - you have any suggestions. - -We recommend that you go through the tutorials in the order they are listed here. - -.. toctree:: - :maxdepth: 2 - - 00_sim/index - 01_assets/index - 02_scene/index - 03_envs/index - 04_sensors/index - 05_controllers/index diff --git a/docs/_build/html/_sphinx_design_static/design-tabs.js b/docs/_build/html/_sphinx_design_static/design-tabs.js deleted file mode 100644 index b25bd6a4fa..0000000000 --- a/docs/_build/html/_sphinx_design_static/design-tabs.js +++ /dev/null @@ -1,101 +0,0 @@ -// @ts-check - -// Extra JS capability for selected tabs to be synced -// The selection is stored in local storage so that it persists across page loads. - -/** - * @type {Record} - */ -let sd_id_to_elements = {}; -const storageKeyPrefix = "sphinx-design-tab-id-"; - -/** - * Create a key for a tab element. - * @param {HTMLElement} el - The tab element. - * @returns {[string, string, string] | null} - The key. - * - */ -function create_key(el) { - let syncId = el.getAttribute("data-sync-id"); - let syncGroup = el.getAttribute("data-sync-group"); - if (!syncId || !syncGroup) return null; - return [syncGroup, syncId, syncGroup + "--" + syncId]; -} - -/** - * Initialize the tab selection. - * - */ -function ready() { - // Find all tabs with sync data - - /** @type {string[]} */ - let groups = []; - - document.querySelectorAll(".sd-tab-label").forEach((label) => { - if (label instanceof HTMLElement) { - let data = create_key(label); - if (data) { - let [group, id, key] = data; - - // add click event listener - // @ts-ignore - label.onclick = onSDLabelClick; - - // store map of key to elements - if (!sd_id_to_elements[key]) { - sd_id_to_elements[key] = []; - } - sd_id_to_elements[key].push(label); - - if (groups.indexOf(group) === -1) { - groups.push(group); - // Check if a specific tab has been selected via URL parameter - const tabParam = new URLSearchParams(window.location.search).get( - group - ); - if (tabParam) { - console.log( - "sphinx-design: Selecting tab id for group '" + - group + - "' from URL parameter: " + - tabParam - ); - window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); - } - } - - // Check is a specific tab has been selected previously - let previousId = window.sessionStorage.getItem( - storageKeyPrefix + group - ); - if (previousId === id) { - // console.log( - // "sphinx-design: Selecting tab from session storage: " + id - // ); - // @ts-ignore - label.previousElementSibling.checked = true; - } - } - } - }); -} - -/** - * Activate other tabs with the same sync id. - * - * @this {HTMLElement} - The element that was clicked. - */ -function onSDLabelClick() { - let data = create_key(this); - if (!data) return; - let [group, id, key] = data; - for (const label of sd_id_to_elements[key]) { - if (label === this) continue; - // @ts-ignore - label.previousElementSibling.checked = true; - } - window.sessionStorage.setItem(storageKeyPrefix + group, id); -} - -document.addEventListener("DOMContentLoaded", ready, false); diff --git a/docs/_build/html/_sphinx_design_static/sphinx-design.5ea377869091fd0449014c60fc090103.min.css b/docs/_build/html/_sphinx_design_static/sphinx-design.5ea377869091fd0449014c60fc090103.min.css deleted file mode 100644 index a325746f2e..0000000000 --- a/docs/_build/html/_sphinx_design_static/sphinx-design.5ea377869091fd0449014c60fc090103.min.css +++ /dev/null @@ -1 +0,0 @@ -.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative;font-size:var(--sd-fontsize-dropdown)}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary.sd-summary-title{padding:.5em 1em;font-size:var(--sd-fontsize-dropdown-title);font-weight:var(--sd-fontweight-dropdown-title);user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;list-style:none;display:inline-flex;justify-content:space-between}details.sd-dropdown summary.sd-summary-title::-webkit-details-marker{display:none}details.sd-dropdown summary.sd-summary-title:focus{outline:none}details.sd-dropdown summary.sd-summary-title .sd-summary-icon{margin-right:.6em;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary.sd-summary-title .sd-summary-text{flex-grow:1;line-height:1.5;padding-right:.5rem}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker{pointer-events:none;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker svg{opacity:.6}details.sd-dropdown summary.sd-summary-title:hover .sd-summary-state-marker svg{opacity:1;transform:scale(1.1)}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown .sd-summary-chevron-right{transition:.25s}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-right{transform:rotate(90deg)}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-down{transform:rotate(180deg)}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-bg: rgba(0, 113, 188, 0.2);--sd-color-secondary-bg: rgba(108, 117, 125, 0.2);--sd-color-success-bg: rgba(40, 167, 69, 0.2);--sd-color-info-bg: rgba(23, 162, 184, 0.2);--sd-color-warning-bg: rgba(240, 179, 126, 0.2);--sd-color-danger-bg: rgba(220, 53, 69, 0.2);--sd-color-light-bg: rgba(248, 249, 250, 0.2);--sd-color-muted-bg: rgba(108, 117, 125, 0.2);--sd-color-dark-bg: rgba(33, 37, 41, 0.2);--sd-color-black-bg: rgba(0, 0, 0, 0.2);--sd-color-white-bg: rgba(255, 255, 255, 0.2);--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem;--sd-fontsize-dropdown: inherit;--sd-fontsize-dropdown-title: 1rem;--sd-fontweight-dropdown-title: 700} diff --git a/docs/_build/html/_static/NVIDIA-logo-black.png b/docs/_build/html/_static/NVIDIA-logo-black.png deleted file mode 100644 index 12e95458e7..0000000000 Binary files a/docs/_build/html/_static/NVIDIA-logo-black.png and /dev/null differ diff --git a/docs/_build/html/_static/NVIDIA-logo-white.png b/docs/_build/html/_static/NVIDIA-logo-white.png deleted file mode 100644 index 1143326b85..0000000000 Binary files a/docs/_build/html/_static/NVIDIA-logo-white.png and /dev/null differ diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css deleted file mode 100644 index 61572969d1..0000000000 --- a/docs/_build/html/_static/basic.css +++ /dev/null @@ -1,903 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 270px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 360px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -nav.contents, -aside.topic, -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -nav.contents, -aside.topic, -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -nav.contents > :last-child, -aside.topic > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -nav.contents::after, -aside.topic::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -aside.footnote > span, -div.citation > span { - float: left; -} -aside.footnote > span:last-of-type, -div.citation > span:last-of-type { - padding-right: 0.5em; -} -aside.footnote > p { - margin-left: 2em; -} -div.citation > p { - margin-left: 4em; -} -aside.footnote > p:last-of-type, -div.citation > p:last-of-type { - margin-bottom: 0em; -} -aside.footnote > p:last-of-type:after, -div.citation > p:last-of-type:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_build/html/_static/check-solid.svg b/docs/_build/html/_static/check-solid.svg deleted file mode 100644 index 92fad4b5c0..0000000000 --- a/docs/_build/html/_static/check-solid.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/_build/html/_static/clipboard.min.js b/docs/_build/html/_static/clipboard.min.js deleted file mode 100644 index 54b3c46381..0000000000 --- a/docs/_build/html/_static/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.8 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 - - - - diff --git a/docs/_build/html/_static/copybutton.css b/docs/_build/html/_static/copybutton.css deleted file mode 100644 index f1916ec7d1..0000000000 --- a/docs/_build/html/_static/copybutton.css +++ /dev/null @@ -1,94 +0,0 @@ -/* Copy buttons */ -button.copybtn { - position: absolute; - display: flex; - top: .3em; - right: .3em; - width: 1.7em; - height: 1.7em; - opacity: 0; - transition: opacity 0.3s, border .3s, background-color .3s; - user-select: none; - padding: 0; - border: none; - outline: none; - border-radius: 0.4em; - /* The colors that GitHub uses */ - border: #1b1f2426 1px solid; - background-color: #f6f8fa; - color: #57606a; -} - -button.copybtn.success { - border-color: #22863a; - color: #22863a; -} - -button.copybtn svg { - stroke: currentColor; - width: 1.5em; - height: 1.5em; - padding: 0.1em; -} - -div.highlight { - position: relative; -} - -/* Show the copybutton */ -.highlight:hover button.copybtn, button.copybtn.success { - opacity: 1; -} - -.highlight button.copybtn:hover { - background-color: rgb(235, 235, 235); -} - -.highlight button.copybtn:active { - background-color: rgb(187, 187, 187); -} - -/** - * A minimal CSS-only tooltip copied from: - * https://codepen.io/mildrenben/pen/rVBrpK - * - * To use, write HTML like the following: - * - *

Short

- */ - .o-tooltip--left { - position: relative; - } - - .o-tooltip--left:after { - opacity: 0; - visibility: hidden; - position: absolute; - content: attr(data-tooltip); - padding: .2em; - font-size: .8em; - left: -.2em; - background: grey; - color: white; - white-space: nowrap; - z-index: 2; - border-radius: 2px; - transform: translateX(-102%) translateY(0); - transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); -} - -.o-tooltip--left:hover:after { - display: block; - opacity: 1; - visibility: visible; - transform: translateX(-100%) translateY(0); - transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); - transition-delay: .5s; -} - -/* By default the copy button shouldn't show up when printing a page */ -@media print { - button.copybtn { - display: none; - } -} diff --git a/docs/_build/html/_static/copybutton.js b/docs/_build/html/_static/copybutton.js deleted file mode 100644 index 2ea7ff3e21..0000000000 --- a/docs/_build/html/_static/copybutton.js +++ /dev/null @@ -1,248 +0,0 @@ -// Localization support -const messages = { - 'en': { - 'copy': 'Copy', - 'copy_to_clipboard': 'Copy to clipboard', - 'copy_success': 'Copied!', - 'copy_failure': 'Failed to copy', - }, - 'es' : { - 'copy': 'Copiar', - 'copy_to_clipboard': 'Copiar al portapapeles', - 'copy_success': '¡Copiado!', - 'copy_failure': 'Error al copiar', - }, - 'de' : { - 'copy': 'Kopieren', - 'copy_to_clipboard': 'In die Zwischenablage kopieren', - 'copy_success': 'Kopiert!', - 'copy_failure': 'Fehler beim Kopieren', - }, - 'fr' : { - 'copy': 'Copier', - 'copy_to_clipboard': 'Copier dans le presse-papier', - 'copy_success': 'Copié !', - 'copy_failure': 'Échec de la copie', - }, - 'ru': { - 'copy': 'Скопировать', - 'copy_to_clipboard': 'Скопировать в буфер', - 'copy_success': 'Скопировано!', - 'copy_failure': 'Не удалось скопировать', - }, - 'zh-CN': { - 'copy': '复制', - 'copy_to_clipboard': '复制到剪贴板', - 'copy_success': '复制成功!', - 'copy_failure': '复制失败', - }, - 'it' : { - 'copy': 'Copiare', - 'copy_to_clipboard': 'Copiato negli appunti', - 'copy_success': 'Copiato!', - 'copy_failure': 'Errore durante la copia', - } -} - -let locale = 'en' -if( document.documentElement.lang !== undefined - && messages[document.documentElement.lang] !== undefined ) { - locale = document.documentElement.lang -} - -let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; -if (doc_url_root == '#') { - doc_url_root = ''; -} - -/** - * SVG files for our copy buttons - */ -let iconCheck = ` - ${messages[locale]['copy_success']} - - -` - -// If the user specified their own SVG use that, otherwise use the default -let iconCopy = ``; -if (!iconCopy) { - iconCopy = ` - ${messages[locale]['copy_to_clipboard']} - - - -` -} - -/** - * Set up copy/paste for code blocks - */ - -const runWhenDOMLoaded = cb => { - if (document.readyState != 'loading') { - cb() - } else if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', cb) - } else { - document.attachEvent('onreadystatechange', function() { - if (document.readyState == 'complete') cb() - }) - } -} - -const codeCellId = index => `codecell${index}` - -// Clears selected text since ClipboardJS will select the text when copying -const clearSelection = () => { - if (window.getSelection) { - window.getSelection().removeAllRanges() - } else if (document.selection) { - document.selection.empty() - } -} - -// Changes tooltip text for a moment, then changes it back -// We want the timeout of our `success` class to be a bit shorter than the -// tooltip and icon change, so that we can hide the icon before changing back. -var timeoutIcon = 2000; -var timeoutSuccessClass = 1500; - -const temporarilyChangeTooltip = (el, oldText, newText) => { - el.setAttribute('data-tooltip', newText) - el.classList.add('success') - // Remove success a little bit sooner than we change the tooltip - // So that we can use CSS to hide the copybutton first - setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) - setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) -} - -// Changes the copy button icon for two seconds, then changes it back -const temporarilyChangeIcon = (el) => { - el.innerHTML = iconCheck; - setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) -} - -const addCopyButtonToCodeCells = () => { - // If ClipboardJS hasn't loaded, wait a bit and try again. This - // happens because we load ClipboardJS asynchronously. - if (window.ClipboardJS === undefined) { - setTimeout(addCopyButtonToCodeCells, 250) - return - } - - // Add copybuttons to all of our code cells - const COPYBUTTON_SELECTOR = 'div.highlight pre'; - const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) - codeCells.forEach((codeCell, index) => { - const id = codeCellId(index) - codeCell.setAttribute('id', id) - - const clipboardButton = id => - `` - codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) - }) - -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Removes excluded text from a Node. - * - * @param {Node} target Node to filter. - * @param {string} exclude CSS selector of nodes to exclude. - * @returns {DOMString} Text from `target` with text removed. - */ -function filterText(target, exclude) { - const clone = target.cloneNode(true); // clone as to not modify the live DOM - if (exclude) { - // remove excluded nodes - clone.querySelectorAll(exclude).forEach(node => node.remove()); - } - return clone.innerText; -} - -// Callback when a copy button is clicked. Will be passed the node that was clicked -// should then grab the text and replace pieces of text that shouldn't be used in output -function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { - var regexp; - var match; - - // Do we check for line continuation characters and "HERE-documents"? - var useLineCont = !!lineContinuationChar - var useHereDoc = !!hereDocDelim - - // create regexp to capture prompt and remaining line - if (isRegexp) { - regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') - } else { - regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') - } - - const outputLines = []; - var promptFound = false; - var gotLineCont = false; - var gotHereDoc = false; - const lineGotPrompt = []; - for (const line of textContent.split('\n')) { - match = line.match(regexp) - if (match || gotLineCont || gotHereDoc) { - promptFound = regexp.test(line) - lineGotPrompt.push(promptFound) - if (removePrompts && promptFound) { - outputLines.push(match[2]) - } else { - outputLines.push(line) - } - gotLineCont = line.endsWith(lineContinuationChar) & useLineCont - if (line.includes(hereDocDelim) & useHereDoc) - gotHereDoc = !gotHereDoc - } else if (!onlyCopyPromptLines) { - outputLines.push(line) - } else if (copyEmptyLines && line.trim() === '') { - outputLines.push(line) - } - } - - // If no lines with the prompt were found then just use original lines - if (lineGotPrompt.some(v => v === true)) { - textContent = outputLines.join('\n'); - } - - // Remove a trailing newline to avoid auto-running when pasting - if (textContent.endsWith("\n")) { - textContent = textContent.slice(0, -1) - } - return textContent -} - - -var copyTargetText = (trigger) => { - var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); - - // get filtered text - let exclude = '.linenos'; - - let text = filterText(target, exclude); - return formatCopyText(text, '', false, true, true, true, '', '') -} - - // Initialize with a callback so we can modify the text before copy - const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) - - // Update UI with error/success messages - clipboard.on('success', event => { - clearSelection() - temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) - temporarilyChangeIcon(event.trigger) - }) - - clipboard.on('error', event => { - temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) - }) -} - -runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/docs/_build/html/_static/copybutton_funcs.js b/docs/_build/html/_static/copybutton_funcs.js deleted file mode 100644 index dbe1aaad79..0000000000 --- a/docs/_build/html/_static/copybutton_funcs.js +++ /dev/null @@ -1,73 +0,0 @@ -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Removes excluded text from a Node. - * - * @param {Node} target Node to filter. - * @param {string} exclude CSS selector of nodes to exclude. - * @returns {DOMString} Text from `target` with text removed. - */ -export function filterText(target, exclude) { - const clone = target.cloneNode(true); // clone as to not modify the live DOM - if (exclude) { - // remove excluded nodes - clone.querySelectorAll(exclude).forEach(node => node.remove()); - } - return clone.innerText; -} - -// Callback when a copy button is clicked. Will be passed the node that was clicked -// should then grab the text and replace pieces of text that shouldn't be used in output -export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { - var regexp; - var match; - - // Do we check for line continuation characters and "HERE-documents"? - var useLineCont = !!lineContinuationChar - var useHereDoc = !!hereDocDelim - - // create regexp to capture prompt and remaining line - if (isRegexp) { - regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') - } else { - regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') - } - - const outputLines = []; - var promptFound = false; - var gotLineCont = false; - var gotHereDoc = false; - const lineGotPrompt = []; - for (const line of textContent.split('\n')) { - match = line.match(regexp) - if (match || gotLineCont || gotHereDoc) { - promptFound = regexp.test(line) - lineGotPrompt.push(promptFound) - if (removePrompts && promptFound) { - outputLines.push(match[2]) - } else { - outputLines.push(line) - } - gotLineCont = line.endsWith(lineContinuationChar) & useLineCont - if (line.includes(hereDocDelim) & useHereDoc) - gotHereDoc = !gotHereDoc - } else if (!onlyCopyPromptLines) { - outputLines.push(line) - } else if (copyEmptyLines && line.trim() === '') { - outputLines.push(line) - } - } - - // If no lines with the prompt were found then just use original lines - if (lineGotPrompt.some(v => v === true)) { - textContent = outputLines.join('\n'); - } - - // Remove a trailing newline to avoid auto-running when pasting - if (textContent.endsWith("\n")) { - textContent = textContent.slice(0, -1) - } - return textContent -} diff --git a/docs/_build/html/_static/custom.css b/docs/_build/html/_static/custom.css deleted file mode 100644 index 0906259305..0000000000 --- a/docs/_build/html/_static/custom.css +++ /dev/null @@ -1,82 +0,0 @@ -/* - * For reference: https://pydata-sphinx-theme.readthedocs.io/en/v0.9.0/user_guide/customizing.html - * For colors: https://clrs.cc/ - */ - -/* anything related to the light theme */ -html[data-theme="light"] { - --pst-color-primary: #76B900; - --pst-color-secondary: #5b8e03; - --pst-color-secondary-highlight: #5b8e03; - --pst-color-inline-code-links: #76B900; - --pst-color-info: var(--pst-color-primary); - --pst-color-info-highlight: var(--pst-color-primary); - --pst-color-info-bg: #daedb9; - --pst-color-attention: #ffc107; - --pst-color-text-base: #323232; - --pst-color-text-muted: #646464; - --pst-color-shadow: #d8d8d8; - --pst-color-border: #c9c9c9; - --pst-color-inline-code: #76B900; - --pst-color-target: #fbe54e; - --pst-color-background: #fff; - --pst-color-on-background: #fff; - --pst-color-surface: #f5f5f5; - --pst-color-on-surface: #e1e1e1; - --pst-color-link: var(--pst-color-primary); - --pst-color-link-hover: #789841; - --pst-color-table-row-hover-bg: #daedb9; -} - -/* anything related to the dark theme */ -html[data-theme="dark"] { - --pst-color-primary: #76B900; - --pst-color-secondary: #c2f26f; - --pst-color-secondary-highlight: #c2f26f; - --pst-color-inline-code-links: #b6e664; - --pst-color-info: var(--pst-color-primary); - --pst-color-info-highlight: var(--pst-color-primary); - --pst-color-info-bg: #3a550b; - --pst-color-attention: #dca90f; - --pst-color-text-base: #cecece; - --pst-color-text-muted: #a6a6a6; - --pst-color-shadow: #212121; - --pst-color-border: silver; - --pst-color-inline-code: #76B900; - --pst-color-target: #472700; - --pst-color-background: #121212; - --pst-color-on-background: #1e1e1e; - --pst-color-surface: #212121; - --pst-color-on-surface: #373737; - --pst-color-link: var(--pst-color-primary); - --pst-color-link-hover: #aee354; - --pst-color-table-row-hover-bg: #3a550b; -} - -a { - text-decoration: none !important; -} - -/* for the announcement link */ -.bd-header-announcement a, -.bd-header-version-warning a { - color: #7FDBFF; -} - -/* for the search box in the navbar */ -.form-control { - border-radius: 0 !important; - border: none !important; - outline: none !important; -} - -/* reduce padding for logo */ -.navbar-brand { - padding-top: 0.0rem !important; - padding-bottom: 0.0rem !important; -} - -.navbar-icon-links { - padding-top: 0.0rem !important; - padding-bottom: 0.0rem !important; -} diff --git a/docs/_build/html/_static/design-tabs.js b/docs/_build/html/_static/design-tabs.js deleted file mode 100644 index b25bd6a4fa..0000000000 --- a/docs/_build/html/_static/design-tabs.js +++ /dev/null @@ -1,101 +0,0 @@ -// @ts-check - -// Extra JS capability for selected tabs to be synced -// The selection is stored in local storage so that it persists across page loads. - -/** - * @type {Record} - */ -let sd_id_to_elements = {}; -const storageKeyPrefix = "sphinx-design-tab-id-"; - -/** - * Create a key for a tab element. - * @param {HTMLElement} el - The tab element. - * @returns {[string, string, string] | null} - The key. - * - */ -function create_key(el) { - let syncId = el.getAttribute("data-sync-id"); - let syncGroup = el.getAttribute("data-sync-group"); - if (!syncId || !syncGroup) return null; - return [syncGroup, syncId, syncGroup + "--" + syncId]; -} - -/** - * Initialize the tab selection. - * - */ -function ready() { - // Find all tabs with sync data - - /** @type {string[]} */ - let groups = []; - - document.querySelectorAll(".sd-tab-label").forEach((label) => { - if (label instanceof HTMLElement) { - let data = create_key(label); - if (data) { - let [group, id, key] = data; - - // add click event listener - // @ts-ignore - label.onclick = onSDLabelClick; - - // store map of key to elements - if (!sd_id_to_elements[key]) { - sd_id_to_elements[key] = []; - } - sd_id_to_elements[key].push(label); - - if (groups.indexOf(group) === -1) { - groups.push(group); - // Check if a specific tab has been selected via URL parameter - const tabParam = new URLSearchParams(window.location.search).get( - group - ); - if (tabParam) { - console.log( - "sphinx-design: Selecting tab id for group '" + - group + - "' from URL parameter: " + - tabParam - ); - window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); - } - } - - // Check is a specific tab has been selected previously - let previousId = window.sessionStorage.getItem( - storageKeyPrefix + group - ); - if (previousId === id) { - // console.log( - // "sphinx-design: Selecting tab from session storage: " + id - // ); - // @ts-ignore - label.previousElementSibling.checked = true; - } - } - } - }); -} - -/** - * Activate other tabs with the same sync id. - * - * @this {HTMLElement} - The element that was clicked. - */ -function onSDLabelClick() { - let data = create_key(this); - if (!data) return; - let [group, id, key] = data; - for (const label of sd_id_to_elements[key]) { - if (label === this) continue; - // @ts-ignore - label.previousElementSibling.checked = true; - } - window.sessionStorage.setItem(storageKeyPrefix + group, id); -} - -document.addEventListener("DOMContentLoaded", ready, false); diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js deleted file mode 100644 index d06a71d751..0000000000 --- a/docs/_build/html/_static/doctools.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ -"use strict"; - -const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ - "TEXTAREA", - "INPUT", - "SELECT", - "BUTTON", -]); - -const _ready = (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } -}; - -/** - * Small JavaScript module for the documentation. - */ -const Documentation = { - init: () => { - Documentation.initDomainIndexTable(); - Documentation.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS: {}, - PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), - LOCALE: "unknown", - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext: (string) => { - const translated = Documentation.TRANSLATIONS[string]; - switch (typeof translated) { - case "undefined": - return string; // no translation - case "string": - return translated; // translation exists - default: - return translated[0]; // (singular, plural) translation tuple exists - } - }, - - ngettext: (singular, plural, n) => { - const translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated !== "undefined") - return translated[Documentation.PLURAL_EXPR(n)]; - return n === 1 ? singular : plural; - }, - - addTranslations: (catalog) => { - Object.assign(Documentation.TRANSLATIONS, catalog.messages); - Documentation.PLURAL_EXPR = new Function( - "n", - `return (${catalog.plural_expr})` - ); - Documentation.LOCALE = catalog.locale; - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar: () => { - document.querySelectorAll("input[name=q]")[0]?.focus(); - }, - - /** - * Initialise the domain index toggle buttons - */ - initDomainIndexTable: () => { - const toggler = (el) => { - const idNumber = el.id.substr(7); - const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); - if (el.src.substr(-9) === "minus.png") { - el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; - toggledRows.forEach((el) => (el.style.display = "none")); - } else { - el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; - toggledRows.forEach((el) => (el.style.display = "")); - } - }; - - const togglerElements = document.querySelectorAll("img.toggler"); - togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)) - ); - togglerElements.forEach((el) => (el.style.display = "")); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); - }, - - initOnKeyListeners: () => { - // only install a listener if it is really needed - if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - ) - return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.altKey || event.ctrlKey || event.metaKey) return; - - if (!event.shiftKey) { - switch (event.key) { - case "ArrowLeft": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const prevLink = document.querySelector('link[rel="prev"]'); - if (prevLink && prevLink.href) { - window.location.href = prevLink.href; - event.preventDefault(); - } - break; - case "ArrowRight": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const nextLink = document.querySelector('link[rel="next"]'); - if (nextLink && nextLink.href) { - window.location.href = nextLink.href; - event.preventDefault(); - } - break; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case "/": - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; - Documentation.focusSearchBar(); - event.preventDefault(); - } - }); - }, -}; - -// quick alias for translations -const _ = Documentation.gettext; - -_ready(Documentation.init); diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js deleted file mode 100644 index 4d08804caa..0000000000 --- a/docs/_build/html/_static/documentation_options.js +++ /dev/null @@ -1,14 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '', - LANGUAGE: 'zh-CN', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '', - NAVIGATION_WITH_KEYS: false, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/docs/_build/html/_static/favicon.ico b/docs/_build/html/_static/favicon.ico deleted file mode 100644 index 7b39f8af7a..0000000000 Binary files a/docs/_build/html/_static/favicon.ico and /dev/null differ diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png deleted file mode 100644 index a858a410e4..0000000000 Binary files a/docs/_build/html/_static/file.png and /dev/null differ diff --git a/docs/_build/html/_static/images/logo_binder.svg b/docs/_build/html/_static/images/logo_binder.svg deleted file mode 100644 index 45fecf7511..0000000000 --- a/docs/_build/html/_static/images/logo_binder.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - -logo - - - - - - - - diff --git a/docs/_build/html/_static/images/logo_colab.png b/docs/_build/html/_static/images/logo_colab.png deleted file mode 100644 index b7560ec216..0000000000 Binary files a/docs/_build/html/_static/images/logo_colab.png and /dev/null differ diff --git a/docs/_build/html/_static/images/logo_deepnote.svg b/docs/_build/html/_static/images/logo_deepnote.svg deleted file mode 100644 index fa77ebfc25..0000000000 --- a/docs/_build/html/_static/images/logo_deepnote.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_build/html/_static/images/logo_jupyterhub.svg b/docs/_build/html/_static/images/logo_jupyterhub.svg deleted file mode 100644 index 60cfe9f222..0000000000 --- a/docs/_build/html/_static/images/logo_jupyterhub.svg +++ /dev/null @@ -1 +0,0 @@ -logo_jupyterhubHub diff --git a/docs/_build/html/_static/language_data.js b/docs/_build/html/_static/language_data.js deleted file mode 100644 index 250f5665fa..0000000000 --- a/docs/_build/html/_static/language_data.js +++ /dev/null @@ -1,199 +0,0 @@ -/* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * - * This script contains the language-specific data used by searchtools.js, - * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; - - -/* Non-minified version is copied as a separate JS file, is available */ - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - diff --git a/docs/_build/html/_static/locales/ar/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ar/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 15541a6a37..0000000000 Binary files a/docs/_build/html/_static/locales/ar/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ar/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ar/LC_MESSAGES/booktheme.po deleted file mode 100644 index 2e8d682024..0000000000 --- a/docs/_build/html/_static/locales/ar/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ar\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "أقترح تحرير" - -msgid "Last updated on" -msgstr "آخر تحديث في" - -msgid "Edit this page" -msgstr "قم بتحرير هذه الصفحة" - -msgid "Launch" -msgstr "إطلاق" - -msgid "Print to PDF" -msgstr "طباعة إلى PDF" - -msgid "open issue" -msgstr "قضية مفتوحة" - -msgid "Download notebook file" -msgstr "تنزيل ملف دفتر الملاحظات" - -msgid "Toggle navigation" -msgstr "تبديل التنقل" - -msgid "Source repository" -msgstr "مستودع المصدر" - -msgid "By the" -msgstr "بواسطة" - -msgid "next page" -msgstr "الصفحة التالية" - -msgid "repository" -msgstr "مخزن" - -msgid "Sphinx Book Theme" -msgstr "موضوع كتاب أبو الهول" - -msgid "Download source file" -msgstr "تنزيل ملف المصدر" - -msgid "Contents" -msgstr "محتويات" - -msgid "By" -msgstr "بواسطة" - -msgid "Copyright" -msgstr "حقوق النشر" - -msgid "Fullscreen mode" -msgstr "وضع ملء الشاشة" - -msgid "Open an issue" -msgstr "افتح قضية" - -msgid "previous page" -msgstr "الصفحة السابقة" - -msgid "Download this page" -msgstr "قم بتنزيل هذه الصفحة" - -msgid "Theme by the" -msgstr "موضوع بواسطة" diff --git a/docs/_build/html/_static/locales/bg/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/bg/LC_MESSAGES/booktheme.mo deleted file mode 100644 index da95120037..0000000000 Binary files a/docs/_build/html/_static/locales/bg/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/bg/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/bg/LC_MESSAGES/booktheme.po deleted file mode 100644 index 56ef0ebdfa..0000000000 --- a/docs/_build/html/_static/locales/bg/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: bg\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "предложи редактиране" - -msgid "Last updated on" -msgstr "Последна актуализация на" - -msgid "Edit this page" -msgstr "Редактирайте тази страница" - -msgid "Launch" -msgstr "Стартиране" - -msgid "Print to PDF" -msgstr "Печат в PDF" - -msgid "open issue" -msgstr "отворен брой" - -msgid "Download notebook file" -msgstr "Изтеглете файла на бележника" - -msgid "Toggle navigation" -msgstr "Превключване на навигацията" - -msgid "Source repository" -msgstr "Хранилище на източника" - -msgid "By the" -msgstr "По" - -msgid "next page" -msgstr "Следваща страница" - -msgid "repository" -msgstr "хранилище" - -msgid "Sphinx Book Theme" -msgstr "Тема на книгата Sphinx" - -msgid "Download source file" -msgstr "Изтеглете изходния файл" - -msgid "Contents" -msgstr "Съдържание" - -msgid "By" -msgstr "От" - -msgid "Copyright" -msgstr "Авторско право" - -msgid "Fullscreen mode" -msgstr "Режим на цял екран" - -msgid "Open an issue" -msgstr "Отворете проблем" - -msgid "previous page" -msgstr "предишна страница" - -msgid "Download this page" -msgstr "Изтеглете тази страница" - -msgid "Theme by the" -msgstr "Тема от" diff --git a/docs/_build/html/_static/locales/bn/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/bn/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 6b96639b72..0000000000 Binary files a/docs/_build/html/_static/locales/bn/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/bn/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/bn/LC_MESSAGES/booktheme.po deleted file mode 100644 index 243ca31f73..0000000000 --- a/docs/_build/html/_static/locales/bn/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,63 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: bn\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "Last updated on" -msgstr "সর্বশেষ আপডেট" - -msgid "Edit this page" -msgstr "এই পৃষ্ঠাটি সম্পাদনা করুন" - -msgid "Launch" -msgstr "শুরু করা" - -msgid "Print to PDF" -msgstr "পিডিএফ প্রিন্ট করুন" - -msgid "open issue" -msgstr "খোলা সমস্যা" - -msgid "Download notebook file" -msgstr "নোটবুক ফাইল ডাউনলোড করুন" - -msgid "Toggle navigation" -msgstr "নেভিগেশন টগল করুন" - -msgid "Source repository" -msgstr "উত্স সংগ্রহস্থল" - -msgid "By the" -msgstr "দ্বারা" - -msgid "next page" -msgstr "পরবর্তী পৃষ্ঠা" - -msgid "Sphinx Book Theme" -msgstr "স্পিনিক্স বুক থিম" - -msgid "Download source file" -msgstr "উত্স ফাইল ডাউনলোড করুন" - -msgid "By" -msgstr "দ্বারা" - -msgid "Copyright" -msgstr "কপিরাইট" - -msgid "Open an issue" -msgstr "একটি সমস্যা খুলুন" - -msgid "previous page" -msgstr "আগের পৃষ্ঠা" - -msgid "Download this page" -msgstr "এই পৃষ্ঠাটি ডাউনলোড করুন" - -msgid "Theme by the" -msgstr "থিম দ্বারা" diff --git a/docs/_build/html/_static/locales/ca/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ca/LC_MESSAGES/booktheme.mo deleted file mode 100644 index a4dd30e9bd..0000000000 Binary files a/docs/_build/html/_static/locales/ca/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ca/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ca/LC_MESSAGES/booktheme.po deleted file mode 100644 index b27a13db9c..0000000000 --- a/docs/_build/html/_static/locales/ca/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ca\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "suggerir edició" - -msgid "Last updated on" -msgstr "Darrera actualització el" - -msgid "Edit this page" -msgstr "Editeu aquesta pàgina" - -msgid "Launch" -msgstr "Llançament" - -msgid "Print to PDF" -msgstr "Imprimeix a PDF" - -msgid "open issue" -msgstr "número obert" - -msgid "Download notebook file" -msgstr "Descarregar fitxer de quadern" - -msgid "Toggle navigation" -msgstr "Commuta la navegació" - -msgid "Source repository" -msgstr "Dipòsit de fonts" - -msgid "By the" -msgstr "Per la" - -msgid "next page" -msgstr "pàgina següent" - -msgid "Sphinx Book Theme" -msgstr "Tema del llibre Esfinx" - -msgid "Download source file" -msgstr "Baixeu el fitxer font" - -msgid "By" -msgstr "Per" - -msgid "Copyright" -msgstr "Copyright" - -msgid "Open an issue" -msgstr "Obriu un número" - -msgid "previous page" -msgstr "Pàgina anterior" - -msgid "Download this page" -msgstr "Descarregueu aquesta pàgina" - -msgid "Theme by the" -msgstr "Tema del" diff --git a/docs/_build/html/_static/locales/cs/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/cs/LC_MESSAGES/booktheme.mo deleted file mode 100644 index c39e01a6ae..0000000000 Binary files a/docs/_build/html/_static/locales/cs/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/cs/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/cs/LC_MESSAGES/booktheme.po deleted file mode 100644 index 3818df9765..0000000000 --- a/docs/_build/html/_static/locales/cs/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: cs\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "navrhnout úpravy" - -msgid "Last updated on" -msgstr "Naposledy aktualizováno" - -msgid "Edit this page" -msgstr "Upravit tuto stránku" - -msgid "Launch" -msgstr "Zahájení" - -msgid "Print to PDF" -msgstr "Tisk do PDF" - -msgid "open issue" -msgstr "otevřené číslo" - -msgid "Download notebook file" -msgstr "Stáhnout soubor poznámkového bloku" - -msgid "Toggle navigation" -msgstr "Přepnout navigaci" - -msgid "Source repository" -msgstr "Zdrojové úložiště" - -msgid "By the" -msgstr "Podle" - -msgid "next page" -msgstr "další strana" - -msgid "repository" -msgstr "úložiště" - -msgid "Sphinx Book Theme" -msgstr "Téma knihy Sfinga" - -msgid "Download source file" -msgstr "Stáhněte si zdrojový soubor" - -msgid "Contents" -msgstr "Obsah" - -msgid "By" -msgstr "Podle" - -msgid "Copyright" -msgstr "autorská práva" - -msgid "Fullscreen mode" -msgstr "Režim celé obrazovky" - -msgid "Open an issue" -msgstr "Otevřete problém" - -msgid "previous page" -msgstr "předchozí stránka" - -msgid "Download this page" -msgstr "Stáhněte si tuto stránku" - -msgid "Theme by the" -msgstr "Téma od" diff --git a/docs/_build/html/_static/locales/da/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/da/LC_MESSAGES/booktheme.mo deleted file mode 100644 index f43157d70c..0000000000 Binary files a/docs/_build/html/_static/locales/da/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/da/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/da/LC_MESSAGES/booktheme.po deleted file mode 100644 index 7f20a3bd09..0000000000 --- a/docs/_build/html/_static/locales/da/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: da\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "foreslå redigering" - -msgid "Last updated on" -msgstr "Sidst opdateret den" - -msgid "Edit this page" -msgstr "Rediger denne side" - -msgid "Launch" -msgstr "Start" - -msgid "Print to PDF" -msgstr "Udskriv til PDF" - -msgid "open issue" -msgstr "åbent nummer" - -msgid "Download notebook file" -msgstr "Download notesbog-fil" - -msgid "Toggle navigation" -msgstr "Skift navigation" - -msgid "Source repository" -msgstr "Kildelager" - -msgid "By the" -msgstr "Ved" - -msgid "next page" -msgstr "Næste side" - -msgid "repository" -msgstr "lager" - -msgid "Sphinx Book Theme" -msgstr "Sphinx bogtema" - -msgid "Download source file" -msgstr "Download kildefil" - -msgid "Contents" -msgstr "Indhold" - -msgid "By" -msgstr "Ved" - -msgid "Copyright" -msgstr "ophavsret" - -msgid "Fullscreen mode" -msgstr "Fuldskærmstilstand" - -msgid "Open an issue" -msgstr "Åbn et problem" - -msgid "previous page" -msgstr "forrige side" - -msgid "Download this page" -msgstr "Download denne side" - -msgid "Theme by the" -msgstr "Tema af" diff --git a/docs/_build/html/_static/locales/de/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/de/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 648b565c78..0000000000 Binary files a/docs/_build/html/_static/locales/de/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/de/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/de/LC_MESSAGES/booktheme.po deleted file mode 100644 index c0027d3ab0..0000000000 --- a/docs/_build/html/_static/locales/de/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: de\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "vorschlagen zu bearbeiten" - -msgid "Last updated on" -msgstr "Zuletzt aktualisiert am" - -msgid "Edit this page" -msgstr "Bearbeite diese Seite" - -msgid "Launch" -msgstr "Starten" - -msgid "Print to PDF" -msgstr "In PDF drucken" - -msgid "open issue" -msgstr "offenes Thema" - -msgid "Download notebook file" -msgstr "Notebook-Datei herunterladen" - -msgid "Toggle navigation" -msgstr "Navigation umschalten" - -msgid "Source repository" -msgstr "Quell-Repository" - -msgid "By the" -msgstr "Bis zum" - -msgid "next page" -msgstr "Nächste Seite" - -msgid "repository" -msgstr "Repository" - -msgid "Sphinx Book Theme" -msgstr "Sphinx-Buch-Thema" - -msgid "Download source file" -msgstr "Quelldatei herunterladen" - -msgid "Contents" -msgstr "Inhalt" - -msgid "By" -msgstr "Durch" - -msgid "Copyright" -msgstr "Urheberrechte ©" - -msgid "Fullscreen mode" -msgstr "Vollbildmodus" - -msgid "Open an issue" -msgstr "Öffnen Sie ein Problem" - -msgid "previous page" -msgstr "vorherige Seite" - -msgid "Download this page" -msgstr "Laden Sie diese Seite herunter" - -msgid "Theme by the" -msgstr "Thema von der" diff --git a/docs/_build/html/_static/locales/el/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/el/LC_MESSAGES/booktheme.mo deleted file mode 100644 index fca6e9355f..0000000000 Binary files a/docs/_build/html/_static/locales/el/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/el/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/el/LC_MESSAGES/booktheme.po deleted file mode 100644 index bdeb3270aa..0000000000 --- a/docs/_build/html/_static/locales/el/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: el\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "προτείνω επεξεργασία" - -msgid "Last updated on" -msgstr "Τελευταία ενημέρωση στις" - -msgid "Edit this page" -msgstr "Επεξεργαστείτε αυτήν τη σελίδα" - -msgid "Launch" -msgstr "Εκτόξευση" - -msgid "Print to PDF" -msgstr "Εκτύπωση σε PDF" - -msgid "open issue" -msgstr "ανοιχτό ζήτημα" - -msgid "Download notebook file" -msgstr "Λήψη αρχείου σημειωματάριου" - -msgid "Toggle navigation" -msgstr "Εναλλαγή πλοήγησης" - -msgid "Source repository" -msgstr "Αποθήκη πηγής" - -msgid "By the" -msgstr "Από το" - -msgid "next page" -msgstr "επόμενη σελίδα" - -msgid "repository" -msgstr "αποθήκη" - -msgid "Sphinx Book Theme" -msgstr "Θέμα βιβλίου Sphinx" - -msgid "Download source file" -msgstr "Λήψη αρχείου προέλευσης" - -msgid "Contents" -msgstr "Περιεχόμενα" - -msgid "By" -msgstr "Με" - -msgid "Copyright" -msgstr "Πνευματική ιδιοκτησία" - -msgid "Fullscreen mode" -msgstr "ΛΕΙΤΟΥΡΓΙΑ ΠΛΗΡΟΥΣ ΟΘΟΝΗΣ" - -msgid "Open an issue" -msgstr "Ανοίξτε ένα ζήτημα" - -msgid "previous page" -msgstr "προηγούμενη σελίδα" - -msgid "Download this page" -msgstr "Λήψη αυτής της σελίδας" - -msgid "Theme by the" -msgstr "Θέμα από το" diff --git a/docs/_build/html/_static/locales/eo/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/eo/LC_MESSAGES/booktheme.mo deleted file mode 100644 index d1072bbec6..0000000000 Binary files a/docs/_build/html/_static/locales/eo/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/eo/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/eo/LC_MESSAGES/booktheme.po deleted file mode 100644 index 6749f3a34a..0000000000 --- a/docs/_build/html/_static/locales/eo/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: eo\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "sugesti redaktadon" - -msgid "Last updated on" -msgstr "Laste ĝisdatigita la" - -msgid "Edit this page" -msgstr "Redaktu ĉi tiun paĝon" - -msgid "Launch" -msgstr "Lanĉo" - -msgid "Print to PDF" -msgstr "Presi al PDF" - -msgid "open issue" -msgstr "malferma numero" - -msgid "Download notebook file" -msgstr "Elŝutu kajeran dosieron" - -msgid "Toggle navigation" -msgstr "Ŝalti navigadon" - -msgid "Source repository" -msgstr "Fonto-deponejo" - -msgid "By the" -msgstr "Per la" - -msgid "next page" -msgstr "sekva paĝo" - -msgid "repository" -msgstr "deponejo" - -msgid "Sphinx Book Theme" -msgstr "Sfinksa Libro-Temo" - -msgid "Download source file" -msgstr "Elŝutu fontodosieron" - -msgid "Contents" -msgstr "Enhavo" - -msgid "By" -msgstr "De" - -msgid "Copyright" -msgstr "Kopirajto" - -msgid "Fullscreen mode" -msgstr "Plenekrana reĝimo" - -msgid "Open an issue" -msgstr "Malfermu numeron" - -msgid "previous page" -msgstr "antaŭa paĝo" - -msgid "Download this page" -msgstr "Elŝutu ĉi tiun paĝon" - -msgid "Theme by the" -msgstr "Temo de la" diff --git a/docs/_build/html/_static/locales/es/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/es/LC_MESSAGES/booktheme.mo deleted file mode 100644 index ba2ee4dc22..0000000000 Binary files a/docs/_build/html/_static/locales/es/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/es/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/es/LC_MESSAGES/booktheme.po deleted file mode 100644 index 71dde37f27..0000000000 --- a/docs/_build/html/_static/locales/es/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "sugerir editar" - -msgid "Last updated on" -msgstr "Ultima actualización en" - -msgid "Edit this page" -msgstr "Edita esta página" - -msgid "Launch" -msgstr "Lanzamiento" - -msgid "Print to PDF" -msgstr "Imprimir en PDF" - -msgid "open issue" -msgstr "Tema abierto" - -msgid "Download notebook file" -msgstr "Descargar archivo de cuaderno" - -msgid "Toggle navigation" -msgstr "Navegación de palanca" - -msgid "Source repository" -msgstr "Repositorio de origen" - -msgid "By the" -msgstr "Por el" - -msgid "next page" -msgstr "siguiente página" - -msgid "repository" -msgstr "repositorio" - -msgid "Sphinx Book Theme" -msgstr "Tema del libro de la esfinge" - -msgid "Download source file" -msgstr "Descargar archivo fuente" - -msgid "Contents" -msgstr "Contenido" - -msgid "By" -msgstr "Por" - -msgid "Copyright" -msgstr "Derechos de autor" - -msgid "Fullscreen mode" -msgstr "Modo de pantalla completa" - -msgid "Open an issue" -msgstr "Abrir un problema" - -msgid "previous page" -msgstr "pagina anterior" - -msgid "Download this page" -msgstr "Descarga esta pagina" - -msgid "Theme by the" -msgstr "Tema por el" diff --git a/docs/_build/html/_static/locales/et/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/et/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 983b82391f..0000000000 Binary files a/docs/_build/html/_static/locales/et/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/et/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/et/LC_MESSAGES/booktheme.po deleted file mode 100644 index cdcd07c7d8..0000000000 --- a/docs/_build/html/_static/locales/et/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: et\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "soovita muuta" - -msgid "Last updated on" -msgstr "Viimati uuendatud" - -msgid "Edit this page" -msgstr "Muutke seda lehte" - -msgid "Launch" -msgstr "Käivitage" - -msgid "Print to PDF" -msgstr "Prindi PDF-i" - -msgid "open issue" -msgstr "avatud küsimus" - -msgid "Download notebook file" -msgstr "Laadige sülearvuti fail alla" - -msgid "Toggle navigation" -msgstr "Lülita navigeerimine sisse" - -msgid "Source repository" -msgstr "Allikahoidla" - -msgid "By the" -msgstr "Autor" - -msgid "next page" -msgstr "järgmine leht" - -msgid "repository" -msgstr "hoidla" - -msgid "Sphinx Book Theme" -msgstr "Sfinksiraamatu teema" - -msgid "Download source file" -msgstr "Laadige alla lähtefail" - -msgid "Contents" -msgstr "Sisu" - -msgid "By" -msgstr "Kõrval" - -msgid "Copyright" -msgstr "Autoriõigus" - -msgid "Fullscreen mode" -msgstr "Täisekraanirežiim" - -msgid "Open an issue" -msgstr "Avage probleem" - -msgid "previous page" -msgstr "eelmine leht" - -msgid "Download this page" -msgstr "Laadige see leht alla" - -msgid "Theme by the" -msgstr "Teema" diff --git a/docs/_build/html/_static/locales/fi/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/fi/LC_MESSAGES/booktheme.mo deleted file mode 100644 index d8ac054597..0000000000 Binary files a/docs/_build/html/_static/locales/fi/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/fi/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/fi/LC_MESSAGES/booktheme.po deleted file mode 100644 index 3c3dd08962..0000000000 --- a/docs/_build/html/_static/locales/fi/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fi\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "ehdottaa muokkausta" - -msgid "Last updated on" -msgstr "Viimeksi päivitetty" - -msgid "Edit this page" -msgstr "Muokkaa tätä sivua" - -msgid "Launch" -msgstr "Tuoda markkinoille" - -msgid "Print to PDF" -msgstr "Tulosta PDF-tiedostoon" - -msgid "open issue" -msgstr "avoin ongelma" - -msgid "Download notebook file" -msgstr "Lataa muistikirjatiedosto" - -msgid "Toggle navigation" -msgstr "Vaihda navigointia" - -msgid "Source repository" -msgstr "Lähteen arkisto" - -msgid "By the" -msgstr "Mukaan" - -msgid "next page" -msgstr "seuraava sivu" - -msgid "repository" -msgstr "arkisto" - -msgid "Sphinx Book Theme" -msgstr "Sphinx-kirjan teema" - -msgid "Download source file" -msgstr "Lataa lähdetiedosto" - -msgid "Contents" -msgstr "Sisällys" - -msgid "By" -msgstr "Tekijä" - -msgid "Copyright" -msgstr "Tekijänoikeus" - -msgid "Fullscreen mode" -msgstr "Koko näytön tila" - -msgid "Open an issue" -msgstr "Avaa ongelma" - -msgid "previous page" -msgstr "Edellinen sivu" - -msgid "Download this page" -msgstr "Lataa tämä sivu" - -msgid "Theme by the" -msgstr "Teeman tekijä" diff --git a/docs/_build/html/_static/locales/fr/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/fr/LC_MESSAGES/booktheme.mo deleted file mode 100644 index f663d39f0f..0000000000 Binary files a/docs/_build/html/_static/locales/fr/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/fr/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/fr/LC_MESSAGES/booktheme.po deleted file mode 100644 index b57d2fe745..0000000000 --- a/docs/_build/html/_static/locales/fr/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "suggestion de modification" - -msgid "Last updated on" -msgstr "Dernière mise à jour le" - -msgid "Edit this page" -msgstr "Modifier cette page" - -msgid "Launch" -msgstr "lancement" - -msgid "Print to PDF" -msgstr "Imprimer au format PDF" - -msgid "open issue" -msgstr "signaler un problème" - -msgid "Download notebook file" -msgstr "Télécharger le fichier notebook" - -msgid "Toggle navigation" -msgstr "Basculer la navigation" - -msgid "Source repository" -msgstr "Dépôt source" - -msgid "By the" -msgstr "Par le" - -msgid "next page" -msgstr "page suivante" - -msgid "repository" -msgstr "dépôt" - -msgid "Sphinx Book Theme" -msgstr "Thème du livre Sphinx" - -msgid "Download source file" -msgstr "Télécharger le fichier source" - -msgid "Contents" -msgstr "Contenu" - -msgid "By" -msgstr "Par" - -msgid "Copyright" -msgstr "droits d'auteur" - -msgid "Fullscreen mode" -msgstr "Mode plein écran" - -msgid "Open an issue" -msgstr "Ouvrez un problème" - -msgid "previous page" -msgstr "page précédente" - -msgid "Download this page" -msgstr "Téléchargez cette page" - -msgid "Theme by the" -msgstr "Thème par le" diff --git a/docs/_build/html/_static/locales/hr/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/hr/LC_MESSAGES/booktheme.mo deleted file mode 100644 index eca4a1a284..0000000000 Binary files a/docs/_build/html/_static/locales/hr/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/hr/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/hr/LC_MESSAGES/booktheme.po deleted file mode 100644 index 4c425e89ae..0000000000 --- a/docs/_build/html/_static/locales/hr/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: hr\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "predloži uređivanje" - -msgid "Last updated on" -msgstr "Posljednje ažuriranje:" - -msgid "Edit this page" -msgstr "Uredite ovu stranicu" - -msgid "Launch" -msgstr "Pokrenite" - -msgid "Print to PDF" -msgstr "Ispis u PDF" - -msgid "open issue" -msgstr "otvoreno izdanje" - -msgid "Download notebook file" -msgstr "Preuzmi datoteku bilježnice" - -msgid "Toggle navigation" -msgstr "Uključi / isključi navigaciju" - -msgid "Source repository" -msgstr "Izvorno spremište" - -msgid "By the" -msgstr "Od strane" - -msgid "next page" -msgstr "sljedeća stranica" - -msgid "repository" -msgstr "spremište" - -msgid "Sphinx Book Theme" -msgstr "Tema knjige Sphinx" - -msgid "Download source file" -msgstr "Preuzmi izvornu datoteku" - -msgid "Contents" -msgstr "Sadržaj" - -msgid "By" -msgstr "Po" - -msgid "Copyright" -msgstr "Autorska prava" - -msgid "Fullscreen mode" -msgstr "Način preko cijelog zaslona" - -msgid "Open an issue" -msgstr "Otvorite izdanje" - -msgid "previous page" -msgstr "Prethodna stranica" - -msgid "Download this page" -msgstr "Preuzmite ovu stranicu" - -msgid "Theme by the" -msgstr "Tema autora" diff --git a/docs/_build/html/_static/locales/id/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/id/LC_MESSAGES/booktheme.mo deleted file mode 100644 index d07a06a9d2..0000000000 Binary files a/docs/_build/html/_static/locales/id/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/id/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/id/LC_MESSAGES/booktheme.po deleted file mode 100644 index 5db2ae1474..0000000000 --- a/docs/_build/html/_static/locales/id/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: id\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "menyarankan edit" - -msgid "Last updated on" -msgstr "Terakhir diperbarui saat" - -msgid "Edit this page" -msgstr "Edit halaman ini" - -msgid "Launch" -msgstr "Meluncurkan" - -msgid "Print to PDF" -msgstr "Cetak ke PDF" - -msgid "open issue" -msgstr "masalah terbuka" - -msgid "Download notebook file" -msgstr "Unduh file notebook" - -msgid "Toggle navigation" -msgstr "Alihkan navigasi" - -msgid "Source repository" -msgstr "Repositori sumber" - -msgid "By the" -msgstr "Oleh" - -msgid "next page" -msgstr "halaman selanjutnya" - -msgid "repository" -msgstr "gudang" - -msgid "Sphinx Book Theme" -msgstr "Tema Buku Sphinx" - -msgid "Download source file" -msgstr "Unduh file sumber" - -msgid "Contents" -msgstr "Isi" - -msgid "By" -msgstr "Oleh" - -msgid "Copyright" -msgstr "hak cipta" - -msgid "Fullscreen mode" -msgstr "Mode layar penuh" - -msgid "Open an issue" -msgstr "Buka masalah" - -msgid "previous page" -msgstr "halaman sebelumnya" - -msgid "Download this page" -msgstr "Unduh halaman ini" - -msgid "Theme by the" -msgstr "Tema oleh" diff --git a/docs/_build/html/_static/locales/it/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/it/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 53ba476edd..0000000000 Binary files a/docs/_build/html/_static/locales/it/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/it/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/it/LC_MESSAGES/booktheme.po deleted file mode 100644 index 7d54fdefa8..0000000000 --- a/docs/_build/html/_static/locales/it/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: it\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "suggerisci modifica" - -msgid "Last updated on" -msgstr "Ultimo aggiornamento il" - -msgid "Edit this page" -msgstr "Modifica questa pagina" - -msgid "Launch" -msgstr "Lanciare" - -msgid "Print to PDF" -msgstr "Stampa in PDF" - -msgid "open issue" -msgstr "questione aperta" - -msgid "Download notebook file" -msgstr "Scarica il file del taccuino" - -msgid "Toggle navigation" -msgstr "Attiva / disattiva la navigazione" - -msgid "Source repository" -msgstr "Repository di origine" - -msgid "By the" -msgstr "Dal" - -msgid "next page" -msgstr "pagina successiva" - -msgid "repository" -msgstr "repository" - -msgid "Sphinx Book Theme" -msgstr "Tema del libro della Sfinge" - -msgid "Download source file" -msgstr "Scarica il file sorgente" - -msgid "Contents" -msgstr "Contenuti" - -msgid "By" -msgstr "Di" - -msgid "Copyright" -msgstr "Diritto d'autore" - -msgid "Fullscreen mode" -msgstr "Modalità schermo intero" - -msgid "Open an issue" -msgstr "Apri un problema" - -msgid "previous page" -msgstr "pagina precedente" - -msgid "Download this page" -msgstr "Scarica questa pagina" - -msgid "Theme by the" -msgstr "Tema di" diff --git a/docs/_build/html/_static/locales/iw/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/iw/LC_MESSAGES/booktheme.mo deleted file mode 100644 index a45c6575e4..0000000000 Binary files a/docs/_build/html/_static/locales/iw/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/iw/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/iw/LC_MESSAGES/booktheme.po deleted file mode 100644 index 32b017cf69..0000000000 --- a/docs/_build/html/_static/locales/iw/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: iw\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "מציע לערוך" - -msgid "Last updated on" -msgstr "עודכן לאחרונה ב" - -msgid "Edit this page" -msgstr "ערוך דף זה" - -msgid "Launch" -msgstr "לְהַשִׁיק" - -msgid "Print to PDF" -msgstr "הדפס לקובץ PDF" - -msgid "open issue" -msgstr "בעיה פתוחה" - -msgid "Download notebook file" -msgstr "הורד קובץ מחברת" - -msgid "Toggle navigation" -msgstr "החלף ניווט" - -msgid "Source repository" -msgstr "מאגר המקורות" - -msgid "By the" -msgstr "דרך" - -msgid "next page" -msgstr "עמוד הבא" - -msgid "repository" -msgstr "מאגר" - -msgid "Sphinx Book Theme" -msgstr "נושא ספר ספינקס" - -msgid "Download source file" -msgstr "הורד את קובץ המקור" - -msgid "Contents" -msgstr "תוכן" - -msgid "By" -msgstr "על ידי" - -msgid "Copyright" -msgstr "זכויות יוצרים" - -msgid "Fullscreen mode" -msgstr "מצב מסך מלא" - -msgid "Open an issue" -msgstr "פתח גיליון" - -msgid "previous page" -msgstr "עמוד קודם" - -msgid "Download this page" -msgstr "הורד דף זה" - -msgid "Theme by the" -msgstr "נושא מאת" diff --git a/docs/_build/html/_static/locales/ja/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ja/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 1cefd29ce3..0000000000 Binary files a/docs/_build/html/_static/locales/ja/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ja/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ja/LC_MESSAGES/booktheme.po deleted file mode 100644 index 16924e1972..0000000000 --- a/docs/_build/html/_static/locales/ja/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ja\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "編集を提案する" - -msgid "Last updated on" -msgstr "最終更新日" - -msgid "Edit this page" -msgstr "このページを編集" - -msgid "Launch" -msgstr "起動" - -msgid "Print to PDF" -msgstr "PDFに印刷" - -msgid "open issue" -msgstr "未解決の問題" - -msgid "Download notebook file" -msgstr "ノートブックファイルをダウンロード" - -msgid "Toggle navigation" -msgstr "ナビゲーションを切り替え" - -msgid "Source repository" -msgstr "ソースリポジトリ" - -msgid "By the" -msgstr "によって" - -msgid "next page" -msgstr "次のページ" - -msgid "repository" -msgstr "リポジトリ" - -msgid "Sphinx Book Theme" -msgstr "スフィンクスの本のテーマ" - -msgid "Download source file" -msgstr "ソースファイルをダウンロード" - -msgid "Contents" -msgstr "目次" - -msgid "By" -msgstr "著者" - -msgid "Copyright" -msgstr "Copyright" - -msgid "Fullscreen mode" -msgstr "全画面モード" - -msgid "Open an issue" -msgstr "問題を報告" - -msgid "previous page" -msgstr "前のページ" - -msgid "Download this page" -msgstr "このページをダウンロード" - -msgid "Theme by the" -msgstr "のテーマ" diff --git a/docs/_build/html/_static/locales/ko/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ko/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 06c7ec938b..0000000000 Binary files a/docs/_build/html/_static/locales/ko/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ko/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ko/LC_MESSAGES/booktheme.po deleted file mode 100644 index 69dd18f773..0000000000 --- a/docs/_build/html/_static/locales/ko/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ko\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "편집 제안" - -msgid "Last updated on" -msgstr "마지막 업데이트" - -msgid "Edit this page" -msgstr "이 페이지 편집" - -msgid "Launch" -msgstr "시작하다" - -msgid "Print to PDF" -msgstr "PDF로 인쇄" - -msgid "open issue" -msgstr "열린 문제" - -msgid "Download notebook file" -msgstr "노트북 파일 다운로드" - -msgid "Toggle navigation" -msgstr "탐색 전환" - -msgid "Source repository" -msgstr "소스 저장소" - -msgid "By the" -msgstr "에 의해" - -msgid "next page" -msgstr "다음 페이지" - -msgid "repository" -msgstr "저장소" - -msgid "Sphinx Book Theme" -msgstr "스핑크스 도서 테마" - -msgid "Download source file" -msgstr "소스 파일 다운로드" - -msgid "Contents" -msgstr "내용" - -msgid "By" -msgstr "으로" - -msgid "Copyright" -msgstr "저작권" - -msgid "Fullscreen mode" -msgstr "전체 화면으로보기" - -msgid "Open an issue" -msgstr "이슈 열기" - -msgid "previous page" -msgstr "이전 페이지" - -msgid "Download this page" -msgstr "이 페이지 다운로드" - -msgid "Theme by the" -msgstr "테마별" diff --git a/docs/_build/html/_static/locales/lt/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/lt/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 4468ba04bc..0000000000 Binary files a/docs/_build/html/_static/locales/lt/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/lt/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/lt/LC_MESSAGES/booktheme.po deleted file mode 100644 index 9f037752c0..0000000000 --- a/docs/_build/html/_static/locales/lt/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: lt\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "pasiūlyti redaguoti" - -msgid "Last updated on" -msgstr "Paskutinį kartą atnaujinta" - -msgid "Edit this page" -msgstr "Redaguoti šį puslapį" - -msgid "Launch" -msgstr "Paleiskite" - -msgid "Print to PDF" -msgstr "Spausdinti į PDF" - -msgid "open issue" -msgstr "atviras klausimas" - -msgid "Download notebook file" -msgstr "Atsisiųsti nešiojamojo kompiuterio failą" - -msgid "Toggle navigation" -msgstr "Perjungti naršymą" - -msgid "Source repository" -msgstr "Šaltinio saugykla" - -msgid "By the" -msgstr "Prie" - -msgid "next page" -msgstr "Kitas puslapis" - -msgid "repository" -msgstr "saugykla" - -msgid "Sphinx Book Theme" -msgstr "Sfinkso knygos tema" - -msgid "Download source file" -msgstr "Atsisiųsti šaltinio failą" - -msgid "Contents" -msgstr "Turinys" - -msgid "By" -msgstr "Iki" - -msgid "Copyright" -msgstr "Autorių teisės" - -msgid "Fullscreen mode" -msgstr "Pilno ekrano režimas" - -msgid "Open an issue" -msgstr "Atidarykite problemą" - -msgid "previous page" -msgstr "Ankstesnis puslapis" - -msgid "Download this page" -msgstr "Atsisiųskite šį puslapį" - -msgid "Theme by the" -msgstr "Tema" diff --git a/docs/_build/html/_static/locales/lv/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/lv/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 74aa4d8985..0000000000 Binary files a/docs/_build/html/_static/locales/lv/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/lv/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/lv/LC_MESSAGES/booktheme.po deleted file mode 100644 index c9633b545c..0000000000 --- a/docs/_build/html/_static/locales/lv/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: lv\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "ieteikt rediģēt" - -msgid "Last updated on" -msgstr "Pēdējoreiz atjaunināts" - -msgid "Edit this page" -msgstr "Rediģēt šo lapu" - -msgid "Launch" -msgstr "Uzsākt" - -msgid "Print to PDF" -msgstr "Drukāt PDF formātā" - -msgid "open issue" -msgstr "atklāts jautājums" - -msgid "Download notebook file" -msgstr "Lejupielādēt piezīmju grāmatiņu" - -msgid "Toggle navigation" -msgstr "Pārslēgt navigāciju" - -msgid "Source repository" -msgstr "Avota krātuve" - -msgid "By the" -msgstr "Ar" - -msgid "next page" -msgstr "nākamā lapaspuse" - -msgid "repository" -msgstr "krātuve" - -msgid "Sphinx Book Theme" -msgstr "Sfinksa grāmatas tēma" - -msgid "Download source file" -msgstr "Lejupielādēt avota failu" - -msgid "Contents" -msgstr "Saturs" - -msgid "By" -msgstr "Autors" - -msgid "Copyright" -msgstr "Autortiesības" - -msgid "Fullscreen mode" -msgstr "Pilnekrāna režīms" - -msgid "Open an issue" -msgstr "Atveriet problēmu" - -msgid "previous page" -msgstr "iepriekšējā lapa" - -msgid "Download this page" -msgstr "Lejupielādējiet šo lapu" - -msgid "Theme by the" -msgstr "Autora tēma" diff --git a/docs/_build/html/_static/locales/ml/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ml/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 2736e8fcf6..0000000000 Binary files a/docs/_build/html/_static/locales/ml/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ml/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ml/LC_MESSAGES/booktheme.po deleted file mode 100644 index 9a6a41e8ec..0000000000 --- a/docs/_build/html/_static/locales/ml/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ml\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "എഡിറ്റുചെയ്യാൻ നിർദ്ദേശിക്കുക" - -msgid "Last updated on" -msgstr "അവസാനം അപ്‌ഡേറ്റുചെയ്‌തത്" - -msgid "Edit this page" -msgstr "ഈ പേജ് എഡിറ്റുചെയ്യുക" - -msgid "Launch" -msgstr "സമാരംഭിക്കുക" - -msgid "Print to PDF" -msgstr "PDF- ലേക്ക് പ്രിന്റുചെയ്യുക" - -msgid "open issue" -msgstr "തുറന്ന പ്രശ്നം" - -msgid "Download notebook file" -msgstr "നോട്ട്ബുക്ക് ഫയൽ ഡൺലോഡ് ചെയ്യുക" - -msgid "Toggle navigation" -msgstr "നാവിഗേഷൻ ടോഗിൾ ചെയ്യുക" - -msgid "Source repository" -msgstr "ഉറവിട ശേഖരം" - -msgid "By the" -msgstr "എഴുതിയത്" - -msgid "next page" -msgstr "അടുത്ത പേജ്" - -msgid "Sphinx Book Theme" -msgstr "സ്ഫിങ്ക്സ് പുസ്തക തീം" - -msgid "Download source file" -msgstr "ഉറവിട ഫയൽ ഡൗൺലോഡുചെയ്യുക" - -msgid "By" -msgstr "എഴുതിയത്" - -msgid "Copyright" -msgstr "പകർപ്പവകാശം" - -msgid "Open an issue" -msgstr "ഒരു പ്രശ്നം തുറക്കുക" - -msgid "previous page" -msgstr "മുൻപത്തെ താൾ" - -msgid "Download this page" -msgstr "ഈ പേജ് ഡൗൺലോഡുചെയ്യുക" - -msgid "Theme by the" -msgstr "പ്രമേയം" diff --git a/docs/_build/html/_static/locales/mr/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/mr/LC_MESSAGES/booktheme.mo deleted file mode 100644 index fe530100d7..0000000000 Binary files a/docs/_build/html/_static/locales/mr/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/mr/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/mr/LC_MESSAGES/booktheme.po deleted file mode 100644 index ef72d8c6bc..0000000000 --- a/docs/_build/html/_static/locales/mr/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: mr\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "संपादन सुचवा" - -msgid "Last updated on" -msgstr "अखेरचे अद्यतनित" - -msgid "Edit this page" -msgstr "हे पृष्ठ संपादित करा" - -msgid "Launch" -msgstr "लाँच करा" - -msgid "Print to PDF" -msgstr "पीडीएफवर मुद्रित करा" - -msgid "open issue" -msgstr "खुला मुद्दा" - -msgid "Download notebook file" -msgstr "नोटबुक फाईल डाउनलोड करा" - -msgid "Toggle navigation" -msgstr "नेव्हिगेशन टॉगल करा" - -msgid "Source repository" -msgstr "स्त्रोत भांडार" - -msgid "By the" -msgstr "द्वारा" - -msgid "next page" -msgstr "पुढील पृष्ठ" - -msgid "Sphinx Book Theme" -msgstr "स्फिंक्स बुक थीम" - -msgid "Download source file" -msgstr "स्त्रोत फाइल डाउनलोड करा" - -msgid "By" -msgstr "द्वारा" - -msgid "Copyright" -msgstr "कॉपीराइट" - -msgid "Open an issue" -msgstr "एक मुद्दा उघडा" - -msgid "previous page" -msgstr "मागील पान" - -msgid "Download this page" -msgstr "हे पृष्ठ डाउनलोड करा" - -msgid "Theme by the" -msgstr "द्वारा थीम" diff --git a/docs/_build/html/_static/locales/ms/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ms/LC_MESSAGES/booktheme.mo deleted file mode 100644 index f02603fa25..0000000000 Binary files a/docs/_build/html/_static/locales/ms/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ms/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ms/LC_MESSAGES/booktheme.po deleted file mode 100644 index e29cbe2ec2..0000000000 --- a/docs/_build/html/_static/locales/ms/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ms\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "cadangkan edit" - -msgid "Last updated on" -msgstr "Terakhir dikemas kini pada" - -msgid "Edit this page" -msgstr "Edit halaman ini" - -msgid "Launch" -msgstr "Lancarkan" - -msgid "Print to PDF" -msgstr "Cetak ke PDF" - -msgid "open issue" -msgstr "isu terbuka" - -msgid "Download notebook file" -msgstr "Muat turun fail buku nota" - -msgid "Toggle navigation" -msgstr "Togol navigasi" - -msgid "Source repository" -msgstr "Repositori sumber" - -msgid "By the" -msgstr "Oleh" - -msgid "next page" -msgstr "muka surat seterusnya" - -msgid "Sphinx Book Theme" -msgstr "Tema Buku Sphinx" - -msgid "Download source file" -msgstr "Muat turun fail sumber" - -msgid "By" -msgstr "Oleh" - -msgid "Copyright" -msgstr "hak cipta" - -msgid "Open an issue" -msgstr "Buka masalah" - -msgid "previous page" -msgstr "halaman sebelumnya" - -msgid "Download this page" -msgstr "Muat turun halaman ini" - -msgid "Theme by the" -msgstr "Tema oleh" diff --git a/docs/_build/html/_static/locales/nl/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/nl/LC_MESSAGES/booktheme.mo deleted file mode 100644 index e59e7ecb30..0000000000 Binary files a/docs/_build/html/_static/locales/nl/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/nl/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/nl/LC_MESSAGES/booktheme.po deleted file mode 100644 index e4844d7c98..0000000000 --- a/docs/_build/html/_static/locales/nl/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: nl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "suggereren bewerken" - -msgid "Last updated on" -msgstr "Laatst geupdate op" - -msgid "Edit this page" -msgstr "bewerk deze pagina" - -msgid "Launch" -msgstr "Lancering" - -msgid "Print to PDF" -msgstr "Afdrukken naar pdf" - -msgid "open issue" -msgstr "open probleem" - -msgid "Download notebook file" -msgstr "Download notebookbestand" - -msgid "Toggle navigation" -msgstr "Schakel navigatie" - -msgid "Source repository" -msgstr "Bronopslagplaats" - -msgid "By the" -msgstr "Door de" - -msgid "next page" -msgstr "volgende bladzijde" - -msgid "repository" -msgstr "repository" - -msgid "Sphinx Book Theme" -msgstr "Sphinx-boekthema" - -msgid "Download source file" -msgstr "Download het bronbestand" - -msgid "Contents" -msgstr "Inhoud" - -msgid "By" -msgstr "Door" - -msgid "Copyright" -msgstr "auteursrechten" - -msgid "Fullscreen mode" -msgstr "Volledig scherm" - -msgid "Open an issue" -msgstr "Open een probleem" - -msgid "previous page" -msgstr "vorige pagina" - -msgid "Download this page" -msgstr "Download deze pagina" - -msgid "Theme by the" -msgstr "Thema door de" diff --git a/docs/_build/html/_static/locales/no/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/no/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 6cd15c88de..0000000000 Binary files a/docs/_build/html/_static/locales/no/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/no/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/no/LC_MESSAGES/booktheme.po deleted file mode 100644 index d079dd9b09..0000000000 --- a/docs/_build/html/_static/locales/no/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: no\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "foreslå redigering" - -msgid "Last updated on" -msgstr "Sist oppdatert den" - -msgid "Edit this page" -msgstr "Rediger denne siden" - -msgid "Launch" -msgstr "Start" - -msgid "Print to PDF" -msgstr "Skriv ut til PDF" - -msgid "open issue" -msgstr "åpent nummer" - -msgid "Download notebook file" -msgstr "Last ned notatbokfilen" - -msgid "Toggle navigation" -msgstr "Bytt navigasjon" - -msgid "Source repository" -msgstr "Kildedepot" - -msgid "By the" -msgstr "Ved" - -msgid "next page" -msgstr "neste side" - -msgid "repository" -msgstr "oppbevaringssted" - -msgid "Sphinx Book Theme" -msgstr "Sphinx boktema" - -msgid "Download source file" -msgstr "Last ned kildefilen" - -msgid "Contents" -msgstr "Innhold" - -msgid "By" -msgstr "Av" - -msgid "Copyright" -msgstr "opphavsrett" - -msgid "Fullscreen mode" -msgstr "Fullskjerm-modus" - -msgid "Open an issue" -msgstr "Åpne et problem" - -msgid "previous page" -msgstr "forrige side" - -msgid "Download this page" -msgstr "Last ned denne siden" - -msgid "Theme by the" -msgstr "Tema av" diff --git a/docs/_build/html/_static/locales/pl/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/pl/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 9ebb584f77..0000000000 Binary files a/docs/_build/html/_static/locales/pl/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/pl/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/pl/LC_MESSAGES/booktheme.po deleted file mode 100644 index fcac51d329..0000000000 --- a/docs/_build/html/_static/locales/pl/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: pl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "zaproponuj edycję" - -msgid "Last updated on" -msgstr "Ostatnia aktualizacja" - -msgid "Edit this page" -msgstr "Edytuj tę strone" - -msgid "Launch" -msgstr "Uruchomić" - -msgid "Print to PDF" -msgstr "Drukuj do PDF" - -msgid "open issue" -msgstr "otwarty problem" - -msgid "Download notebook file" -msgstr "Pobierz plik notatnika" - -msgid "Toggle navigation" -msgstr "Przełącz nawigację" - -msgid "Source repository" -msgstr "Repozytorium źródłowe" - -msgid "By the" -msgstr "Przez" - -msgid "next page" -msgstr "Następna strona" - -msgid "repository" -msgstr "magazyn" - -msgid "Sphinx Book Theme" -msgstr "Motyw książki Sphinx" - -msgid "Download source file" -msgstr "Pobierz plik źródłowy" - -msgid "Contents" -msgstr "Zawartość" - -msgid "By" -msgstr "Przez" - -msgid "Copyright" -msgstr "prawa autorskie" - -msgid "Fullscreen mode" -msgstr "Pełny ekran" - -msgid "Open an issue" -msgstr "Otwórz problem" - -msgid "previous page" -msgstr "Poprzednia strona" - -msgid "Download this page" -msgstr "Pobierz tę stronę" - -msgid "Theme by the" -msgstr "Motyw autorstwa" diff --git a/docs/_build/html/_static/locales/pt/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/pt/LC_MESSAGES/booktheme.mo deleted file mode 100644 index d0ddb8728e..0000000000 Binary files a/docs/_build/html/_static/locales/pt/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/pt/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/pt/LC_MESSAGES/booktheme.po deleted file mode 100644 index 1761db08ae..0000000000 --- a/docs/_build/html/_static/locales/pt/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: pt\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "sugerir edição" - -msgid "Last updated on" -msgstr "Última atualização em" - -msgid "Edit this page" -msgstr "Edite essa página" - -msgid "Launch" -msgstr "Lançamento" - -msgid "Print to PDF" -msgstr "Imprimir em PDF" - -msgid "open issue" -msgstr "questão aberta" - -msgid "Download notebook file" -msgstr "Baixar arquivo de notebook" - -msgid "Toggle navigation" -msgstr "Alternar de navegação" - -msgid "Source repository" -msgstr "Repositório fonte" - -msgid "By the" -msgstr "Pelo" - -msgid "next page" -msgstr "próxima página" - -msgid "repository" -msgstr "repositório" - -msgid "Sphinx Book Theme" -msgstr "Tema do livro Sphinx" - -msgid "Download source file" -msgstr "Baixar arquivo fonte" - -msgid "Contents" -msgstr "Conteúdo" - -msgid "By" -msgstr "De" - -msgid "Copyright" -msgstr "direito autoral" - -msgid "Fullscreen mode" -msgstr "Modo tela cheia" - -msgid "Open an issue" -msgstr "Abra um problema" - -msgid "previous page" -msgstr "página anterior" - -msgid "Download this page" -msgstr "Baixe esta página" - -msgid "Theme by the" -msgstr "Tema por" diff --git a/docs/_build/html/_static/locales/ro/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ro/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 3c36ab1df7..0000000000 Binary files a/docs/_build/html/_static/locales/ro/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ro/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ro/LC_MESSAGES/booktheme.po deleted file mode 100644 index db865c8f65..0000000000 --- a/docs/_build/html/_static/locales/ro/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ro\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "sugerează editare" - -msgid "Last updated on" -msgstr "Ultima actualizare la" - -msgid "Edit this page" -msgstr "Editați această pagină" - -msgid "Launch" -msgstr "Lansa" - -msgid "Print to PDF" -msgstr "Imprimați în PDF" - -msgid "open issue" -msgstr "problema deschisă" - -msgid "Download notebook file" -msgstr "Descărcați fișierul notebook" - -msgid "Toggle navigation" -msgstr "Comutare navigare" - -msgid "Source repository" -msgstr "Depozit sursă" - -msgid "By the" -msgstr "Langa" - -msgid "next page" -msgstr "pagina următoare" - -msgid "repository" -msgstr "repertoriu" - -msgid "Sphinx Book Theme" -msgstr "Tema Sphinx Book" - -msgid "Download source file" -msgstr "Descărcați fișierul sursă" - -msgid "Contents" -msgstr "Cuprins" - -msgid "By" -msgstr "De" - -msgid "Copyright" -msgstr "Drepturi de autor" - -msgid "Fullscreen mode" -msgstr "Modul ecran întreg" - -msgid "Open an issue" -msgstr "Deschideți o problemă" - -msgid "previous page" -msgstr "pagina anterioară" - -msgid "Download this page" -msgstr "Descarcă această pagină" - -msgid "Theme by the" -msgstr "Tema de" diff --git a/docs/_build/html/_static/locales/ru/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ru/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 6b8ca41f36..0000000000 Binary files a/docs/_build/html/_static/locales/ru/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ru/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ru/LC_MESSAGES/booktheme.po deleted file mode 100644 index 84ab6eb531..0000000000 --- a/docs/_build/html/_static/locales/ru/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ru\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "предложить редактировать" - -msgid "Last updated on" -msgstr "Последнее обновление" - -msgid "Edit this page" -msgstr "Редактировать эту страницу" - -msgid "Launch" -msgstr "Запуск" - -msgid "Print to PDF" -msgstr "Распечатать в PDF" - -msgid "open issue" -msgstr "открытый вопрос" - -msgid "Download notebook file" -msgstr "Скачать файл записной книжки" - -msgid "Toggle navigation" -msgstr "Переключить навигацию" - -msgid "Source repository" -msgstr "Исходный репозиторий" - -msgid "By the" -msgstr "Посредством" - -msgid "next page" -msgstr "Следующая страница" - -msgid "repository" -msgstr "хранилище" - -msgid "Sphinx Book Theme" -msgstr "Тема книги Сфинкс" - -msgid "Download source file" -msgstr "Скачать исходный файл" - -msgid "Contents" -msgstr "Содержание" - -msgid "By" -msgstr "По" - -msgid "Copyright" -msgstr "авторское право" - -msgid "Fullscreen mode" -msgstr "Полноэкранный режим" - -msgid "Open an issue" -msgstr "Открыть вопрос" - -msgid "previous page" -msgstr "Предыдущая страница" - -msgid "Download this page" -msgstr "Загрузите эту страницу" - -msgid "Theme by the" -msgstr "Тема от" diff --git a/docs/_build/html/_static/locales/sk/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/sk/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 59bd0ddfa3..0000000000 Binary files a/docs/_build/html/_static/locales/sk/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/sk/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/sk/LC_MESSAGES/booktheme.po deleted file mode 100644 index e44878b50e..0000000000 --- a/docs/_build/html/_static/locales/sk/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sk\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "navrhnúť úpravu" - -msgid "Last updated on" -msgstr "Posledná aktualizácia dňa" - -msgid "Edit this page" -msgstr "Upraviť túto stránku" - -msgid "Launch" -msgstr "Spustiť" - -msgid "Print to PDF" -msgstr "Tlač do PDF" - -msgid "open issue" -msgstr "otvorené vydanie" - -msgid "Download notebook file" -msgstr "Stiahnite si zošit" - -msgid "Toggle navigation" -msgstr "Prepnúť navigáciu" - -msgid "Source repository" -msgstr "Zdrojové úložisko" - -msgid "By the" -msgstr "Podľa" - -msgid "next page" -msgstr "ďalšia strana" - -msgid "repository" -msgstr "Úložisko" - -msgid "Sphinx Book Theme" -msgstr "Téma knihy Sfinga" - -msgid "Download source file" -msgstr "Stiahnite si zdrojový súbor" - -msgid "Contents" -msgstr "Obsah" - -msgid "By" -msgstr "Autor:" - -msgid "Copyright" -msgstr "Autorské práva" - -msgid "Fullscreen mode" -msgstr "Režim celej obrazovky" - -msgid "Open an issue" -msgstr "Otvorte problém" - -msgid "previous page" -msgstr "predchádzajúca strana" - -msgid "Download this page" -msgstr "Stiahnite si túto stránku" - -msgid "Theme by the" -msgstr "Téma od" diff --git a/docs/_build/html/_static/locales/sl/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/sl/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 87bf26de68..0000000000 Binary files a/docs/_build/html/_static/locales/sl/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/sl/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/sl/LC_MESSAGES/booktheme.po deleted file mode 100644 index 228939bcdd..0000000000 --- a/docs/_build/html/_static/locales/sl/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "predlagajte urejanje" - -msgid "Last updated on" -msgstr "Nazadnje posodobljeno dne" - -msgid "Edit this page" -msgstr "Uredite to stran" - -msgid "Launch" -msgstr "Kosilo" - -msgid "Print to PDF" -msgstr "Natisni v PDF" - -msgid "open issue" -msgstr "odprto vprašanje" - -msgid "Download notebook file" -msgstr "Prenesite datoteko zvezka" - -msgid "Toggle navigation" -msgstr "Preklopi navigacijo" - -msgid "Source repository" -msgstr "Izvorno skladišče" - -msgid "By the" -msgstr "Avtor" - -msgid "next page" -msgstr "Naslednja stran" - -msgid "repository" -msgstr "odlagališče" - -msgid "Sphinx Book Theme" -msgstr "Tema knjige Sphinx" - -msgid "Download source file" -msgstr "Prenesite izvorno datoteko" - -msgid "Contents" -msgstr "Vsebina" - -msgid "By" -msgstr "Avtor" - -msgid "Copyright" -msgstr "avtorske pravice" - -msgid "Fullscreen mode" -msgstr "Celozaslonski način" - -msgid "Open an issue" -msgstr "Odprite številko" - -msgid "previous page" -msgstr "Prejšnja stran" - -msgid "Download this page" -msgstr "Prenesite to stran" - -msgid "Theme by the" -msgstr "Tema avtorja" diff --git a/docs/_build/html/_static/locales/sr/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/sr/LC_MESSAGES/booktheme.mo deleted file mode 100644 index ec740f4852..0000000000 Binary files a/docs/_build/html/_static/locales/sr/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/sr/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/sr/LC_MESSAGES/booktheme.po deleted file mode 100644 index 1a712a18d8..0000000000 --- a/docs/_build/html/_static/locales/sr/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sr\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "предложи уређивање" - -msgid "Last updated on" -msgstr "Последње ажурирање" - -msgid "Edit this page" -msgstr "Уредите ову страницу" - -msgid "Launch" -msgstr "Лансирање" - -msgid "Print to PDF" -msgstr "Испис у ПДФ" - -msgid "open issue" -msgstr "отворено издање" - -msgid "Download notebook file" -msgstr "Преузмите датотеку бележнице" - -msgid "Toggle navigation" -msgstr "Укључи / искључи навигацију" - -msgid "Source repository" -msgstr "Изворно спремиште" - -msgid "By the" -msgstr "Од" - -msgid "next page" -msgstr "Следећа страна" - -msgid "repository" -msgstr "спремиште" - -msgid "Sphinx Book Theme" -msgstr "Тема књиге Спхинк" - -msgid "Download source file" -msgstr "Преузми изворну датотеку" - -msgid "Contents" -msgstr "Садржај" - -msgid "By" -msgstr "Од стране" - -msgid "Copyright" -msgstr "Ауторско право" - -msgid "Fullscreen mode" -msgstr "Режим целог екрана" - -msgid "Open an issue" -msgstr "Отворите издање" - -msgid "previous page" -msgstr "Претходна страница" - -msgid "Download this page" -msgstr "Преузмите ову страницу" - -msgid "Theme by the" -msgstr "Тхеме би" diff --git a/docs/_build/html/_static/locales/sv/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/sv/LC_MESSAGES/booktheme.mo deleted file mode 100644 index be951bec20..0000000000 Binary files a/docs/_build/html/_static/locales/sv/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/sv/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/sv/LC_MESSAGES/booktheme.po deleted file mode 100644 index 7d2b56d949..0000000000 --- a/docs/_build/html/_static/locales/sv/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sv\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "föreslå redigering" - -msgid "Last updated on" -msgstr "Senast uppdaterad den" - -msgid "Edit this page" -msgstr "Redigera den här sidan" - -msgid "Launch" -msgstr "Lansera" - -msgid "Print to PDF" -msgstr "Skriv ut till PDF" - -msgid "open issue" -msgstr "öppet problem" - -msgid "Download notebook file" -msgstr "Ladda ner anteckningsbokfilen" - -msgid "Toggle navigation" -msgstr "Växla navigering" - -msgid "Source repository" -msgstr "Källförvar" - -msgid "By the" -msgstr "Vid" - -msgid "next page" -msgstr "nästa sida" - -msgid "repository" -msgstr "förvar" - -msgid "Sphinx Book Theme" -msgstr "Sphinx boktema" - -msgid "Download source file" -msgstr "Ladda ner källfil" - -msgid "Contents" -msgstr "Innehåll" - -msgid "By" -msgstr "Förbi" - -msgid "Copyright" -msgstr "upphovsrätt" - -msgid "Fullscreen mode" -msgstr "Fullskärmsläge" - -msgid "Open an issue" -msgstr "Öppna ett problem" - -msgid "previous page" -msgstr "föregående sida" - -msgid "Download this page" -msgstr "Ladda ner den här sidan" - -msgid "Theme by the" -msgstr "Tema av" diff --git a/docs/_build/html/_static/locales/ta/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ta/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 29f52e1f6f..0000000000 Binary files a/docs/_build/html/_static/locales/ta/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ta/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ta/LC_MESSAGES/booktheme.po deleted file mode 100644 index c75ffe192c..0000000000 --- a/docs/_build/html/_static/locales/ta/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ta\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "திருத்த பரிந்துரைக்கவும்" - -msgid "Last updated on" -msgstr "கடைசியாக புதுப்பிக்கப்பட்டது" - -msgid "Edit this page" -msgstr "இந்தப் பக்கத்தைத் திருத்தவும்" - -msgid "Launch" -msgstr "தொடங்க" - -msgid "Print to PDF" -msgstr "PDF இல் அச்சிடுக" - -msgid "open issue" -msgstr "திறந்த பிரச்சினை" - -msgid "Download notebook file" -msgstr "நோட்புக் கோப்பைப் பதிவிறக்கவும்" - -msgid "Toggle navigation" -msgstr "வழிசெலுத்தலை நிலைமாற்று" - -msgid "Source repository" -msgstr "மூல களஞ்சியம்" - -msgid "By the" -msgstr "மூலம்" - -msgid "next page" -msgstr "அடுத்த பக்கம்" - -msgid "Sphinx Book Theme" -msgstr "ஸ்பிங்க்ஸ் புத்தக தீம்" - -msgid "Download source file" -msgstr "மூல கோப்பைப் பதிவிறக்குக" - -msgid "By" -msgstr "வழங்கியவர்" - -msgid "Copyright" -msgstr "பதிப்புரிமை" - -msgid "Open an issue" -msgstr "சிக்கலைத் திறக்கவும்" - -msgid "previous page" -msgstr "முந்தைய பக்கம்" - -msgid "Download this page" -msgstr "இந்தப் பக்கத்தைப் பதிவிறக்கவும்" - -msgid "Theme by the" -msgstr "வழங்கிய தீம்" diff --git a/docs/_build/html/_static/locales/te/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/te/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 0a5f4b46ad..0000000000 Binary files a/docs/_build/html/_static/locales/te/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/te/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/te/LC_MESSAGES/booktheme.po deleted file mode 100644 index 2595c03590..0000000000 --- a/docs/_build/html/_static/locales/te/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: te\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "సవరించమని సూచించండి" - -msgid "Last updated on" -msgstr "చివరిగా నవీకరించబడింది" - -msgid "Edit this page" -msgstr "ఈ పేజీని సవరించండి" - -msgid "Launch" -msgstr "ప్రారంభించండి" - -msgid "Print to PDF" -msgstr "PDF కి ముద్రించండి" - -msgid "open issue" -msgstr "ఓపెన్ ఇష్యూ" - -msgid "Download notebook file" -msgstr "నోట్బుక్ ఫైల్ను డౌన్లోడ్ చేయండి" - -msgid "Toggle navigation" -msgstr "నావిగేషన్‌ను టోగుల్ చేయండి" - -msgid "Source repository" -msgstr "మూల రిపోజిటరీ" - -msgid "By the" -msgstr "ద్వారా" - -msgid "next page" -msgstr "తరువాతి పేజీ" - -msgid "Sphinx Book Theme" -msgstr "సింహిక పుస్తక థీమ్" - -msgid "Download source file" -msgstr "మూల ఫైల్‌ను డౌన్‌లోడ్ చేయండి" - -msgid "By" -msgstr "ద్వారా" - -msgid "Copyright" -msgstr "కాపీరైట్" - -msgid "Open an issue" -msgstr "సమస్యను తెరవండి" - -msgid "previous page" -msgstr "ముందు పేజి" - -msgid "Download this page" -msgstr "ఈ పేజీని డౌన్‌లోడ్ చేయండి" - -msgid "Theme by the" -msgstr "ద్వారా థీమ్" diff --git a/docs/_build/html/_static/locales/tg/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/tg/LC_MESSAGES/booktheme.mo deleted file mode 100644 index b21c6c6340..0000000000 Binary files a/docs/_build/html/_static/locales/tg/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/tg/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/tg/LC_MESSAGES/booktheme.po deleted file mode 100644 index 73cd30ea97..0000000000 --- a/docs/_build/html/_static/locales/tg/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: tg\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "пешниҳод вироиш" - -msgid "Last updated on" -msgstr "Last навсозӣ дар" - -msgid "Edit this page" -msgstr "Ин саҳифаро таҳрир кунед" - -msgid "Launch" -msgstr "Оғоз" - -msgid "Print to PDF" -msgstr "Чоп ба PDF" - -msgid "open issue" -msgstr "барориши кушод" - -msgid "Download notebook file" -msgstr "Файли дафтарро зеркашӣ кунед" - -msgid "Toggle navigation" -msgstr "Гузаришро иваз кунед" - -msgid "Source repository" -msgstr "Анбори манбаъ" - -msgid "By the" -msgstr "Бо" - -msgid "next page" -msgstr "саҳифаи оянда" - -msgid "repository" -msgstr "анбор" - -msgid "Sphinx Book Theme" -msgstr "Сфинкс Мавзӯи китоб" - -msgid "Download source file" -msgstr "Файли манбаъро зеркашӣ кунед" - -msgid "Contents" -msgstr "Мундариҷа" - -msgid "By" -msgstr "Бо" - -msgid "Copyright" -msgstr "Ҳуқуқи муаллиф" - -msgid "Fullscreen mode" -msgstr "Ҳолати экрани пурра" - -msgid "Open an issue" -msgstr "Масъаларо кушоед" - -msgid "previous page" -msgstr "саҳифаи қаблӣ" - -msgid "Download this page" -msgstr "Ин саҳифаро зеркашӣ кунед" - -msgid "Theme by the" -msgstr "Мавзӯъи аз" diff --git a/docs/_build/html/_static/locales/th/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/th/LC_MESSAGES/booktheme.mo deleted file mode 100644 index abede98aa1..0000000000 Binary files a/docs/_build/html/_static/locales/th/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/th/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/th/LC_MESSAGES/booktheme.po deleted file mode 100644 index 0392b4ad39..0000000000 --- a/docs/_build/html/_static/locales/th/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: th\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "แนะนำแก้ไข" - -msgid "Last updated on" -msgstr "ปรับปรุงล่าสุดเมื่อ" - -msgid "Edit this page" -msgstr "แก้ไขหน้านี้" - -msgid "Launch" -msgstr "เปิด" - -msgid "Print to PDF" -msgstr "พิมพ์เป็น PDF" - -msgid "open issue" -msgstr "เปิดปัญหา" - -msgid "Download notebook file" -msgstr "ดาวน์โหลดไฟล์สมุดบันทึก" - -msgid "Toggle navigation" -msgstr "ไม่ต้องสลับช่องทาง" - -msgid "Source repository" -msgstr "ที่เก็บซอร์ส" - -msgid "By the" -msgstr "โดย" - -msgid "next page" -msgstr "หน้าต่อไป" - -msgid "repository" -msgstr "ที่เก็บ" - -msgid "Sphinx Book Theme" -msgstr "ธีมหนังสือสฟิงซ์" - -msgid "Download source file" -msgstr "ดาวน์โหลดไฟล์ต้นฉบับ" - -msgid "Contents" -msgstr "สารบัญ" - -msgid "By" -msgstr "โดย" - -msgid "Copyright" -msgstr "ลิขสิทธิ์" - -msgid "Fullscreen mode" -msgstr "โหมดเต็มหน้าจอ" - -msgid "Open an issue" -msgstr "เปิดปัญหา" - -msgid "previous page" -msgstr "หน้าที่แล้ว" - -msgid "Download this page" -msgstr "ดาวน์โหลดหน้านี้" - -msgid "Theme by the" -msgstr "ธีมโดย" diff --git a/docs/_build/html/_static/locales/tl/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/tl/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 8df1b73310..0000000000 Binary files a/docs/_build/html/_static/locales/tl/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/tl/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/tl/LC_MESSAGES/booktheme.po deleted file mode 100644 index c8375b5431..0000000000 --- a/docs/_build/html/_static/locales/tl/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: tl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "iminumungkahi i-edit" - -msgid "Last updated on" -msgstr "Huling na-update noong" - -msgid "Edit this page" -msgstr "I-edit ang pahinang ito" - -msgid "Launch" -msgstr "Ilunsad" - -msgid "Print to PDF" -msgstr "I-print sa PDF" - -msgid "open issue" -msgstr "bukas na isyu" - -msgid "Download notebook file" -msgstr "Mag-download ng file ng notebook" - -msgid "Toggle navigation" -msgstr "I-toggle ang pag-navigate" - -msgid "Source repository" -msgstr "Pinagmulan ng imbakan" - -msgid "By the" -msgstr "Sa pamamagitan ng" - -msgid "next page" -msgstr "Susunod na pahina" - -msgid "Sphinx Book Theme" -msgstr "Tema ng Sphinx Book" - -msgid "Download source file" -msgstr "Mag-download ng file ng pinagmulan" - -msgid "By" -msgstr "Ni" - -msgid "Copyright" -msgstr "Copyright" - -msgid "Open an issue" -msgstr "Magbukas ng isyu" - -msgid "previous page" -msgstr "Nakaraang pahina" - -msgid "Download this page" -msgstr "I-download ang pahinang ito" - -msgid "Theme by the" -msgstr "Tema ng" diff --git a/docs/_build/html/_static/locales/tr/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/tr/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 029ae18afb..0000000000 Binary files a/docs/_build/html/_static/locales/tr/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/tr/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/tr/LC_MESSAGES/booktheme.po deleted file mode 100644 index 47d7bdf7f5..0000000000 --- a/docs/_build/html/_static/locales/tr/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: tr\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "düzenleme öner" - -msgid "Last updated on" -msgstr "Son güncelleme tarihi" - -msgid "Edit this page" -msgstr "Bu sayfayı düzenle" - -msgid "Launch" -msgstr "Başlatmak" - -msgid "Print to PDF" -msgstr "PDF olarak yazdır" - -msgid "open issue" -msgstr "Açık konu" - -msgid "Download notebook file" -msgstr "Defter dosyasını indirin" - -msgid "Toggle navigation" -msgstr "Gezinmeyi değiştir" - -msgid "Source repository" -msgstr "Kaynak kod deposu" - -msgid "By the" -msgstr "Tarafından" - -msgid "next page" -msgstr "sonraki Sayfa" - -msgid "repository" -msgstr "depo" - -msgid "Sphinx Book Theme" -msgstr "Sfenks Kitap Teması" - -msgid "Download source file" -msgstr "Kaynak dosyayı indirin" - -msgid "Contents" -msgstr "İçindekiler" - -msgid "By" -msgstr "Tarafından" - -msgid "Copyright" -msgstr "Telif hakkı" - -msgid "Fullscreen mode" -msgstr "Tam ekran modu" - -msgid "Open an issue" -msgstr "Bir sorunu açın" - -msgid "previous page" -msgstr "önceki sayfa" - -msgid "Download this page" -msgstr "Bu sayfayı indirin" - -msgid "Theme by the" -msgstr "Tarafından tema" diff --git a/docs/_build/html/_static/locales/uk/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/uk/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 16ab78909c..0000000000 Binary files a/docs/_build/html/_static/locales/uk/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/uk/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/uk/LC_MESSAGES/booktheme.po deleted file mode 100644 index e85f6f16ac..0000000000 --- a/docs/_build/html/_static/locales/uk/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: uk\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "запропонувати редагувати" - -msgid "Last updated on" -msgstr "Останнє оновлення:" - -msgid "Edit this page" -msgstr "Редагувати цю сторінку" - -msgid "Launch" -msgstr "Запуск" - -msgid "Print to PDF" -msgstr "Друк у форматі PDF" - -msgid "open issue" -msgstr "відкритий випуск" - -msgid "Download notebook file" -msgstr "Завантажте файл блокнота" - -msgid "Toggle navigation" -msgstr "Переключити навігацію" - -msgid "Source repository" -msgstr "Джерело сховища" - -msgid "By the" -msgstr "По" - -msgid "next page" -msgstr "Наступна сторінка" - -msgid "repository" -msgstr "сховище" - -msgid "Sphinx Book Theme" -msgstr "Тема книги \"Сфінкс\"" - -msgid "Download source file" -msgstr "Завантажити вихідний файл" - -msgid "Contents" -msgstr "Зміст" - -msgid "By" -msgstr "Автор" - -msgid "Copyright" -msgstr "Авторське право" - -msgid "Fullscreen mode" -msgstr "Повноекранний режим" - -msgid "Open an issue" -msgstr "Відкрийте випуск" - -msgid "previous page" -msgstr "Попередня сторінка" - -msgid "Download this page" -msgstr "Завантажте цю сторінку" - -msgid "Theme by the" -msgstr "Тема від" diff --git a/docs/_build/html/_static/locales/ur/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/ur/LC_MESSAGES/booktheme.mo deleted file mode 100644 index de8c84b935..0000000000 Binary files a/docs/_build/html/_static/locales/ur/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/ur/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/ur/LC_MESSAGES/booktheme.po deleted file mode 100644 index 0f90726c12..0000000000 --- a/docs/_build/html/_static/locales/ur/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,66 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ur\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "ترمیم کی تجویز کریں" - -msgid "Last updated on" -msgstr "آخری بار تازہ کاری ہوئی" - -msgid "Edit this page" -msgstr "اس صفحے میں ترمیم کریں" - -msgid "Launch" -msgstr "لانچ کریں" - -msgid "Print to PDF" -msgstr "پی ڈی ایف پرنٹ کریں" - -msgid "open issue" -msgstr "کھلا مسئلہ" - -msgid "Download notebook file" -msgstr "نوٹ بک فائل ڈاؤن لوڈ کریں" - -msgid "Toggle navigation" -msgstr "نیویگیشن ٹوگل کریں" - -msgid "Source repository" -msgstr "ماخذ ذخیرہ" - -msgid "By the" -msgstr "کی طرف" - -msgid "next page" -msgstr "اگلا صفحہ" - -msgid "Sphinx Book Theme" -msgstr "سپنکس بک تھیم" - -msgid "Download source file" -msgstr "سورس فائل ڈاؤن لوڈ کریں" - -msgid "By" -msgstr "بذریعہ" - -msgid "Copyright" -msgstr "کاپی رائٹ" - -msgid "Open an issue" -msgstr "ایک مسئلہ کھولیں" - -msgid "previous page" -msgstr "سابقہ ​​صفحہ" - -msgid "Download this page" -msgstr "اس صفحے کو ڈاؤن لوڈ کریں" - -msgid "Theme by the" -msgstr "کے ذریعہ تھیم" diff --git a/docs/_build/html/_static/locales/vi/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/vi/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 2bb32555c3..0000000000 Binary files a/docs/_build/html/_static/locales/vi/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/vi/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/vi/LC_MESSAGES/booktheme.po deleted file mode 100644 index 2cb5cf3b82..0000000000 --- a/docs/_build/html/_static/locales/vi/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: vi\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "đề nghị chỉnh sửa" - -msgid "Last updated on" -msgstr "Cập nhật lần cuối vào" - -msgid "Edit this page" -msgstr "chỉnh sửa trang này" - -msgid "Launch" -msgstr "Phóng" - -msgid "Print to PDF" -msgstr "In sang PDF" - -msgid "open issue" -msgstr "vấn đề mở" - -msgid "Download notebook file" -msgstr "Tải xuống tệp sổ tay" - -msgid "Toggle navigation" -msgstr "Chuyển đổi điều hướng thành" - -msgid "Source repository" -msgstr "Kho nguồn" - -msgid "By the" -msgstr "Bằng" - -msgid "next page" -msgstr "Trang tiếp theo" - -msgid "repository" -msgstr "kho" - -msgid "Sphinx Book Theme" -msgstr "Chủ đề sách nhân sư" - -msgid "Download source file" -msgstr "Tải xuống tệp nguồn" - -msgid "Contents" -msgstr "Nội dung" - -msgid "By" -msgstr "Bởi" - -msgid "Copyright" -msgstr "Bản quyền" - -msgid "Fullscreen mode" -msgstr "Chế độ toàn màn hình" - -msgid "Open an issue" -msgstr "Mở một vấn đề" - -msgid "previous page" -msgstr "trang trước" - -msgid "Download this page" -msgstr "Tải xuống trang này" - -msgid "Theme by the" -msgstr "Chủ đề của" diff --git a/docs/_build/html/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 0e3235d090..0000000000 Binary files a/docs/_build/html/_static/locales/zh_CN/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/zh_CN/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/zh_CN/LC_MESSAGES/booktheme.po deleted file mode 100644 index f91f3ba0a2..0000000000 --- a/docs/_build/html/_static/locales/zh_CN/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "提出修改建议" - -msgid "Last updated on" -msgstr "上次更新时间:" - -msgid "Edit this page" -msgstr "编辑此页面" - -msgid "Launch" -msgstr "启动" - -msgid "Print to PDF" -msgstr "列印成 PDF" - -msgid "open issue" -msgstr "创建议题" - -msgid "Download notebook file" -msgstr "下载笔记本文件" - -msgid "Toggle navigation" -msgstr "显示或隐藏导航栏" - -msgid "Source repository" -msgstr "源码库" - -msgid "By the" -msgstr "作者:" - -msgid "next page" -msgstr "下一页" - -msgid "repository" -msgstr "仓库" - -msgid "Sphinx Book Theme" -msgstr "Sphinx Book 主题" - -msgid "Download source file" -msgstr "下载源文件" - -msgid "Contents" -msgstr "目录" - -msgid "By" -msgstr "作者:" - -msgid "Copyright" -msgstr "版权" - -msgid "Fullscreen mode" -msgstr "全屏模式" - -msgid "Open an issue" -msgstr "创建议题" - -msgid "previous page" -msgstr "上一页" - -msgid "Download this page" -msgstr "下载此页面" - -msgid "Theme by the" -msgstr "主题作者:" diff --git a/docs/_build/html/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo b/docs/_build/html/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo deleted file mode 100644 index 9116fa95d0..0000000000 Binary files a/docs/_build/html/_static/locales/zh_TW/LC_MESSAGES/booktheme.mo and /dev/null differ diff --git a/docs/_build/html/_static/locales/zh_TW/LC_MESSAGES/booktheme.po b/docs/_build/html/_static/locales/zh_TW/LC_MESSAGES/booktheme.po deleted file mode 100644 index 7833d90432..0000000000 --- a/docs/_build/html/_static/locales/zh_TW/LC_MESSAGES/booktheme.po +++ /dev/null @@ -1,75 +0,0 @@ - -msgid "" -msgstr "" -"Project-Id-Version: Sphinx-Book-Theme\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: zh_TW\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -msgid "suggest edit" -msgstr "提出修改建議" - -msgid "Last updated on" -msgstr "最後更新時間:" - -msgid "Edit this page" -msgstr "編輯此頁面" - -msgid "Launch" -msgstr "啟動" - -msgid "Print to PDF" -msgstr "列印成 PDF" - -msgid "open issue" -msgstr "公開的問題" - -msgid "Download notebook file" -msgstr "下載 Notebook 檔案" - -msgid "Toggle navigation" -msgstr "顯示或隱藏導覽列" - -msgid "Source repository" -msgstr "來源儲存庫" - -msgid "By the" -msgstr "作者:" - -msgid "next page" -msgstr "下一頁" - -msgid "repository" -msgstr "儲存庫" - -msgid "Sphinx Book Theme" -msgstr "Sphinx Book 佈景主題" - -msgid "Download source file" -msgstr "下載原始檔" - -msgid "Contents" -msgstr "目錄" - -msgid "By" -msgstr "作者:" - -msgid "Copyright" -msgstr "Copyright" - -msgid "Fullscreen mode" -msgstr "全螢幕模式" - -msgid "Open an issue" -msgstr "開啟議題" - -msgid "previous page" -msgstr "上一頁" - -msgid "Download this page" -msgstr "下載此頁面" - -msgid "Theme by the" -msgstr "佈景主題作者:" diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png deleted file mode 100644 index d96755fdaf..0000000000 Binary files a/docs/_build/html/_static/minus.png and /dev/null differ diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png deleted file mode 100644 index 7107cec93a..0000000000 Binary files a/docs/_build/html/_static/plus.png and /dev/null differ diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css deleted file mode 100644 index 012e6a00a4..0000000000 --- a/docs/_build/html/_static/pygments.css +++ /dev/null @@ -1,152 +0,0 @@ -html[data-theme="light"] .highlight pre { line-height: 125%; } -html[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -html[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -html[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -html[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -html[data-theme="light"] .highlight .hll { background-color: #fae4c2 } -html[data-theme="light"] .highlight { background: #fefefe; color: #080808 } -html[data-theme="light"] .highlight .c { color: #515151 } /* Comment */ -html[data-theme="light"] .highlight .err { color: #a12236 } /* Error */ -html[data-theme="light"] .highlight .k { color: #6730c5 } /* Keyword */ -html[data-theme="light"] .highlight .l { color: #7f4707 } /* Literal */ -html[data-theme="light"] .highlight .n { color: #080808 } /* Name */ -html[data-theme="light"] .highlight .o { color: #00622f } /* Operator */ -html[data-theme="light"] .highlight .p { color: #080808 } /* Punctuation */ -html[data-theme="light"] .highlight .ch { color: #515151 } /* Comment.Hashbang */ -html[data-theme="light"] .highlight .cm { color: #515151 } /* Comment.Multiline */ -html[data-theme="light"] .highlight .cp { color: #515151 } /* Comment.Preproc */ -html[data-theme="light"] .highlight .cpf { color: #515151 } /* Comment.PreprocFile */ -html[data-theme="light"] .highlight .c1 { color: #515151 } /* Comment.Single */ -html[data-theme="light"] .highlight .cs { color: #515151 } /* Comment.Special */ -html[data-theme="light"] .highlight .gd { color: #005b82 } /* Generic.Deleted */ -html[data-theme="light"] .highlight .ge { font-style: italic } /* Generic.Emph */ -html[data-theme="light"] .highlight .gh { color: #005b82 } /* Generic.Heading */ -html[data-theme="light"] .highlight .gs { font-weight: bold } /* Generic.Strong */ -html[data-theme="light"] .highlight .gu { color: #005b82 } /* Generic.Subheading */ -html[data-theme="light"] .highlight .kc { color: #6730c5 } /* Keyword.Constant */ -html[data-theme="light"] .highlight .kd { color: #6730c5 } /* Keyword.Declaration */ -html[data-theme="light"] .highlight .kn { color: #6730c5 } /* Keyword.Namespace */ -html[data-theme="light"] .highlight .kp { color: #6730c5 } /* Keyword.Pseudo */ -html[data-theme="light"] .highlight .kr { color: #6730c5 } /* Keyword.Reserved */ -html[data-theme="light"] .highlight .kt { color: #7f4707 } /* Keyword.Type */ -html[data-theme="light"] .highlight .ld { color: #7f4707 } /* Literal.Date */ -html[data-theme="light"] .highlight .m { color: #7f4707 } /* Literal.Number */ -html[data-theme="light"] .highlight .s { color: #00622f } /* Literal.String */ -html[data-theme="light"] .highlight .na { color: #912583 } /* Name.Attribute */ -html[data-theme="light"] .highlight .nb { color: #7f4707 } /* Name.Builtin */ -html[data-theme="light"] .highlight .nc { color: #005b82 } /* Name.Class */ -html[data-theme="light"] .highlight .no { color: #005b82 } /* Name.Constant */ -html[data-theme="light"] .highlight .nd { color: #7f4707 } /* Name.Decorator */ -html[data-theme="light"] .highlight .ni { color: #00622f } /* Name.Entity */ -html[data-theme="light"] .highlight .ne { color: #6730c5 } /* Name.Exception */ -html[data-theme="light"] .highlight .nf { color: #005b82 } /* Name.Function */ -html[data-theme="light"] .highlight .nl { color: #7f4707 } /* Name.Label */ -html[data-theme="light"] .highlight .nn { color: #080808 } /* Name.Namespace */ -html[data-theme="light"] .highlight .nx { color: #080808 } /* Name.Other */ -html[data-theme="light"] .highlight .py { color: #005b82 } /* Name.Property */ -html[data-theme="light"] .highlight .nt { color: #005b82 } /* Name.Tag */ -html[data-theme="light"] .highlight .nv { color: #a12236 } /* Name.Variable */ -html[data-theme="light"] .highlight .ow { color: #6730c5 } /* Operator.Word */ -html[data-theme="light"] .highlight .pm { color: #080808 } /* Punctuation.Marker */ -html[data-theme="light"] .highlight .w { color: #080808 } /* Text.Whitespace */ -html[data-theme="light"] .highlight .mb { color: #7f4707 } /* Literal.Number.Bin */ -html[data-theme="light"] .highlight .mf { color: #7f4707 } /* Literal.Number.Float */ -html[data-theme="light"] .highlight .mh { color: #7f4707 } /* Literal.Number.Hex */ -html[data-theme="light"] .highlight .mi { color: #7f4707 } /* Literal.Number.Integer */ -html[data-theme="light"] .highlight .mo { color: #7f4707 } /* Literal.Number.Oct */ -html[data-theme="light"] .highlight .sa { color: #00622f } /* Literal.String.Affix */ -html[data-theme="light"] .highlight .sb { color: #00622f } /* Literal.String.Backtick */ -html[data-theme="light"] .highlight .sc { color: #00622f } /* Literal.String.Char */ -html[data-theme="light"] .highlight .dl { color: #00622f } /* Literal.String.Delimiter */ -html[data-theme="light"] .highlight .sd { color: #00622f } /* Literal.String.Doc */ -html[data-theme="light"] .highlight .s2 { color: #00622f } /* Literal.String.Double */ -html[data-theme="light"] .highlight .se { color: #00622f } /* Literal.String.Escape */ -html[data-theme="light"] .highlight .sh { color: #00622f } /* Literal.String.Heredoc */ -html[data-theme="light"] .highlight .si { color: #00622f } /* Literal.String.Interpol */ -html[data-theme="light"] .highlight .sx { color: #00622f } /* Literal.String.Other */ -html[data-theme="light"] .highlight .sr { color: #a12236 } /* Literal.String.Regex */ -html[data-theme="light"] .highlight .s1 { color: #00622f } /* Literal.String.Single */ -html[data-theme="light"] .highlight .ss { color: #005b82 } /* Literal.String.Symbol */ -html[data-theme="light"] .highlight .bp { color: #7f4707 } /* Name.Builtin.Pseudo */ -html[data-theme="light"] .highlight .fm { color: #005b82 } /* Name.Function.Magic */ -html[data-theme="light"] .highlight .vc { color: #a12236 } /* Name.Variable.Class */ -html[data-theme="light"] .highlight .vg { color: #a12236 } /* Name.Variable.Global */ -html[data-theme="light"] .highlight .vi { color: #a12236 } /* Name.Variable.Instance */ -html[data-theme="light"] .highlight .vm { color: #7f4707 } /* Name.Variable.Magic */ -html[data-theme="light"] .highlight .il { color: #7f4707 } /* Literal.Number.Integer.Long */ -html[data-theme="dark"] .highlight pre { line-height: 125%; } -html[data-theme="dark"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -html[data-theme="dark"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -html[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -html[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -html[data-theme="dark"] .highlight .hll { background-color: #ffd9002e } -html[data-theme="dark"] .highlight { background: #2b2b2b; color: #f8f8f2 } -html[data-theme="dark"] .highlight .c { color: #ffd900 } /* Comment */ -html[data-theme="dark"] .highlight .err { color: #ffa07a } /* Error */ -html[data-theme="dark"] .highlight .k { color: #dcc6e0 } /* Keyword */ -html[data-theme="dark"] .highlight .l { color: #ffd900 } /* Literal */ -html[data-theme="dark"] .highlight .n { color: #f8f8f2 } /* Name */ -html[data-theme="dark"] .highlight .o { color: #abe338 } /* Operator */ -html[data-theme="dark"] .highlight .p { color: #f8f8f2 } /* Punctuation */ -html[data-theme="dark"] .highlight .ch { color: #ffd900 } /* Comment.Hashbang */ -html[data-theme="dark"] .highlight .cm { color: #ffd900 } /* Comment.Multiline */ -html[data-theme="dark"] .highlight .cp { color: #ffd900 } /* Comment.Preproc */ -html[data-theme="dark"] .highlight .cpf { color: #ffd900 } /* Comment.PreprocFile */ -html[data-theme="dark"] .highlight .c1 { color: #ffd900 } /* Comment.Single */ -html[data-theme="dark"] .highlight .cs { color: #ffd900 } /* Comment.Special */ -html[data-theme="dark"] .highlight .gd { color: #00e0e0 } /* Generic.Deleted */ -html[data-theme="dark"] .highlight .ge { font-style: italic } /* Generic.Emph */ -html[data-theme="dark"] .highlight .gh { color: #00e0e0 } /* Generic.Heading */ -html[data-theme="dark"] .highlight .gs { font-weight: bold } /* Generic.Strong */ -html[data-theme="dark"] .highlight .gu { color: #00e0e0 } /* Generic.Subheading */ -html[data-theme="dark"] .highlight .kc { color: #dcc6e0 } /* Keyword.Constant */ -html[data-theme="dark"] .highlight .kd { color: #dcc6e0 } /* Keyword.Declaration */ -html[data-theme="dark"] .highlight .kn { color: #dcc6e0 } /* Keyword.Namespace */ -html[data-theme="dark"] .highlight .kp { color: #dcc6e0 } /* Keyword.Pseudo */ -html[data-theme="dark"] .highlight .kr { color: #dcc6e0 } /* Keyword.Reserved */ -html[data-theme="dark"] .highlight .kt { color: #ffd900 } /* Keyword.Type */ -html[data-theme="dark"] .highlight .ld { color: #ffd900 } /* Literal.Date */ -html[data-theme="dark"] .highlight .m { color: #ffd900 } /* Literal.Number */ -html[data-theme="dark"] .highlight .s { color: #abe338 } /* Literal.String */ -html[data-theme="dark"] .highlight .na { color: #ffd900 } /* Name.Attribute */ -html[data-theme="dark"] .highlight .nb { color: #ffd900 } /* Name.Builtin */ -html[data-theme="dark"] .highlight .nc { color: #00e0e0 } /* Name.Class */ -html[data-theme="dark"] .highlight .no { color: #00e0e0 } /* Name.Constant */ -html[data-theme="dark"] .highlight .nd { color: #ffd900 } /* Name.Decorator */ -html[data-theme="dark"] .highlight .ni { color: #abe338 } /* Name.Entity */ -html[data-theme="dark"] .highlight .ne { color: #dcc6e0 } /* Name.Exception */ -html[data-theme="dark"] .highlight .nf { color: #00e0e0 } /* Name.Function */ -html[data-theme="dark"] .highlight .nl { color: #ffd900 } /* Name.Label */ -html[data-theme="dark"] .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ -html[data-theme="dark"] .highlight .nx { color: #f8f8f2 } /* Name.Other */ -html[data-theme="dark"] .highlight .py { color: #00e0e0 } /* Name.Property */ -html[data-theme="dark"] .highlight .nt { color: #00e0e0 } /* Name.Tag */ -html[data-theme="dark"] .highlight .nv { color: #ffa07a } /* Name.Variable */ -html[data-theme="dark"] .highlight .ow { color: #dcc6e0 } /* Operator.Word */ -html[data-theme="dark"] .highlight .pm { color: #f8f8f2 } /* Punctuation.Marker */ -html[data-theme="dark"] .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ -html[data-theme="dark"] .highlight .mb { color: #ffd900 } /* Literal.Number.Bin */ -html[data-theme="dark"] .highlight .mf { color: #ffd900 } /* Literal.Number.Float */ -html[data-theme="dark"] .highlight .mh { color: #ffd900 } /* Literal.Number.Hex */ -html[data-theme="dark"] .highlight .mi { color: #ffd900 } /* Literal.Number.Integer */ -html[data-theme="dark"] .highlight .mo { color: #ffd900 } /* Literal.Number.Oct */ -html[data-theme="dark"] .highlight .sa { color: #abe338 } /* Literal.String.Affix */ -html[data-theme="dark"] .highlight .sb { color: #abe338 } /* Literal.String.Backtick */ -html[data-theme="dark"] .highlight .sc { color: #abe338 } /* Literal.String.Char */ -html[data-theme="dark"] .highlight .dl { color: #abe338 } /* Literal.String.Delimiter */ -html[data-theme="dark"] .highlight .sd { color: #abe338 } /* Literal.String.Doc */ -html[data-theme="dark"] .highlight .s2 { color: #abe338 } /* Literal.String.Double */ -html[data-theme="dark"] .highlight .se { color: #abe338 } /* Literal.String.Escape */ -html[data-theme="dark"] .highlight .sh { color: #abe338 } /* Literal.String.Heredoc */ -html[data-theme="dark"] .highlight .si { color: #abe338 } /* Literal.String.Interpol */ -html[data-theme="dark"] .highlight .sx { color: #abe338 } /* Literal.String.Other */ -html[data-theme="dark"] .highlight .sr { color: #ffa07a } /* Literal.String.Regex */ -html[data-theme="dark"] .highlight .s1 { color: #abe338 } /* Literal.String.Single */ -html[data-theme="dark"] .highlight .ss { color: #00e0e0 } /* Literal.String.Symbol */ -html[data-theme="dark"] .highlight .bp { color: #ffd900 } /* Name.Builtin.Pseudo */ -html[data-theme="dark"] .highlight .fm { color: #00e0e0 } /* Name.Function.Magic */ -html[data-theme="dark"] .highlight .vc { color: #ffa07a } /* Name.Variable.Class */ -html[data-theme="dark"] .highlight .vg { color: #ffa07a } /* Name.Variable.Global */ -html[data-theme="dark"] .highlight .vi { color: #ffa07a } /* Name.Variable.Instance */ -html[data-theme="dark"] .highlight .vm { color: #ffd900 } /* Name.Variable.Magic */ -html[data-theme="dark"] .highlight .il { color: #ffd900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/sbt-webpack-macros.html b/docs/_build/html/_static/sbt-webpack-macros.html deleted file mode 100644 index 6cbf559faa..0000000000 --- a/docs/_build/html/_static/sbt-webpack-macros.html +++ /dev/null @@ -1,11 +0,0 @@ - -{% macro head_pre_bootstrap() %} - -{% endmacro %} - -{% macro body_post() %} - -{% endmacro %} diff --git a/docs/_build/html/_static/scripts/bootstrap.js b/docs/_build/html/_static/scripts/bootstrap.js deleted file mode 100644 index c8178debbc..0000000000 --- a/docs/_build/html/_static/scripts/bootstrap.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see bootstrap.js.LICENSE.txt */ -(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{afterMain:()=>E,afterRead:()=>v,afterWrite:()=>C,applyStyles:()=>$,arrow:()=>J,auto:()=>a,basePlacements:()=>l,beforeMain:()=>y,beforeRead:()=>_,beforeWrite:()=>A,bottom:()=>s,clippingParents:()=>d,computeStyles:()=>it,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>h,eventListeners:()=>st,flip:()=>bt,hide:()=>wt,left:()=>r,main:()=>w,modifierPhases:()=>O,offset:()=>Et,placements:()=>g,popper:()=>f,popperGenerator:()=>Lt,popperOffsets:()=>At,preventOverflow:()=>Tt,read:()=>b,reference:()=>p,right:()=>o,start:()=>c,top:()=>n,variationPlacements:()=>m,viewport:()=>u,write:()=>T});var i={};t.r(i),t.d(i,{Alert:()=>Oe,Button:()=>ke,Carousel:()=>li,Collapse:()=>Ei,Dropdown:()=>Ki,Modal:()=>Ln,Offcanvas:()=>Kn,Popover:()=>bs,ScrollSpy:()=>Ls,Tab:()=>Js,Toast:()=>po,Tooltip:()=>fs});var n="top",s="bottom",o="right",r="left",a="auto",l=[n,s,o,r],c="start",h="end",d="clippingParents",u="viewport",f="popper",p="reference",m=l.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+h])}),[]),g=[].concat(l,[a]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+h])}),[]),_="beforeRead",b="read",v="afterRead",y="beforeMain",w="main",E="afterMain",A="beforeWrite",T="write",C="afterWrite",O=[_,b,v,y,w,E,A,T,C];function x(t){return t?(t.nodeName||"").toLowerCase():null}function k(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof k(t).Element||t instanceof Element}function S(t){return t instanceof k(t).HTMLElement||t instanceof HTMLElement}function D(t){return"undefined"!=typeof ShadowRoot&&(t instanceof k(t).ShadowRoot||t instanceof ShadowRoot)}const $={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];S(s)&&x(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});S(n)&&x(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function I(t){return t.split("-")[0]}var N=Math.max,P=Math.min,M=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(j())}function H(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&S(t)&&(s=t.offsetWidth>0&&M(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&M(n.height)/t.offsetHeight||1);var r=(L(t)?k(t):window).visualViewport,a=!F()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function B(t){var e=H(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&D(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function z(t){return k(t).getComputedStyle(t)}function R(t){return["table","td","th"].indexOf(x(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function V(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(D(t)?t.host:null)||q(t)}function Y(t){return S(t)&&"fixed"!==z(t).position?t.offsetParent:null}function K(t){for(var e=k(t),i=Y(t);i&&R(i)&&"static"===z(i).position;)i=Y(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===z(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&S(t)&&"fixed"===z(t).position)return null;var i=V(t);for(D(i)&&(i=i.host);S(i)&&["html","body"].indexOf(x(i))<0;){var n=z(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return N(t,P(e,i))}function U(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const J={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,c=t.options,h=i.elements.arrow,d=i.modifiersData.popperOffsets,u=I(i.placement),f=Q(u),p=[r,o].indexOf(u)>=0?"height":"width";if(h&&d){var m=function(t,e){return U("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,l))}(c.padding,i),g=B(h),_="y"===f?n:r,b="y"===f?s:o,v=i.rects.reference[p]+i.rects.reference[f]-d[f]-i.rects.popper[p],y=d[f]-i.rects.reference[f],w=K(h),E=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,A=v/2-y/2,T=m[_],C=E-g[p]-m[b],O=E/2-g[p]/2+A,x=X(T,O,C),k=f;i.modifiersData[a]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,l=t.placement,c=t.variation,d=t.offsets,u=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=d.x,b=void 0===_?0:_,v=d.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var E=d.hasOwnProperty("x"),A=d.hasOwnProperty("y"),T=r,C=n,O=window;if(p){var x=K(i),L="clientHeight",S="clientWidth";x===k(i)&&"static"!==z(x=q(i)).position&&"absolute"===u&&(L="scrollHeight",S="scrollWidth"),(l===n||(l===r||l===o)&&c===h)&&(C=s,y-=(g&&x===O&&O.visualViewport?O.visualViewport.height:x[L])-a.height,y*=f?1:-1),l!==r&&(l!==n&&l!==s||c!==h)||(T=o,b-=(g&&x===O&&O.visualViewport?O.visualViewport.width:x[S])-a.width,b*=f?1:-1)}var D,$=Object.assign({position:u},p&&tt),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:M(i*s)/s||0,y:M(n*s)/s||0}}({x:b,y},k(i)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=A?"0":"",D[T]=E?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=A?y+"px":"",e[T]=E?b+"px":"",e.transform="",e))}const it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:I(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var nt={passive:!0};const st={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=k(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),a&&l.addEventListener("resize",i.update,nt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),a&&l.removeEventListener("resize",i.update,nt)}},data:{}};var ot={left:"right",right:"left",bottom:"top",top:"bottom"};function rt(t){return t.replace(/left|right|bottom|top/g,(function(t){return ot[t]}))}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function ct(t){var e=k(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ht(t){return H(q(t)).left+ct(t).scrollLeft}function dt(t){var e=z(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:S(t)&&dt(t)?t:ut(V(t))}function ft(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=k(n),r=s?[o].concat(o.visualViewport||[],dt(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ft(V(r)))}function pt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function mt(t,e,i){return e===u?pt(function(t,e){var i=k(t),n=q(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=F();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ht(t),y:l}}(t,i)):L(e)?function(t,e){var i=H(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):pt(function(t){var e,i=q(t),n=ct(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=N(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=N(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ht(t),l=-n.scrollTop;return"rtl"===z(s||i).direction&&(a+=N(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(q(t)))}function gt(t){var e,i=t.reference,a=t.element,l=t.placement,d=l?I(l):null,u=l?Z(l):null,f=i.x+i.width/2-a.width/2,p=i.y+i.height/2-a.height/2;switch(d){case n:e={x:f,y:i.y-a.height};break;case s:e={x:f,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:p};break;case r:e={x:i.x-a.width,y:p};break;default:e={x:i.x,y:i.y}}var m=d?Q(d):null;if(null!=m){var g="y"===m?"height":"width";switch(u){case c:e[m]=e[m]-(i[g]/2-a[g]/2);break;case h:e[m]=e[m]+(i[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var i=e,r=i.placement,a=void 0===r?t.placement:r,c=i.strategy,h=void 0===c?t.strategy:c,m=i.boundary,g=void 0===m?d:m,_=i.rootBoundary,b=void 0===_?u:_,v=i.elementContext,y=void 0===v?f:v,w=i.altBoundary,E=void 0!==w&&w,A=i.padding,T=void 0===A?0:A,C=U("number"!=typeof T?T:G(T,l)),O=y===f?p:f,k=t.rects.popper,D=t.elements[E?O:y],$=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ft(V(t)),i=["absolute","fixed"].indexOf(z(t).position)>=0&&S(t)?K(t):t;return L(i)?e.filter((function(t){return L(t)&&W(t,i)&&"body"!==x(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=mt(t,i,n);return e.top=N(s.top,e.top),e.right=P(s.right,e.right),e.bottom=P(s.bottom,e.bottom),e.left=N(s.left,e.left),e}),mt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(D)?D:D.contextElement||q(t.elements.popper),g,b,h),I=H(t.elements.reference),M=gt({reference:I,element:k,strategy:"absolute",placement:a}),j=pt(Object.assign({},k,M)),F=y===f?j:I,B={top:$.top-F.top+C.top,bottom:F.bottom-$.bottom+C.bottom,left:$.left-F.left+C.left,right:F.right-$.right+C.right},R=t.modifiersData.offset;if(y===f&&R){var Y=R[a];Object.keys(B).forEach((function(t){var e=[o,s].indexOf(t)>=0?1:-1,i=[n,s].indexOf(t)>=0?"y":"x";B[t]+=Y[i]*e}))}return B}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var d=i.mainAxis,u=void 0===d||d,f=i.altAxis,p=void 0===f||f,_=i.fallbackPlacements,b=i.padding,v=i.boundary,y=i.rootBoundary,w=i.altBoundary,E=i.flipVariations,A=void 0===E||E,T=i.allowedAutoPlacements,C=e.options.placement,O=I(C),x=_||(O!==C&&A?function(t){if(I(t)===a)return[];var e=rt(t);return[lt(t),e,lt(e)]}(C):[rt(C)]),k=[C].concat(x).reduce((function(t,i){return t.concat(I(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?g:c,d=Z(n),u=d?a?m:m.filter((function(t){return Z(t)===d})):l,f=u.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=u);var p=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[I(i)],e}),{});return Object.keys(p).sort((function(t,e){return p[t]-p[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:A,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,$=!0,N=k[0],P=0;P=0,B=H?"width":"height",W=_t(e,{placement:M,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?o:r:F?s:n;L[B]>S[B]&&(z=rt(z));var R=rt(z),q=[];if(u&&q.push(W[j]<=0),p&&q.push(W[z]<=0,W[R]<=0),q.every((function(t){return t}))){N=M,$=!1;break}D.set(M,q)}if($)for(var V=function(t){var e=k.find((function(e){var i=D.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return N=e,"break"},Y=A?3:1;Y>0&&"break"!==V(Y);Y--);e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[n,o,s,r].some((function(e){return t[e]>=0}))}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),d=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Et={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,s=t.name,a=i.offset,l=void 0===a?[0,0]:a,c=g.reduce((function(t,i){return t[i]=function(t,e,i){var s=I(t),a=[r,n].indexOf(s)>=0?-1:1,l="function"==typeof i?i(Object.assign({},e,{placement:t})):i,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[r,o].indexOf(s)>=0?{x:h,y:c}:{x:c,y:h}}(i,e.rects,l),t}),{}),h=c[e.placement],d=h.x,u=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=d,e.modifiersData.popperOffsets.y+=u),e.modifiersData[s]=c}},At={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Tt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,a=t.name,l=i.mainAxis,h=void 0===l||l,d=i.altAxis,u=void 0!==d&&d,f=i.boundary,p=i.rootBoundary,m=i.altBoundary,g=i.padding,_=i.tether,b=void 0===_||_,v=i.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),E=I(e.placement),A=Z(e.placement),T=!A,C=Q(E),O="x"===C?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),$=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,M={x:0,y:0};if(x){if(h){var j,F="y"===C?n:r,H="y"===C?s:o,W="y"===C?"height":"width",z=x[C],R=z+w[F],q=z-w[H],V=b?-L[W]/2:0,Y=A===c?k[W]:L[W],U=A===c?-L[W]:-k[W],G=e.elements.arrow,J=b&&G?B(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[H],nt=X(0,k[W],J[W]),st=T?k[W]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=T?-k[W]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(j=null==$?void 0:$[C])?j:0,ct=z+ot-lt,ht=X(b?P(R,z+st-lt-at):R,z,b?N(q,ct):q);x[C]=ht,M[C]=ht-z}if(u){var dt,ut="x"===C?n:r,ft="x"===C?s:o,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[ut],bt=pt-w[ft],vt=-1!==[n,r].indexOf(E),yt=null!=(dt=null==$?void 0:$[O])?dt:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,Et=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,At=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,Et):X(b?wt:gt,pt,b?Et:bt);x[O]=At,M[O]=At-pt}e.modifiersData[a]=M}},requiresIfExists:["offset"]};function Ct(t,e,i){void 0===i&&(i=!1);var n,s,o=S(e),r=S(e)&&function(t){var e=t.getBoundingClientRect(),i=M(e.width)/t.offsetWidth||1,n=M(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=q(e),l=H(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==x(e)||dt(a))&&(c=(n=e)!==k(n)&&S(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:ct(n)),S(e)?((h=H(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ht(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",Mt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),jt=t=>{t.dispatchEvent(new Event(Pt))},Ft=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Ht=t=>Ft(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(Mt(t)):null,Bt=t=>{if(!Ft(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},Wt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),zt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?zt(t.parentNode):null},Rt=()=>{},qt=t=>{t.offsetHeight},Vt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Yt=[],Kt=()=>"rtl"===document.documentElement.dir,Qt=t=>{var e;e=()=>{const e=Vt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Yt.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of Yt)t()})),Yt.push(e)):e()},Xt=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Ut=(t,e,i=!0)=>{if(!i)return void Xt(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Xt(t))};e.addEventListener(Pt,o),setTimeout((()=>{s||jt(e)}),n)},Gt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Jt=/[^.]*(?=\..*)\.|.*/,Zt=/\..*/,te=/::\d+$/,ee={};let ie=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},se=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function oe(t,e){return e&&`${e}::${ie++}`||t.uidEvent||ie++}function re(t){const e=oe(t);return t.uidEvent=e,ee[e]=ee[e]||{},ee[e]}function ae(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function le(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=ue(t);return se.has(o)||(o=t),[n,s,o]}function ce(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=le(e,i,n);if(e in ne){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=re(t),c=l[a]||(l[a]={}),h=ae(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=oe(r,e.replace(Jt,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return pe(s,{delegateTarget:r}),n.oneOff&&fe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return pe(n,{delegateTarget:t}),i.oneOff&&fe.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function he(t,e,i,n,s){const o=ae(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function de(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&he(t,e,i,r.callable,r.delegationSelector)}function ue(t){return t=t.replace(Zt,""),ne[t]||t}const fe={on(t,e,i,n){ce(t,e,i,n,!1)},one(t,e,i,n){ce(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=le(e,i,n),a=r!==e,l=re(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))de(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(te,"");a&&!e.includes(s)||he(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;he(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Vt();let s=null,o=!0,r=!0,a=!1;e!==ue(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=pe(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function pe(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function me(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function ge(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const _e={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${ge(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${ge(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=me(t.dataset[n])}return e},getDataAttribute:(t,e)=>me(t.getAttribute(`data-bs-${ge(e)}`))};class be{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ft(e)?_e.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ft(e)?_e.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],o=Ft(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${o}" but expected type "${s}".`)}var i}}class ve extends be{constructor(t,e){super(),(t=Ht(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),fe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Ut(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Ht(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const ye=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>Mt(t))).join(","):null},we={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!Wt(t)&&Bt(t)))},getSelectorFromElement(t){const e=ye(t);return e&&we.findOne(e)?e:null},getElementFromSelector(t){const e=ye(t);return e?we.findOne(e):null},getMultipleElementsFromSelector(t){const e=ye(t);return e?we.find(e):[]}},Ee=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;fe.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),Wt(this))return;const s=we.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},Ae=".bs.alert",Te=`close${Ae}`,Ce=`closed${Ae}`;class Oe extends ve{static get NAME(){return"alert"}close(){if(fe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),fe.trigger(this._element,Ce),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Oe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ee(Oe,"close"),Qt(Oe);const xe='[data-bs-toggle="button"]';class ke extends ve{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ke.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}fe.on(document,"click.bs.button.data-api",xe,(t=>{t.preventDefault();const e=t.target.closest(xe);ke.getOrCreateInstance(e).toggle()})),Qt(ke);const Le=".bs.swipe",Se=`touchstart${Le}`,De=`touchmove${Le}`,$e=`touchend${Le}`,Ie=`pointerdown${Le}`,Ne=`pointerup${Le}`,Pe={endCallback:null,leftCallback:null,rightCallback:null},Me={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class je extends be{constructor(t,e){super(),this._element=t,t&&je.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pe}static get DefaultType(){return Me}static get NAME(){return"swipe"}dispose(){fe.off(this._element,Le)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Xt(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Xt(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(fe.on(this._element,Ie,(t=>this._start(t))),fe.on(this._element,Ne,(t=>this._end(t))),this._element.classList.add("pointer-event")):(fe.on(this._element,Se,(t=>this._start(t))),fe.on(this._element,De,(t=>this._move(t))),fe.on(this._element,$e,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Fe=".bs.carousel",He=".data-api",Be="ArrowLeft",We="ArrowRight",ze="next",Re="prev",qe="left",Ve="right",Ye=`slide${Fe}`,Ke=`slid${Fe}`,Qe=`keydown${Fe}`,Xe=`mouseenter${Fe}`,Ue=`mouseleave${Fe}`,Ge=`dragstart${Fe}`,Je=`load${Fe}${He}`,Ze=`click${Fe}${He}`,ti="carousel",ei="active",ii=".active",ni=".carousel-item",si=ii+ni,oi={[Be]:Ve,[We]:qe},ri={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},ai={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class li extends ve{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=we.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===ti&&this.cycle()}static get Default(){return ri}static get DefaultType(){return ai}static get NAME(){return"carousel"}next(){this._slide(ze)}nextWhenVisible(){!document.hidden&&Bt(this._element)&&this.next()}prev(){this._slide(Re)}pause(){this._isSliding&&jt(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?fe.one(this._element,Ke,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void fe.one(this._element,Ke,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?ze:Re;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&fe.on(this._element,Qe,(t=>this._keydown(t))),"hover"===this._config.pause&&(fe.on(this._element,Xe,(()=>this.pause())),fe.on(this._element,Ue,(()=>this._maybeEnableCycle()))),this._config.touch&&je.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of we.find(".carousel-item img",this._element))fe.on(t,Ge,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(qe)),rightCallback:()=>this._slide(this._directionToOrder(Ve)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new je(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=oi[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=we.findOne(ii,this._indicatorsElement);e.classList.remove(ei),e.removeAttribute("aria-current");const i=we.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(ei),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===ze,s=e||Gt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>fe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(Ye).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),qt(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(ei),i.classList.remove(ei,c,l),this._isSliding=!1,r(Ke)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return we.findOne(si,this._element)}_getItems(){return we.find(ni,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Kt()?t===qe?Re:ze:t===qe?ze:Re}_orderToDirection(t){return Kt()?t===Re?qe:Ve:t===Re?Ve:qe}static jQueryInterface(t){return this.each((function(){const e=li.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}fe.on(document,Ze,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=we.getElementFromSelector(this);if(!e||!e.classList.contains(ti))return;t.preventDefault();const i=li.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===_e.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),fe.on(window,Je,(()=>{const t=we.find('[data-bs-ride="carousel"]');for(const e of t)li.getOrCreateInstance(e)})),Qt(li);const ci=".bs.collapse",hi=`show${ci}`,di=`shown${ci}`,ui=`hide${ci}`,fi=`hidden${ci}`,pi=`click${ci}.data-api`,mi="show",gi="collapse",_i="collapsing",bi=`:scope .${gi} .${gi}`,vi='[data-bs-toggle="collapse"]',yi={parent:null,toggle:!0},wi={parent:"(null|element)",toggle:"boolean"};class Ei extends ve{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=we.find(vi);for(const t of i){const e=we.getSelectorFromElement(t),i=we.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return yi}static get DefaultType(){return wi}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Ei.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(fe.trigger(this._element,hi).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(gi),this._element.classList.add(_i),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi,mi),this._element.style[e]="",fe.trigger(this._element,di)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(fe.trigger(this._element,ui).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,qt(this._element),this._element.classList.add(_i),this._element.classList.remove(gi,mi);for(const t of this._triggerArray){const e=we.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi),fe.trigger(this._element,fi)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(mi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Ht(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(vi);for(const e of t){const t=we.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=we.find(bi,this._config.parent);return we.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Ei.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}fe.on(document,pi,vi,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of we.getMultipleElementsFromSelector(this))Ei.getOrCreateInstance(t,{toggle:!1}).toggle()})),Qt(Ei);const Ai="dropdown",Ti=".bs.dropdown",Ci=".data-api",Oi="ArrowUp",xi="ArrowDown",ki=`hide${Ti}`,Li=`hidden${Ti}`,Si=`show${Ti}`,Di=`shown${Ti}`,$i=`click${Ti}${Ci}`,Ii=`keydown${Ti}${Ci}`,Ni=`keyup${Ti}${Ci}`,Pi="show",Mi='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',ji=`${Mi}.${Pi}`,Fi=".dropdown-menu",Hi=Kt()?"top-end":"top-start",Bi=Kt()?"top-start":"top-end",Wi=Kt()?"bottom-end":"bottom-start",zi=Kt()?"bottom-start":"bottom-end",Ri=Kt()?"left-start":"right-start",qi=Kt()?"right-start":"left-start",Vi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Yi={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Ki extends ve{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=we.next(this._element,Fi)[0]||we.prev(this._element,Fi)[0]||we.findOne(Fi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Vi}static get DefaultType(){return Yi}static get NAME(){return Ai}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Wt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!fe.trigger(this._element,Si,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Pi),this._element.classList.add(Pi),fe.trigger(this._element,Di,t)}}hide(){if(Wt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!fe.trigger(this._element,ki,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Pi),this._element.classList.remove(Pi),this._element.setAttribute("aria-expanded","false"),_e.removeDataAttribute(this._menu,"popper"),fe.trigger(this._element,Li,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ft(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ai.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===e)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:Ft(this._config.reference)?t=Ht(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const i=this._getPopperConfig();this._popper=Dt(t,this._menu,i)}_isShown(){return this._menu.classList.contains(Pi)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Ri;if(t.classList.contains("dropstart"))return qi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Bi:Hi:e?zi:Wi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_e.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Xt(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=we.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>Bt(t)));i.length&&Gt(i,e,t===xi,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Ki.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=we.find(ji);for(const i of e){const e=Ki.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Oi,xi].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Mi)?this:we.prev(this,Mi)[0]||we.next(this,Mi)[0]||we.findOne(Mi,t.delegateTarget.parentNode),o=Ki.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}fe.on(document,Ii,Mi,Ki.dataApiKeydownHandler),fe.on(document,Ii,Fi,Ki.dataApiKeydownHandler),fe.on(document,$i,Ki.clearMenus),fe.on(document,Ni,Ki.clearMenus),fe.on(document,$i,Mi,(function(t){t.preventDefault(),Ki.getOrCreateInstance(this).toggle()})),Qt(Ki);const Qi="backdrop",Xi="show",Ui=`mousedown.bs.${Qi}`,Gi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ji={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Zi extends be{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Gi}static get DefaultType(){return Ji}static get NAME(){return Qi}show(t){if(!this._config.isVisible)return void Xt(t);this._append();const e=this._getElement();this._config.isAnimated&&qt(e),e.classList.add(Xi),this._emulateAnimation((()=>{Xt(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Xi),this._emulateAnimation((()=>{this.dispose(),Xt(t)}))):Xt(t)}dispose(){this._isAppended&&(fe.off(this._element,Ui),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Ht(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),fe.on(t,Ui,(()=>{Xt(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){Ut(t,this._getElement(),this._config.isAnimated)}}const tn=".bs.focustrap",en=`focusin${tn}`,nn=`keydown.tab${tn}`,sn="backward",on={autofocus:!0,trapElement:null},rn={autofocus:"boolean",trapElement:"element"};class an extends be{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return on}static get DefaultType(){return rn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),fe.off(document,tn),fe.on(document,en,(t=>this._handleFocusin(t))),fe.on(document,nn,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,fe.off(document,tn))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=we.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===sn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?sn:"forward")}}const ln=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",cn=".sticky-top",hn="padding-right",dn="margin-right";class un{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,hn,(e=>e+t)),this._setElementAttributes(ln,hn,(e=>e+t)),this._setElementAttributes(cn,dn,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,hn),this._resetElementAttributes(ln,hn),this._resetElementAttributes(cn,dn)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&_e.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=_e.getDataAttribute(t,e);null!==i?(_e.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(Ft(t))e(t);else for(const i of we.find(t,this._element))e(i)}}const fn=".bs.modal",pn=`hide${fn}`,mn=`hidePrevented${fn}`,gn=`hidden${fn}`,_n=`show${fn}`,bn=`shown${fn}`,vn=`resize${fn}`,yn=`click.dismiss${fn}`,wn=`mousedown.dismiss${fn}`,En=`keydown.dismiss${fn}`,An=`click${fn}.data-api`,Tn="modal-open",Cn="show",On="modal-static",xn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends ve{constructor(t,e){super(t,e),this._dialog=we.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new un,this._addEventListeners()}static get Default(){return xn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||fe.trigger(this._element,_n,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Tn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(fe.trigger(this._element,pn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Cn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){fe.off(window,fn),fe.off(this._dialog,fn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Zi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new an({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=we.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),qt(this._element),this._element.classList.add(Cn),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,fe.trigger(this._element,bn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){fe.on(this._element,En,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),fe.on(window,vn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),fe.on(this._element,wn,(t=>{fe.one(this._element,yn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Tn),this._resetAdjustments(),this._scrollBar.reset(),fe.trigger(this._element,gn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(fe.trigger(this._element,mn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(On)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(On),this._queueCallback((()=>{this._element.classList.remove(On),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Kt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Kt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ln.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}fe.on(document,An,'[data-bs-toggle="modal"]',(function(t){const e=we.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),fe.one(e,_n,(t=>{t.defaultPrevented||fe.one(e,gn,(()=>{Bt(this)&&this.focus()}))}));const i=we.findOne(".modal.show");i&&Ln.getInstance(i).hide(),Ln.getOrCreateInstance(e).toggle(this)})),Ee(Ln),Qt(Ln);const Sn=".bs.offcanvas",Dn=".data-api",$n=`load${Sn}${Dn}`,In="show",Nn="showing",Pn="hiding",Mn=".offcanvas.show",jn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Bn=`hidePrevented${Sn}`,Wn=`hidden${Sn}`,zn=`resize${Sn}`,Rn=`click${Sn}${Dn}`,qn=`keydown.dismiss${Sn}`,Vn={backdrop:!0,keyboard:!0,scroll:!1},Yn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Kn extends ve{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Vn}static get DefaultType(){return Yn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||fe.trigger(this._element,jn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new un).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Nn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(In),this._element.classList.remove(Nn),fe.trigger(this._element,Fn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(fe.trigger(this._element,Hn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Pn),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(In,Pn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new un).reset(),fe.trigger(this._element,Wn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Zi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():fe.trigger(this._element,Bn)}:null})}_initializeFocusTrap(){return new an({trapElement:this._element})}_addEventListeners(){fe.on(this._element,qn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():fe.trigger(this._element,Bn))}))}static jQueryInterface(t){return this.each((function(){const e=Kn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}fe.on(document,Rn,'[data-bs-toggle="offcanvas"]',(function(t){const e=we.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this))return;fe.one(e,Wn,(()=>{Bt(this)&&this.focus()}));const i=we.findOne(Mn);i&&i!==e&&Kn.getInstance(i).hide(),Kn.getOrCreateInstance(e).toggle(this)})),fe.on(window,$n,(()=>{for(const t of we.find(Mn))Kn.getOrCreateInstance(t).show()})),fe.on(window,zn,(()=>{for(const t of we.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Kn.getOrCreateInstance(t).hide()})),Ee(Kn),Qt(Kn);const Qn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Xn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Un=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Gn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Xn.has(i)||Boolean(Un.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Jn={allowList:Qn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Zn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ts={entry:"(string|element|function|null)",selector:"(string|element)"};class es extends be{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Jn}static get DefaultType(){return Zn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},ts)}_setContent(t,e,i){const n=we.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ft(e)?this._putElementInTemplate(Ht(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Gn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Xt(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const is=new Set(["sanitize","allowList","sanitizeFn"]),ns="fade",ss="show",os=".tooltip-inner",rs=".modal",as="hide.bs.modal",ls="hover",cs="focus",hs={AUTO:"auto",TOP:"top",RIGHT:Kt()?"left":"right",BOTTOM:"bottom",LEFT:Kt()?"right":"left"},ds={allowList:Qn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},us={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class fs extends ve{constructor(t,i){if(void 0===e)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,i),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return ds}static get DefaultType(){return us}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),fe.off(this._element.closest(rs),as,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=fe.trigger(this._element,this.constructor.eventName("show")),e=(zt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),fe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._queueCallback((()=>{fe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!fe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._activeTrigger.click=!1,this._activeTrigger[cs]=!1,this._activeTrigger[ls]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),fe.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ns,ss),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ns),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new es({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[os]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ns)}_isShown(){return this.tip&&this.tip.classList.contains(ss)}_createPopper(t){const e=Xt(this._config.placement,[this,t,this._element]),i=hs[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Xt(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Xt(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)fe.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ls?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ls?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");fe.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?cs:ls]=!0,e._enter()})),fe.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?cs:ls]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},fe.on(this._element.closest(rs),as,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=_e.getDataAttributes(this._element);for(const t of Object.keys(e))is.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Ht(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(fs);const ps=".popover-header",ms=".popover-body",gs={...fs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},_s={...fs.DefaultType,content:"(null|string|element|function)"};class bs extends fs{static get Default(){return gs}static get DefaultType(){return _s}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ps]:this._getTitle(),[ms]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=bs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(bs);const vs=".bs.scrollspy",ys=`activate${vs}`,ws=`click${vs}`,Es=`load${vs}.data-api`,As="active",Ts="[href]",Cs=".nav-link",Os=`${Cs}, .nav-item > ${Cs}, .list-group-item`,xs={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},ks={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ls extends ve{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return xs}static get DefaultType(){return ks}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Ht(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(fe.off(this._config.target,ws),fe.on(this._config.target,ws,Ts,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=we.find(Ts,this._config.target);for(const e of t){if(!e.hash||Wt(e))continue;const t=we.findOne(decodeURI(e.hash),this._element);Bt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(As),this._activateParents(t),fe.trigger(this._element,ys,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))we.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(As);else for(const e of we.parents(t,".nav, .list-group"))for(const t of we.prev(e,Os))t.classList.add(As)}_clearActiveClass(t){t.classList.remove(As);const e=we.find(`${Ts}.${As}`,t);for(const t of e)t.classList.remove(As)}static jQueryInterface(t){return this.each((function(){const e=Ls.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(window,Es,(()=>{for(const t of we.find('[data-bs-spy="scroll"]'))Ls.getOrCreateInstance(t)})),Qt(Ls);const Ss=".bs.tab",Ds=`hide${Ss}`,$s=`hidden${Ss}`,Is=`show${Ss}`,Ns=`shown${Ss}`,Ps=`click${Ss}`,Ms=`keydown${Ss}`,js=`load${Ss}`,Fs="ArrowLeft",Hs="ArrowRight",Bs="ArrowUp",Ws="ArrowDown",zs="Home",Rs="End",qs="active",Vs="fade",Ys="show",Ks=".dropdown-toggle",Qs=`:not(${Ks})`,Xs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Us=`.nav-link${Qs}, .list-group-item${Qs}, [role="tab"]${Qs}, ${Xs}`,Gs=`.${qs}[data-bs-toggle="tab"], .${qs}[data-bs-toggle="pill"], .${qs}[data-bs-toggle="list"]`;class Js extends ve{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),fe.on(this._element,Ms,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?fe.trigger(e,Ds,{relatedTarget:t}):null;fe.trigger(t,Is,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(qs),this._activate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),fe.trigger(t,Ns,{relatedTarget:e})):t.classList.add(Ys)}),t,t.classList.contains(Vs)))}_deactivate(t,e){t&&(t.classList.remove(qs),t.blur(),this._deactivate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),fe.trigger(t,$s,{relatedTarget:e})):t.classList.remove(Ys)}),t,t.classList.contains(Vs)))}_keydown(t){if(![Fs,Hs,Bs,Ws,zs,Rs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!Wt(t)));let i;if([zs,Rs].includes(t.key))i=e[t.key===zs?0:e.length-1];else{const n=[Hs,Ws].includes(t.key);i=Gt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Js.getOrCreateInstance(i).show())}_getChildren(){return we.find(Us,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=we.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=we.findOne(t,i);s&&s.classList.toggle(n,e)};n(Ks,qs),n(".dropdown-menu",Ys),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(qs)}_getInnerElement(t){return t.matches(Us)?t:we.findOne(Us,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Js.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(document,Ps,Xs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this)||Js.getOrCreateInstance(this).show()})),fe.on(window,js,(()=>{for(const t of we.find(Gs))Js.getOrCreateInstance(t)})),Qt(Js);const Zs=".bs.toast",to=`mouseover${Zs}`,eo=`mouseout${Zs}`,io=`focusin${Zs}`,no=`focusout${Zs}`,so=`hide${Zs}`,oo=`hidden${Zs}`,ro=`show${Zs}`,ao=`shown${Zs}`,lo="hide",co="show",ho="showing",uo={animation:"boolean",autohide:"boolean",delay:"number"},fo={animation:!0,autohide:!0,delay:5e3};class po extends ve{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return fo}static get DefaultType(){return uo}static get NAME(){return"toast"}show(){fe.trigger(this._element,ro).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(lo),qt(this._element),this._element.classList.add(co,ho),this._queueCallback((()=>{this._element.classList.remove(ho),fe.trigger(this._element,ao),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(fe.trigger(this._element,so).defaultPrevented||(this._element.classList.add(ho),this._queueCallback((()=>{this._element.classList.add(lo),this._element.classList.remove(ho,co),fe.trigger(this._element,oo)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(co),super.dispose()}isShown(){return this._element.classList.contains(co)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){fe.on(this._element,to,(t=>this._onInteraction(t,!0))),fe.on(this._element,eo,(t=>this._onInteraction(t,!1))),fe.on(this._element,io,(t=>this._onInteraction(t,!0))),fe.on(this._element,no,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=po.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}function mo(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}Ee(po),Qt(po),mo((function(){[].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')).map((function(t){return new fs(t,{delay:{show:500,hide:100}})}))})),mo((function(){document.getElementById("pst-back-to-top").addEventListener("click",(function(){document.body.scrollTop=0,document.documentElement.scrollTop=0}))})),mo((function(){var t=document.getElementById("pst-back-to-top"),e=document.getElementsByClassName("bd-header")[0].getBoundingClientRect();window.addEventListener("scroll",(function(){this.oldScroll>this.scrollY&&this.scrollY>e.bottom?t.style.display="block":t.style.display="none",this.oldScroll=this.scrollY}))})),window.bootstrap=i})(); -//# sourceMappingURL=bootstrap.js.map \ No newline at end of file diff --git a/docs/_build/html/_static/scripts/bootstrap.js.LICENSE.txt b/docs/_build/html/_static/scripts/bootstrap.js.LICENSE.txt deleted file mode 100644 index 28755c2c5b..0000000000 --- a/docs/_build/html/_static/scripts/bootstrap.js.LICENSE.txt +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ diff --git a/docs/_build/html/_static/scripts/bootstrap.js.map b/docs/_build/html/_static/scripts/bootstrap.js.map deleted file mode 100644 index e9e815891f..0000000000 --- a/docs/_build/html/_static/scripts/bootstrap.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"scripts/bootstrap.js","mappings":";mBACA,IAAIA,EAAsB,CCA1BA,EAAwB,CAACC,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXF,EAAoBI,EAAEF,EAAYC,KAASH,EAAoBI,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,IAE1E,ECNDH,EAAwB,CAACS,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFV,EAAyBC,IACH,oBAAXa,QAA0BA,OAAOC,aAC1CV,OAAOC,eAAeL,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DX,OAAOC,eAAeL,EAAS,aAAc,CAAEe,OAAO,GAAO,01BCLvD,IAAI,EAAM,MACNC,EAAS,SACTC,EAAQ,QACRC,EAAO,OACPC,EAAO,OACPC,EAAiB,CAAC,EAAKJ,EAAQC,EAAOC,GACtCG,EAAQ,QACRC,EAAM,MACNC,EAAkB,kBAClBC,EAAW,WACXC,EAAS,SACTC,EAAY,YACZC,EAAmCP,EAAeQ,QAAO,SAAUC,EAAKC,GACjF,OAAOD,EAAIE,OAAO,CAACD,EAAY,IAAMT,EAAOS,EAAY,IAAMR,GAChE,GAAG,IACQ,EAA0B,GAAGS,OAAOX,EAAgB,CAACD,IAAOS,QAAO,SAAUC,EAAKC,GAC3F,OAAOD,EAAIE,OAAO,CAACD,EAAWA,EAAY,IAAMT,EAAOS,EAAY,IAAMR,GAC3E,GAAG,IAEQU,EAAa,aACbC,EAAO,OACPC,EAAY,YAEZC,EAAa,aACbC,EAAO,OACPC,EAAY,YAEZC,EAAc,cACdC,EAAQ,QACRC,EAAa,aACbC,EAAiB,CAACT,EAAYC,EAAMC,EAAWC,EAAYC,EAAMC,EAAWC,EAAaC,EAAOC,GC9B5F,SAASE,EAAYC,GAClC,OAAOA,GAAWA,EAAQC,UAAY,IAAIC,cAAgB,IAC5D,CCFe,SAASC,EAAUC,GAChC,GAAY,MAARA,EACF,OAAOC,OAGT,GAAwB,oBAApBD,EAAKE,WAAkC,CACzC,IAAIC,EAAgBH,EAAKG,cACzB,OAAOA,GAAgBA,EAAcC,aAAwBH,MAC/D,CAEA,OAAOD,CACT,CCTA,SAASK,EAAUL,GAEjB,OAAOA,aADUD,EAAUC,GAAMM,SACIN,aAAgBM,OACvD,CAEA,SAASC,EAAcP,GAErB,OAAOA,aADUD,EAAUC,GAAMQ,aACIR,aAAgBQ,WACvD,CAEA,SAASC,EAAaT,GAEpB,MAA0B,oBAAfU,aAKJV,aADUD,EAAUC,GAAMU,YACIV,aAAgBU,WACvD,CCwDA,SACEC,KAAM,cACNC,SAAS,EACTC,MAAO,QACPC,GA5EF,SAAqBC,GACnB,IAAIC,EAAQD,EAAKC,MACjB3D,OAAO4D,KAAKD,EAAME,UAAUC,SAAQ,SAAUR,GAC5C,IAAIS,EAAQJ,EAAMK,OAAOV,IAAS,CAAC,EAC/BW,EAAaN,EAAMM,WAAWX,IAAS,CAAC,EACxCf,EAAUoB,EAAME,SAASP,GAExBJ,EAAcX,IAAaD,EAAYC,KAO5CvC,OAAOkE,OAAO3B,EAAQwB,MAAOA,GAC7B/D,OAAO4D,KAAKK,GAAYH,SAAQ,SAAUR,GACxC,IAAI3C,EAAQsD,EAAWX,IAET,IAAV3C,EACF4B,EAAQ4B,gBAAgBb,GAExBf,EAAQ6B,aAAad,GAAgB,IAAV3C,EAAiB,GAAKA,EAErD,IACF,GACF,EAoDE0D,OAlDF,SAAgBC,GACd,IAAIX,EAAQW,EAAMX,MACdY,EAAgB,CAClBlD,OAAQ,CACNmD,SAAUb,EAAMc,QAAQC,SACxB5D,KAAM,IACN6D,IAAK,IACLC,OAAQ,KAEVC,MAAO,CACLL,SAAU,YAEZlD,UAAW,CAAC,GASd,OAPAtB,OAAOkE,OAAOP,EAAME,SAASxC,OAAO0C,MAAOQ,EAAclD,QACzDsC,EAAMK,OAASO,EAEXZ,EAAME,SAASgB,OACjB7E,OAAOkE,OAAOP,EAAME,SAASgB,MAAMd,MAAOQ,EAAcM,OAGnD,WACL7E,OAAO4D,KAAKD,EAAME,UAAUC,SAAQ,SAAUR,GAC5C,IAAIf,EAAUoB,EAAME,SAASP,GACzBW,EAAaN,EAAMM,WAAWX,IAAS,CAAC,EAGxCS,EAFkB/D,OAAO4D,KAAKD,EAAMK,OAAOzD,eAAe+C,GAAQK,EAAMK,OAAOV,GAAQiB,EAAcjB,IAE7E9B,QAAO,SAAUuC,EAAOe,GAElD,OADAf,EAAMe,GAAY,GACXf,CACT,GAAG,CAAC,GAECb,EAAcX,IAAaD,EAAYC,KAI5CvC,OAAOkE,OAAO3B,EAAQwB,MAAOA,GAC7B/D,OAAO4D,KAAKK,GAAYH,SAAQ,SAAUiB,GACxCxC,EAAQ4B,gBAAgBY,EAC1B,IACF,GACF,CACF,EASEC,SAAU,CAAC,kBCjFE,SAASC,EAAiBvD,GACvC,OAAOA,EAAUwD,MAAM,KAAK,EAC9B,CCHO,IAAI,EAAMC,KAAKC,IACX,EAAMD,KAAKE,IACXC,EAAQH,KAAKG,MCFT,SAASC,IACtB,IAAIC,EAASC,UAAUC,cAEvB,OAAc,MAAVF,GAAkBA,EAAOG,QAAUC,MAAMC,QAAQL,EAAOG,QACnDH,EAAOG,OAAOG,KAAI,SAAUC,GACjC,OAAOA,EAAKC,MAAQ,IAAMD,EAAKE,OACjC,IAAGC,KAAK,KAGHT,UAAUU,SACnB,CCTe,SAASC,IACtB,OAAQ,iCAAiCC,KAAKd,IAChD,CCCe,SAASe,EAAsB/D,EAASgE,EAAcC,QAC9C,IAAjBD,IACFA,GAAe,QAGO,IAApBC,IACFA,GAAkB,GAGpB,IAAIC,EAAalE,EAAQ+D,wBACrBI,EAAS,EACTC,EAAS,EAETJ,GAAgBrD,EAAcX,KAChCmE,EAASnE,EAAQqE,YAAc,GAAItB,EAAMmB,EAAWI,OAAStE,EAAQqE,aAAmB,EACxFD,EAASpE,EAAQuE,aAAe,GAAIxB,EAAMmB,EAAWM,QAAUxE,EAAQuE,cAAoB,GAG7F,IACIE,GADOhE,EAAUT,GAAWG,EAAUH,GAAWK,QAC3BoE,eAEtBC,GAAoBb,KAAsBI,EAC1CU,GAAKT,EAAW3F,MAAQmG,GAAoBD,EAAiBA,EAAeG,WAAa,IAAMT,EAC/FU,GAAKX,EAAW9B,KAAOsC,GAAoBD,EAAiBA,EAAeK,UAAY,IAAMV,EAC7FE,EAAQJ,EAAWI,MAAQH,EAC3BK,EAASN,EAAWM,OAASJ,EACjC,MAAO,CACLE,MAAOA,EACPE,OAAQA,EACRpC,IAAKyC,EACLvG,MAAOqG,EAAIL,EACXjG,OAAQwG,EAAIL,EACZjG,KAAMoG,EACNA,EAAGA,EACHE,EAAGA,EAEP,CCrCe,SAASE,EAAc/E,GACpC,IAAIkE,EAAaH,EAAsB/D,GAGnCsE,EAAQtE,EAAQqE,YAChBG,EAASxE,EAAQuE,aAUrB,OARI3B,KAAKoC,IAAId,EAAWI,MAAQA,IAAU,IACxCA,EAAQJ,EAAWI,OAGjB1B,KAAKoC,IAAId,EAAWM,OAASA,IAAW,IAC1CA,EAASN,EAAWM,QAGf,CACLG,EAAG3E,EAAQ4E,WACXC,EAAG7E,EAAQ8E,UACXR,MAAOA,EACPE,OAAQA,EAEZ,CCvBe,SAASS,EAASC,EAAQC,GACvC,IAAIC,EAAWD,EAAME,aAAeF,EAAME,cAE1C,GAAIH,EAAOD,SAASE,GAClB,OAAO,EAEJ,GAAIC,GAAYvE,EAAauE,GAAW,CACzC,IAAIE,EAAOH,EAEX,EAAG,CACD,GAAIG,GAAQJ,EAAOK,WAAWD,GAC5B,OAAO,EAITA,EAAOA,EAAKE,YAAcF,EAAKG,IACjC,OAASH,EACX,CAGF,OAAO,CACT,CCrBe,SAAS,EAAiBtF,GACvC,OAAOG,EAAUH,GAAS0F,iBAAiB1F,EAC7C,CCFe,SAAS2F,EAAe3F,GACrC,MAAO,CAAC,QAAS,KAAM,MAAM4F,QAAQ7F,EAAYC,KAAa,CAChE,CCFe,SAAS6F,EAAmB7F,GAEzC,QAASS,EAAUT,GAAWA,EAAQO,cACtCP,EAAQ8F,WAAazF,OAAOyF,UAAUC,eACxC,CCFe,SAASC,EAAchG,GACpC,MAA6B,SAAzBD,EAAYC,GACPA,EAMPA,EAAQiG,cACRjG,EAAQwF,aACR3E,EAAab,GAAWA,EAAQyF,KAAO,OAEvCI,EAAmB7F,EAGvB,CCVA,SAASkG,EAAoBlG,GAC3B,OAAKW,EAAcX,IACoB,UAAvC,EAAiBA,GAASiC,SAInBjC,EAAQmG,aAHN,IAIX,CAwCe,SAASC,EAAgBpG,GAItC,IAHA,IAAIK,EAASF,EAAUH,GACnBmG,EAAeD,EAAoBlG,GAEhCmG,GAAgBR,EAAeQ,IAA6D,WAA5C,EAAiBA,GAAclE,UACpFkE,EAAeD,EAAoBC,GAGrC,OAAIA,IAA+C,SAA9BpG,EAAYoG,IAA0D,SAA9BpG,EAAYoG,IAAwE,WAA5C,EAAiBA,GAAclE,UAC3H5B,EAGF8F,GAhDT,SAA4BnG,GAC1B,IAAIqG,EAAY,WAAWvC,KAAKd,KAGhC,GAFW,WAAWc,KAAKd,MAEfrC,EAAcX,IAII,UAFX,EAAiBA,GAEnBiC,SACb,OAAO,KAIX,IAAIqE,EAAcN,EAAchG,GAMhC,IAJIa,EAAayF,KACfA,EAAcA,EAAYb,MAGrB9E,EAAc2F,IAAgB,CAAC,OAAQ,QAAQV,QAAQ7F,EAAYuG,IAAgB,GAAG,CAC3F,IAAIC,EAAM,EAAiBD,GAI3B,GAAsB,SAAlBC,EAAIC,WAA4C,SAApBD,EAAIE,aAA0C,UAAhBF,EAAIG,UAAiF,IAA1D,CAAC,YAAa,eAAed,QAAQW,EAAII,aAAsBN,GAAgC,WAAnBE,EAAII,YAA2BN,GAAaE,EAAIK,QAAyB,SAAfL,EAAIK,OACjO,OAAON,EAEPA,EAAcA,EAAYd,UAE9B,CAEA,OAAO,IACT,CAgByBqB,CAAmB7G,IAAYK,CACxD,CCpEe,SAASyG,EAAyB3H,GAC/C,MAAO,CAAC,MAAO,UAAUyG,QAAQzG,IAAc,EAAI,IAAM,GAC3D,CCDO,SAAS4H,EAAOjE,EAAK1E,EAAOyE,GACjC,OAAO,EAAQC,EAAK,EAAQ1E,EAAOyE,GACrC,CCFe,SAASmE,EAAmBC,GACzC,OAAOxJ,OAAOkE,OAAO,CAAC,ECDf,CACLS,IAAK,EACL9D,MAAO,EACPD,OAAQ,EACRE,KAAM,GDHuC0I,EACjD,CEHe,SAASC,EAAgB9I,EAAOiD,GAC7C,OAAOA,EAAKpC,QAAO,SAAUkI,EAAS5J,GAEpC,OADA4J,EAAQ5J,GAAOa,EACR+I,CACT,GAAG,CAAC,EACN,CC4EA,SACEpG,KAAM,QACNC,SAAS,EACTC,MAAO,OACPC,GApEF,SAAeC,GACb,IAAIiG,EAEAhG,EAAQD,EAAKC,MACbL,EAAOI,EAAKJ,KACZmB,EAAUf,EAAKe,QACfmF,EAAejG,EAAME,SAASgB,MAC9BgF,EAAgBlG,EAAMmG,cAAcD,cACpCE,EAAgB9E,EAAiBtB,EAAMjC,WACvCsI,EAAOX,EAAyBU,GAEhCE,EADa,CAACnJ,EAAMD,GAAOsH,QAAQ4B,IAAkB,EAClC,SAAW,QAElC,GAAKH,GAAiBC,EAAtB,CAIA,IAAIL,EAxBgB,SAAyBU,EAASvG,GAItD,OAAO4F,EAAsC,iBAH7CW,EAA6B,mBAAZA,EAAyBA,EAAQlK,OAAOkE,OAAO,CAAC,EAAGP,EAAMwG,MAAO,CAC/EzI,UAAWiC,EAAMjC,aACbwI,GACkDA,EAAUT,EAAgBS,EAASlJ,GAC7F,CAmBsBoJ,CAAgB3F,EAAQyF,QAASvG,GACjD0G,EAAY/C,EAAcsC,GAC1BU,EAAmB,MAATN,EAAe,EAAMlJ,EAC/ByJ,EAAmB,MAATP,EAAepJ,EAASC,EAClC2J,EAAU7G,EAAMwG,MAAM7I,UAAU2I,GAAOtG,EAAMwG,MAAM7I,UAAU0I,GAAQH,EAAcG,GAAQrG,EAAMwG,MAAM9I,OAAO4I,GAC9GQ,EAAYZ,EAAcG,GAAQrG,EAAMwG,MAAM7I,UAAU0I,GACxDU,EAAoB/B,EAAgBiB,GACpCe,EAAaD,EAA6B,MAATV,EAAeU,EAAkBE,cAAgB,EAAIF,EAAkBG,aAAe,EAAI,EAC3HC,EAAoBN,EAAU,EAAIC,EAAY,EAG9CpF,EAAMmE,EAAcc,GACpBlF,EAAMuF,EAAaN,EAAUJ,GAAOT,EAAce,GAClDQ,EAASJ,EAAa,EAAIN,EAAUJ,GAAO,EAAIa,EAC/CE,EAAS1B,EAAOjE,EAAK0F,EAAQ3F,GAE7B6F,EAAWjB,EACfrG,EAAMmG,cAAcxG,KAASqG,EAAwB,CAAC,GAAyBsB,GAAYD,EAAQrB,EAAsBuB,aAAeF,EAASD,EAAQpB,EAnBzJ,CAoBF,EAkCEtF,OAhCF,SAAgBC,GACd,IAAIX,EAAQW,EAAMX,MAEdwH,EADU7G,EAAMG,QACWlC,QAC3BqH,OAAoC,IAArBuB,EAA8B,sBAAwBA,EAErD,MAAhBvB,IAKwB,iBAAjBA,IACTA,EAAejG,EAAME,SAASxC,OAAO+J,cAAcxB,MAOhDpC,EAAS7D,EAAME,SAASxC,OAAQuI,KAIrCjG,EAAME,SAASgB,MAAQ+E,EACzB,EASE5E,SAAU,CAAC,iBACXqG,iBAAkB,CAAC,oBCxFN,SAASC,EAAa5J,GACnC,OAAOA,EAAUwD,MAAM,KAAK,EAC9B,CCOA,IAAIqG,GAAa,CACf5G,IAAK,OACL9D,MAAO,OACPD,OAAQ,OACRE,KAAM,QAeD,SAAS0K,GAAYlH,GAC1B,IAAImH,EAEApK,EAASiD,EAAMjD,OACfqK,EAAapH,EAAMoH,WACnBhK,EAAY4C,EAAM5C,UAClBiK,EAAYrH,EAAMqH,UAClBC,EAAUtH,EAAMsH,QAChBpH,EAAWF,EAAME,SACjBqH,EAAkBvH,EAAMuH,gBACxBC,EAAWxH,EAAMwH,SACjBC,EAAezH,EAAMyH,aACrBC,EAAU1H,EAAM0H,QAChBC,EAAaL,EAAQ1E,EACrBA,OAAmB,IAAf+E,EAAwB,EAAIA,EAChCC,EAAaN,EAAQxE,EACrBA,OAAmB,IAAf8E,EAAwB,EAAIA,EAEhCC,EAAgC,mBAAjBJ,EAA8BA,EAAa,CAC5D7E,EAAGA,EACHE,IACG,CACHF,EAAGA,EACHE,GAGFF,EAAIiF,EAAMjF,EACVE,EAAI+E,EAAM/E,EACV,IAAIgF,EAAOR,EAAQrL,eAAe,KAC9B8L,EAAOT,EAAQrL,eAAe,KAC9B+L,EAAQxL,EACRyL,EAAQ,EACRC,EAAM5J,OAEV,GAAIkJ,EAAU,CACZ,IAAIpD,EAAeC,EAAgBtH,GAC/BoL,EAAa,eACbC,EAAY,cAEZhE,IAAiBhG,EAAUrB,IAGmB,WAA5C,EAFJqH,EAAeN,EAAmB/G,IAECmD,UAAsC,aAAbA,IAC1DiI,EAAa,eACbC,EAAY,gBAOZhL,IAAc,IAAQA,IAAcZ,GAAQY,IAAcb,IAAU8K,IAAczK,KACpFqL,EAAQ3L,EAGRwG,IAFc4E,GAAWtD,IAAiB8D,GAAOA,EAAIxF,eAAiBwF,EAAIxF,eAAeD,OACzF2B,EAAa+D,IACEf,EAAW3E,OAC1BK,GAAKyE,EAAkB,GAAK,GAG1BnK,IAAcZ,IAASY,IAAc,GAAOA,IAAcd,GAAW+K,IAAczK,KACrFoL,EAAQzL,EAGRqG,IAFc8E,GAAWtD,IAAiB8D,GAAOA,EAAIxF,eAAiBwF,EAAIxF,eAAeH,MACzF6B,EAAagE,IACEhB,EAAW7E,MAC1BK,GAAK2E,EAAkB,GAAK,EAEhC,CAEA,IAgBMc,EAhBFC,EAAe5M,OAAOkE,OAAO,CAC/BM,SAAUA,GACTsH,GAAYP,IAEXsB,GAAyB,IAAjBd,EAlFd,SAA2BrI,EAAM8I,GAC/B,IAAItF,EAAIxD,EAAKwD,EACTE,EAAI1D,EAAK0D,EACT0F,EAAMN,EAAIO,kBAAoB,EAClC,MAAO,CACL7F,EAAG5B,EAAM4B,EAAI4F,GAAOA,GAAO,EAC3B1F,EAAG9B,EAAM8B,EAAI0F,GAAOA,GAAO,EAE/B,CA0EsCE,CAAkB,CACpD9F,EAAGA,EACHE,GACC1E,EAAUrB,IAAW,CACtB6F,EAAGA,EACHE,GAMF,OAHAF,EAAI2F,EAAM3F,EACVE,EAAIyF,EAAMzF,EAENyE,EAGK7L,OAAOkE,OAAO,CAAC,EAAG0I,IAAeD,EAAiB,CAAC,GAAkBJ,GAASF,EAAO,IAAM,GAAIM,EAAeL,GAASF,EAAO,IAAM,GAAIO,EAAe5D,WAAayD,EAAIO,kBAAoB,IAAM,EAAI,aAAe7F,EAAI,OAASE,EAAI,MAAQ,eAAiBF,EAAI,OAASE,EAAI,SAAUuF,IAG5R3M,OAAOkE,OAAO,CAAC,EAAG0I,IAAenB,EAAkB,CAAC,GAAmBc,GAASF,EAAOjF,EAAI,KAAO,GAAIqE,EAAgBa,GAASF,EAAOlF,EAAI,KAAO,GAAIuE,EAAgB1C,UAAY,GAAI0C,GAC9L,CA4CA,UACEnI,KAAM,gBACNC,SAAS,EACTC,MAAO,cACPC,GA9CF,SAAuBwJ,GACrB,IAAItJ,EAAQsJ,EAAMtJ,MACdc,EAAUwI,EAAMxI,QAChByI,EAAwBzI,EAAQoH,gBAChCA,OAA4C,IAA1BqB,GAA0CA,EAC5DC,EAAoB1I,EAAQqH,SAC5BA,OAAiC,IAAtBqB,GAAsCA,EACjDC,EAAwB3I,EAAQsH,aAChCA,OAAyC,IAA1BqB,GAA0CA,EACzDR,EAAe,CACjBlL,UAAWuD,EAAiBtB,EAAMjC,WAClCiK,UAAWL,EAAa3H,EAAMjC,WAC9BL,OAAQsC,EAAME,SAASxC,OACvBqK,WAAY/H,EAAMwG,MAAM9I,OACxBwK,gBAAiBA,EACjBG,QAAoC,UAA3BrI,EAAMc,QAAQC,UAGgB,MAArCf,EAAMmG,cAAcD,gBACtBlG,EAAMK,OAAO3C,OAASrB,OAAOkE,OAAO,CAAC,EAAGP,EAAMK,OAAO3C,OAAQmK,GAAYxL,OAAOkE,OAAO,CAAC,EAAG0I,EAAc,CACvGhB,QAASjI,EAAMmG,cAAcD,cAC7BrF,SAAUb,EAAMc,QAAQC,SACxBoH,SAAUA,EACVC,aAAcA,OAIe,MAA7BpI,EAAMmG,cAAcjF,QACtBlB,EAAMK,OAAOa,MAAQ7E,OAAOkE,OAAO,CAAC,EAAGP,EAAMK,OAAOa,MAAO2G,GAAYxL,OAAOkE,OAAO,CAAC,EAAG0I,EAAc,CACrGhB,QAASjI,EAAMmG,cAAcjF,MAC7BL,SAAU,WACVsH,UAAU,EACVC,aAAcA,OAIlBpI,EAAMM,WAAW5C,OAASrB,OAAOkE,OAAO,CAAC,EAAGP,EAAMM,WAAW5C,OAAQ,CACnE,wBAAyBsC,EAAMjC,WAEnC,EAQE2L,KAAM,CAAC,GCrKT,IAAIC,GAAU,CACZA,SAAS,GAsCX,UACEhK,KAAM,iBACNC,SAAS,EACTC,MAAO,QACPC,GAAI,WAAe,EACnBY,OAxCF,SAAgBX,GACd,IAAIC,EAAQD,EAAKC,MACb4J,EAAW7J,EAAK6J,SAChB9I,EAAUf,EAAKe,QACf+I,EAAkB/I,EAAQgJ,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7CE,EAAkBjJ,EAAQkJ,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7C9K,EAASF,EAAUiB,EAAME,SAASxC,QAClCuM,EAAgB,GAAGjM,OAAOgC,EAAMiK,cAActM,UAAWqC,EAAMiK,cAAcvM,QAYjF,OAVIoM,GACFG,EAAc9J,SAAQ,SAAU+J,GAC9BA,EAAaC,iBAAiB,SAAUP,EAASQ,OAAQT,GAC3D,IAGEK,GACF/K,EAAOkL,iBAAiB,SAAUP,EAASQ,OAAQT,IAG9C,WACDG,GACFG,EAAc9J,SAAQ,SAAU+J,GAC9BA,EAAaG,oBAAoB,SAAUT,EAASQ,OAAQT,GAC9D,IAGEK,GACF/K,EAAOoL,oBAAoB,SAAUT,EAASQ,OAAQT,GAE1D,CACF,EASED,KAAM,CAAC,GC/CT,IAAIY,GAAO,CACTnN,KAAM,QACND,MAAO,OACPD,OAAQ,MACR+D,IAAK,UAEQ,SAASuJ,GAAqBxM,GAC3C,OAAOA,EAAUyM,QAAQ,0BAA0B,SAAUC,GAC3D,OAAOH,GAAKG,EACd,GACF,CCVA,IAAI,GAAO,CACTnN,MAAO,MACPC,IAAK,SAEQ,SAASmN,GAA8B3M,GACpD,OAAOA,EAAUyM,QAAQ,cAAc,SAAUC,GAC/C,OAAO,GAAKA,EACd,GACF,CCPe,SAASE,GAAgB3L,GACtC,IAAI6J,EAAM9J,EAAUC,GAGpB,MAAO,CACL4L,WAHe/B,EAAIgC,YAInBC,UAHcjC,EAAIkC,YAKtB,CCNe,SAASC,GAAoBpM,GAQ1C,OAAO+D,EAAsB8B,EAAmB7F,IAAUzB,KAAOwN,GAAgB/L,GAASgM,UAC5F,CCXe,SAASK,GAAerM,GAErC,IAAIsM,EAAoB,EAAiBtM,GACrCuM,EAAWD,EAAkBC,SAC7BC,EAAYF,EAAkBE,UAC9BC,EAAYH,EAAkBG,UAElC,MAAO,6BAA6B3I,KAAKyI,EAAWE,EAAYD,EAClE,CCLe,SAASE,GAAgBtM,GACtC,MAAI,CAAC,OAAQ,OAAQ,aAAawF,QAAQ7F,EAAYK,KAAU,EAEvDA,EAAKG,cAAcoM,KAGxBhM,EAAcP,IAASiM,GAAejM,GACjCA,EAGFsM,GAAgB1G,EAAc5F,GACvC,CCJe,SAASwM,GAAkB5M,EAAS6M,GACjD,IAAIC,OAES,IAATD,IACFA,EAAO,IAGT,IAAIvB,EAAeoB,GAAgB1M,GAC/B+M,EAASzB,KAAqE,OAAlDwB,EAAwB9M,EAAQO,oBAAyB,EAASuM,EAAsBH,MACpH1C,EAAM9J,EAAUmL,GAChB0B,EAASD,EAAS,CAAC9C,GAAK7K,OAAO6K,EAAIxF,gBAAkB,GAAI4H,GAAef,GAAgBA,EAAe,IAAMA,EAC7G2B,EAAcJ,EAAKzN,OAAO4N,GAC9B,OAAOD,EAASE,EAChBA,EAAY7N,OAAOwN,GAAkB5G,EAAcgH,IACrD,CCzBe,SAASE,GAAiBC,GACvC,OAAO1P,OAAOkE,OAAO,CAAC,EAAGwL,EAAM,CAC7B5O,KAAM4O,EAAKxI,EACXvC,IAAK+K,EAAKtI,EACVvG,MAAO6O,EAAKxI,EAAIwI,EAAK7I,MACrBjG,OAAQ8O,EAAKtI,EAAIsI,EAAK3I,QAE1B,CCqBA,SAAS4I,GAA2BpN,EAASqN,EAAgBlL,GAC3D,OAAOkL,IAAmBxO,EAAWqO,GCzBxB,SAAyBlN,EAASmC,GAC/C,IAAI8H,EAAM9J,EAAUH,GAChBsN,EAAOzH,EAAmB7F,GAC1ByE,EAAiBwF,EAAIxF,eACrBH,EAAQgJ,EAAKhF,YACb9D,EAAS8I,EAAKjF,aACd1D,EAAI,EACJE,EAAI,EAER,GAAIJ,EAAgB,CAClBH,EAAQG,EAAeH,MACvBE,EAASC,EAAeD,OACxB,IAAI+I,EAAiB1J,KAEjB0J,IAAmBA,GAA+B,UAAbpL,KACvCwC,EAAIF,EAAeG,WACnBC,EAAIJ,EAAeK,UAEvB,CAEA,MAAO,CACLR,MAAOA,EACPE,OAAQA,EACRG,EAAGA,EAAIyH,GAAoBpM,GAC3B6E,EAAGA,EAEP,CDDwD2I,CAAgBxN,EAASmC,IAAa1B,EAAU4M,GAdxG,SAAoCrN,EAASmC,GAC3C,IAAIgL,EAAOpJ,EAAsB/D,GAAS,EAAoB,UAAbmC,GASjD,OARAgL,EAAK/K,IAAM+K,EAAK/K,IAAMpC,EAAQyN,UAC9BN,EAAK5O,KAAO4O,EAAK5O,KAAOyB,EAAQ0N,WAChCP,EAAK9O,OAAS8O,EAAK/K,IAAMpC,EAAQqI,aACjC8E,EAAK7O,MAAQ6O,EAAK5O,KAAOyB,EAAQsI,YACjC6E,EAAK7I,MAAQtE,EAAQsI,YACrB6E,EAAK3I,OAASxE,EAAQqI,aACtB8E,EAAKxI,EAAIwI,EAAK5O,KACd4O,EAAKtI,EAAIsI,EAAK/K,IACP+K,CACT,CAG0HQ,CAA2BN,EAAgBlL,GAAY+K,GEtBlK,SAAyBlN,GACtC,IAAI8M,EAEAQ,EAAOzH,EAAmB7F,GAC1B4N,EAAY7B,GAAgB/L,GAC5B2M,EAA0D,OAAlDG,EAAwB9M,EAAQO,oBAAyB,EAASuM,EAAsBH,KAChGrI,EAAQ,EAAIgJ,EAAKO,YAAaP,EAAKhF,YAAaqE,EAAOA,EAAKkB,YAAc,EAAGlB,EAAOA,EAAKrE,YAAc,GACvG9D,EAAS,EAAI8I,EAAKQ,aAAcR,EAAKjF,aAAcsE,EAAOA,EAAKmB,aAAe,EAAGnB,EAAOA,EAAKtE,aAAe,GAC5G1D,GAAKiJ,EAAU5B,WAAaI,GAAoBpM,GAChD6E,GAAK+I,EAAU1B,UAMnB,MAJiD,QAA7C,EAAiBS,GAAQW,GAAMS,YACjCpJ,GAAK,EAAI2I,EAAKhF,YAAaqE,EAAOA,EAAKrE,YAAc,GAAKhE,GAGrD,CACLA,MAAOA,EACPE,OAAQA,EACRG,EAAGA,EACHE,EAAGA,EAEP,CFCkMmJ,CAAgBnI,EAAmB7F,IACrO,CG1Be,SAASiO,GAAe9M,GACrC,IAOIkI,EAPAtK,EAAYoC,EAAKpC,UACjBiB,EAAUmB,EAAKnB,QACfb,EAAYgC,EAAKhC,UACjBqI,EAAgBrI,EAAYuD,EAAiBvD,GAAa,KAC1DiK,EAAYjK,EAAY4J,EAAa5J,GAAa,KAClD+O,EAAUnP,EAAU4F,EAAI5F,EAAUuF,MAAQ,EAAItE,EAAQsE,MAAQ,EAC9D6J,EAAUpP,EAAU8F,EAAI9F,EAAUyF,OAAS,EAAIxE,EAAQwE,OAAS,EAGpE,OAAQgD,GACN,KAAK,EACH6B,EAAU,CACR1E,EAAGuJ,EACHrJ,EAAG9F,EAAU8F,EAAI7E,EAAQwE,QAE3B,MAEF,KAAKnG,EACHgL,EAAU,CACR1E,EAAGuJ,EACHrJ,EAAG9F,EAAU8F,EAAI9F,EAAUyF,QAE7B,MAEF,KAAKlG,EACH+K,EAAU,CACR1E,EAAG5F,EAAU4F,EAAI5F,EAAUuF,MAC3BO,EAAGsJ,GAEL,MAEF,KAAK5P,EACH8K,EAAU,CACR1E,EAAG5F,EAAU4F,EAAI3E,EAAQsE,MACzBO,EAAGsJ,GAEL,MAEF,QACE9E,EAAU,CACR1E,EAAG5F,EAAU4F,EACbE,EAAG9F,EAAU8F,GAInB,IAAIuJ,EAAW5G,EAAgBV,EAAyBU,GAAiB,KAEzE,GAAgB,MAAZ4G,EAAkB,CACpB,IAAI1G,EAAmB,MAAb0G,EAAmB,SAAW,QAExC,OAAQhF,GACN,KAAK1K,EACH2K,EAAQ+E,GAAY/E,EAAQ+E,IAAarP,EAAU2I,GAAO,EAAI1H,EAAQ0H,GAAO,GAC7E,MAEF,KAAK/I,EACH0K,EAAQ+E,GAAY/E,EAAQ+E,IAAarP,EAAU2I,GAAO,EAAI1H,EAAQ0H,GAAO,GAKnF,CAEA,OAAO2B,CACT,CC3De,SAASgF,GAAejN,EAAOc,QAC5B,IAAZA,IACFA,EAAU,CAAC,GAGb,IAAIoM,EAAWpM,EACXqM,EAAqBD,EAASnP,UAC9BA,OAAmC,IAAvBoP,EAAgCnN,EAAMjC,UAAYoP,EAC9DC,EAAoBF,EAASnM,SAC7BA,OAAiC,IAAtBqM,EAA+BpN,EAAMe,SAAWqM,EAC3DC,EAAoBH,EAASI,SAC7BA,OAAiC,IAAtBD,EAA+B7P,EAAkB6P,EAC5DE,EAAwBL,EAASM,aACjCA,OAAyC,IAA1BD,EAAmC9P,EAAW8P,EAC7DE,EAAwBP,EAASQ,eACjCA,OAA2C,IAA1BD,EAAmC/P,EAAS+P,EAC7DE,EAAuBT,EAASU,YAChCA,OAAuC,IAAzBD,GAA0CA,EACxDE,EAAmBX,EAAS3G,QAC5BA,OAA+B,IAArBsH,EAA8B,EAAIA,EAC5ChI,EAAgBD,EAAsC,iBAAZW,EAAuBA,EAAUT,EAAgBS,EAASlJ,IACpGyQ,EAAaJ,IAAmBhQ,EAASC,EAAYD,EACrDqK,EAAa/H,EAAMwG,MAAM9I,OACzBkB,EAAUoB,EAAME,SAAS0N,EAAcE,EAAaJ,GACpDK,EJkBS,SAAyBnP,EAAS0O,EAAUE,EAAczM,GACvE,IAAIiN,EAAmC,oBAAbV,EAlB5B,SAA4B1O,GAC1B,IAAIpB,EAAkBgO,GAAkB5G,EAAchG,IAElDqP,EADoB,CAAC,WAAY,SAASzJ,QAAQ,EAAiB5F,GAASiC,WAAa,GACnDtB,EAAcX,GAAWoG,EAAgBpG,GAAWA,EAE9F,OAAKS,EAAU4O,GAKRzQ,EAAgBgI,QAAO,SAAUyG,GACtC,OAAO5M,EAAU4M,IAAmBpI,EAASoI,EAAgBgC,IAAmD,SAAhCtP,EAAYsN,EAC9F,IANS,EAOX,CAK6DiC,CAAmBtP,GAAW,GAAGZ,OAAOsP,GAC/F9P,EAAkB,GAAGQ,OAAOgQ,EAAqB,CAACR,IAClDW,EAAsB3Q,EAAgB,GACtC4Q,EAAe5Q,EAAgBK,QAAO,SAAUwQ,EAASpC,GAC3D,IAAIF,EAAOC,GAA2BpN,EAASqN,EAAgBlL,GAK/D,OAJAsN,EAAQrN,IAAM,EAAI+K,EAAK/K,IAAKqN,EAAQrN,KACpCqN,EAAQnR,MAAQ,EAAI6O,EAAK7O,MAAOmR,EAAQnR,OACxCmR,EAAQpR,OAAS,EAAI8O,EAAK9O,OAAQoR,EAAQpR,QAC1CoR,EAAQlR,KAAO,EAAI4O,EAAK5O,KAAMkR,EAAQlR,MAC/BkR,CACT,GAAGrC,GAA2BpN,EAASuP,EAAqBpN,IAK5D,OAJAqN,EAAalL,MAAQkL,EAAalR,MAAQkR,EAAajR,KACvDiR,EAAahL,OAASgL,EAAanR,OAASmR,EAAapN,IACzDoN,EAAa7K,EAAI6K,EAAajR,KAC9BiR,EAAa3K,EAAI2K,EAAapN,IACvBoN,CACT,CInC2BE,CAAgBjP,EAAUT,GAAWA,EAAUA,EAAQ2P,gBAAkB9J,EAAmBzE,EAAME,SAASxC,QAAS4P,EAAUE,EAAczM,GACjKyN,EAAsB7L,EAAsB3C,EAAME,SAASvC,WAC3DuI,EAAgB2G,GAAe,CACjClP,UAAW6Q,EACX5P,QAASmJ,EACThH,SAAU,WACVhD,UAAWA,IAET0Q,EAAmB3C,GAAiBzP,OAAOkE,OAAO,CAAC,EAAGwH,EAAY7B,IAClEwI,EAAoBhB,IAAmBhQ,EAAS+Q,EAAmBD,EAGnEG,EAAkB,CACpB3N,IAAK+M,EAAmB/M,IAAM0N,EAAkB1N,IAAM6E,EAAc7E,IACpE/D,OAAQyR,EAAkBzR,OAAS8Q,EAAmB9Q,OAAS4I,EAAc5I,OAC7EE,KAAM4Q,EAAmB5Q,KAAOuR,EAAkBvR,KAAO0I,EAAc1I,KACvED,MAAOwR,EAAkBxR,MAAQ6Q,EAAmB7Q,MAAQ2I,EAAc3I,OAExE0R,EAAa5O,EAAMmG,cAAckB,OAErC,GAAIqG,IAAmBhQ,GAAUkR,EAAY,CAC3C,IAAIvH,EAASuH,EAAW7Q,GACxB1B,OAAO4D,KAAK0O,GAAiBxO,SAAQ,SAAUhE,GAC7C,IAAI0S,EAAW,CAAC3R,EAAOD,GAAQuH,QAAQrI,IAAQ,EAAI,GAAK,EACpDkK,EAAO,CAAC,EAAKpJ,GAAQuH,QAAQrI,IAAQ,EAAI,IAAM,IACnDwS,EAAgBxS,IAAQkL,EAAOhB,GAAQwI,CACzC,GACF,CAEA,OAAOF,CACT,CCyEA,UACEhP,KAAM,OACNC,SAAS,EACTC,MAAO,OACPC,GA5HF,SAAcC,GACZ,IAAIC,EAAQD,EAAKC,MACbc,EAAUf,EAAKe,QACfnB,EAAOI,EAAKJ,KAEhB,IAAIK,EAAMmG,cAAcxG,GAAMmP,MAA9B,CAoCA,IAhCA,IAAIC,EAAoBjO,EAAQkM,SAC5BgC,OAAsC,IAAtBD,GAAsCA,EACtDE,EAAmBnO,EAAQoO,QAC3BC,OAAoC,IAArBF,GAAqCA,EACpDG,EAA8BtO,EAAQuO,mBACtC9I,EAAUzF,EAAQyF,QAClB+G,EAAWxM,EAAQwM,SACnBE,EAAe1M,EAAQ0M,aACvBI,EAAc9M,EAAQ8M,YACtB0B,EAAwBxO,EAAQyO,eAChCA,OAA2C,IAA1BD,GAA0CA,EAC3DE,EAAwB1O,EAAQ0O,sBAChCC,EAAqBzP,EAAMc,QAAQ/C,UACnCqI,EAAgB9E,EAAiBmO,GAEjCJ,EAAqBD,IADHhJ,IAAkBqJ,GACqCF,EAjC/E,SAAuCxR,GACrC,GAAIuD,EAAiBvD,KAAeX,EAClC,MAAO,GAGT,IAAIsS,EAAoBnF,GAAqBxM,GAC7C,MAAO,CAAC2M,GAA8B3M,GAAY2R,EAAmBhF,GAA8BgF,GACrG,CA0B6IC,CAA8BF,GAA3E,CAAClF,GAAqBkF,KAChHG,EAAa,CAACH,GAAoBzR,OAAOqR,GAAoBxR,QAAO,SAAUC,EAAKC,GACrF,OAAOD,EAAIE,OAAOsD,EAAiBvD,KAAeX,ECvCvC,SAA8B4C,EAAOc,QAClC,IAAZA,IACFA,EAAU,CAAC,GAGb,IAAIoM,EAAWpM,EACX/C,EAAYmP,EAASnP,UACrBuP,EAAWJ,EAASI,SACpBE,EAAeN,EAASM,aACxBjH,EAAU2G,EAAS3G,QACnBgJ,EAAiBrC,EAASqC,eAC1BM,EAAwB3C,EAASsC,sBACjCA,OAAkD,IAA1BK,EAAmC,EAAgBA,EAC3E7H,EAAYL,EAAa5J,GACzB6R,EAAa5H,EAAYuH,EAAiB3R,EAAsBA,EAAoB4H,QAAO,SAAUzH,GACvG,OAAO4J,EAAa5J,KAAeiK,CACrC,IAAK3K,EACDyS,EAAoBF,EAAWpK,QAAO,SAAUzH,GAClD,OAAOyR,EAAsBhL,QAAQzG,IAAc,CACrD,IAEiC,IAA7B+R,EAAkBC,SACpBD,EAAoBF,GAItB,IAAII,EAAYF,EAAkBjS,QAAO,SAAUC,EAAKC,GAOtD,OANAD,EAAIC,GAAakP,GAAejN,EAAO,CACrCjC,UAAWA,EACXuP,SAAUA,EACVE,aAAcA,EACdjH,QAASA,IACRjF,EAAiBvD,IACbD,CACT,GAAG,CAAC,GACJ,OAAOzB,OAAO4D,KAAK+P,GAAWC,MAAK,SAAUC,EAAGC,GAC9C,OAAOH,EAAUE,GAAKF,EAAUG,EAClC,GACF,CDC6DC,CAAqBpQ,EAAO,CACnFjC,UAAWA,EACXuP,SAAUA,EACVE,aAAcA,EACdjH,QAASA,EACTgJ,eAAgBA,EAChBC,sBAAuBA,IACpBzR,EACP,GAAG,IACCsS,EAAgBrQ,EAAMwG,MAAM7I,UAC5BoK,EAAa/H,EAAMwG,MAAM9I,OACzB4S,EAAY,IAAIC,IAChBC,GAAqB,EACrBC,EAAwBb,EAAW,GAE9Bc,EAAI,EAAGA,EAAId,EAAWG,OAAQW,IAAK,CAC1C,IAAI3S,EAAY6R,EAAWc,GAEvBC,EAAiBrP,EAAiBvD,GAElC6S,EAAmBjJ,EAAa5J,KAAeT,EAC/CuT,EAAa,CAAC,EAAK5T,GAAQuH,QAAQmM,IAAmB,EACtDrK,EAAMuK,EAAa,QAAU,SAC7B1F,EAAW8B,GAAejN,EAAO,CACnCjC,UAAWA,EACXuP,SAAUA,EACVE,aAAcA,EACdI,YAAaA,EACbrH,QAASA,IAEPuK,EAAoBD,EAAaD,EAAmB1T,EAAQC,EAAOyT,EAAmB3T,EAAS,EAE/FoT,EAAc/J,GAAOyB,EAAWzB,KAClCwK,EAAoBvG,GAAqBuG,IAG3C,IAAIC,EAAmBxG,GAAqBuG,GACxCE,EAAS,GAUb,GARIhC,GACFgC,EAAOC,KAAK9F,EAASwF,IAAmB,GAGtCxB,GACF6B,EAAOC,KAAK9F,EAAS2F,IAAsB,EAAG3F,EAAS4F,IAAqB,GAG1EC,EAAOE,OAAM,SAAUC,GACzB,OAAOA,CACT,IAAI,CACFV,EAAwB1S,EACxByS,GAAqB,EACrB,KACF,CAEAF,EAAUc,IAAIrT,EAAWiT,EAC3B,CAEA,GAAIR,EAqBF,IAnBA,IAEIa,EAAQ,SAAeC,GACzB,IAAIC,EAAmB3B,EAAW4B,MAAK,SAAUzT,GAC/C,IAAIiT,EAASV,EAAU9T,IAAIuB,GAE3B,GAAIiT,EACF,OAAOA,EAAOS,MAAM,EAAGH,GAAIJ,OAAM,SAAUC,GACzC,OAAOA,CACT,GAEJ,IAEA,GAAII,EAEF,OADAd,EAAwBc,EACjB,OAEX,EAESD,EAnBY/B,EAAiB,EAAI,EAmBZ+B,EAAK,GAGpB,UAFFD,EAAMC,GADmBA,KAOpCtR,EAAMjC,YAAc0S,IACtBzQ,EAAMmG,cAAcxG,GAAMmP,OAAQ,EAClC9O,EAAMjC,UAAY0S,EAClBzQ,EAAM0R,OAAQ,EA5GhB,CA8GF,EAQEhK,iBAAkB,CAAC,UACnBgC,KAAM,CACJoF,OAAO,IE7IX,SAAS6C,GAAexG,EAAUY,EAAM6F,GAQtC,YAPyB,IAArBA,IACFA,EAAmB,CACjBrO,EAAG,EACHE,EAAG,IAIA,CACLzC,IAAKmK,EAASnK,IAAM+K,EAAK3I,OAASwO,EAAiBnO,EACnDvG,MAAOiO,EAASjO,MAAQ6O,EAAK7I,MAAQ0O,EAAiBrO,EACtDtG,OAAQkO,EAASlO,OAAS8O,EAAK3I,OAASwO,EAAiBnO,EACzDtG,KAAMgO,EAAShO,KAAO4O,EAAK7I,MAAQ0O,EAAiBrO,EAExD,CAEA,SAASsO,GAAsB1G,GAC7B,MAAO,CAAC,EAAKjO,EAAOD,EAAQE,GAAM2U,MAAK,SAAUC,GAC/C,OAAO5G,EAAS4G,IAAS,CAC3B,GACF,CA+BA,UACEpS,KAAM,OACNC,SAAS,EACTC,MAAO,OACP6H,iBAAkB,CAAC,mBACnB5H,GAlCF,SAAcC,GACZ,IAAIC,EAAQD,EAAKC,MACbL,EAAOI,EAAKJ,KACZ0Q,EAAgBrQ,EAAMwG,MAAM7I,UAC5BoK,EAAa/H,EAAMwG,MAAM9I,OACzBkU,EAAmB5R,EAAMmG,cAAc6L,gBACvCC,EAAoBhF,GAAejN,EAAO,CAC5C0N,eAAgB,cAEdwE,EAAoBjF,GAAejN,EAAO,CAC5C4N,aAAa,IAEXuE,EAA2BR,GAAeM,EAAmB5B,GAC7D+B,EAAsBT,GAAeO,EAAmBnK,EAAY6J,GACpES,EAAoBR,GAAsBM,GAC1CG,EAAmBT,GAAsBO,GAC7CpS,EAAMmG,cAAcxG,GAAQ,CAC1BwS,yBAA0BA,EAC1BC,oBAAqBA,EACrBC,kBAAmBA,EACnBC,iBAAkBA,GAEpBtS,EAAMM,WAAW5C,OAASrB,OAAOkE,OAAO,CAAC,EAAGP,EAAMM,WAAW5C,OAAQ,CACnE,+BAAgC2U,EAChC,sBAAuBC,GAE3B,GCJA,IACE3S,KAAM,SACNC,SAAS,EACTC,MAAO,OACPwB,SAAU,CAAC,iBACXvB,GA5BF,SAAgBa,GACd,IAAIX,EAAQW,EAAMX,MACdc,EAAUH,EAAMG,QAChBnB,EAAOgB,EAAMhB,KACb4S,EAAkBzR,EAAQuG,OAC1BA,OAA6B,IAApBkL,EAA6B,CAAC,EAAG,GAAKA,EAC/C7I,EAAO,EAAW7L,QAAO,SAAUC,EAAKC,GAE1C,OADAD,EAAIC,GA5BD,SAAiCA,EAAWyI,EAAOa,GACxD,IAAIjB,EAAgB9E,EAAiBvD,GACjCyU,EAAiB,CAACrV,EAAM,GAAKqH,QAAQ4B,IAAkB,GAAK,EAAI,EAEhErG,EAAyB,mBAAXsH,EAAwBA,EAAOhL,OAAOkE,OAAO,CAAC,EAAGiG,EAAO,CACxEzI,UAAWA,KACPsJ,EACFoL,EAAW1S,EAAK,GAChB2S,EAAW3S,EAAK,GAIpB,OAFA0S,EAAWA,GAAY,EACvBC,GAAYA,GAAY,GAAKF,EACtB,CAACrV,EAAMD,GAAOsH,QAAQ4B,IAAkB,EAAI,CACjD7C,EAAGmP,EACHjP,EAAGgP,GACD,CACFlP,EAAGkP,EACHhP,EAAGiP,EAEP,CASqBC,CAAwB5U,EAAWiC,EAAMwG,MAAOa,GAC1DvJ,CACT,GAAG,CAAC,GACA8U,EAAwBlJ,EAAK1J,EAAMjC,WACnCwF,EAAIqP,EAAsBrP,EAC1BE,EAAImP,EAAsBnP,EAEW,MAArCzD,EAAMmG,cAAcD,gBACtBlG,EAAMmG,cAAcD,cAAc3C,GAAKA,EACvCvD,EAAMmG,cAAcD,cAAczC,GAAKA,GAGzCzD,EAAMmG,cAAcxG,GAAQ+J,CAC9B,GC1BA,IACE/J,KAAM,gBACNC,SAAS,EACTC,MAAO,OACPC,GApBF,SAAuBC,GACrB,IAAIC,EAAQD,EAAKC,MACbL,EAAOI,EAAKJ,KAKhBK,EAAMmG,cAAcxG,GAAQkN,GAAe,CACzClP,UAAWqC,EAAMwG,MAAM7I,UACvBiB,QAASoB,EAAMwG,MAAM9I,OACrBqD,SAAU,WACVhD,UAAWiC,EAAMjC,WAErB,EAQE2L,KAAM,CAAC,GCgHT,IACE/J,KAAM,kBACNC,SAAS,EACTC,MAAO,OACPC,GA/HF,SAAyBC,GACvB,IAAIC,EAAQD,EAAKC,MACbc,EAAUf,EAAKe,QACfnB,EAAOI,EAAKJ,KACZoP,EAAoBjO,EAAQkM,SAC5BgC,OAAsC,IAAtBD,GAAsCA,EACtDE,EAAmBnO,EAAQoO,QAC3BC,OAAoC,IAArBF,GAAsCA,EACrD3B,EAAWxM,EAAQwM,SACnBE,EAAe1M,EAAQ0M,aACvBI,EAAc9M,EAAQ8M,YACtBrH,EAAUzF,EAAQyF,QAClBsM,EAAkB/R,EAAQgS,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7CE,EAAwBjS,EAAQkS,aAChCA,OAAyC,IAA1BD,EAAmC,EAAIA,EACtD5H,EAAW8B,GAAejN,EAAO,CACnCsN,SAAUA,EACVE,aAAcA,EACdjH,QAASA,EACTqH,YAAaA,IAEXxH,EAAgB9E,EAAiBtB,EAAMjC,WACvCiK,EAAYL,EAAa3H,EAAMjC,WAC/BkV,GAAmBjL,EACnBgF,EAAWtH,EAAyBU,GACpC8I,ECrCY,MDqCSlC,ECrCH,IAAM,IDsCxB9G,EAAgBlG,EAAMmG,cAAcD,cACpCmK,EAAgBrQ,EAAMwG,MAAM7I,UAC5BoK,EAAa/H,EAAMwG,MAAM9I,OACzBwV,EAA4C,mBAAjBF,EAA8BA,EAAa3W,OAAOkE,OAAO,CAAC,EAAGP,EAAMwG,MAAO,CACvGzI,UAAWiC,EAAMjC,aACbiV,EACFG,EAA2D,iBAAtBD,EAAiC,CACxElG,SAAUkG,EACVhE,QAASgE,GACP7W,OAAOkE,OAAO,CAChByM,SAAU,EACVkC,QAAS,GACRgE,GACCE,EAAsBpT,EAAMmG,cAAckB,OAASrH,EAAMmG,cAAckB,OAAOrH,EAAMjC,WAAa,KACjG2L,EAAO,CACTnG,EAAG,EACHE,EAAG,GAGL,GAAKyC,EAAL,CAIA,GAAI8I,EAAe,CACjB,IAAIqE,EAEAC,EAAwB,MAAbtG,EAAmB,EAAM7P,EACpCoW,EAAuB,MAAbvG,EAAmB/P,EAASC,EACtCoJ,EAAmB,MAAb0G,EAAmB,SAAW,QACpC3F,EAASnB,EAAc8G,GACvBtL,EAAM2F,EAAS8D,EAASmI,GACxB7R,EAAM4F,EAAS8D,EAASoI,GACxBC,EAAWV,GAAU/K,EAAWzB,GAAO,EAAI,EAC3CmN,EAASzL,IAAc1K,EAAQ+S,EAAc/J,GAAOyB,EAAWzB,GAC/DoN,EAAS1L,IAAc1K,GAASyK,EAAWzB,IAAQ+J,EAAc/J,GAGjEL,EAAejG,EAAME,SAASgB,MAC9BwF,EAAYoM,GAAU7M,EAAetC,EAAcsC,GAAgB,CACrE/C,MAAO,EACPE,OAAQ,GAENuQ,GAAqB3T,EAAMmG,cAAc,oBAAsBnG,EAAMmG,cAAc,oBAAoBI,QxBhFtG,CACLvF,IAAK,EACL9D,MAAO,EACPD,OAAQ,EACRE,KAAM,GwB6EFyW,GAAkBD,GAAmBL,GACrCO,GAAkBF,GAAmBJ,GAMrCO,GAAWnO,EAAO,EAAG0K,EAAc/J,GAAMI,EAAUJ,IACnDyN,GAAYd,EAAkB5C,EAAc/J,GAAO,EAAIkN,EAAWM,GAAWF,GAAkBT,EAA4BnG,SAAWyG,EAASK,GAAWF,GAAkBT,EAA4BnG,SACxMgH,GAAYf,GAAmB5C,EAAc/J,GAAO,EAAIkN,EAAWM,GAAWD,GAAkBV,EAA4BnG,SAAW0G,EAASI,GAAWD,GAAkBV,EAA4BnG,SACzMjG,GAAoB/G,EAAME,SAASgB,OAAS8D,EAAgBhF,EAAME,SAASgB,OAC3E+S,GAAelN,GAAiC,MAAbiG,EAAmBjG,GAAkBsF,WAAa,EAAItF,GAAkBuF,YAAc,EAAI,EAC7H4H,GAAwH,OAAjGb,EAA+C,MAAvBD,OAA8B,EAASA,EAAoBpG,IAAqBqG,EAAwB,EAEvJc,GAAY9M,EAAS2M,GAAYE,GACjCE,GAAkBzO,EAAOmN,EAAS,EAAQpR,EAF9B2F,EAAS0M,GAAYG,GAAsBD,IAEKvS,EAAK2F,EAAQyL,EAAS,EAAQrR,EAAK0S,IAAa1S,GAChHyE,EAAc8G,GAAYoH,GAC1B1K,EAAKsD,GAAYoH,GAAkB/M,CACrC,CAEA,GAAI8H,EAAc,CAChB,IAAIkF,GAEAC,GAAyB,MAAbtH,EAAmB,EAAM7P,EAErCoX,GAAwB,MAAbvH,EAAmB/P,EAASC,EAEvCsX,GAAUtO,EAAcgJ,GAExBuF,GAAmB,MAAZvF,EAAkB,SAAW,QAEpCwF,GAAOF,GAAUrJ,EAASmJ,IAE1BK,GAAOH,GAAUrJ,EAASoJ,IAE1BK,IAAuD,IAAxC,CAAC,EAAKzX,GAAMqH,QAAQ4B,GAEnCyO,GAAyH,OAAjGR,GAAgD,MAAvBjB,OAA8B,EAASA,EAAoBlE,IAAoBmF,GAAyB,EAEzJS,GAAaF,GAAeF,GAAOF,GAAUnE,EAAcoE,IAAQ1M,EAAW0M,IAAQI,GAAuB1B,EAA4BjE,QAEzI6F,GAAaH,GAAeJ,GAAUnE,EAAcoE,IAAQ1M,EAAW0M,IAAQI,GAAuB1B,EAA4BjE,QAAUyF,GAE5IK,GAAmBlC,GAAU8B,G1BzH9B,SAAwBlT,EAAK1E,EAAOyE,GACzC,IAAIwT,EAAItP,EAAOjE,EAAK1E,EAAOyE,GAC3B,OAAOwT,EAAIxT,EAAMA,EAAMwT,CACzB,C0BsHoDC,CAAeJ,GAAYN,GAASO,IAAcpP,EAAOmN,EAASgC,GAAaJ,GAAMF,GAAS1B,EAASiC,GAAaJ,IAEpKzO,EAAcgJ,GAAW8F,GACzBtL,EAAKwF,GAAW8F,GAAmBR,EACrC,CAEAxU,EAAMmG,cAAcxG,GAAQ+J,CAvE5B,CAwEF,EAQEhC,iBAAkB,CAAC,WE1HN,SAASyN,GAAiBC,EAAyBrQ,EAAcsD,QAC9D,IAAZA,IACFA,GAAU,GAGZ,ICnBoCrJ,ECJOJ,EFuBvCyW,EAA0B9V,EAAcwF,GACxCuQ,EAAuB/V,EAAcwF,IAf3C,SAAyBnG,GACvB,IAAImN,EAAOnN,EAAQ+D,wBACfI,EAASpB,EAAMoK,EAAK7I,OAAStE,EAAQqE,aAAe,EACpDD,EAASrB,EAAMoK,EAAK3I,QAAUxE,EAAQuE,cAAgB,EAC1D,OAAkB,IAAXJ,GAA2B,IAAXC,CACzB,CAU4DuS,CAAgBxQ,GACtEJ,EAAkBF,EAAmBM,GACrCgH,EAAOpJ,EAAsByS,EAAyBE,EAAsBjN,GAC5EyB,EAAS,CACXc,WAAY,EACZE,UAAW,GAET7C,EAAU,CACZ1E,EAAG,EACHE,EAAG,GAkBL,OAfI4R,IAA4BA,IAA4BhN,MACxB,SAA9B1J,EAAYoG,IAChBkG,GAAetG,MACbmF,GCnCgC9K,EDmCT+F,KClCdhG,EAAUC,IAAUO,EAAcP,GCJxC,CACL4L,YAFyChM,EDQbI,GCNR4L,WACpBE,UAAWlM,EAAQkM,WDGZH,GAAgB3L,IDoCnBO,EAAcwF,KAChBkD,EAAUtF,EAAsBoC,GAAc,IACtCxB,GAAKwB,EAAauH,WAC1BrE,EAAQxE,GAAKsB,EAAasH,WACjB1H,IACTsD,EAAQ1E,EAAIyH,GAAoBrG,KAI7B,CACLpB,EAAGwI,EAAK5O,KAAO2M,EAAOc,WAAa3C,EAAQ1E,EAC3CE,EAAGsI,EAAK/K,IAAM8I,EAAOgB,UAAY7C,EAAQxE,EACzCP,MAAO6I,EAAK7I,MACZE,OAAQ2I,EAAK3I,OAEjB,CGvDA,SAASoS,GAAMC,GACb,IAAItT,EAAM,IAAIoO,IACVmF,EAAU,IAAIC,IACdC,EAAS,GAKb,SAAS3F,EAAK4F,GACZH,EAAQI,IAAID,EAASlW,MACN,GAAG3B,OAAO6X,EAASxU,UAAY,GAAIwU,EAASnO,kBAAoB,IACtEvH,SAAQ,SAAU4V,GACzB,IAAKL,EAAQM,IAAID,GAAM,CACrB,IAAIE,EAAc9T,EAAI3F,IAAIuZ,GAEtBE,GACFhG,EAAKgG,EAET,CACF,IACAL,EAAO3E,KAAK4E,EACd,CAQA,OAzBAJ,EAAUtV,SAAQ,SAAU0V,GAC1B1T,EAAIiP,IAAIyE,EAASlW,KAAMkW,EACzB,IAiBAJ,EAAUtV,SAAQ,SAAU0V,GACrBH,EAAQM,IAAIH,EAASlW,OAExBsQ,EAAK4F,EAET,IACOD,CACT,CCvBA,IAAIM,GAAkB,CACpBnY,UAAW,SACX0X,UAAW,GACX1U,SAAU,YAGZ,SAASoV,KACP,IAAK,IAAI1B,EAAO2B,UAAUrG,OAAQsG,EAAO,IAAIpU,MAAMwS,GAAO6B,EAAO,EAAGA,EAAO7B,EAAM6B,IAC/ED,EAAKC,GAAQF,UAAUE,GAGzB,OAAQD,EAAKvE,MAAK,SAAUlT,GAC1B,QAASA,GAAoD,mBAAlCA,EAAQ+D,sBACrC,GACF,CAEO,SAAS4T,GAAgBC,QACL,IAArBA,IACFA,EAAmB,CAAC,GAGtB,IAAIC,EAAoBD,EACpBE,EAAwBD,EAAkBE,iBAC1CA,OAA6C,IAA1BD,EAAmC,GAAKA,EAC3DE,EAAyBH,EAAkBI,eAC3CA,OAA4C,IAA3BD,EAAoCV,GAAkBU,EAC3E,OAAO,SAAsBjZ,EAAWD,EAAQoD,QAC9B,IAAZA,IACFA,EAAU+V,GAGZ,ICxC6B/W,EAC3BgX,EDuCE9W,EAAQ,CACVjC,UAAW,SACXgZ,iBAAkB,GAClBjW,QAASzE,OAAOkE,OAAO,CAAC,EAAG2V,GAAiBW,GAC5C1Q,cAAe,CAAC,EAChBjG,SAAU,CACRvC,UAAWA,EACXD,OAAQA,GAEV4C,WAAY,CAAC,EACbD,OAAQ,CAAC,GAEP2W,EAAmB,GACnBC,GAAc,EACdrN,EAAW,CACb5J,MAAOA,EACPkX,WAAY,SAAoBC,GAC9B,IAAIrW,EAAsC,mBAArBqW,EAAkCA,EAAiBnX,EAAMc,SAAWqW,EACzFC,IACApX,EAAMc,QAAUzE,OAAOkE,OAAO,CAAC,EAAGsW,EAAgB7W,EAAMc,QAASA,GACjEd,EAAMiK,cAAgB,CACpBtM,UAAW0B,EAAU1B,GAAa6N,GAAkB7N,GAAaA,EAAU4Q,eAAiB/C,GAAkB7N,EAAU4Q,gBAAkB,GAC1I7Q,OAAQ8N,GAAkB9N,IAI5B,IElE4B+X,EAC9B4B,EFiEMN,EDhCG,SAAwBtB,GAErC,IAAIsB,EAAmBvB,GAAMC,GAE7B,OAAO/W,EAAeb,QAAO,SAAUC,EAAK+B,GAC1C,OAAO/B,EAAIE,OAAO+Y,EAAiBvR,QAAO,SAAUqQ,GAClD,OAAOA,EAAShW,QAAUA,CAC5B,IACF,GAAG,GACL,CCuB+ByX,EElEK7B,EFkEsB,GAAGzX,OAAO2Y,EAAkB3W,EAAMc,QAAQ2U,WEjE9F4B,EAAS5B,EAAU5X,QAAO,SAAUwZ,EAAQE,GAC9C,IAAIC,EAAWH,EAAOE,EAAQ5X,MAK9B,OAJA0X,EAAOE,EAAQ5X,MAAQ6X,EAAWnb,OAAOkE,OAAO,CAAC,EAAGiX,EAAUD,EAAS,CACrEzW,QAASzE,OAAOkE,OAAO,CAAC,EAAGiX,EAAS1W,QAASyW,EAAQzW,SACrD4I,KAAMrN,OAAOkE,OAAO,CAAC,EAAGiX,EAAS9N,KAAM6N,EAAQ7N,QAC5C6N,EACEF,CACT,GAAG,CAAC,GAEGhb,OAAO4D,KAAKoX,GAAQlV,KAAI,SAAUhG,GACvC,OAAOkb,EAAOlb,EAChB,MF4DM,OAJA6D,EAAM+W,iBAAmBA,EAAiBvR,QAAO,SAAUiS,GACzD,OAAOA,EAAE7X,OACX,IA+FFI,EAAM+W,iBAAiB5W,SAAQ,SAAUJ,GACvC,IAAIJ,EAAOI,EAAKJ,KACZ+X,EAAe3X,EAAKe,QACpBA,OAA2B,IAAjB4W,EAA0B,CAAC,EAAIA,EACzChX,EAASX,EAAKW,OAElB,GAAsB,mBAAXA,EAAuB,CAChC,IAAIiX,EAAYjX,EAAO,CACrBV,MAAOA,EACPL,KAAMA,EACNiK,SAAUA,EACV9I,QAASA,IAKXkW,EAAiB/F,KAAK0G,GAFT,WAAmB,EAGlC,CACF,IA/GS/N,EAASQ,QAClB,EAMAwN,YAAa,WACX,IAAIX,EAAJ,CAIA,IAAIY,EAAkB7X,EAAME,SACxBvC,EAAYka,EAAgBla,UAC5BD,EAASma,EAAgBna,OAG7B,GAAKyY,GAAiBxY,EAAWD,GAAjC,CAKAsC,EAAMwG,MAAQ,CACZ7I,UAAWwX,GAAiBxX,EAAWqH,EAAgBtH,GAAoC,UAA3BsC,EAAMc,QAAQC,UAC9ErD,OAAQiG,EAAcjG,IAOxBsC,EAAM0R,OAAQ,EACd1R,EAAMjC,UAAYiC,EAAMc,QAAQ/C,UAKhCiC,EAAM+W,iBAAiB5W,SAAQ,SAAU0V,GACvC,OAAO7V,EAAMmG,cAAc0P,EAASlW,MAAQtD,OAAOkE,OAAO,CAAC,EAAGsV,EAASnM,KACzE,IAEA,IAAK,IAAIoO,EAAQ,EAAGA,EAAQ9X,EAAM+W,iBAAiBhH,OAAQ+H,IACzD,IAAoB,IAAhB9X,EAAM0R,MAAV,CAMA,IAAIqG,EAAwB/X,EAAM+W,iBAAiBe,GAC/ChY,EAAKiY,EAAsBjY,GAC3BkY,EAAyBD,EAAsBjX,QAC/CoM,OAAsC,IAA3B8K,EAAoC,CAAC,EAAIA,EACpDrY,EAAOoY,EAAsBpY,KAEf,mBAAPG,IACTE,EAAQF,EAAG,CACTE,MAAOA,EACPc,QAASoM,EACTvN,KAAMA,EACNiK,SAAUA,KACN5J,EAdR,MAHEA,EAAM0R,OAAQ,EACdoG,GAAS,CAzBb,CATA,CAqDF,EAGA1N,QC1I2BtK,ED0IV,WACf,OAAO,IAAImY,SAAQ,SAAUC,GAC3BtO,EAASgO,cACTM,EAAQlY,EACV,GACF,EC7IG,WAUL,OATK8W,IACHA,EAAU,IAAImB,SAAQ,SAAUC,GAC9BD,QAAQC,UAAUC,MAAK,WACrBrB,OAAUsB,EACVF,EAAQpY,IACV,GACF,KAGKgX,CACT,GDmIIuB,QAAS,WACPjB,IACAH,GAAc,CAChB,GAGF,IAAKd,GAAiBxY,EAAWD,GAC/B,OAAOkM,EAmCT,SAASwN,IACPJ,EAAiB7W,SAAQ,SAAUL,GACjC,OAAOA,GACT,IACAkX,EAAmB,EACrB,CAEA,OAvCApN,EAASsN,WAAWpW,GAASqX,MAAK,SAAUnY,IACrCiX,GAAenW,EAAQwX,eAC1BxX,EAAQwX,cAActY,EAE1B,IAmCO4J,CACT,CACF,CACO,IAAI2O,GAA4BhC,KGzLnC,GAA4BA,GAAgB,CAC9CI,iBAFqB,CAAC6B,GAAgB,GAAe,GAAe,EAAa,GAAQ,GAAM,GAAiB,EAAO,MCJrH,GAA4BjC,GAAgB,CAC9CI,iBAFqB,CAAC6B,GAAgB,GAAe,GAAe,KCatE,MAAMC,GAAa,IAAIlI,IACjBmI,GAAO,CACX,GAAAtH,CAAIxS,EAASzC,EAAKyN,GACX6O,GAAWzC,IAAIpX,IAClB6Z,GAAWrH,IAAIxS,EAAS,IAAI2R,KAE9B,MAAMoI,EAAcF,GAAWjc,IAAIoC,GAI9B+Z,EAAY3C,IAAI7Z,IAA6B,IAArBwc,EAAYC,KAKzCD,EAAYvH,IAAIjV,EAAKyN,GAHnBiP,QAAQC,MAAM,+EAA+E7W,MAAM8W,KAAKJ,EAAY1Y,QAAQ,MAIhI,EACAzD,IAAG,CAACoC,EAASzC,IACPsc,GAAWzC,IAAIpX,IACV6Z,GAAWjc,IAAIoC,GAASpC,IAAIL,IAE9B,KAET,MAAA6c,CAAOpa,EAASzC,GACd,IAAKsc,GAAWzC,IAAIpX,GAClB,OAEF,MAAM+Z,EAAcF,GAAWjc,IAAIoC,GACnC+Z,EAAYM,OAAO9c,GAGM,IAArBwc,EAAYC,MACdH,GAAWQ,OAAOra,EAEtB,GAYIsa,GAAiB,gBAOjBC,GAAgBC,IAChBA,GAAYna,OAAOoa,KAAOpa,OAAOoa,IAAIC,SAEvCF,EAAWA,EAAS5O,QAAQ,iBAAiB,CAAC+O,EAAOC,IAAO,IAAIH,IAAIC,OAAOE,QAEtEJ,GA4CHK,GAAuB7a,IAC3BA,EAAQ8a,cAAc,IAAIC,MAAMT,IAAgB,EAE5C,GAAYU,MACXA,GAA4B,iBAAXA,UAGO,IAAlBA,EAAOC,SAChBD,EAASA,EAAO,SAEgB,IAApBA,EAAOE,UAEjBC,GAAaH,GAEb,GAAUA,GACLA,EAAOC,OAASD,EAAO,GAAKA,EAEf,iBAAXA,GAAuBA,EAAO7J,OAAS,EACzCrL,SAAS+C,cAAc0R,GAAcS,IAEvC,KAEHI,GAAYpb,IAChB,IAAK,GAAUA,IAAgD,IAApCA,EAAQqb,iBAAiBlK,OAClD,OAAO,EAET,MAAMmK,EAAgF,YAA7D5V,iBAAiB1F,GAASub,iBAAiB,cAE9DC,EAAgBxb,EAAQyb,QAAQ,uBACtC,IAAKD,EACH,OAAOF,EAET,GAAIE,IAAkBxb,EAAS,CAC7B,MAAM0b,EAAU1b,EAAQyb,QAAQ,WAChC,GAAIC,GAAWA,EAAQlW,aAAegW,EACpC,OAAO,EAET,GAAgB,OAAZE,EACF,OAAO,CAEX,CACA,OAAOJ,CAAgB,EAEnBK,GAAa3b,IACZA,GAAWA,EAAQkb,WAAaU,KAAKC,gBAGtC7b,EAAQ8b,UAAU7W,SAAS,mBAGC,IAArBjF,EAAQ+b,SACV/b,EAAQ+b,SAEV/b,EAAQgc,aAAa,aAAoD,UAArChc,EAAQic,aAAa,aAE5DC,GAAiBlc,IACrB,IAAK8F,SAASC,gBAAgBoW,aAC5B,OAAO,KAIT,GAAmC,mBAAxBnc,EAAQqF,YAA4B,CAC7C,MAAM+W,EAAOpc,EAAQqF,cACrB,OAAO+W,aAAgBtb,WAAasb,EAAO,IAC7C,CACA,OAAIpc,aAAmBc,WACdd,EAIJA,EAAQwF,WAGN0W,GAAelc,EAAQwF,YAFrB,IAEgC,EAErC6W,GAAO,OAUPC,GAAStc,IACbA,EAAQuE,YAAY,EAEhBgY,GAAY,IACZlc,OAAOmc,SAAW1W,SAAS6G,KAAKqP,aAAa,qBACxC3b,OAAOmc,OAET,KAEHC,GAA4B,GAgB5BC,GAAQ,IAAuC,QAAjC5W,SAASC,gBAAgB4W,IACvCC,GAAqBC,IAhBAC,QAiBN,KACjB,MAAMC,EAAIR,KAEV,GAAIQ,EAAG,CACL,MAAMhc,EAAO8b,EAAOG,KACdC,EAAqBF,EAAE7b,GAAGH,GAChCgc,EAAE7b,GAAGH,GAAQ8b,EAAOK,gBACpBH,EAAE7b,GAAGH,GAAMoc,YAAcN,EACzBE,EAAE7b,GAAGH,GAAMqc,WAAa,KACtBL,EAAE7b,GAAGH,GAAQkc,EACNJ,EAAOK,gBAElB,GA5B0B,YAAxBpX,SAASuX,YAENZ,GAA0BtL,QAC7BrL,SAASyF,iBAAiB,oBAAoB,KAC5C,IAAK,MAAMuR,KAAYL,GACrBK,GACF,IAGJL,GAA0BpK,KAAKyK,IAE/BA,GAkBA,EAEEQ,GAAU,CAACC,EAAkB9F,EAAO,GAAI+F,EAAeD,IACxB,mBAArBA,EAAkCA,KAAoB9F,GAAQ+F,EAExEC,GAAyB,CAACX,EAAUY,EAAmBC,GAAoB,KAC/E,IAAKA,EAEH,YADAL,GAAQR,GAGV,MACMc,EA/JiC5d,KACvC,IAAKA,EACH,OAAO,EAIT,IAAI,mBACF6d,EAAkB,gBAClBC,GACEzd,OAAOqF,iBAAiB1F,GAC5B,MAAM+d,EAA0BC,OAAOC,WAAWJ,GAC5CK,EAAuBF,OAAOC,WAAWH,GAG/C,OAAKC,GAA4BG,GAKjCL,EAAqBA,EAAmBlb,MAAM,KAAK,GACnDmb,EAAkBA,EAAgBnb,MAAM,KAAK,GAtDf,KAuDtBqb,OAAOC,WAAWJ,GAAsBG,OAAOC,WAAWH,KANzD,CAMoG,EA0IpFK,CAAiCT,GADlC,EAExB,IAAIU,GAAS,EACb,MAAMC,EAAU,EACdrR,aAEIA,IAAW0Q,IAGfU,GAAS,EACTV,EAAkBjS,oBAAoB6O,GAAgB+D,GACtDf,GAAQR,GAAS,EAEnBY,EAAkBnS,iBAAiB+O,GAAgB+D,GACnDC,YAAW,KACJF,GACHvD,GAAqB6C,EACvB,GACCE,EAAiB,EAYhBW,GAAuB,CAAC1R,EAAM2R,EAAeC,EAAeC,KAChE,MAAMC,EAAa9R,EAAKsE,OACxB,IAAI+H,EAAQrM,EAAKjH,QAAQ4Y,GAIzB,OAAe,IAAXtF,GACMuF,GAAiBC,EAAiB7R,EAAK8R,EAAa,GAAK9R,EAAK,IAExEqM,GAASuF,EAAgB,GAAK,EAC1BC,IACFxF,GAASA,EAAQyF,GAAcA,GAE1B9R,EAAKjK,KAAKC,IAAI,EAAGD,KAAKE,IAAIoW,EAAOyF,EAAa,KAAI,EAerDC,GAAiB,qBACjBC,GAAiB,OACjBC,GAAgB,SAChBC,GAAgB,CAAC,EACvB,IAAIC,GAAW,EACf,MAAMC,GAAe,CACnBC,WAAY,YACZC,WAAY,YAERC,GAAe,IAAIrI,IAAI,CAAC,QAAS,WAAY,UAAW,YAAa,cAAe,aAAc,iBAAkB,YAAa,WAAY,YAAa,cAAe,YAAa,UAAW,WAAY,QAAS,oBAAqB,aAAc,YAAa,WAAY,cAAe,cAAe,cAAe,YAAa,eAAgB,gBAAiB,eAAgB,gBAAiB,aAAc,QAAS,OAAQ,SAAU,QAAS,SAAU,SAAU,UAAW,WAAY,OAAQ,SAAU,eAAgB,SAAU,OAAQ,mBAAoB,mBAAoB,QAAS,QAAS,WAM/lB,SAASsI,GAAarf,EAASsf,GAC7B,OAAOA,GAAO,GAAGA,MAAQN,QAAgBhf,EAAQgf,UAAYA,IAC/D,CACA,SAASO,GAAiBvf,GACxB,MAAMsf,EAAMD,GAAarf,GAGzB,OAFAA,EAAQgf,SAAWM,EACnBP,GAAcO,GAAOP,GAAcO,IAAQ,CAAC,EACrCP,GAAcO,EACvB,CAiCA,SAASE,GAAYC,EAAQC,EAAUC,EAAqB,MAC1D,OAAOliB,OAAOmiB,OAAOH,GAAQ7M,MAAKiN,GAASA,EAAMH,WAAaA,GAAYG,EAAMF,qBAAuBA,GACzG,CACA,SAASG,GAAoBC,EAAmB1B,EAAS2B,GACvD,MAAMC,EAAiC,iBAAZ5B,EAErBqB,EAAWO,EAAcD,EAAqB3B,GAAW2B,EAC/D,IAAIE,EAAYC,GAAaJ,GAI7B,OAHKX,GAAahI,IAAI8I,KACpBA,EAAYH,GAEP,CAACE,EAAaP,EAAUQ,EACjC,CACA,SAASE,GAAWpgB,EAAS+f,EAAmB1B,EAAS2B,EAAoBK,GAC3E,GAAiC,iBAAtBN,IAAmC/f,EAC5C,OAEF,IAAKigB,EAAaP,EAAUQ,GAAaJ,GAAoBC,EAAmB1B,EAAS2B,GAIzF,GAAID,KAAqBd,GAAc,CACrC,MAAMqB,EAAepf,GACZ,SAAU2e,GACf,IAAKA,EAAMU,eAAiBV,EAAMU,gBAAkBV,EAAMW,iBAAmBX,EAAMW,eAAevb,SAAS4a,EAAMU,eAC/G,OAAOrf,EAAGjD,KAAKwiB,KAAMZ,EAEzB,EAEFH,EAAWY,EAAaZ,EAC1B,CACA,MAAMD,EAASF,GAAiBvf,GAC1B0gB,EAAWjB,EAAOS,KAAeT,EAAOS,GAAa,CAAC,GACtDS,EAAmBnB,GAAYkB,EAAUhB,EAAUO,EAAc5B,EAAU,MACjF,GAAIsC,EAEF,YADAA,EAAiBN,OAASM,EAAiBN,QAAUA,GAGvD,MAAMf,EAAMD,GAAaK,EAAUK,EAAkBnU,QAAQgT,GAAgB,KACvE1d,EAAK+e,EA5Db,SAAoCjgB,EAASwa,EAAUtZ,GACrD,OAAO,SAASmd,EAAQwB,GACtB,MAAMe,EAAc5gB,EAAQ6gB,iBAAiBrG,GAC7C,IAAK,IAAI,OACPxN,GACE6S,EAAO7S,GAAUA,IAAWyT,KAAMzT,EAASA,EAAOxH,WACpD,IAAK,MAAMsb,KAAcF,EACvB,GAAIE,IAAe9T,EASnB,OANA+T,GAAWlB,EAAO,CAChBW,eAAgBxT,IAEdqR,EAAQgC,QACVW,GAAaC,IAAIjhB,EAAS6f,EAAMqB,KAAM1G,EAAUtZ,GAE3CA,EAAGigB,MAAMnU,EAAQ,CAAC6S,GAG/B,CACF,CAwC2BuB,CAA2BphB,EAASqe,EAASqB,GAvExE,SAA0B1f,EAASkB,GACjC,OAAO,SAASmd,EAAQwB,GAOtB,OANAkB,GAAWlB,EAAO,CAChBW,eAAgBxgB,IAEdqe,EAAQgC,QACVW,GAAaC,IAAIjhB,EAAS6f,EAAMqB,KAAMhgB,GAEjCA,EAAGigB,MAAMnhB,EAAS,CAAC6f,GAC5B,CACF,CA6DoFwB,CAAiBrhB,EAAS0f,GAC5Gxe,EAAGye,mBAAqBM,EAAc5B,EAAU,KAChDnd,EAAGwe,SAAWA,EACdxe,EAAGmf,OAASA,EACZnf,EAAG8d,SAAWM,EACdoB,EAASpB,GAAOpe,EAChBlB,EAAQuL,iBAAiB2U,EAAWhf,EAAI+e,EAC1C,CACA,SAASqB,GAActhB,EAASyf,EAAQS,EAAW7B,EAASsB,GAC1D,MAAMze,EAAKse,GAAYC,EAAOS,GAAY7B,EAASsB,GAC9Cze,IAGLlB,EAAQyL,oBAAoByU,EAAWhf,EAAIqgB,QAAQ5B,WAC5CF,EAAOS,GAAWhf,EAAG8d,UAC9B,CACA,SAASwC,GAAyBxhB,EAASyf,EAAQS,EAAWuB,GAC5D,MAAMC,EAAoBjC,EAAOS,IAAc,CAAC,EAChD,IAAK,MAAOyB,EAAY9B,KAAUpiB,OAAOmkB,QAAQF,GAC3CC,EAAWE,SAASJ,IACtBH,GAActhB,EAASyf,EAAQS,EAAWL,EAAMH,SAAUG,EAAMF,mBAGtE,CACA,SAASQ,GAAaN,GAGpB,OADAA,EAAQA,EAAMjU,QAAQiT,GAAgB,IAC/BI,GAAaY,IAAUA,CAChC,CACA,MAAMmB,GAAe,CACnB,EAAAc,CAAG9hB,EAAS6f,EAAOxB,EAAS2B,GAC1BI,GAAWpgB,EAAS6f,EAAOxB,EAAS2B,GAAoB,EAC1D,EACA,GAAA+B,CAAI/hB,EAAS6f,EAAOxB,EAAS2B,GAC3BI,GAAWpgB,EAAS6f,EAAOxB,EAAS2B,GAAoB,EAC1D,EACA,GAAAiB,CAAIjhB,EAAS+f,EAAmB1B,EAAS2B,GACvC,GAAiC,iBAAtBD,IAAmC/f,EAC5C,OAEF,MAAOigB,EAAaP,EAAUQ,GAAaJ,GAAoBC,EAAmB1B,EAAS2B,GACrFgC,EAAc9B,IAAcH,EAC5BN,EAASF,GAAiBvf,GAC1B0hB,EAAoBjC,EAAOS,IAAc,CAAC,EAC1C+B,EAAclC,EAAkBmC,WAAW,KACjD,QAAwB,IAAbxC,EAAX,CAQA,GAAIuC,EACF,IAAK,MAAME,KAAgB1kB,OAAO4D,KAAKoe,GACrC+B,GAAyBxhB,EAASyf,EAAQ0C,EAAcpC,EAAkBlN,MAAM,IAGpF,IAAK,MAAOuP,EAAavC,KAAUpiB,OAAOmkB,QAAQF,GAAoB,CACpE,MAAMC,EAAaS,EAAYxW,QAAQkT,GAAe,IACjDkD,IAAejC,EAAkB8B,SAASF,IAC7CL,GAActhB,EAASyf,EAAQS,EAAWL,EAAMH,SAAUG,EAAMF,mBAEpE,CAXA,KAPA,CAEE,IAAKliB,OAAO4D,KAAKqgB,GAAmBvQ,OAClC,OAEFmQ,GAActhB,EAASyf,EAAQS,EAAWR,EAAUO,EAAc5B,EAAU,KAE9E,CAYF,EACA,OAAAgE,CAAQriB,EAAS6f,EAAOpI,GACtB,GAAqB,iBAAVoI,IAAuB7f,EAChC,OAAO,KAET,MAAM+c,EAAIR,KAGV,IAAI+F,EAAc,KACdC,GAAU,EACVC,GAAiB,EACjBC,GAAmB,EAJH5C,IADFM,GAAaN,IAMZ9C,IACjBuF,EAAcvF,EAAEhC,MAAM8E,EAAOpI,GAC7BsF,EAAE/c,GAASqiB,QAAQC,GACnBC,GAAWD,EAAYI,uBACvBF,GAAkBF,EAAYK,gCAC9BF,EAAmBH,EAAYM,sBAEjC,MAAMC,EAAM9B,GAAW,IAAIhG,MAAM8E,EAAO,CACtC0C,UACAO,YAAY,IACVrL,GAUJ,OATIgL,GACFI,EAAIE,iBAEFP,GACFxiB,EAAQ8a,cAAc+H,GAEpBA,EAAIJ,kBAAoBH,GAC1BA,EAAYS,iBAEPF,CACT,GAEF,SAAS9B,GAAWljB,EAAKmlB,EAAO,CAAC,GAC/B,IAAK,MAAOzlB,EAAKa,KAAUX,OAAOmkB,QAAQoB,GACxC,IACEnlB,EAAIN,GAAOa,CACb,CAAE,MAAO6kB,GACPxlB,OAAOC,eAAeG,EAAKN,EAAK,CAC9B2lB,cAAc,EACdtlB,IAAG,IACMQ,GAGb,CAEF,OAAOP,CACT,CASA,SAASslB,GAAc/kB,GACrB,GAAc,SAAVA,EACF,OAAO,EAET,GAAc,UAAVA,EACF,OAAO,EAET,GAAIA,IAAU4f,OAAO5f,GAAOkC,WAC1B,OAAO0d,OAAO5f,GAEhB,GAAc,KAAVA,GAA0B,SAAVA,EAClB,OAAO,KAET,GAAqB,iBAAVA,EACT,OAAOA,EAET,IACE,OAAOglB,KAAKC,MAAMC,mBAAmBllB,GACvC,CAAE,MAAO6kB,GACP,OAAO7kB,CACT,CACF,CACA,SAASmlB,GAAiBhmB,GACxB,OAAOA,EAAIqO,QAAQ,UAAU4X,GAAO,IAAIA,EAAItjB,iBAC9C,CACA,MAAMujB,GAAc,CAClB,gBAAAC,CAAiB1jB,EAASzC,EAAKa,GAC7B4B,EAAQ6B,aAAa,WAAW0hB,GAAiBhmB,KAAQa,EAC3D,EACA,mBAAAulB,CAAoB3jB,EAASzC,GAC3ByC,EAAQ4B,gBAAgB,WAAW2hB,GAAiBhmB,KACtD,EACA,iBAAAqmB,CAAkB5jB,GAChB,IAAKA,EACH,MAAO,CAAC,EAEV,MAAM0B,EAAa,CAAC,EACdmiB,EAASpmB,OAAO4D,KAAKrB,EAAQ8jB,SAASld,QAAOrJ,GAAOA,EAAI2kB,WAAW,QAAU3kB,EAAI2kB,WAAW,cAClG,IAAK,MAAM3kB,KAAOsmB,EAAQ,CACxB,IAAIE,EAAUxmB,EAAIqO,QAAQ,MAAO,IACjCmY,EAAUA,EAAQC,OAAO,GAAG9jB,cAAgB6jB,EAAQlR,MAAM,EAAGkR,EAAQ5S,QACrEzP,EAAWqiB,GAAWZ,GAAcnjB,EAAQ8jB,QAAQvmB,GACtD,CACA,OAAOmE,CACT,EACAuiB,iBAAgB,CAACjkB,EAASzC,IACjB4lB,GAAcnjB,EAAQic,aAAa,WAAWsH,GAAiBhmB,QAgB1E,MAAM2mB,GAEJ,kBAAWC,GACT,MAAO,CAAC,CACV,CACA,sBAAWC,GACT,MAAO,CAAC,CACV,CACA,eAAWpH,GACT,MAAM,IAAIqH,MAAM,sEAClB,CACA,UAAAC,CAAWC,GAIT,OAHAA,EAAS9D,KAAK+D,gBAAgBD,GAC9BA,EAAS9D,KAAKgE,kBAAkBF,GAChC9D,KAAKiE,iBAAiBH,GACfA,CACT,CACA,iBAAAE,CAAkBF,GAChB,OAAOA,CACT,CACA,eAAAC,CAAgBD,EAAQvkB,GACtB,MAAM2kB,EAAa,GAAU3kB,GAAWyjB,GAAYQ,iBAAiBjkB,EAAS,UAAY,CAAC,EAE3F,MAAO,IACFygB,KAAKmE,YAAYT,WACM,iBAAfQ,EAA0BA,EAAa,CAAC,KAC/C,GAAU3kB,GAAWyjB,GAAYG,kBAAkB5jB,GAAW,CAAC,KAC7C,iBAAXukB,EAAsBA,EAAS,CAAC,EAE/C,CACA,gBAAAG,CAAiBH,EAAQM,EAAcpE,KAAKmE,YAAYR,aACtD,IAAK,MAAO7hB,EAAUuiB,KAAkBrnB,OAAOmkB,QAAQiD,GAAc,CACnE,MAAMzmB,EAAQmmB,EAAOhiB,GACfwiB,EAAY,GAAU3mB,GAAS,UAhiBrC4c,OADSA,EAiiB+C5c,GA/hBnD,GAAG4c,IAELvd,OAAOM,UAAUuC,SAASrC,KAAK+c,GAAQL,MAAM,eAAe,GAAGza,cA8hBlE,IAAK,IAAI8kB,OAAOF,GAAehhB,KAAKihB,GAClC,MAAM,IAAIE,UAAU,GAAGxE,KAAKmE,YAAY5H,KAAKkI,0BAA0B3iB,qBAA4BwiB,yBAAiCD,MAExI,CAriBW9J,KAsiBb,EAqBF,MAAMmK,WAAsBjB,GAC1B,WAAAU,CAAY5kB,EAASukB,GACnBa,SACAplB,EAAUmb,GAAWnb,MAIrBygB,KAAK4E,SAAWrlB,EAChBygB,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/BzK,GAAKtH,IAAIiO,KAAK4E,SAAU5E,KAAKmE,YAAYW,SAAU9E,MACrD,CAGA,OAAA+E,GACE1L,GAAKM,OAAOqG,KAAK4E,SAAU5E,KAAKmE,YAAYW,UAC5CvE,GAAaC,IAAIR,KAAK4E,SAAU5E,KAAKmE,YAAYa,WACjD,IAAK,MAAMC,KAAgBjoB,OAAOkoB,oBAAoBlF,MACpDA,KAAKiF,GAAgB,IAEzB,CACA,cAAAE,CAAe9I,EAAU9c,EAAS6lB,GAAa,GAC7CpI,GAAuBX,EAAU9c,EAAS6lB,EAC5C,CACA,UAAAvB,CAAWC,GAIT,OAHAA,EAAS9D,KAAK+D,gBAAgBD,EAAQ9D,KAAK4E,UAC3Cd,EAAS9D,KAAKgE,kBAAkBF,GAChC9D,KAAKiE,iBAAiBH,GACfA,CACT,CAGA,kBAAOuB,CAAY9lB,GACjB,OAAO8Z,GAAKlc,IAAIud,GAAWnb,GAAUygB,KAAK8E,SAC5C,CACA,0BAAOQ,CAAoB/lB,EAASukB,EAAS,CAAC,GAC5C,OAAO9D,KAAKqF,YAAY9lB,IAAY,IAAIygB,KAAKzgB,EAA2B,iBAAXukB,EAAsBA,EAAS,KAC9F,CACA,kBAAWyB,GACT,MA5CY,OA6Cd,CACA,mBAAWT,GACT,MAAO,MAAM9E,KAAKzD,MACpB,CACA,oBAAWyI,GACT,MAAO,IAAIhF,KAAK8E,UAClB,CACA,gBAAOU,CAAUllB,GACf,MAAO,GAAGA,IAAO0f,KAAKgF,WACxB,EAUF,MAAMS,GAAclmB,IAClB,IAAIwa,EAAWxa,EAAQic,aAAa,kBACpC,IAAKzB,GAAyB,MAAbA,EAAkB,CACjC,IAAI2L,EAAgBnmB,EAAQic,aAAa,QAMzC,IAAKkK,IAAkBA,EAActE,SAAS,OAASsE,EAAcjE,WAAW,KAC9E,OAAO,KAILiE,EAActE,SAAS,OAASsE,EAAcjE,WAAW,OAC3DiE,EAAgB,IAAIA,EAAcxjB,MAAM,KAAK,MAE/C6X,EAAW2L,GAAmC,MAAlBA,EAAwBA,EAAcC,OAAS,IAC7E,CACA,OAAO5L,EAAWA,EAAS7X,MAAM,KAAKY,KAAI8iB,GAAO9L,GAAc8L,KAAM1iB,KAAK,KAAO,IAAI,EAEjF2iB,GAAiB,CACrB1T,KAAI,CAAC4H,EAAUxa,EAAU8F,SAASC,kBACzB,GAAG3G,UAAUsB,QAAQ3C,UAAU8iB,iBAAiB5iB,KAAK+B,EAASwa,IAEvE+L,QAAO,CAAC/L,EAAUxa,EAAU8F,SAASC,kBAC5BrF,QAAQ3C,UAAU8K,cAAc5K,KAAK+B,EAASwa,GAEvDgM,SAAQ,CAACxmB,EAASwa,IACT,GAAGpb,UAAUY,EAAQwmB,UAAU5f,QAAOzB,GAASA,EAAMshB,QAAQjM,KAEtE,OAAAkM,CAAQ1mB,EAASwa,GACf,MAAMkM,EAAU,GAChB,IAAIC,EAAW3mB,EAAQwF,WAAWiW,QAAQjB,GAC1C,KAAOmM,GACLD,EAAQrU,KAAKsU,GACbA,EAAWA,EAASnhB,WAAWiW,QAAQjB,GAEzC,OAAOkM,CACT,EACA,IAAAE,CAAK5mB,EAASwa,GACZ,IAAIqM,EAAW7mB,EAAQ8mB,uBACvB,KAAOD,GAAU,CACf,GAAIA,EAASJ,QAAQjM,GACnB,MAAO,CAACqM,GAEVA,EAAWA,EAASC,sBACtB,CACA,MAAO,EACT,EAEA,IAAAxhB,CAAKtF,EAASwa,GACZ,IAAIlV,EAAOtF,EAAQ+mB,mBACnB,KAAOzhB,GAAM,CACX,GAAIA,EAAKmhB,QAAQjM,GACf,MAAO,CAAClV,GAEVA,EAAOA,EAAKyhB,kBACd,CACA,MAAO,EACT,EACA,iBAAAC,CAAkBhnB,GAChB,MAAMinB,EAAa,CAAC,IAAK,SAAU,QAAS,WAAY,SAAU,UAAW,aAAc,4BAA4B1jB,KAAIiX,GAAY,GAAGA,2BAAiC7W,KAAK,KAChL,OAAO8c,KAAK7N,KAAKqU,EAAYjnB,GAAS4G,QAAOsgB,IAAOvL,GAAWuL,IAAO9L,GAAU8L,IAClF,EACA,sBAAAC,CAAuBnnB,GACrB,MAAMwa,EAAW0L,GAAYlmB,GAC7B,OAAIwa,GACK8L,GAAeC,QAAQ/L,GAAYA,EAErC,IACT,EACA,sBAAA4M,CAAuBpnB,GACrB,MAAMwa,EAAW0L,GAAYlmB,GAC7B,OAAOwa,EAAW8L,GAAeC,QAAQ/L,GAAY,IACvD,EACA,+BAAA6M,CAAgCrnB,GAC9B,MAAMwa,EAAW0L,GAAYlmB,GAC7B,OAAOwa,EAAW8L,GAAe1T,KAAK4H,GAAY,EACpD,GAUI8M,GAAuB,CAACC,EAAWC,EAAS,UAChD,MAAMC,EAAa,gBAAgBF,EAAU9B,YACvC1kB,EAAOwmB,EAAUvK,KACvBgE,GAAac,GAAGhc,SAAU2hB,EAAY,qBAAqB1mB,OAAU,SAAU8e,GAI7E,GAHI,CAAC,IAAK,QAAQgC,SAASpB,KAAKiH,UAC9B7H,EAAMkD,iBAEJpH,GAAW8E,MACb,OAEF,MAAMzT,EAASsZ,GAAec,uBAAuB3G,OAASA,KAAKhF,QAAQ,IAAI1a,KAC9DwmB,EAAUxB,oBAAoB/Y,GAGtCwa,IACX,GAAE,EAiBEG,GAAc,YACdC,GAAc,QAAQD,KACtBE,GAAe,SAASF,KAQ9B,MAAMG,WAAc3C,GAElB,eAAWnI,GACT,MAfW,OAgBb,CAGA,KAAA+K,GAEE,GADmB/G,GAAaqB,QAAQ5B,KAAK4E,SAAUuC,IACxCnF,iBACb,OAEFhC,KAAK4E,SAASvJ,UAAU1B,OAlBF,QAmBtB,MAAMyL,EAAapF,KAAK4E,SAASvJ,UAAU7W,SApBrB,QAqBtBwb,KAAKmF,gBAAe,IAAMnF,KAAKuH,mBAAmBvH,KAAK4E,SAAUQ,EACnE,CAGA,eAAAmC,GACEvH,KAAK4E,SAASjL,SACd4G,GAAaqB,QAAQ5B,KAAK4E,SAAUwC,IACpCpH,KAAK+E,SACP,CAGA,sBAAOtI,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOgd,GAAM/B,oBAAoBtF,MACvC,GAAsB,iBAAX8D,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQ9D,KAJb,CAKF,GACF,EAOF6G,GAAqBQ,GAAO,SAM5BlL,GAAmBkL,IAcnB,MAKMI,GAAyB,4BAO/B,MAAMC,WAAehD,GAEnB,eAAWnI,GACT,MAfW,QAgBb,CAGA,MAAAoL,GAEE3H,KAAK4E,SAASxjB,aAAa,eAAgB4e,KAAK4E,SAASvJ,UAAUsM,OAjB3C,UAkB1B,CAGA,sBAAOlL,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOqd,GAAOpC,oBAAoBtF,MACzB,WAAX8D,GACFzZ,EAAKyZ,IAET,GACF,EAOFvD,GAAac,GAAGhc,SAjCe,2BAiCmBoiB,IAAwBrI,IACxEA,EAAMkD,iBACN,MAAMsF,EAASxI,EAAM7S,OAAOyO,QAAQyM,IACvBC,GAAOpC,oBAAoBsC,GACnCD,QAAQ,IAOfxL,GAAmBuL,IAcnB,MACMG,GAAc,YACdC,GAAmB,aAAaD,KAChCE,GAAkB,YAAYF,KAC9BG,GAAiB,WAAWH,KAC5BI,GAAoB,cAAcJ,KAClCK,GAAkB,YAAYL,KAK9BM,GAAY,CAChBC,YAAa,KACbC,aAAc,KACdC,cAAe,MAEXC,GAAgB,CACpBH,YAAa,kBACbC,aAAc,kBACdC,cAAe,mBAOjB,MAAME,WAAc/E,GAClB,WAAAU,CAAY5kB,EAASukB,GACnBa,QACA3E,KAAK4E,SAAWrlB,EACXA,GAAYipB,GAAMC,gBAGvBzI,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/B9D,KAAK0I,QAAU,EACf1I,KAAK2I,sBAAwB7H,QAAQlhB,OAAOgpB,cAC5C5I,KAAK6I,cACP,CAGA,kBAAWnF,GACT,OAAOyE,EACT,CACA,sBAAWxE,GACT,OAAO4E,EACT,CACA,eAAWhM,GACT,MA/CW,OAgDb,CAGA,OAAAwI,GACExE,GAAaC,IAAIR,KAAK4E,SAAUiD,GAClC,CAGA,MAAAiB,CAAO1J,GACAY,KAAK2I,sBAIN3I,KAAK+I,wBAAwB3J,KAC/BY,KAAK0I,QAAUtJ,EAAM4J,SAJrBhJ,KAAK0I,QAAUtJ,EAAM6J,QAAQ,GAAGD,OAMpC,CACA,IAAAE,CAAK9J,GACCY,KAAK+I,wBAAwB3J,KAC/BY,KAAK0I,QAAUtJ,EAAM4J,QAAUhJ,KAAK0I,SAEtC1I,KAAKmJ,eACLtM,GAAQmD,KAAK6E,QAAQuD,YACvB,CACA,KAAAgB,CAAMhK,GACJY,KAAK0I,QAAUtJ,EAAM6J,SAAW7J,EAAM6J,QAAQvY,OAAS,EAAI,EAAI0O,EAAM6J,QAAQ,GAAGD,QAAUhJ,KAAK0I,OACjG,CACA,YAAAS,GACE,MAAME,EAAYlnB,KAAKoC,IAAIyb,KAAK0I,SAChC,GAAIW,GAnEgB,GAoElB,OAEF,MAAM/b,EAAY+b,EAAYrJ,KAAK0I,QACnC1I,KAAK0I,QAAU,EACVpb,GAGLuP,GAAQvP,EAAY,EAAI0S,KAAK6E,QAAQyD,cAAgBtI,KAAK6E,QAAQwD,aACpE,CACA,WAAAQ,GACM7I,KAAK2I,uBACPpI,GAAac,GAAGrB,KAAK4E,SAAUqD,IAAmB7I,GAASY,KAAK8I,OAAO1J,KACvEmB,GAAac,GAAGrB,KAAK4E,SAAUsD,IAAiB9I,GAASY,KAAKkJ,KAAK9J,KACnEY,KAAK4E,SAASvJ,UAAU5E,IAlFG,mBAoF3B8J,GAAac,GAAGrB,KAAK4E,SAAUkD,IAAkB1I,GAASY,KAAK8I,OAAO1J,KACtEmB,GAAac,GAAGrB,KAAK4E,SAAUmD,IAAiB3I,GAASY,KAAKoJ,MAAMhK,KACpEmB,GAAac,GAAGrB,KAAK4E,SAAUoD,IAAgB5I,GAASY,KAAKkJ,KAAK9J,KAEtE,CACA,uBAAA2J,CAAwB3J,GACtB,OAAOY,KAAK2I,wBA3FS,QA2FiBvJ,EAAMkK,aA5FrB,UA4FyDlK,EAAMkK,YACxF,CAGA,kBAAOb,GACL,MAAO,iBAAkBpjB,SAASC,iBAAmB7C,UAAU8mB,eAAiB,CAClF,EAeF,MAEMC,GAAc,eACdC,GAAiB,YACjBC,GAAmB,YACnBC,GAAoB,aAGpBC,GAAa,OACbC,GAAa,OACbC,GAAiB,OACjBC,GAAkB,QAClBC,GAAc,QAAQR,KACtBS,GAAa,OAAOT,KACpBU,GAAkB,UAAUV,KAC5BW,GAAqB,aAAaX,KAClCY,GAAqB,aAAaZ,KAClCa,GAAmB,YAAYb,KAC/Bc,GAAwB,OAAOd,KAAcC,KAC7Cc,GAAyB,QAAQf,KAAcC,KAC/Ce,GAAsB,WACtBC,GAAsB,SAMtBC,GAAkB,UAClBC,GAAgB,iBAChBC,GAAuBF,GAAkBC,GAKzCE,GAAmB,CACvB,CAACnB,IAAmBK,GACpB,CAACJ,IAAoBG,IAEjBgB,GAAY,CAChBC,SAAU,IACVC,UAAU,EACVC,MAAO,QACPC,MAAM,EACNC,OAAO,EACPC,MAAM,GAEFC,GAAgB,CACpBN,SAAU,mBAEVC,SAAU,UACVC,MAAO,mBACPC,KAAM,mBACNC,MAAO,UACPC,KAAM,WAOR,MAAME,WAAiB5G,GACrB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKuL,UAAY,KACjBvL,KAAKwL,eAAiB,KACtBxL,KAAKyL,YAAa,EAClBzL,KAAK0L,aAAe,KACpB1L,KAAK2L,aAAe,KACpB3L,KAAK4L,mBAAqB/F,GAAeC,QArCjB,uBAqC8C9F,KAAK4E,UAC3E5E,KAAK6L,qBACD7L,KAAK6E,QAAQqG,OAASV,IACxBxK,KAAK8L,OAET,CAGA,kBAAWpI,GACT,OAAOoH,EACT,CACA,sBAAWnH,GACT,OAAO0H,EACT,CACA,eAAW9O,GACT,MAnFW,UAoFb,CAGA,IAAA1X,GACEmb,KAAK+L,OAAOnC,GACd,CACA,eAAAoC,IAIO3mB,SAAS4mB,QAAUtR,GAAUqF,KAAK4E,WACrC5E,KAAKnb,MAET,CACA,IAAAshB,GACEnG,KAAK+L,OAAOlC,GACd,CACA,KAAAoB,GACMjL,KAAKyL,YACPrR,GAAqB4F,KAAK4E,UAE5B5E,KAAKkM,gBACP,CACA,KAAAJ,GACE9L,KAAKkM,iBACLlM,KAAKmM,kBACLnM,KAAKuL,UAAYa,aAAY,IAAMpM,KAAKgM,mBAAmBhM,KAAK6E,QAAQkG,SAC1E,CACA,iBAAAsB,GACOrM,KAAK6E,QAAQqG,OAGdlL,KAAKyL,WACPlL,GAAae,IAAItB,KAAK4E,SAAUqF,IAAY,IAAMjK,KAAK8L,UAGzD9L,KAAK8L,QACP,CACA,EAAAQ,CAAG7T,GACD,MAAM8T,EAAQvM,KAAKwM,YACnB,GAAI/T,EAAQ8T,EAAM7b,OAAS,GAAK+H,EAAQ,EACtC,OAEF,GAAIuH,KAAKyL,WAEP,YADAlL,GAAae,IAAItB,KAAK4E,SAAUqF,IAAY,IAAMjK,KAAKsM,GAAG7T,KAG5D,MAAMgU,EAAczM,KAAK0M,cAAc1M,KAAK2M,cAC5C,GAAIF,IAAgBhU,EAClB,OAEF,MAAMtC,EAAQsC,EAAQgU,EAAc7C,GAAaC,GACjD7J,KAAK+L,OAAO5V,EAAOoW,EAAM9T,GAC3B,CACA,OAAAsM,GACM/E,KAAK2L,cACP3L,KAAK2L,aAAa5G,UAEpBJ,MAAMI,SACR,CAGA,iBAAAf,CAAkBF,GAEhB,OADAA,EAAO8I,gBAAkB9I,EAAOiH,SACzBjH,CACT,CACA,kBAAA+H,GACM7L,KAAK6E,QAAQmG,UACfzK,GAAac,GAAGrB,KAAK4E,SAAUsF,IAAiB9K,GAASY,KAAK6M,SAASzN,KAE9C,UAAvBY,KAAK6E,QAAQoG,QACf1K,GAAac,GAAGrB,KAAK4E,SAAUuF,IAAoB,IAAMnK,KAAKiL,UAC9D1K,GAAac,GAAGrB,KAAK4E,SAAUwF,IAAoB,IAAMpK,KAAKqM,uBAE5DrM,KAAK6E,QAAQsG,OAAS3C,GAAMC,eAC9BzI,KAAK8M,yBAET,CACA,uBAAAA,GACE,IAAK,MAAMC,KAAOlH,GAAe1T,KArIX,qBAqImC6N,KAAK4E,UAC5DrE,GAAac,GAAG0L,EAAK1C,IAAkBjL,GAASA,EAAMkD,mBAExD,MAmBM0K,EAAc,CAClB3E,aAAc,IAAMrI,KAAK+L,OAAO/L,KAAKiN,kBAAkBnD,KACvDxB,cAAe,IAAMtI,KAAK+L,OAAO/L,KAAKiN,kBAAkBlD,KACxD3B,YAtBkB,KACS,UAAvBpI,KAAK6E,QAAQoG,QAYjBjL,KAAKiL,QACDjL,KAAK0L,cACPwB,aAAalN,KAAK0L,cAEpB1L,KAAK0L,aAAe7N,YAAW,IAAMmC,KAAKqM,qBAjLjB,IAiL+DrM,KAAK6E,QAAQkG,UAAS,GAOhH/K,KAAK2L,aAAe,IAAInD,GAAMxI,KAAK4E,SAAUoI,EAC/C,CACA,QAAAH,CAASzN,GACP,GAAI,kBAAkB/b,KAAK+b,EAAM7S,OAAO0a,SACtC,OAEF,MAAM3Z,EAAYud,GAAiBzL,EAAMtiB,KACrCwQ,IACF8R,EAAMkD,iBACNtC,KAAK+L,OAAO/L,KAAKiN,kBAAkB3f,IAEvC,CACA,aAAAof,CAAcntB,GACZ,OAAOygB,KAAKwM,YAAYrnB,QAAQ5F,EAClC,CACA,0BAAA4tB,CAA2B1U,GACzB,IAAKuH,KAAK4L,mBACR,OAEF,MAAMwB,EAAkBvH,GAAeC,QAAQ4E,GAAiB1K,KAAK4L,oBACrEwB,EAAgB/R,UAAU1B,OAAO8Q,IACjC2C,EAAgBjsB,gBAAgB,gBAChC,MAAMksB,EAAqBxH,GAAeC,QAAQ,sBAAsBrN,MAAWuH,KAAK4L,oBACpFyB,IACFA,EAAmBhS,UAAU5E,IAAIgU,IACjC4C,EAAmBjsB,aAAa,eAAgB,QAEpD,CACA,eAAA+qB,GACE,MAAM5sB,EAAUygB,KAAKwL,gBAAkBxL,KAAK2M,aAC5C,IAAKptB,EACH,OAEF,MAAM+tB,EAAkB/P,OAAOgQ,SAAShuB,EAAQic,aAAa,oBAAqB,IAClFwE,KAAK6E,QAAQkG,SAAWuC,GAAmBtN,KAAK6E,QAAQ+H,eAC1D,CACA,MAAAb,CAAO5V,EAAO5W,EAAU,MACtB,GAAIygB,KAAKyL,WACP,OAEF,MAAM1N,EAAgBiC,KAAK2M,aACrBa,EAASrX,IAAUyT,GACnB6D,EAAcluB,GAAWue,GAAqBkC,KAAKwM,YAAazO,EAAeyP,EAAQxN,KAAK6E,QAAQuG,MAC1G,GAAIqC,IAAgB1P,EAClB,OAEF,MAAM2P,EAAmB1N,KAAK0M,cAAce,GACtCE,EAAenI,GACZjF,GAAaqB,QAAQ5B,KAAK4E,SAAUY,EAAW,CACpD1F,cAAe2N,EACfngB,UAAW0S,KAAK4N,kBAAkBzX,GAClCuD,KAAMsG,KAAK0M,cAAc3O,GACzBuO,GAAIoB,IAIR,GADmBC,EAAa3D,IACjBhI,iBACb,OAEF,IAAKjE,IAAkB0P,EAGrB,OAEF,MAAMI,EAAY/M,QAAQd,KAAKuL,WAC/BvL,KAAKiL,QACLjL,KAAKyL,YAAa,EAClBzL,KAAKmN,2BAA2BO,GAChC1N,KAAKwL,eAAiBiC,EACtB,MAAMK,EAAuBN,EA3OR,sBADF,oBA6ObO,EAAiBP,EA3OH,qBACA,qBA2OpBC,EAAYpS,UAAU5E,IAAIsX,GAC1BlS,GAAO4R,GACP1P,EAAc1C,UAAU5E,IAAIqX,GAC5BL,EAAYpS,UAAU5E,IAAIqX,GAQ1B9N,KAAKmF,gBAPoB,KACvBsI,EAAYpS,UAAU1B,OAAOmU,EAAsBC,GACnDN,EAAYpS,UAAU5E,IAAIgU,IAC1B1M,EAAc1C,UAAU1B,OAAO8Q,GAAqBsD,EAAgBD,GACpE9N,KAAKyL,YAAa,EAClBkC,EAAa1D,GAAW,GAEYlM,EAAeiC,KAAKgO,eACtDH,GACF7N,KAAK8L,OAET,CACA,WAAAkC,GACE,OAAOhO,KAAK4E,SAASvJ,UAAU7W,SAhQV,QAiQvB,CACA,UAAAmoB,GACE,OAAO9G,GAAeC,QAAQ8E,GAAsB5K,KAAK4E,SAC3D,CACA,SAAA4H,GACE,OAAO3G,GAAe1T,KAAKwY,GAAe3K,KAAK4E,SACjD,CACA,cAAAsH,GACMlM,KAAKuL,YACP0C,cAAcjO,KAAKuL,WACnBvL,KAAKuL,UAAY,KAErB,CACA,iBAAA0B,CAAkB3f,GAChB,OAAI2O,KACK3O,IAAcwc,GAAiBD,GAAaD,GAE9Ctc,IAAcwc,GAAiBF,GAAaC,EACrD,CACA,iBAAA+D,CAAkBzX,GAChB,OAAI8F,KACK9F,IAAU0T,GAAaC,GAAiBC,GAE1C5T,IAAU0T,GAAaE,GAAkBD,EAClD,CAGA,sBAAOrN,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOihB,GAAShG,oBAAoBtF,KAAM8D,GAChD,GAAsB,iBAAXA,GAIX,GAAsB,iBAAXA,EAAqB,CAC9B,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IACP,OAREzZ,EAAKiiB,GAAGxI,EASZ,GACF,EAOFvD,GAAac,GAAGhc,SAAUklB,GAvSE,uCAuS2C,SAAUnL,GAC/E,MAAM7S,EAASsZ,GAAec,uBAAuB3G,MACrD,IAAKzT,IAAWA,EAAO8O,UAAU7W,SAASgmB,IACxC,OAEFpL,EAAMkD,iBACN,MAAM4L,EAAW5C,GAAShG,oBAAoB/Y,GACxC4hB,EAAanO,KAAKxE,aAAa,oBACrC,OAAI2S,GACFD,EAAS5B,GAAG6B,QACZD,EAAS7B,qBAGyC,SAAhDrJ,GAAYQ,iBAAiBxD,KAAM,UACrCkO,EAASrpB,YACTqpB,EAAS7B,sBAGX6B,EAAS/H,YACT+H,EAAS7B,oBACX,IACA9L,GAAac,GAAGzhB,OAAQ0qB,IAAuB,KAC7C,MAAM8D,EAAYvI,GAAe1T,KA5TR,6BA6TzB,IAAK,MAAM+b,KAAYE,EACrB9C,GAAShG,oBAAoB4I,EAC/B,IAOF/R,GAAmBmP,IAcnB,MAEM+C,GAAc,eAEdC,GAAe,OAAOD,KACtBE,GAAgB,QAAQF,KACxBG,GAAe,OAAOH,KACtBI,GAAiB,SAASJ,KAC1BK,GAAyB,QAAQL,cACjCM,GAAoB,OACpBC,GAAsB,WACtBC,GAAwB,aAExBC,GAA6B,WAAWF,OAAwBA,KAKhEG,GAAyB,8BACzBC,GAAY,CAChBvqB,OAAQ,KACRkjB,QAAQ,GAEJsH,GAAgB,CACpBxqB,OAAQ,iBACRkjB,OAAQ,WAOV,MAAMuH,WAAiBxK,GACrB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKmP,kBAAmB,EACxBnP,KAAKoP,cAAgB,GACrB,MAAMC,EAAaxJ,GAAe1T,KAAK4c,IACvC,IAAK,MAAMO,KAAQD,EAAY,CAC7B,MAAMtV,EAAW8L,GAAea,uBAAuB4I,GACjDC,EAAgB1J,GAAe1T,KAAK4H,GAAU5T,QAAOqpB,GAAgBA,IAAiBxP,KAAK4E,WAChF,OAAb7K,GAAqBwV,EAAc7e,QACrCsP,KAAKoP,cAAcxd,KAAK0d,EAE5B,CACAtP,KAAKyP,sBACAzP,KAAK6E,QAAQpgB,QAChBub,KAAK0P,0BAA0B1P,KAAKoP,cAAepP,KAAK2P,YAEtD3P,KAAK6E,QAAQ8C,QACf3H,KAAK2H,QAET,CAGA,kBAAWjE,GACT,OAAOsL,EACT,CACA,sBAAWrL,GACT,OAAOsL,EACT,CACA,eAAW1S,GACT,MA9DW,UA+Db,CAGA,MAAAoL,GACM3H,KAAK2P,WACP3P,KAAK4P,OAEL5P,KAAK6P,MAET,CACA,IAAAA,GACE,GAAI7P,KAAKmP,kBAAoBnP,KAAK2P,WAChC,OAEF,IAAIG,EAAiB,GAQrB,GALI9P,KAAK6E,QAAQpgB,SACfqrB,EAAiB9P,KAAK+P,uBAhEH,wCAgE4C5pB,QAAO5G,GAAWA,IAAYygB,KAAK4E,WAAU9hB,KAAIvD,GAAW2vB,GAAS5J,oBAAoB/lB,EAAS,CAC/JooB,QAAQ,OAGRmI,EAAepf,QAAUof,EAAe,GAAGX,iBAC7C,OAGF,GADmB5O,GAAaqB,QAAQ5B,KAAK4E,SAAU0J,IACxCtM,iBACb,OAEF,IAAK,MAAMgO,KAAkBF,EAC3BE,EAAeJ,OAEjB,MAAMK,EAAYjQ,KAAKkQ,gBACvBlQ,KAAK4E,SAASvJ,UAAU1B,OAAOiV,IAC/B5O,KAAK4E,SAASvJ,UAAU5E,IAAIoY,IAC5B7O,KAAK4E,SAAS7jB,MAAMkvB,GAAa,EACjCjQ,KAAK0P,0BAA0B1P,KAAKoP,eAAe,GACnDpP,KAAKmP,kBAAmB,EACxB,MAQMgB,EAAa,SADUF,EAAU,GAAGxL,cAAgBwL,EAAU7d,MAAM,KAE1E4N,KAAKmF,gBATY,KACfnF,KAAKmP,kBAAmB,EACxBnP,KAAK4E,SAASvJ,UAAU1B,OAAOkV,IAC/B7O,KAAK4E,SAASvJ,UAAU5E,IAAImY,GAAqBD,IACjD3O,KAAK4E,SAAS7jB,MAAMkvB,GAAa,GACjC1P,GAAaqB,QAAQ5B,KAAK4E,SAAU2J,GAAc,GAItBvO,KAAK4E,UAAU,GAC7C5E,KAAK4E,SAAS7jB,MAAMkvB,GAAa,GAAGjQ,KAAK4E,SAASuL,MACpD,CACA,IAAAP,GACE,GAAI5P,KAAKmP,mBAAqBnP,KAAK2P,WACjC,OAGF,GADmBpP,GAAaqB,QAAQ5B,KAAK4E,SAAU4J,IACxCxM,iBACb,OAEF,MAAMiO,EAAYjQ,KAAKkQ,gBACvBlQ,KAAK4E,SAAS7jB,MAAMkvB,GAAa,GAAGjQ,KAAK4E,SAASthB,wBAAwB2sB,OAC1EpU,GAAOmE,KAAK4E,UACZ5E,KAAK4E,SAASvJ,UAAU5E,IAAIoY,IAC5B7O,KAAK4E,SAASvJ,UAAU1B,OAAOiV,GAAqBD,IACpD,IAAK,MAAM/M,KAAW5B,KAAKoP,cAAe,CACxC,MAAM7vB,EAAUsmB,GAAec,uBAAuB/E,GAClDriB,IAAYygB,KAAK2P,SAASpwB,IAC5BygB,KAAK0P,0BAA0B,CAAC9N,IAAU,EAE9C,CACA5B,KAAKmP,kBAAmB,EAOxBnP,KAAK4E,SAAS7jB,MAAMkvB,GAAa,GACjCjQ,KAAKmF,gBAPY,KACfnF,KAAKmP,kBAAmB,EACxBnP,KAAK4E,SAASvJ,UAAU1B,OAAOkV,IAC/B7O,KAAK4E,SAASvJ,UAAU5E,IAAImY,IAC5BrO,GAAaqB,QAAQ5B,KAAK4E,SAAU6J,GAAe,GAGvBzO,KAAK4E,UAAU,EAC/C,CACA,QAAA+K,CAASpwB,EAAUygB,KAAK4E,UACtB,OAAOrlB,EAAQ8b,UAAU7W,SAASmqB,GACpC,CAGA,iBAAA3K,CAAkBF,GAGhB,OAFAA,EAAO6D,OAAS7G,QAAQgD,EAAO6D,QAC/B7D,EAAOrf,OAASiW,GAAWoJ,EAAOrf,QAC3Bqf,CACT,CACA,aAAAoM,GACE,OAAOlQ,KAAK4E,SAASvJ,UAAU7W,SA3IL,uBAChB,QACC,QA0Ib,CACA,mBAAAirB,GACE,IAAKzP,KAAK6E,QAAQpgB,OAChB,OAEF,MAAMshB,EAAW/F,KAAK+P,uBAAuBhB,IAC7C,IAAK,MAAMxvB,KAAWwmB,EAAU,CAC9B,MAAMqK,EAAWvK,GAAec,uBAAuBpnB,GACnD6wB,GACFpQ,KAAK0P,0BAA0B,CAACnwB,GAAUygB,KAAK2P,SAASS,GAE5D,CACF,CACA,sBAAAL,CAAuBhW,GACrB,MAAMgM,EAAWF,GAAe1T,KAAK2c,GAA4B9O,KAAK6E,QAAQpgB,QAE9E,OAAOohB,GAAe1T,KAAK4H,EAAUiG,KAAK6E,QAAQpgB,QAAQ0B,QAAO5G,IAAYwmB,EAAS3E,SAAS7hB,IACjG,CACA,yBAAAmwB,CAA0BW,EAAcC,GACtC,GAAKD,EAAa3f,OAGlB,IAAK,MAAMnR,KAAW8wB,EACpB9wB,EAAQ8b,UAAUsM,OArKK,aAqKyB2I,GAChD/wB,EAAQ6B,aAAa,gBAAiBkvB,EAE1C,CAGA,sBAAO7T,CAAgBqH,GACrB,MAAMe,EAAU,CAAC,EAIjB,MAHsB,iBAAXf,GAAuB,YAAYzgB,KAAKygB,KACjDe,EAAQ8C,QAAS,GAEZ3H,KAAKwH,MAAK,WACf,MAAMnd,EAAO6kB,GAAS5J,oBAAoBtF,KAAM6E,GAChD,GAAsB,iBAAXf,EAAqB,CAC9B,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IACP,CACF,GACF,EAOFvD,GAAac,GAAGhc,SAAUqpB,GAAwBK,IAAwB,SAAU3P,IAErD,MAAzBA,EAAM7S,OAAO0a,SAAmB7H,EAAMW,gBAAmD,MAAjCX,EAAMW,eAAekH,UAC/E7H,EAAMkD,iBAER,IAAK,MAAM/iB,KAAWsmB,GAAee,gCAAgC5G,MACnEkP,GAAS5J,oBAAoB/lB,EAAS,CACpCooB,QAAQ,IACPA,QAEP,IAMAxL,GAAmB+S,IAcnB,MAAMqB,GAAS,WAETC,GAAc,eACdC,GAAiB,YAGjBC,GAAiB,UACjBC,GAAmB,YAGnBC,GAAe,OAAOJ,KACtBK,GAAiB,SAASL,KAC1BM,GAAe,OAAON,KACtBO,GAAgB,QAAQP,KACxBQ,GAAyB,QAAQR,KAAcC,KAC/CQ,GAAyB,UAAUT,KAAcC,KACjDS,GAAuB,QAAQV,KAAcC,KAC7CU,GAAoB,OAMpBC,GAAyB,4DACzBC,GAA6B,GAAGD,MAA0BD,KAC1DG,GAAgB,iBAIhBC,GAAgBtV,KAAU,UAAY,YACtCuV,GAAmBvV,KAAU,YAAc,UAC3CwV,GAAmBxV,KAAU,aAAe,eAC5CyV,GAAsBzV,KAAU,eAAiB,aACjD0V,GAAkB1V,KAAU,aAAe,cAC3C2V,GAAiB3V,KAAU,cAAgB,aAG3C4V,GAAY,CAChBC,WAAW,EACX7jB,SAAU,kBACV8jB,QAAS,UACT/pB,OAAQ,CAAC,EAAG,GACZgqB,aAAc,KACd1zB,UAAW,UAEP2zB,GAAgB,CACpBH,UAAW,mBACX7jB,SAAU,mBACV8jB,QAAS,SACT/pB,OAAQ,0BACRgqB,aAAc,yBACd1zB,UAAW,2BAOb,MAAM4zB,WAAiBxN,GACrB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKmS,QAAU,KACfnS,KAAKoS,QAAUpS,KAAK4E,SAAS7f,WAE7Bib,KAAKqS,MAAQxM,GAAehhB,KAAKmb,KAAK4E,SAAU0M,IAAe,IAAMzL,GAAeM,KAAKnG,KAAK4E,SAAU0M,IAAe,IAAMzL,GAAeC,QAAQwL,GAAetR,KAAKoS,SACxKpS,KAAKsS,UAAYtS,KAAKuS,eACxB,CAGA,kBAAW7O,GACT,OAAOmO,EACT,CACA,sBAAWlO,GACT,OAAOsO,EACT,CACA,eAAW1V,GACT,OAAOgU,EACT,CAGA,MAAA5I,GACE,OAAO3H,KAAK2P,WAAa3P,KAAK4P,OAAS5P,KAAK6P,MAC9C,CACA,IAAAA,GACE,GAAI3U,GAAW8E,KAAK4E,WAAa5E,KAAK2P,WACpC,OAEF,MAAM7P,EAAgB,CACpBA,cAAeE,KAAK4E,UAGtB,IADkBrE,GAAaqB,QAAQ5B,KAAK4E,SAAUkM,GAAchR,GACtDkC,iBAAd,CASA,GANAhC,KAAKwS,gBAMD,iBAAkBntB,SAASC,kBAAoB0a,KAAKoS,QAAQpX,QAzExC,eA0EtB,IAAK,MAAMzb,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK6Z,UAC/CxF,GAAac,GAAG9hB,EAAS,YAAaqc,IAG1CoE,KAAK4E,SAAS6N,QACdzS,KAAK4E,SAASxjB,aAAa,iBAAiB,GAC5C4e,KAAKqS,MAAMhX,UAAU5E,IAAI0a,IACzBnR,KAAK4E,SAASvJ,UAAU5E,IAAI0a,IAC5B5Q,GAAaqB,QAAQ5B,KAAK4E,SAAUmM,GAAejR,EAhBnD,CAiBF,CACA,IAAA8P,GACE,GAAI1U,GAAW8E,KAAK4E,YAAc5E,KAAK2P,WACrC,OAEF,MAAM7P,EAAgB,CACpBA,cAAeE,KAAK4E,UAEtB5E,KAAK0S,cAAc5S,EACrB,CACA,OAAAiF,GACM/E,KAAKmS,SACPnS,KAAKmS,QAAQnZ,UAEf2L,MAAMI,SACR,CACA,MAAAha,GACEiV,KAAKsS,UAAYtS,KAAKuS,gBAClBvS,KAAKmS,SACPnS,KAAKmS,QAAQpnB,QAEjB,CAGA,aAAA2nB,CAAc5S,GAEZ,IADkBS,GAAaqB,QAAQ5B,KAAK4E,SAAUgM,GAAc9Q,GACtDkC,iBAAd,CAMA,GAAI,iBAAkB3c,SAASC,gBAC7B,IAAK,MAAM/F,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK6Z,UAC/CxF,GAAaC,IAAIjhB,EAAS,YAAaqc,IAGvCoE,KAAKmS,SACPnS,KAAKmS,QAAQnZ,UAEfgH,KAAKqS,MAAMhX,UAAU1B,OAAOwX,IAC5BnR,KAAK4E,SAASvJ,UAAU1B,OAAOwX,IAC/BnR,KAAK4E,SAASxjB,aAAa,gBAAiB,SAC5C4hB,GAAYE,oBAAoBlD,KAAKqS,MAAO,UAC5C9R,GAAaqB,QAAQ5B,KAAK4E,SAAUiM,GAAgB/Q,EAhBpD,CAiBF,CACA,UAAA+D,CAAWC,GAET,GAAgC,iBADhCA,EAASa,MAAMd,WAAWC,IACRxlB,YAA2B,GAAUwlB,EAAOxlB,YAAgE,mBAA3CwlB,EAAOxlB,UAAUgF,sBAElG,MAAM,IAAIkhB,UAAU,GAAG+L,GAAO9L,+GAEhC,OAAOX,CACT,CACA,aAAA0O,GACE,QAAsB,IAAX,EACT,MAAM,IAAIhO,UAAU,gEAEtB,IAAImO,EAAmB3S,KAAK4E,SACG,WAA3B5E,KAAK6E,QAAQvmB,UACfq0B,EAAmB3S,KAAKoS,QACf,GAAUpS,KAAK6E,QAAQvmB,WAChCq0B,EAAmBjY,GAAWsF,KAAK6E,QAAQvmB,WACA,iBAA3B0hB,KAAK6E,QAAQvmB,YAC7Bq0B,EAAmB3S,KAAK6E,QAAQvmB,WAElC,MAAM0zB,EAAehS,KAAK4S,mBAC1B5S,KAAKmS,QAAU,GAAoBQ,EAAkB3S,KAAKqS,MAAOL,EACnE,CACA,QAAArC,GACE,OAAO3P,KAAKqS,MAAMhX,UAAU7W,SAAS2sB,GACvC,CACA,aAAA0B,GACE,MAAMC,EAAiB9S,KAAKoS,QAC5B,GAAIU,EAAezX,UAAU7W,SArKN,WAsKrB,OAAOmtB,GAET,GAAImB,EAAezX,UAAU7W,SAvKJ,aAwKvB,OAAOotB,GAET,GAAIkB,EAAezX,UAAU7W,SAzKA,iBA0K3B,MA5JsB,MA8JxB,GAAIsuB,EAAezX,UAAU7W,SA3KE,mBA4K7B,MA9JyB,SAkK3B,MAAMuuB,EAAkF,QAA1E9tB,iBAAiB+a,KAAKqS,OAAOvX,iBAAiB,iBAAiB6K,OAC7E,OAAImN,EAAezX,UAAU7W,SArLP,UAsLbuuB,EAAQvB,GAAmBD,GAE7BwB,EAAQrB,GAAsBD,EACvC,CACA,aAAAc,GACE,OAAkD,OAA3CvS,KAAK4E,SAAS5J,QAnLD,UAoLtB,CACA,UAAAgY,GACE,MAAM,OACJhrB,GACEgY,KAAK6E,QACT,MAAsB,iBAAX7c,EACFA,EAAO9F,MAAM,KAAKY,KAAInF,GAAS4f,OAAOgQ,SAAS5vB,EAAO,MAEzC,mBAAXqK,EACFirB,GAAcjrB,EAAOirB,EAAYjT,KAAK4E,UAExC5c,CACT,CACA,gBAAA4qB,GACE,MAAMM,EAAwB,CAC5Bx0B,UAAWshB,KAAK6S,gBAChBzc,UAAW,CAAC,CACV9V,KAAM,kBACNmB,QAAS,CACPwM,SAAU+R,KAAK6E,QAAQ5W,WAExB,CACD3N,KAAM,SACNmB,QAAS,CACPuG,OAAQgY,KAAKgT,iBAanB,OAPIhT,KAAKsS,WAAsC,WAAzBtS,KAAK6E,QAAQkN,WACjC/O,GAAYC,iBAAiBjD,KAAKqS,MAAO,SAAU,UACnDa,EAAsB9c,UAAY,CAAC,CACjC9V,KAAM,cACNC,SAAS,KAGN,IACF2yB,KACArW,GAAQmD,KAAK6E,QAAQmN,aAAc,CAACkB,IAE3C,CACA,eAAAC,EAAgB,IACdr2B,EAAG,OACHyP,IAEA,MAAMggB,EAAQ1G,GAAe1T,KAhOF,8DAgO+B6N,KAAKqS,OAAOlsB,QAAO5G,GAAWob,GAAUpb,KAC7FgtB,EAAM7b,QAMXoN,GAAqByO,EAAOhgB,EAAQzP,IAAQ6zB,IAAmBpE,EAAMnL,SAAS7U,IAASkmB,OACzF,CAGA,sBAAOhW,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAO6nB,GAAS5M,oBAAoBtF,KAAM8D,GAChD,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,CACA,iBAAOsP,CAAWhU,GAChB,GA5QuB,IA4QnBA,EAAMwI,QAAgD,UAAfxI,EAAMqB,MA/QnC,QA+QuDrB,EAAMtiB,IACzE,OAEF,MAAMu2B,EAAcxN,GAAe1T,KAAKkf,IACxC,IAAK,MAAM1J,KAAU0L,EAAa,CAChC,MAAMC,EAAUpB,GAAS7M,YAAYsC,GACrC,IAAK2L,IAAyC,IAA9BA,EAAQzO,QAAQiN,UAC9B,SAEF,MAAMyB,EAAenU,EAAMmU,eACrBC,EAAeD,EAAanS,SAASkS,EAAQjB,OACnD,GAAIkB,EAAanS,SAASkS,EAAQ1O,WAA2C,WAA9B0O,EAAQzO,QAAQiN,YAA2B0B,GAA8C,YAA9BF,EAAQzO,QAAQiN,WAA2B0B,EACnJ,SAIF,GAAIF,EAAQjB,MAAM7tB,SAAS4a,EAAM7S,UAA2B,UAAf6S,EAAMqB,MA/RvC,QA+R2DrB,EAAMtiB,KAAqB,qCAAqCuG,KAAK+b,EAAM7S,OAAO0a,UACvJ,SAEF,MAAMnH,EAAgB,CACpBA,cAAewT,EAAQ1O,UAEN,UAAfxF,EAAMqB,OACRX,EAAckH,WAAa5H,GAE7BkU,EAAQZ,cAAc5S,EACxB,CACF,CACA,4BAAO2T,CAAsBrU,GAI3B,MAAMsU,EAAU,kBAAkBrwB,KAAK+b,EAAM7S,OAAO0a,SAC9C0M,EAjTW,WAiTKvU,EAAMtiB,IACtB82B,EAAkB,CAAClD,GAAgBC,IAAkBvP,SAAShC,EAAMtiB,KAC1E,IAAK82B,IAAoBD,EACvB,OAEF,GAAID,IAAYC,EACd,OAEFvU,EAAMkD,iBAGN,MAAMuR,EAAkB7T,KAAKgG,QAAQoL,IAA0BpR,KAAO6F,GAAeM,KAAKnG,KAAMoR,IAAwB,IAAMvL,GAAehhB,KAAKmb,KAAMoR,IAAwB,IAAMvL,GAAeC,QAAQsL,GAAwBhS,EAAMW,eAAehb,YACpPwF,EAAW2nB,GAAS5M,oBAAoBuO,GAC9C,GAAID,EAIF,OAHAxU,EAAM0U,kBACNvpB,EAASslB,YACTtlB,EAAS4oB,gBAAgB/T,GAGvB7U,EAASolB,aAEXvQ,EAAM0U,kBACNvpB,EAASqlB,OACTiE,EAAgBpB,QAEpB,EAOFlS,GAAac,GAAGhc,SAAU4rB,GAAwBG,GAAwBc,GAASuB,uBACnFlT,GAAac,GAAGhc,SAAU4rB,GAAwBK,GAAeY,GAASuB,uBAC1ElT,GAAac,GAAGhc,SAAU2rB,GAAwBkB,GAASkB,YAC3D7S,GAAac,GAAGhc,SAAU6rB,GAAsBgB,GAASkB,YACzD7S,GAAac,GAAGhc,SAAU2rB,GAAwBI,IAAwB,SAAUhS,GAClFA,EAAMkD,iBACN4P,GAAS5M,oBAAoBtF,MAAM2H,QACrC,IAMAxL,GAAmB+V,IAcnB,MAAM6B,GAAS,WAETC,GAAoB,OACpBC,GAAkB,gBAAgBF,KAClCG,GAAY,CAChBC,UAAW,iBACXC,cAAe,KACfhP,YAAY,EACZzK,WAAW,EAEX0Z,YAAa,QAETC,GAAgB,CACpBH,UAAW,SACXC,cAAe,kBACfhP,WAAY,UACZzK,UAAW,UACX0Z,YAAa,oBAOf,MAAME,WAAiB9Q,GACrB,WAAAU,CAAYL,GACVa,QACA3E,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/B9D,KAAKwU,aAAc,EACnBxU,KAAK4E,SAAW,IAClB,CAGA,kBAAWlB,GACT,OAAOwQ,EACT,CACA,sBAAWvQ,GACT,OAAO2Q,EACT,CACA,eAAW/X,GACT,OAAOwX,EACT,CAGA,IAAAlE,CAAKxT,GACH,IAAK2D,KAAK6E,QAAQlK,UAEhB,YADAkC,GAAQR,GAGV2D,KAAKyU,UACL,MAAMl1B,EAAUygB,KAAK0U,cACjB1U,KAAK6E,QAAQO,YACfvJ,GAAOtc,GAETA,EAAQ8b,UAAU5E,IAAIud,IACtBhU,KAAK2U,mBAAkB,KACrB9X,GAAQR,EAAS,GAErB,CACA,IAAAuT,CAAKvT,GACE2D,KAAK6E,QAAQlK,WAIlBqF,KAAK0U,cAAcrZ,UAAU1B,OAAOqa,IACpChU,KAAK2U,mBAAkB,KACrB3U,KAAK+E,UACLlI,GAAQR,EAAS,KANjBQ,GAAQR,EAQZ,CACA,OAAA0I,GACO/E,KAAKwU,cAGVjU,GAAaC,IAAIR,KAAK4E,SAAUqP,IAChCjU,KAAK4E,SAASjL,SACdqG,KAAKwU,aAAc,EACrB,CAGA,WAAAE,GACE,IAAK1U,KAAK4E,SAAU,CAClB,MAAMgQ,EAAWvvB,SAASwvB,cAAc,OACxCD,EAAST,UAAYnU,KAAK6E,QAAQsP,UAC9BnU,KAAK6E,QAAQO,YACfwP,EAASvZ,UAAU5E,IApFD,QAsFpBuJ,KAAK4E,SAAWgQ,CAClB,CACA,OAAO5U,KAAK4E,QACd,CACA,iBAAAZ,CAAkBF,GAGhB,OADAA,EAAOuQ,YAAc3Z,GAAWoJ,EAAOuQ,aAChCvQ,CACT,CACA,OAAA2Q,GACE,GAAIzU,KAAKwU,YACP,OAEF,MAAMj1B,EAAUygB,KAAK0U,cACrB1U,KAAK6E,QAAQwP,YAAYS,OAAOv1B,GAChCghB,GAAac,GAAG9hB,EAAS00B,IAAiB,KACxCpX,GAAQmD,KAAK6E,QAAQuP,cAAc,IAErCpU,KAAKwU,aAAc,CACrB,CACA,iBAAAG,CAAkBtY,GAChBW,GAAuBX,EAAU2D,KAAK0U,cAAe1U,KAAK6E,QAAQO,WACpE,EAeF,MAEM2P,GAAc,gBACdC,GAAkB,UAAUD,KAC5BE,GAAoB,cAAcF,KAGlCG,GAAmB,WACnBC,GAAY,CAChBC,WAAW,EACXC,YAAa,MAETC,GAAgB,CACpBF,UAAW,UACXC,YAAa,WAOf,MAAME,WAAkB9R,GACtB,WAAAU,CAAYL,GACVa,QACA3E,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/B9D,KAAKwV,WAAY,EACjBxV,KAAKyV,qBAAuB,IAC9B,CAGA,kBAAW/R,GACT,OAAOyR,EACT,CACA,sBAAWxR,GACT,OAAO2R,EACT,CACA,eAAW/Y,GACT,MArCW,WAsCb,CAGA,QAAAmZ,GACM1V,KAAKwV,YAGLxV,KAAK6E,QAAQuQ,WACfpV,KAAK6E,QAAQwQ,YAAY5C,QAE3BlS,GAAaC,IAAInb,SAAU0vB,IAC3BxU,GAAac,GAAGhc,SAAU2vB,IAAiB5V,GAASY,KAAK2V,eAAevW,KACxEmB,GAAac,GAAGhc,SAAU4vB,IAAmB7V,GAASY,KAAK4V,eAAexW,KAC1EY,KAAKwV,WAAY,EACnB,CACA,UAAAK,GACO7V,KAAKwV,YAGVxV,KAAKwV,WAAY,EACjBjV,GAAaC,IAAInb,SAAU0vB,IAC7B,CAGA,cAAAY,CAAevW,GACb,MAAM,YACJiW,GACErV,KAAK6E,QACT,GAAIzF,EAAM7S,SAAWlH,UAAY+Z,EAAM7S,SAAW8oB,GAAeA,EAAY7wB,SAAS4a,EAAM7S,QAC1F,OAEF,MAAM1L,EAAWglB,GAAeU,kBAAkB8O,GAC1B,IAApBx0B,EAAS6P,OACX2kB,EAAY5C,QACHzS,KAAKyV,uBAAyBP,GACvCr0B,EAASA,EAAS6P,OAAS,GAAG+hB,QAE9B5xB,EAAS,GAAG4xB,OAEhB,CACA,cAAAmD,CAAexW,GAzED,QA0ERA,EAAMtiB,MAGVkjB,KAAKyV,qBAAuBrW,EAAM0W,SAAWZ,GA5EzB,UA6EtB,EAeF,MAAMa,GAAyB,oDACzBC,GAA0B,cAC1BC,GAAmB,gBACnBC,GAAkB,eAMxB,MAAMC,GACJ,WAAAhS,GACEnE,KAAK4E,SAAWvf,SAAS6G,IAC3B,CAGA,QAAAkqB,GAEE,MAAMC,EAAgBhxB,SAASC,gBAAgBuC,YAC/C,OAAO1F,KAAKoC,IAAI3E,OAAO02B,WAAaD,EACtC,CACA,IAAAzG,GACE,MAAM/rB,EAAQmc,KAAKoW,WACnBpW,KAAKuW,mBAELvW,KAAKwW,sBAAsBxW,KAAK4E,SAAUqR,IAAkBQ,GAAmBA,EAAkB5yB,IAEjGmc,KAAKwW,sBAAsBT,GAAwBE,IAAkBQ,GAAmBA,EAAkB5yB,IAC1Gmc,KAAKwW,sBAAsBR,GAAyBE,IAAiBO,GAAmBA,EAAkB5yB,GAC5G,CACA,KAAAwO,GACE2N,KAAK0W,wBAAwB1W,KAAK4E,SAAU,YAC5C5E,KAAK0W,wBAAwB1W,KAAK4E,SAAUqR,IAC5CjW,KAAK0W,wBAAwBX,GAAwBE,IACrDjW,KAAK0W,wBAAwBV,GAAyBE,GACxD,CACA,aAAAS,GACE,OAAO3W,KAAKoW,WAAa,CAC3B,CAGA,gBAAAG,GACEvW,KAAK4W,sBAAsB5W,KAAK4E,SAAU,YAC1C5E,KAAK4E,SAAS7jB,MAAM+K,SAAW,QACjC,CACA,qBAAA0qB,CAAsBzc,EAAU8c,EAAexa,GAC7C,MAAMya,EAAiB9W,KAAKoW,WAS5BpW,KAAK+W,2BAA2Bhd,GARHxa,IAC3B,GAAIA,IAAYygB,KAAK4E,UAAYhlB,OAAO02B,WAAa/2B,EAAQsI,YAAcivB,EACzE,OAEF9W,KAAK4W,sBAAsBr3B,EAASs3B,GACpC,MAAMJ,EAAkB72B,OAAOqF,iBAAiB1F,GAASub,iBAAiB+b,GAC1Et3B,EAAQwB,MAAMi2B,YAAYH,EAAe,GAAGxa,EAASkB,OAAOC,WAAWiZ,QAAsB,GAGjG,CACA,qBAAAG,CAAsBr3B,EAASs3B,GAC7B,MAAMI,EAAc13B,EAAQwB,MAAM+Z,iBAAiB+b,GAC/CI,GACFjU,GAAYC,iBAAiB1jB,EAASs3B,EAAeI,EAEzD,CACA,uBAAAP,CAAwB3c,EAAU8c,GAWhC7W,KAAK+W,2BAA2Bhd,GAVHxa,IAC3B,MAAM5B,EAAQqlB,GAAYQ,iBAAiBjkB,EAASs3B,GAEtC,OAAVl5B,GAIJqlB,GAAYE,oBAAoB3jB,EAASs3B,GACzCt3B,EAAQwB,MAAMi2B,YAAYH,EAAel5B,IAJvC4B,EAAQwB,MAAMm2B,eAAeL,EAIgB,GAGnD,CACA,0BAAAE,CAA2Bhd,EAAUod,GACnC,GAAI,GAAUpd,GACZod,EAASpd,QAGX,IAAK,MAAM6L,KAAOC,GAAe1T,KAAK4H,EAAUiG,KAAK4E,UACnDuS,EAASvR,EAEb,EAeF,MAEMwR,GAAc,YAGdC,GAAe,OAAOD,KACtBE,GAAyB,gBAAgBF,KACzCG,GAAiB,SAASH,KAC1BI,GAAe,OAAOJ,KACtBK,GAAgB,QAAQL,KACxBM,GAAiB,SAASN,KAC1BO,GAAsB,gBAAgBP,KACtCQ,GAA0B,oBAAoBR,KAC9CS,GAA0B,kBAAkBT,KAC5CU,GAAyB,QAAQV,cACjCW,GAAkB,aAElBC,GAAoB,OACpBC,GAAoB,eAKpBC,GAAY,CAChBtD,UAAU,EACVnC,OAAO,EACPzH,UAAU,GAENmN,GAAgB,CACpBvD,SAAU,mBACVnC,MAAO,UACPzH,SAAU,WAOZ,MAAMoN,WAAc1T,GAClB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKqY,QAAUxS,GAAeC,QArBV,gBAqBmC9F,KAAK4E,UAC5D5E,KAAKsY,UAAYtY,KAAKuY,sBACtBvY,KAAKwY,WAAaxY,KAAKyY,uBACvBzY,KAAK2P,UAAW,EAChB3P,KAAKmP,kBAAmB,EACxBnP,KAAK0Y,WAAa,IAAIvC,GACtBnW,KAAK6L,oBACP,CAGA,kBAAWnI,GACT,OAAOwU,EACT,CACA,sBAAWvU,GACT,OAAOwU,EACT,CACA,eAAW5b,GACT,MA1DW,OA2Db,CAGA,MAAAoL,CAAO7H,GACL,OAAOE,KAAK2P,SAAW3P,KAAK4P,OAAS5P,KAAK6P,KAAK/P,EACjD,CACA,IAAA+P,CAAK/P,GACCE,KAAK2P,UAAY3P,KAAKmP,kBAGR5O,GAAaqB,QAAQ5B,KAAK4E,SAAU4S,GAAc,CAClE1X,kBAEYkC,mBAGdhC,KAAK2P,UAAW,EAChB3P,KAAKmP,kBAAmB,EACxBnP,KAAK0Y,WAAW9I,OAChBvqB,SAAS6G,KAAKmP,UAAU5E,IAAIshB,IAC5B/X,KAAK2Y,gBACL3Y,KAAKsY,UAAUzI,MAAK,IAAM7P,KAAK4Y,aAAa9Y,KAC9C,CACA,IAAA8P,GACO5P,KAAK2P,WAAY3P,KAAKmP,mBAGT5O,GAAaqB,QAAQ5B,KAAK4E,SAAUyS,IACxCrV,mBAGdhC,KAAK2P,UAAW,EAChB3P,KAAKmP,kBAAmB,EACxBnP,KAAKwY,WAAW3C,aAChB7V,KAAK4E,SAASvJ,UAAU1B,OAAOqe,IAC/BhY,KAAKmF,gBAAe,IAAMnF,KAAK6Y,cAAc7Y,KAAK4E,SAAU5E,KAAKgO,gBACnE,CACA,OAAAjJ,GACExE,GAAaC,IAAI5gB,OAAQw3B,IACzB7W,GAAaC,IAAIR,KAAKqY,QAASjB,IAC/BpX,KAAKsY,UAAUvT,UACf/E,KAAKwY,WAAW3C,aAChBlR,MAAMI,SACR,CACA,YAAA+T,GACE9Y,KAAK2Y,eACP,CAGA,mBAAAJ,GACE,OAAO,IAAIhE,GAAS,CAClB5Z,UAAWmG,QAAQd,KAAK6E,QAAQ+P,UAEhCxP,WAAYpF,KAAKgO,eAErB,CACA,oBAAAyK,GACE,OAAO,IAAIlD,GAAU,CACnBF,YAAarV,KAAK4E,UAEtB,CACA,YAAAgU,CAAa9Y,GAENza,SAAS6G,KAAK1H,SAASwb,KAAK4E,WAC/Bvf,SAAS6G,KAAK4oB,OAAO9U,KAAK4E,UAE5B5E,KAAK4E,SAAS7jB,MAAMgxB,QAAU,QAC9B/R,KAAK4E,SAASzjB,gBAAgB,eAC9B6e,KAAK4E,SAASxjB,aAAa,cAAc,GACzC4e,KAAK4E,SAASxjB,aAAa,OAAQ,UACnC4e,KAAK4E,SAASnZ,UAAY,EAC1B,MAAMstB,EAAYlT,GAAeC,QA7GT,cA6GsC9F,KAAKqY,SAC/DU,IACFA,EAAUttB,UAAY,GAExBoQ,GAAOmE,KAAK4E,UACZ5E,KAAK4E,SAASvJ,UAAU5E,IAAIuhB,IAU5BhY,KAAKmF,gBATsB,KACrBnF,KAAK6E,QAAQ4N,OACfzS,KAAKwY,WAAW9C,WAElB1V,KAAKmP,kBAAmB,EACxB5O,GAAaqB,QAAQ5B,KAAK4E,SAAU6S,GAAe,CACjD3X,iBACA,GAEoCE,KAAKqY,QAASrY,KAAKgO,cAC7D,CACA,kBAAAnC,GACEtL,GAAac,GAAGrB,KAAK4E,SAAUiT,IAAyBzY,IAhJvC,WAiJXA,EAAMtiB,MAGNkjB,KAAK6E,QAAQmG,SACfhL,KAAK4P,OAGP5P,KAAKgZ,6BAA4B,IAEnCzY,GAAac,GAAGzhB,OAAQ83B,IAAgB,KAClC1X,KAAK2P,WAAa3P,KAAKmP,kBACzBnP,KAAK2Y,eACP,IAEFpY,GAAac,GAAGrB,KAAK4E,SAAUgT,IAAyBxY,IAEtDmB,GAAae,IAAItB,KAAK4E,SAAU+S,IAAqBsB,IAC/CjZ,KAAK4E,WAAaxF,EAAM7S,QAAUyT,KAAK4E,WAAaqU,EAAO1sB,SAGjC,WAA1ByT,KAAK6E,QAAQ+P,SAIb5U,KAAK6E,QAAQ+P,UACf5U,KAAK4P,OAJL5P,KAAKgZ,6BAKP,GACA,GAEN,CACA,UAAAH,GACE7Y,KAAK4E,SAAS7jB,MAAMgxB,QAAU,OAC9B/R,KAAK4E,SAASxjB,aAAa,eAAe,GAC1C4e,KAAK4E,SAASzjB,gBAAgB,cAC9B6e,KAAK4E,SAASzjB,gBAAgB,QAC9B6e,KAAKmP,kBAAmB,EACxBnP,KAAKsY,UAAU1I,MAAK,KAClBvqB,SAAS6G,KAAKmP,UAAU1B,OAAOoe,IAC/B/X,KAAKkZ,oBACLlZ,KAAK0Y,WAAWrmB,QAChBkO,GAAaqB,QAAQ5B,KAAK4E,SAAU2S,GAAe,GAEvD,CACA,WAAAvJ,GACE,OAAOhO,KAAK4E,SAASvJ,UAAU7W,SAjLT,OAkLxB,CACA,0BAAAw0B,GAEE,GADkBzY,GAAaqB,QAAQ5B,KAAK4E,SAAU0S,IACxCtV,iBACZ,OAEF,MAAMmX,EAAqBnZ,KAAK4E,SAASvX,aAAehI,SAASC,gBAAgBsC,aAC3EwxB,EAAmBpZ,KAAK4E,SAAS7jB,MAAMiL,UAEpB,WAArBotB,GAAiCpZ,KAAK4E,SAASvJ,UAAU7W,SAASyzB,MAGjEkB,IACHnZ,KAAK4E,SAAS7jB,MAAMiL,UAAY,UAElCgU,KAAK4E,SAASvJ,UAAU5E,IAAIwhB,IAC5BjY,KAAKmF,gBAAe,KAClBnF,KAAK4E,SAASvJ,UAAU1B,OAAOse,IAC/BjY,KAAKmF,gBAAe,KAClBnF,KAAK4E,SAAS7jB,MAAMiL,UAAYotB,CAAgB,GAC/CpZ,KAAKqY,QAAQ,GACfrY,KAAKqY,SACRrY,KAAK4E,SAAS6N,QAChB,CAMA,aAAAkG,GACE,MAAMQ,EAAqBnZ,KAAK4E,SAASvX,aAAehI,SAASC,gBAAgBsC,aAC3EkvB,EAAiB9W,KAAK0Y,WAAWtC,WACjCiD,EAAoBvC,EAAiB,EAC3C,GAAIuC,IAAsBF,EAAoB,CAC5C,MAAMr3B,EAAWma,KAAU,cAAgB,eAC3C+D,KAAK4E,SAAS7jB,MAAMe,GAAY,GAAGg1B,KACrC,CACA,IAAKuC,GAAqBF,EAAoB,CAC5C,MAAMr3B,EAAWma,KAAU,eAAiB,cAC5C+D,KAAK4E,SAAS7jB,MAAMe,GAAY,GAAGg1B,KACrC,CACF,CACA,iBAAAoC,GACElZ,KAAK4E,SAAS7jB,MAAMu4B,YAAc,GAClCtZ,KAAK4E,SAAS7jB,MAAMw4B,aAAe,EACrC,CAGA,sBAAO9c,CAAgBqH,EAAQhE,GAC7B,OAAOE,KAAKwH,MAAK,WACf,MAAMnd,EAAO+tB,GAAM9S,oBAAoBtF,KAAM8D,GAC7C,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQhE,EAJb,CAKF,GACF,EAOFS,GAAac,GAAGhc,SAAUyyB,GA9OK,4BA8O2C,SAAU1Y,GAClF,MAAM7S,EAASsZ,GAAec,uBAAuB3G,MACjD,CAAC,IAAK,QAAQoB,SAASpB,KAAKiH,UAC9B7H,EAAMkD,iBAER/B,GAAae,IAAI/U,EAAQirB,IAAcgC,IACjCA,EAAUxX,kBAIdzB,GAAae,IAAI/U,EAAQgrB,IAAgB,KACnC5c,GAAUqF,OACZA,KAAKyS,OACP,GACA,IAIJ,MAAMgH,EAAc5T,GAAeC,QAnQb,eAoQlB2T,GACFrB,GAAM/S,YAAYoU,GAAa7J,OAEpBwI,GAAM9S,oBAAoB/Y,GAClCob,OAAO3H,KACd,IACA6G,GAAqBuR,IAMrBjc,GAAmBic,IAcnB,MAEMsB,GAAc,gBACdC,GAAiB,YACjBC,GAAwB,OAAOF,KAAcC,KAE7CE,GAAoB,OACpBC,GAAuB,UACvBC,GAAoB,SAEpBC,GAAgB,kBAChBC,GAAe,OAAOP,KACtBQ,GAAgB,QAAQR,KACxBS,GAAe,OAAOT,KACtBU,GAAuB,gBAAgBV,KACvCW,GAAiB,SAASX,KAC1BY,GAAe,SAASZ,KACxBa,GAAyB,QAAQb,KAAcC,KAC/Ca,GAAwB,kBAAkBd,KAE1Ce,GAAY,CAChB7F,UAAU,EACV5J,UAAU,EACVvgB,QAAQ,GAEJiwB,GAAgB,CACpB9F,SAAU,mBACV5J,SAAU,UACVvgB,OAAQ,WAOV,MAAMkwB,WAAkBjW,GACtB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAK2P,UAAW,EAChB3P,KAAKsY,UAAYtY,KAAKuY,sBACtBvY,KAAKwY,WAAaxY,KAAKyY,uBACvBzY,KAAK6L,oBACP,CAGA,kBAAWnI,GACT,OAAO+W,EACT,CACA,sBAAW9W,GACT,OAAO+W,EACT,CACA,eAAWne,GACT,MApDW,WAqDb,CAGA,MAAAoL,CAAO7H,GACL,OAAOE,KAAK2P,SAAW3P,KAAK4P,OAAS5P,KAAK6P,KAAK/P,EACjD,CACA,IAAA+P,CAAK/P,GACCE,KAAK2P,UAGSpP,GAAaqB,QAAQ5B,KAAK4E,SAAUqV,GAAc,CAClEna,kBAEYkC,mBAGdhC,KAAK2P,UAAW,EAChB3P,KAAKsY,UAAUzI,OACV7P,KAAK6E,QAAQpa,SAChB,IAAI0rB,IAAkBvG,OAExB5P,KAAK4E,SAASxjB,aAAa,cAAc,GACzC4e,KAAK4E,SAASxjB,aAAa,OAAQ,UACnC4e,KAAK4E,SAASvJ,UAAU5E,IAAIqjB,IAW5B9Z,KAAKmF,gBAVoB,KAClBnF,KAAK6E,QAAQpa,SAAUuV,KAAK6E,QAAQ+P,UACvC5U,KAAKwY,WAAW9C,WAElB1V,KAAK4E,SAASvJ,UAAU5E,IAAIojB,IAC5B7Z,KAAK4E,SAASvJ,UAAU1B,OAAOmgB,IAC/BvZ,GAAaqB,QAAQ5B,KAAK4E,SAAUsV,GAAe,CACjDpa,iBACA,GAEkCE,KAAK4E,UAAU,GACvD,CACA,IAAAgL,GACO5P,KAAK2P,WAGQpP,GAAaqB,QAAQ5B,KAAK4E,SAAUuV,IACxCnY,mBAGdhC,KAAKwY,WAAW3C,aAChB7V,KAAK4E,SAASgW,OACd5a,KAAK2P,UAAW,EAChB3P,KAAK4E,SAASvJ,UAAU5E,IAAIsjB,IAC5B/Z,KAAKsY,UAAU1I,OAUf5P,KAAKmF,gBAToB,KACvBnF,KAAK4E,SAASvJ,UAAU1B,OAAOkgB,GAAmBE,IAClD/Z,KAAK4E,SAASzjB,gBAAgB,cAC9B6e,KAAK4E,SAASzjB,gBAAgB,QACzB6e,KAAK6E,QAAQpa,SAChB,IAAI0rB,IAAkB9jB,QAExBkO,GAAaqB,QAAQ5B,KAAK4E,SAAUyV,GAAe,GAEfra,KAAK4E,UAAU,IACvD,CACA,OAAAG,GACE/E,KAAKsY,UAAUvT,UACf/E,KAAKwY,WAAW3C,aAChBlR,MAAMI,SACR,CAGA,mBAAAwT,GACE,MASM5d,EAAYmG,QAAQd,KAAK6E,QAAQ+P,UACvC,OAAO,IAAIL,GAAS,CAClBJ,UA3HsB,qBA4HtBxZ,YACAyK,YAAY,EACZiP,YAAarU,KAAK4E,SAAS7f,WAC3BqvB,cAAezZ,EAfK,KACU,WAA1BqF,KAAK6E,QAAQ+P,SAIjB5U,KAAK4P,OAHHrP,GAAaqB,QAAQ5B,KAAK4E,SAAUwV,GAG3B,EAUgC,MAE/C,CACA,oBAAA3B,GACE,OAAO,IAAIlD,GAAU,CACnBF,YAAarV,KAAK4E,UAEtB,CACA,kBAAAiH,GACEtL,GAAac,GAAGrB,KAAK4E,SAAU4V,IAAuBpb,IA5IvC,WA6ITA,EAAMtiB,MAGNkjB,KAAK6E,QAAQmG,SACfhL,KAAK4P,OAGPrP,GAAaqB,QAAQ5B,KAAK4E,SAAUwV,IAAqB,GAE7D,CAGA,sBAAO3d,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOswB,GAAUrV,oBAAoBtF,KAAM8D,GACjD,GAAsB,iBAAXA,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQ9D,KAJb,CAKF,GACF,EAOFO,GAAac,GAAGhc,SAAUk1B,GA7JK,gCA6J2C,SAAUnb,GAClF,MAAM7S,EAASsZ,GAAec,uBAAuB3G,MAIrD,GAHI,CAAC,IAAK,QAAQoB,SAASpB,KAAKiH,UAC9B7H,EAAMkD,iBAEJpH,GAAW8E,MACb,OAEFO,GAAae,IAAI/U,EAAQ8tB,IAAgB,KAEnC1f,GAAUqF,OACZA,KAAKyS,OACP,IAIF,MAAMgH,EAAc5T,GAAeC,QAAQkU,IACvCP,GAAeA,IAAgBltB,GACjCouB,GAAUtV,YAAYoU,GAAa7J,OAExB+K,GAAUrV,oBAAoB/Y,GACtCob,OAAO3H,KACd,IACAO,GAAac,GAAGzhB,OAAQg6B,IAAuB,KAC7C,IAAK,MAAM7f,KAAY8L,GAAe1T,KAAK6nB,IACzCW,GAAUrV,oBAAoBvL,GAAU8V,MAC1C,IAEFtP,GAAac,GAAGzhB,OAAQ06B,IAAc,KACpC,IAAK,MAAM/6B,KAAWsmB,GAAe1T,KAAK,gDACG,UAAvClN,iBAAiB1F,GAASiC,UAC5Bm5B,GAAUrV,oBAAoB/lB,GAASqwB,MAE3C,IAEF/I,GAAqB8T,IAMrBxe,GAAmBwe,IAUnB,MACME,GAAmB,CAEvB,IAAK,CAAC,QAAS,MAAO,KAAM,OAAQ,OAHP,kBAI7BhqB,EAAG,CAAC,SAAU,OAAQ,QAAS,OAC/BiqB,KAAM,GACNhqB,EAAG,GACHiqB,GAAI,GACJC,IAAK,GACLC,KAAM,GACNC,GAAI,GACJC,IAAK,GACLC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJxqB,EAAG,GACH0b,IAAK,CAAC,MAAO,SAAU,MAAO,QAAS,QAAS,UAChD+O,GAAI,GACJC,GAAI,GACJC,EAAG,GACHC,IAAK,GACLC,EAAG,GACHC,MAAO,GACPC,KAAM,GACNC,IAAK,GACLC,IAAK,GACLC,OAAQ,GACRC,EAAG,GACHC,GAAI,IAIAC,GAAgB,IAAIpmB,IAAI,CAAC,aAAc,OAAQ,OAAQ,WAAY,WAAY,SAAU,MAAO,eAShGqmB,GAAmB,0DACnBC,GAAmB,CAAC76B,EAAW86B,KACnC,MAAMC,EAAgB/6B,EAAUvC,SAASC,cACzC,OAAIo9B,EAAqBzb,SAAS0b,IAC5BJ,GAAc/lB,IAAImmB,IACbhc,QAAQ6b,GAAiBt5B,KAAKtB,EAAUg7B,YAM5CF,EAAqB12B,QAAO62B,GAAkBA,aAA0BzY,SAAQ9R,MAAKwqB,GAASA,EAAM55B,KAAKy5B,IAAe,EA0C3HI,GAAY,CAChBC,UAAWtC,GACXuC,QAAS,CAAC,EAEVC,WAAY,GACZxwB,MAAM,EACNywB,UAAU,EACVC,WAAY,KACZC,SAAU,eAENC,GAAgB,CACpBN,UAAW,SACXC,QAAS,SACTC,WAAY,oBACZxwB,KAAM,UACNywB,SAAU,UACVC,WAAY,kBACZC,SAAU,UAENE,GAAqB,CACzBC,MAAO,iCACP5jB,SAAU,oBAOZ,MAAM6jB,WAAwBna,GAC5B,WAAAU,CAAYL,GACVa,QACA3E,KAAK6E,QAAU7E,KAAK6D,WAAWC,EACjC,CAGA,kBAAWJ,GACT,OAAOwZ,EACT,CACA,sBAAWvZ,GACT,OAAO8Z,EACT,CACA,eAAWlhB,GACT,MA3CW,iBA4Cb,CAGA,UAAAshB,GACE,OAAO7gC,OAAOmiB,OAAOa,KAAK6E,QAAQuY,SAASt6B,KAAIghB,GAAU9D,KAAK8d,yBAAyBha,KAAS3d,OAAO2a,QACzG,CACA,UAAAid,GACE,OAAO/d,KAAK6d,aAAantB,OAAS,CACpC,CACA,aAAAstB,CAAcZ,GAMZ,OALApd,KAAKie,cAAcb,GACnBpd,KAAK6E,QAAQuY,QAAU,IAClBpd,KAAK6E,QAAQuY,WACbA,GAEEpd,IACT,CACA,MAAAke,GACE,MAAMC,EAAkB94B,SAASwvB,cAAc,OAC/CsJ,EAAgBC,UAAYpe,KAAKqe,eAAere,KAAK6E,QAAQ2Y,UAC7D,IAAK,MAAOzjB,EAAUukB,KAASthC,OAAOmkB,QAAQnB,KAAK6E,QAAQuY,SACzDpd,KAAKue,YAAYJ,EAAiBG,EAAMvkB,GAE1C,MAAMyjB,EAAWW,EAAgBpY,SAAS,GACpCsX,EAAard,KAAK8d,yBAAyB9d,KAAK6E,QAAQwY,YAI9D,OAHIA,GACFG,EAASniB,UAAU5E,OAAO4mB,EAAWn7B,MAAM,MAEtCs7B,CACT,CAGA,gBAAAvZ,CAAiBH,GACfa,MAAMV,iBAAiBH,GACvB9D,KAAKie,cAAcna,EAAOsZ,QAC5B,CACA,aAAAa,CAAcO,GACZ,IAAK,MAAOzkB,EAAUqjB,KAAYpgC,OAAOmkB,QAAQqd,GAC/C7Z,MAAMV,iBAAiB,CACrBlK,WACA4jB,MAAOP,GACNM,GAEP,CACA,WAAAa,CAAYf,EAAUJ,EAASrjB,GAC7B,MAAM0kB,EAAkB5Y,GAAeC,QAAQ/L,EAAUyjB,GACpDiB,KAGLrB,EAAUpd,KAAK8d,yBAAyBV,IAKpC,GAAUA,GACZpd,KAAK0e,sBAAsBhkB,GAAW0iB,GAAUqB,GAG9Cze,KAAK6E,QAAQhY,KACf4xB,EAAgBL,UAAYpe,KAAKqe,eAAejB,GAGlDqB,EAAgBE,YAAcvB,EAX5BqB,EAAgB9kB,SAYpB,CACA,cAAA0kB,CAAeG,GACb,OAAOxe,KAAK6E,QAAQyY,SApJxB,SAAsBsB,EAAYzB,EAAW0B,GAC3C,IAAKD,EAAWluB,OACd,OAAOkuB,EAET,GAAIC,GAAgD,mBAArBA,EAC7B,OAAOA,EAAiBD,GAE1B,MACME,GADY,IAAIl/B,OAAOm/B,WACKC,gBAAgBJ,EAAY,aACxD/9B,EAAW,GAAGlC,UAAUmgC,EAAgB5yB,KAAKkU,iBAAiB,MACpE,IAAK,MAAM7gB,KAAWsB,EAAU,CAC9B,MAAMo+B,EAAc1/B,EAAQC,SAASC,cACrC,IAAKzC,OAAO4D,KAAKu8B,GAAW/b,SAAS6d,GAAc,CACjD1/B,EAAQoa,SACR,QACF,CACA,MAAMulB,EAAgB,GAAGvgC,UAAUY,EAAQ0B,YACrCk+B,EAAoB,GAAGxgC,OAAOw+B,EAAU,MAAQ,GAAIA,EAAU8B,IAAgB,IACpF,IAAK,MAAMl9B,KAAam9B,EACjBtC,GAAiB76B,EAAWo9B,IAC/B5/B,EAAQ4B,gBAAgBY,EAAUvC,SAGxC,CACA,OAAOs/B,EAAgB5yB,KAAKkyB,SAC9B,CA2HmCgB,CAAaZ,EAAKxe,KAAK6E,QAAQsY,UAAWnd,KAAK6E,QAAQ0Y,YAAciB,CACtG,CACA,wBAAAV,CAAyBU,GACvB,OAAO3hB,GAAQ2hB,EAAK,CAACxe,MACvB,CACA,qBAAA0e,CAAsBn/B,EAASk/B,GAC7B,GAAIze,KAAK6E,QAAQhY,KAGf,OAFA4xB,EAAgBL,UAAY,QAC5BK,EAAgB3J,OAAOv1B,GAGzBk/B,EAAgBE,YAAcp/B,EAAQo/B,WACxC,EAeF,MACMU,GAAwB,IAAI/oB,IAAI,CAAC,WAAY,YAAa,eAC1DgpB,GAAoB,OAEpBC,GAAoB,OACpBC,GAAyB,iBACzBC,GAAiB,SACjBC,GAAmB,gBACnBC,GAAgB,QAChBC,GAAgB,QAahBC,GAAgB,CACpBC,KAAM,OACNC,IAAK,MACLC,MAAO/jB,KAAU,OAAS,QAC1BgkB,OAAQ,SACRC,KAAMjkB,KAAU,QAAU,QAEtBkkB,GAAY,CAChBhD,UAAWtC,GACXuF,WAAW,EACXnyB,SAAU,kBACVoyB,WAAW,EACXC,YAAa,GACbC,MAAO,EACPvwB,mBAAoB,CAAC,MAAO,QAAS,SAAU,QAC/CnD,MAAM,EACN7E,OAAQ,CAAC,EAAG,GACZtJ,UAAW,MACXszB,aAAc,KACdsL,UAAU,EACVC,WAAY,KACZxjB,UAAU,EACVyjB,SAAU,+GACVgD,MAAO,GACP5e,QAAS,eAEL6e,GAAgB,CACpBtD,UAAW,SACXiD,UAAW,UACXnyB,SAAU,mBACVoyB,UAAW,2BACXC,YAAa,oBACbC,MAAO,kBACPvwB,mBAAoB,QACpBnD,KAAM,UACN7E,OAAQ,0BACRtJ,UAAW,oBACXszB,aAAc,yBACdsL,SAAU,UACVC,WAAY,kBACZxjB,SAAU,mBACVyjB,SAAU,SACVgD,MAAO,4BACP5e,QAAS,UAOX,MAAM8e,WAAgBhc,GACpB,WAAAP,CAAY5kB,EAASukB,GACnB,QAAsB,IAAX,EACT,MAAM,IAAIU,UAAU,+DAEtBG,MAAMplB,EAASukB,GAGf9D,KAAK2gB,YAAa,EAClB3gB,KAAK4gB,SAAW,EAChB5gB,KAAK6gB,WAAa,KAClB7gB,KAAK8gB,eAAiB,CAAC,EACvB9gB,KAAKmS,QAAU,KACfnS,KAAK+gB,iBAAmB,KACxB/gB,KAAKghB,YAAc,KAGnBhhB,KAAKihB,IAAM,KACXjhB,KAAKkhB,gBACAlhB,KAAK6E,QAAQ9K,UAChBiG,KAAKmhB,WAET,CAGA,kBAAWzd,GACT,OAAOyc,EACT,CACA,sBAAWxc,GACT,OAAO8c,EACT,CACA,eAAWlkB,GACT,MAxGW,SAyGb,CAGA,MAAA6kB,GACEphB,KAAK2gB,YAAa,CACpB,CACA,OAAAU,GACErhB,KAAK2gB,YAAa,CACpB,CACA,aAAAW,GACEthB,KAAK2gB,YAAc3gB,KAAK2gB,UAC1B,CACA,MAAAhZ,GACO3H,KAAK2gB,aAGV3gB,KAAK8gB,eAAeS,OAASvhB,KAAK8gB,eAAeS,MAC7CvhB,KAAK2P,WACP3P,KAAKwhB,SAGPxhB,KAAKyhB,SACP,CACA,OAAA1c,GACEmI,aAAalN,KAAK4gB,UAClBrgB,GAAaC,IAAIR,KAAK4E,SAAS5J,QAAQykB,IAAiBC,GAAkB1f,KAAK0hB,mBAC3E1hB,KAAK4E,SAASpJ,aAAa,2BAC7BwE,KAAK4E,SAASxjB,aAAa,QAAS4e,KAAK4E,SAASpJ,aAAa,2BAEjEwE,KAAK2hB,iBACLhd,MAAMI,SACR,CACA,IAAA8K,GACE,GAAoC,SAAhC7P,KAAK4E,SAAS7jB,MAAMgxB,QACtB,MAAM,IAAInO,MAAM,uCAElB,IAAM5D,KAAK4hB,mBAAoB5hB,KAAK2gB,WAClC,OAEF,MAAMnH,EAAYjZ,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAlItD,SAoIXqc,GADapmB,GAAeuE,KAAK4E,WACL5E,KAAK4E,SAAS9kB,cAAcwF,iBAAiBd,SAASwb,KAAK4E,UAC7F,GAAI4U,EAAUxX,mBAAqB6f,EACjC,OAIF7hB,KAAK2hB,iBACL,MAAMV,EAAMjhB,KAAK8hB,iBACjB9hB,KAAK4E,SAASxjB,aAAa,mBAAoB6/B,EAAIzlB,aAAa,OAChE,MAAM,UACJ6kB,GACErgB,KAAK6E,QAYT,GAXK7E,KAAK4E,SAAS9kB,cAAcwF,gBAAgBd,SAASwb,KAAKihB,OAC7DZ,EAAUvL,OAAOmM,GACjB1gB,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAhJpC,cAkJnBxF,KAAKmS,QAAUnS,KAAKwS,cAAcyO,GAClCA,EAAI5lB,UAAU5E,IAAI8oB,IAMd,iBAAkBl6B,SAASC,gBAC7B,IAAK,MAAM/F,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK6Z,UAC/CxF,GAAac,GAAG9hB,EAAS,YAAaqc,IAU1CoE,KAAKmF,gBAPY,KACf5E,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAhKrC,WAiKQ,IAApBxF,KAAK6gB,YACP7gB,KAAKwhB,SAEPxhB,KAAK6gB,YAAa,CAAK,GAEK7gB,KAAKihB,IAAKjhB,KAAKgO,cAC/C,CACA,IAAA4B,GACE,GAAK5P,KAAK2P,aAGQpP,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UA/KtD,SAgLHxD,iBAAd,CAQA,GALYhC,KAAK8hB,iBACbzmB,UAAU1B,OAAO4lB,IAIjB,iBAAkBl6B,SAASC,gBAC7B,IAAK,MAAM/F,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK6Z,UAC/CxF,GAAaC,IAAIjhB,EAAS,YAAaqc,IAG3CoE,KAAK8gB,eAA4B,OAAI,EACrC9gB,KAAK8gB,eAAelB,KAAiB,EACrC5f,KAAK8gB,eAAenB,KAAiB,EACrC3f,KAAK6gB,WAAa,KAYlB7gB,KAAKmF,gBAVY,KACXnF,KAAK+hB,yBAGJ/hB,KAAK6gB,YACR7gB,KAAK2hB,iBAEP3hB,KAAK4E,SAASzjB,gBAAgB,oBAC9Bof,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAzMpC,WAyM8D,GAEnDxF,KAAKihB,IAAKjhB,KAAKgO,cA1B7C,CA2BF,CACA,MAAAjjB,GACMiV,KAAKmS,SACPnS,KAAKmS,QAAQpnB,QAEjB,CAGA,cAAA62B,GACE,OAAO9gB,QAAQd,KAAKgiB,YACtB,CACA,cAAAF,GAIE,OAHK9hB,KAAKihB,MACRjhB,KAAKihB,IAAMjhB,KAAKiiB,kBAAkBjiB,KAAKghB,aAAehhB,KAAKkiB,2BAEtDliB,KAAKihB,GACd,CACA,iBAAAgB,CAAkB7E,GAChB,MAAM6D,EAAMjhB,KAAKmiB,oBAAoB/E,GAASc,SAG9C,IAAK+C,EACH,OAAO,KAETA,EAAI5lB,UAAU1B,OAAO2lB,GAAmBC,IAExC0B,EAAI5lB,UAAU5E,IAAI,MAAMuJ,KAAKmE,YAAY5H,aACzC,MAAM6lB,EAvuGKC,KACb,GACEA,GAAUlgC,KAAKmgC,MA/BH,IA+BSngC,KAAKogC,gBACnBl9B,SAASm9B,eAAeH,IACjC,OAAOA,CAAM,EAmuGGI,CAAOziB,KAAKmE,YAAY5H,MAAM1c,WAK5C,OAJAohC,EAAI7/B,aAAa,KAAMghC,GACnBpiB,KAAKgO,eACPiT,EAAI5lB,UAAU5E,IAAI6oB,IAEb2B,CACT,CACA,UAAAyB,CAAWtF,GACTpd,KAAKghB,YAAc5D,EACfpd,KAAK2P,aACP3P,KAAK2hB,iBACL3hB,KAAK6P,OAET,CACA,mBAAAsS,CAAoB/E,GAYlB,OAXIpd,KAAK+gB,iBACP/gB,KAAK+gB,iBAAiB/C,cAAcZ,GAEpCpd,KAAK+gB,iBAAmB,IAAInD,GAAgB,IACvC5d,KAAK6E,QAGRuY,UACAC,WAAYrd,KAAK8d,yBAAyB9d,KAAK6E,QAAQyb,eAGpDtgB,KAAK+gB,gBACd,CACA,sBAAAmB,GACE,MAAO,CACL,CAAC1C,IAAyBxf,KAAKgiB,YAEnC,CACA,SAAAA,GACE,OAAOhiB,KAAK8d,yBAAyB9d,KAAK6E,QAAQ2b,QAAUxgB,KAAK4E,SAASpJ,aAAa,yBACzF,CAGA,4BAAAmnB,CAA6BvjB,GAC3B,OAAOY,KAAKmE,YAAYmB,oBAAoBlG,EAAMW,eAAgBC,KAAK4iB,qBACzE,CACA,WAAA5U,GACE,OAAOhO,KAAK6E,QAAQub,WAAapgB,KAAKihB,KAAOjhB,KAAKihB,IAAI5lB,UAAU7W,SAAS86B,GAC3E,CACA,QAAA3P,GACE,OAAO3P,KAAKihB,KAAOjhB,KAAKihB,IAAI5lB,UAAU7W,SAAS+6B,GACjD,CACA,aAAA/M,CAAcyO,GACZ,MAAMviC,EAAYme,GAAQmD,KAAK6E,QAAQnmB,UAAW,CAACshB,KAAMihB,EAAKjhB,KAAK4E,WAC7Die,EAAahD,GAAcnhC,EAAU+lB,eAC3C,OAAO,GAAoBzE,KAAK4E,SAAUqc,EAAKjhB,KAAK4S,iBAAiBiQ,GACvE,CACA,UAAA7P,GACE,MAAM,OACJhrB,GACEgY,KAAK6E,QACT,MAAsB,iBAAX7c,EACFA,EAAO9F,MAAM,KAAKY,KAAInF,GAAS4f,OAAOgQ,SAAS5vB,EAAO,MAEzC,mBAAXqK,EACFirB,GAAcjrB,EAAOirB,EAAYjT,KAAK4E,UAExC5c,CACT,CACA,wBAAA81B,CAAyBU,GACvB,OAAO3hB,GAAQ2hB,EAAK,CAACxe,KAAK4E,UAC5B,CACA,gBAAAgO,CAAiBiQ,GACf,MAAM3P,EAAwB,CAC5Bx0B,UAAWmkC,EACXzsB,UAAW,CAAC,CACV9V,KAAM,OACNmB,QAAS,CACPuO,mBAAoBgQ,KAAK6E,QAAQ7U,qBAElC,CACD1P,KAAM,SACNmB,QAAS,CACPuG,OAAQgY,KAAKgT,eAEd,CACD1yB,KAAM,kBACNmB,QAAS,CACPwM,SAAU+R,KAAK6E,QAAQ5W,WAExB,CACD3N,KAAM,QACNmB,QAAS,CACPlC,QAAS,IAAIygB,KAAKmE,YAAY5H,eAE/B,CACDjc,KAAM,kBACNC,SAAS,EACTC,MAAO,aACPC,GAAI4J,IAGF2V,KAAK8hB,iBAAiB1gC,aAAa,wBAAyBiJ,EAAK1J,MAAMjC,UAAU,KAIvF,MAAO,IACFw0B,KACArW,GAAQmD,KAAK6E,QAAQmN,aAAc,CAACkB,IAE3C,CACA,aAAAgO,GACE,MAAM4B,EAAW9iB,KAAK6E,QAAQjD,QAAQ1f,MAAM,KAC5C,IAAK,MAAM0f,KAAWkhB,EACpB,GAAgB,UAAZlhB,EACFrB,GAAac,GAAGrB,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAjVlC,SAiV4DxF,KAAK6E,QAAQ9K,UAAUqF,IAC/EY,KAAK2iB,6BAA6BvjB,GAC1CuI,QAAQ,SAEb,GA3VU,WA2VN/F,EAA4B,CACrC,MAAMmhB,EAAUnhB,IAAY+d,GAAgB3f,KAAKmE,YAAYqB,UAnV5C,cAmV0ExF,KAAKmE,YAAYqB,UArV5F,WAsVVwd,EAAWphB,IAAY+d,GAAgB3f,KAAKmE,YAAYqB,UAnV7C,cAmV2ExF,KAAKmE,YAAYqB,UArV5F,YAsVjBjF,GAAac,GAAGrB,KAAK4E,SAAUme,EAAS/iB,KAAK6E,QAAQ9K,UAAUqF,IAC7D,MAAMkU,EAAUtT,KAAK2iB,6BAA6BvjB,GAClDkU,EAAQwN,eAA8B,YAAf1hB,EAAMqB,KAAqBmf,GAAgBD,KAAiB,EACnFrM,EAAQmO,QAAQ,IAElBlhB,GAAac,GAAGrB,KAAK4E,SAAUoe,EAAUhjB,KAAK6E,QAAQ9K,UAAUqF,IAC9D,MAAMkU,EAAUtT,KAAK2iB,6BAA6BvjB,GAClDkU,EAAQwN,eAA8B,aAAf1hB,EAAMqB,KAAsBmf,GAAgBD,IAAiBrM,EAAQ1O,SAASpgB,SAAS4a,EAAMU,eACpHwT,EAAQkO,QAAQ,GAEpB,CAEFxhB,KAAK0hB,kBAAoB,KACnB1hB,KAAK4E,UACP5E,KAAK4P,MACP,EAEFrP,GAAac,GAAGrB,KAAK4E,SAAS5J,QAAQykB,IAAiBC,GAAkB1f,KAAK0hB,kBAChF,CACA,SAAAP,GACE,MAAMX,EAAQxgB,KAAK4E,SAASpJ,aAAa,SACpCglB,IAGAxgB,KAAK4E,SAASpJ,aAAa,eAAkBwE,KAAK4E,SAAS+Z,YAAYhZ,QAC1E3F,KAAK4E,SAASxjB,aAAa,aAAco/B,GAE3CxgB,KAAK4E,SAASxjB,aAAa,yBAA0Bo/B,GACrDxgB,KAAK4E,SAASzjB,gBAAgB,SAChC,CACA,MAAAsgC,GACMzhB,KAAK2P,YAAc3P,KAAK6gB,WAC1B7gB,KAAK6gB,YAAa,GAGpB7gB,KAAK6gB,YAAa,EAClB7gB,KAAKijB,aAAY,KACXjjB,KAAK6gB,YACP7gB,KAAK6P,MACP,GACC7P,KAAK6E,QAAQ0b,MAAM1Q,MACxB,CACA,MAAA2R,GACMxhB,KAAK+hB,yBAGT/hB,KAAK6gB,YAAa,EAClB7gB,KAAKijB,aAAY,KACVjjB,KAAK6gB,YACR7gB,KAAK4P,MACP,GACC5P,KAAK6E,QAAQ0b,MAAM3Q,MACxB,CACA,WAAAqT,CAAYrlB,EAASslB,GACnBhW,aAAalN,KAAK4gB,UAClB5gB,KAAK4gB,SAAW/iB,WAAWD,EAASslB,EACtC,CACA,oBAAAnB,GACE,OAAO/kC,OAAOmiB,OAAOa,KAAK8gB,gBAAgB1f,UAAS,EACrD,CACA,UAAAyC,CAAWC,GACT,MAAMqf,EAAiBngB,GAAYG,kBAAkBnD,KAAK4E,UAC1D,IAAK,MAAMwe,KAAiBpmC,OAAO4D,KAAKuiC,GAClC9D,GAAsB1oB,IAAIysB,WACrBD,EAAeC,GAU1B,OAPAtf,EAAS,IACJqf,KACmB,iBAAXrf,GAAuBA,EAASA,EAAS,CAAC,GAEvDA,EAAS9D,KAAK+D,gBAAgBD,GAC9BA,EAAS9D,KAAKgE,kBAAkBF,GAChC9D,KAAKiE,iBAAiBH,GACfA,CACT,CACA,iBAAAE,CAAkBF,GAchB,OAbAA,EAAOuc,WAAiC,IAArBvc,EAAOuc,UAAsBh7B,SAAS6G,KAAOwO,GAAWoJ,EAAOuc,WACtD,iBAAjBvc,EAAOyc,QAChBzc,EAAOyc,MAAQ,CACb1Q,KAAM/L,EAAOyc,MACb3Q,KAAM9L,EAAOyc,QAGW,iBAAjBzc,EAAO0c,QAChB1c,EAAO0c,MAAQ1c,EAAO0c,MAAM3gC,YAEA,iBAAnBikB,EAAOsZ,UAChBtZ,EAAOsZ,QAAUtZ,EAAOsZ,QAAQv9B,YAE3BikB,CACT,CACA,kBAAA8e,GACE,MAAM9e,EAAS,CAAC,EAChB,IAAK,MAAOhnB,EAAKa,KAAUX,OAAOmkB,QAAQnB,KAAK6E,SACzC7E,KAAKmE,YAAYT,QAAQ5mB,KAASa,IACpCmmB,EAAOhnB,GAAOa,GASlB,OANAmmB,EAAO/J,UAAW,EAClB+J,EAAOlC,QAAU,SAKVkC,CACT,CACA,cAAA6d,GACM3hB,KAAKmS,UACPnS,KAAKmS,QAAQnZ,UACbgH,KAAKmS,QAAU,MAEbnS,KAAKihB,MACPjhB,KAAKihB,IAAItnB,SACTqG,KAAKihB,IAAM,KAEf,CAGA,sBAAOxkB,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOq2B,GAAQpb,oBAAoBtF,KAAM8D,GAC/C,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOF3H,GAAmBukB,IAcnB,MACM2C,GAAiB,kBACjBC,GAAmB,gBACnBC,GAAY,IACb7C,GAAQhd,QACX0Z,QAAS,GACTp1B,OAAQ,CAAC,EAAG,GACZtJ,UAAW,QACX8+B,SAAU,8IACV5b,QAAS,SAEL4hB,GAAgB,IACjB9C,GAAQ/c,YACXyZ,QAAS,kCAOX,MAAMqG,WAAgB/C,GAEpB,kBAAWhd,GACT,OAAO6f,EACT,CACA,sBAAW5f,GACT,OAAO6f,EACT,CACA,eAAWjnB,GACT,MA7BW,SA8Bb,CAGA,cAAAqlB,GACE,OAAO5hB,KAAKgiB,aAAehiB,KAAK0jB,aAClC,CAGA,sBAAAxB,GACE,MAAO,CACL,CAACmB,IAAiBrjB,KAAKgiB,YACvB,CAACsB,IAAmBtjB,KAAK0jB,cAE7B,CACA,WAAAA,GACE,OAAO1jB,KAAK8d,yBAAyB9d,KAAK6E,QAAQuY,QACpD,CAGA,sBAAO3gB,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOo5B,GAAQne,oBAAoBtF,KAAM8D,GAC/C,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOF3H,GAAmBsnB,IAcnB,MAEME,GAAc,gBAEdC,GAAiB,WAAWD,KAC5BE,GAAc,QAAQF,KACtBG,GAAwB,OAAOH,cAE/BI,GAAsB,SAEtBC,GAAwB,SAExBC,GAAqB,YAGrBC,GAAsB,GAAGD,mBAA+CA,uBAGxEE,GAAY,CAChBn8B,OAAQ,KAERo8B,WAAY,eACZC,cAAc,EACd93B,OAAQ,KACR+3B,UAAW,CAAC,GAAK,GAAK,IAElBC,GAAgB,CACpBv8B,OAAQ,gBAERo8B,WAAY,SACZC,aAAc,UACd93B,OAAQ,UACR+3B,UAAW,SAOb,MAAME,WAAkB9f,GACtB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GAGf9D,KAAKykB,aAAe,IAAIvzB,IACxB8O,KAAK0kB,oBAAsB,IAAIxzB,IAC/B8O,KAAK2kB,aAA6D,YAA9C1/B,iBAAiB+a,KAAK4E,UAAU5Y,UAA0B,KAAOgU,KAAK4E,SAC1F5E,KAAK4kB,cAAgB,KACrB5kB,KAAK6kB,UAAY,KACjB7kB,KAAK8kB,oBAAsB,CACzBC,gBAAiB,EACjBC,gBAAiB,GAEnBhlB,KAAKilB,SACP,CAGA,kBAAWvhB,GACT,OAAOygB,EACT,CACA,sBAAWxgB,GACT,OAAO4gB,EACT,CACA,eAAWhoB,GACT,MAhEW,WAiEb,CAGA,OAAA0oB,GACEjlB,KAAKklB,mCACLllB,KAAKmlB,2BACDnlB,KAAK6kB,UACP7kB,KAAK6kB,UAAUO,aAEfplB,KAAK6kB,UAAY7kB,KAAKqlB,kBAExB,IAAK,MAAMC,KAAWtlB,KAAK0kB,oBAAoBvlB,SAC7Ca,KAAK6kB,UAAUU,QAAQD,EAE3B,CACA,OAAAvgB,GACE/E,KAAK6kB,UAAUO,aACfzgB,MAAMI,SACR,CAGA,iBAAAf,CAAkBF,GAShB,OAPAA,EAAOvX,OAASmO,GAAWoJ,EAAOvX,SAAWlH,SAAS6G,KAGtD4X,EAAOsgB,WAAatgB,EAAO9b,OAAS,GAAG8b,EAAO9b,oBAAsB8b,EAAOsgB,WAC3C,iBAArBtgB,EAAOwgB,YAChBxgB,EAAOwgB,UAAYxgB,EAAOwgB,UAAUpiC,MAAM,KAAKY,KAAInF,GAAS4f,OAAOC,WAAW7f,MAEzEmmB,CACT,CACA,wBAAAqhB,GACOnlB,KAAK6E,QAAQwf,eAKlB9jB,GAAaC,IAAIR,KAAK6E,QAAQtY,OAAQs3B,IACtCtjB,GAAac,GAAGrB,KAAK6E,QAAQtY,OAAQs3B,GAAaG,IAAuB5kB,IACvE,MAAMomB,EAAoBxlB,KAAK0kB,oBAAoBvnC,IAAIiiB,EAAM7S,OAAOtB,MACpE,GAAIu6B,EAAmB,CACrBpmB,EAAMkD,iBACN,MAAM3G,EAAOqE,KAAK2kB,cAAgB/kC,OAC5BmE,EAASyhC,EAAkBnhC,UAAY2b,KAAK4E,SAASvgB,UAC3D,GAAIsX,EAAK8pB,SAKP,YAJA9pB,EAAK8pB,SAAS,CACZ9jC,IAAKoC,EACL2hC,SAAU,WAMd/pB,EAAKlQ,UAAY1H,CACnB,KAEJ,CACA,eAAAshC,GACE,MAAM5jC,EAAU,CACdka,KAAMqE,KAAK2kB,aACXL,UAAWtkB,KAAK6E,QAAQyf,UACxBF,WAAYpkB,KAAK6E,QAAQuf,YAE3B,OAAO,IAAIuB,sBAAqBxkB,GAAWnB,KAAK4lB,kBAAkBzkB,IAAU1f,EAC9E,CAGA,iBAAAmkC,CAAkBzkB,GAChB,MAAM0kB,EAAgBlI,GAAS3d,KAAKykB,aAAatnC,IAAI,IAAIwgC,EAAMpxB,OAAO4N,MAChEub,EAAWiI,IACf3d,KAAK8kB,oBAAoBC,gBAAkBpH,EAAMpxB,OAAOlI,UACxD2b,KAAK8lB,SAASD,EAAclI,GAAO,EAE/BqH,GAAmBhlB,KAAK2kB,cAAgBt/B,SAASC,iBAAiBmG,UAClEs6B,EAAkBf,GAAmBhlB,KAAK8kB,oBAAoBE,gBACpEhlB,KAAK8kB,oBAAoBE,gBAAkBA,EAC3C,IAAK,MAAMrH,KAASxc,EAAS,CAC3B,IAAKwc,EAAMqI,eAAgB,CACzBhmB,KAAK4kB,cAAgB,KACrB5kB,KAAKimB,kBAAkBJ,EAAclI,IACrC,QACF,CACA,MAAMuI,EAA2BvI,EAAMpxB,OAAOlI,WAAa2b,KAAK8kB,oBAAoBC,gBAEpF,GAAIgB,GAAmBG,GAGrB,GAFAxQ,EAASiI,IAEJqH,EACH,YAMCe,GAAoBG,GACvBxQ,EAASiI,EAEb,CACF,CACA,gCAAAuH,GACEllB,KAAKykB,aAAe,IAAIvzB,IACxB8O,KAAK0kB,oBAAsB,IAAIxzB,IAC/B,MAAMi1B,EAActgB,GAAe1T,KAAK6xB,GAAuBhkB,KAAK6E,QAAQtY,QAC5E,IAAK,MAAM65B,KAAUD,EAAa,CAEhC,IAAKC,EAAOn7B,MAAQiQ,GAAWkrB,GAC7B,SAEF,MAAMZ,EAAoB3f,GAAeC,QAAQugB,UAAUD,EAAOn7B,MAAO+U,KAAK4E,UAG1EjK,GAAU6qB,KACZxlB,KAAKykB,aAAa1yB,IAAIs0B,UAAUD,EAAOn7B,MAAOm7B,GAC9CpmB,KAAK0kB,oBAAoB3yB,IAAIq0B,EAAOn7B,KAAMu6B,GAE9C,CACF,CACA,QAAAM,CAASv5B,GACHyT,KAAK4kB,gBAAkBr4B,IAG3ByT,KAAKimB,kBAAkBjmB,KAAK6E,QAAQtY,QACpCyT,KAAK4kB,cAAgBr4B,EACrBA,EAAO8O,UAAU5E,IAAIstB,IACrB/jB,KAAKsmB,iBAAiB/5B,GACtBgU,GAAaqB,QAAQ5B,KAAK4E,SAAUgf,GAAgB,CAClD9jB,cAAevT,IAEnB,CACA,gBAAA+5B,CAAiB/5B,GAEf,GAAIA,EAAO8O,UAAU7W,SA9LQ,iBA+L3BqhB,GAAeC,QArLc,mBAqLsBvZ,EAAOyO,QAtLtC,cAsLkEK,UAAU5E,IAAIstB,SAGtG,IAAK,MAAMwC,KAAa1gB,GAAeI,QAAQ1Z,EA9LnB,qBAiM1B,IAAK,MAAMxJ,KAAQ8iB,GAAeM,KAAKogB,EAAWrC,IAChDnhC,EAAKsY,UAAU5E,IAAIstB,GAGzB,CACA,iBAAAkC,CAAkBxhC,GAChBA,EAAO4W,UAAU1B,OAAOoqB,IACxB,MAAMyC,EAAc3gB,GAAe1T,KAAK,GAAG6xB,MAAyBD,KAAuBt/B,GAC3F,IAAK,MAAM9E,KAAQ6mC,EACjB7mC,EAAK0b,UAAU1B,OAAOoqB,GAE1B,CAGA,sBAAOtnB,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAOm6B,GAAUlf,oBAAoBtF,KAAM8D,GACjD,GAAsB,iBAAXA,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOFvD,GAAac,GAAGzhB,OAAQkkC,IAAuB,KAC7C,IAAK,MAAM2C,KAAO5gB,GAAe1T,KApOT,0BAqOtBqyB,GAAUlf,oBAAoBmhB,EAChC,IAOFtqB,GAAmBqoB,IAcnB,MAEMkC,GAAc,UACdC,GAAe,OAAOD,KACtBE,GAAiB,SAASF,KAC1BG,GAAe,OAAOH,KACtBI,GAAgB,QAAQJ,KACxBK,GAAuB,QAAQL,KAC/BM,GAAgB,UAAUN,KAC1BO,GAAsB,OAAOP,KAC7BQ,GAAiB,YACjBC,GAAkB,aAClBC,GAAe,UACfC,GAAiB,YACjBC,GAAW,OACXC,GAAU,MACVC,GAAoB,SACpBC,GAAoB,OACpBC,GAAoB,OAEpBC,GAA2B,mBAE3BC,GAA+B,QAAQD,MAIvCE,GAAuB,2EACvBC,GAAsB,YAFOF,uBAAiDA,mBAA6CA,OAE/EC,KAC5CE,GAA8B,IAAIP,8BAA6CA,+BAA8CA,4BAMnI,MAAMQ,WAAYtjB,GAChB,WAAAP,CAAY5kB,GACVolB,MAAMplB,GACNygB,KAAKoS,QAAUpS,KAAK4E,SAAS5J,QAdN,uCAelBgF,KAAKoS,UAOVpS,KAAKioB,sBAAsBjoB,KAAKoS,QAASpS,KAAKkoB,gBAC9C3nB,GAAac,GAAGrB,KAAK4E,SAAUoiB,IAAe5nB,GAASY,KAAK6M,SAASzN,KACvE,CAGA,eAAW7C,GACT,MAnDW,KAoDb,CAGA,IAAAsT,GAEE,MAAMsY,EAAYnoB,KAAK4E,SACvB,GAAI5E,KAAKooB,cAAcD,GACrB,OAIF,MAAME,EAASroB,KAAKsoB,iBACdC,EAAYF,EAAS9nB,GAAaqB,QAAQymB,EAAQ1B,GAAc,CACpE7mB,cAAeqoB,IACZ,KACa5nB,GAAaqB,QAAQumB,EAAWtB,GAAc,CAC9D/mB,cAAeuoB,IAEHrmB,kBAAoBumB,GAAaA,EAAUvmB,mBAGzDhC,KAAKwoB,YAAYH,EAAQF,GACzBnoB,KAAKyoB,UAAUN,EAAWE,GAC5B,CAGA,SAAAI,CAAUlpC,EAASmpC,GACZnpC,IAGLA,EAAQ8b,UAAU5E,IAAI+wB,IACtBxnB,KAAKyoB,UAAU5iB,GAAec,uBAAuBpnB,IAcrDygB,KAAKmF,gBAZY,KACsB,QAAjC5lB,EAAQic,aAAa,SAIzBjc,EAAQ4B,gBAAgB,YACxB5B,EAAQ6B,aAAa,iBAAiB,GACtC4e,KAAK2oB,gBAAgBppC,GAAS,GAC9BghB,GAAaqB,QAAQriB,EAASunC,GAAe,CAC3ChnB,cAAe4oB,KAPfnpC,EAAQ8b,UAAU5E,IAAIixB,GAQtB,GAE0BnoC,EAASA,EAAQ8b,UAAU7W,SAASijC,KACpE,CACA,WAAAe,CAAYjpC,EAASmpC,GACdnpC,IAGLA,EAAQ8b,UAAU1B,OAAO6tB,IACzBjoC,EAAQq7B,OACR5a,KAAKwoB,YAAY3iB,GAAec,uBAAuBpnB,IAcvDygB,KAAKmF,gBAZY,KACsB,QAAjC5lB,EAAQic,aAAa,SAIzBjc,EAAQ6B,aAAa,iBAAiB,GACtC7B,EAAQ6B,aAAa,WAAY,MACjC4e,KAAK2oB,gBAAgBppC,GAAS,GAC9BghB,GAAaqB,QAAQriB,EAASqnC,GAAgB,CAC5C9mB,cAAe4oB,KAPfnpC,EAAQ8b,UAAU1B,OAAO+tB,GAQzB,GAE0BnoC,EAASA,EAAQ8b,UAAU7W,SAASijC,KACpE,CACA,QAAA5a,CAASzN,GACP,IAAK,CAAC8nB,GAAgBC,GAAiBC,GAAcC,GAAgBC,GAAUC,IAASnmB,SAAShC,EAAMtiB,KACrG,OAEFsiB,EAAM0U,kBACN1U,EAAMkD,iBACN,MAAMyD,EAAW/F,KAAKkoB,eAAe/hC,QAAO5G,IAAY2b,GAAW3b,KACnE,IAAIqpC,EACJ,GAAI,CAACtB,GAAUC,IAASnmB,SAAShC,EAAMtiB,KACrC8rC,EAAoB7iB,EAAS3G,EAAMtiB,MAAQwqC,GAAW,EAAIvhB,EAASrV,OAAS,OACvE,CACL,MAAM8c,EAAS,CAAC2Z,GAAiBE,IAAgBjmB,SAAShC,EAAMtiB,KAChE8rC,EAAoB9qB,GAAqBiI,EAAU3G,EAAM7S,OAAQihB,GAAQ,EAC3E,CACIob,IACFA,EAAkBnW,MAAM,CACtBoW,eAAe,IAEjBb,GAAI1iB,oBAAoBsjB,GAAmB/Y,OAE/C,CACA,YAAAqY,GAEE,OAAOriB,GAAe1T,KAAK21B,GAAqB9nB,KAAKoS,QACvD,CACA,cAAAkW,GACE,OAAOtoB,KAAKkoB,eAAe/1B,MAAKzN,GAASsb,KAAKooB,cAAc1jC,MAAW,IACzE,CACA,qBAAAujC,CAAsBxjC,EAAQshB,GAC5B/F,KAAK8oB,yBAAyBrkC,EAAQ,OAAQ,WAC9C,IAAK,MAAMC,KAASqhB,EAClB/F,KAAK+oB,6BAA6BrkC,EAEtC,CACA,4BAAAqkC,CAA6BrkC,GAC3BA,EAAQsb,KAAKgpB,iBAAiBtkC,GAC9B,MAAMukC,EAAWjpB,KAAKooB,cAAc1jC,GAC9BwkC,EAAYlpB,KAAKmpB,iBAAiBzkC,GACxCA,EAAMtD,aAAa,gBAAiB6nC,GAChCC,IAAcxkC,GAChBsb,KAAK8oB,yBAAyBI,EAAW,OAAQ,gBAE9CD,GACHvkC,EAAMtD,aAAa,WAAY,MAEjC4e,KAAK8oB,yBAAyBpkC,EAAO,OAAQ,OAG7Csb,KAAKopB,mCAAmC1kC,EAC1C,CACA,kCAAA0kC,CAAmC1kC,GACjC,MAAM6H,EAASsZ,GAAec,uBAAuBjiB,GAChD6H,IAGLyT,KAAK8oB,yBAAyBv8B,EAAQ,OAAQ,YAC1C7H,EAAMyV,IACR6F,KAAK8oB,yBAAyBv8B,EAAQ,kBAAmB,GAAG7H,EAAMyV,MAEtE,CACA,eAAAwuB,CAAgBppC,EAAS8pC,GACvB,MAAMH,EAAYlpB,KAAKmpB,iBAAiB5pC,GACxC,IAAK2pC,EAAU7tB,UAAU7W,SApKN,YAqKjB,OAEF,MAAMmjB,EAAS,CAAC5N,EAAUoa,KACxB,MAAM50B,EAAUsmB,GAAeC,QAAQ/L,EAAUmvB,GAC7C3pC,GACFA,EAAQ8b,UAAUsM,OAAOwM,EAAWkV,EACtC,EAEF1hB,EAAOggB,GAA0BH,IACjC7f,EA5K2B,iBA4KI+f,IAC/BwB,EAAU9nC,aAAa,gBAAiBioC,EAC1C,CACA,wBAAAP,CAAyBvpC,EAASwC,EAAWpE,GACtC4B,EAAQgc,aAAaxZ,IACxBxC,EAAQ6B,aAAaW,EAAWpE,EAEpC,CACA,aAAAyqC,CAAc9Y,GACZ,OAAOA,EAAKjU,UAAU7W,SAASgjC,GACjC,CAGA,gBAAAwB,CAAiB1Z,GACf,OAAOA,EAAKtJ,QAAQ8hB,IAAuBxY,EAAOzJ,GAAeC,QAAQgiB,GAAqBxY,EAChG,CAGA,gBAAA6Z,CAAiB7Z,GACf,OAAOA,EAAKtU,QA5LO,gCA4LoBsU,CACzC,CAGA,sBAAO7S,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAO29B,GAAI1iB,oBAAoBtF,MACrC,GAAsB,iBAAX8D,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOFvD,GAAac,GAAGhc,SAAU0hC,GAAsBc,IAAsB,SAAUzoB,GAC1E,CAAC,IAAK,QAAQgC,SAASpB,KAAKiH,UAC9B7H,EAAMkD,iBAEJpH,GAAW8E,OAGfgoB,GAAI1iB,oBAAoBtF,MAAM6P,MAChC,IAKAtP,GAAac,GAAGzhB,OAAQqnC,IAAqB,KAC3C,IAAK,MAAM1nC,KAAWsmB,GAAe1T,KAAK41B,IACxCC,GAAI1iB,oBAAoB/lB,EAC1B,IAMF4c,GAAmB6rB,IAcnB,MAEMhjB,GAAY,YACZskB,GAAkB,YAAYtkB,KAC9BukB,GAAiB,WAAWvkB,KAC5BwkB,GAAgB,UAAUxkB,KAC1BykB,GAAiB,WAAWzkB,KAC5B0kB,GAAa,OAAO1kB,KACpB2kB,GAAe,SAAS3kB,KACxB4kB,GAAa,OAAO5kB,KACpB6kB,GAAc,QAAQ7kB,KAEtB8kB,GAAkB,OAClBC,GAAkB,OAClBC,GAAqB,UACrBrmB,GAAc,CAClByc,UAAW,UACX6J,SAAU,UACV1J,MAAO,UAEH7c,GAAU,CACd0c,WAAW,EACX6J,UAAU,EACV1J,MAAO,KAOT,MAAM2J,WAAcxlB,GAClB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAK4gB,SAAW,KAChB5gB,KAAKmqB,sBAAuB,EAC5BnqB,KAAKoqB,yBAA0B,EAC/BpqB,KAAKkhB,eACP,CAGA,kBAAWxd,GACT,OAAOA,EACT,CACA,sBAAWC,GACT,OAAOA,EACT,CACA,eAAWpH,GACT,MA/CS,OAgDX,CAGA,IAAAsT,GACoBtP,GAAaqB,QAAQ5B,KAAK4E,SAAUglB,IACxC5nB,mBAGdhC,KAAKqqB,gBACDrqB,KAAK6E,QAAQub,WACfpgB,KAAK4E,SAASvJ,UAAU5E,IA/CN,QAsDpBuJ,KAAK4E,SAASvJ,UAAU1B,OAAOmwB,IAC/BjuB,GAAOmE,KAAK4E,UACZ5E,KAAK4E,SAASvJ,UAAU5E,IAAIszB,GAAiBC,IAC7ChqB,KAAKmF,gBARY,KACfnF,KAAK4E,SAASvJ,UAAU1B,OAAOqwB,IAC/BzpB,GAAaqB,QAAQ5B,KAAK4E,SAAUilB,IACpC7pB,KAAKsqB,oBAAoB,GAKGtqB,KAAK4E,SAAU5E,KAAK6E,QAAQub,WAC5D,CACA,IAAAxQ,GACO5P,KAAKuqB,YAGQhqB,GAAaqB,QAAQ5B,KAAK4E,SAAU8kB,IACxC1nB,mBAQdhC,KAAK4E,SAASvJ,UAAU5E,IAAIuzB,IAC5BhqB,KAAKmF,gBANY,KACfnF,KAAK4E,SAASvJ,UAAU5E,IAAIqzB,IAC5B9pB,KAAK4E,SAASvJ,UAAU1B,OAAOqwB,GAAoBD,IACnDxpB,GAAaqB,QAAQ5B,KAAK4E,SAAU+kB,GAAa,GAGrB3pB,KAAK4E,SAAU5E,KAAK6E,QAAQub,YAC5D,CACA,OAAArb,GACE/E,KAAKqqB,gBACDrqB,KAAKuqB,WACPvqB,KAAK4E,SAASvJ,UAAU1B,OAAOowB,IAEjCplB,MAAMI,SACR,CACA,OAAAwlB,GACE,OAAOvqB,KAAK4E,SAASvJ,UAAU7W,SAASulC,GAC1C,CAIA,kBAAAO,GACOtqB,KAAK6E,QAAQolB,WAGdjqB,KAAKmqB,sBAAwBnqB,KAAKoqB,0BAGtCpqB,KAAK4gB,SAAW/iB,YAAW,KACzBmC,KAAK4P,MAAM,GACV5P,KAAK6E,QAAQ0b,QAClB,CACA,cAAAiK,CAAeprB,EAAOqrB,GACpB,OAAQrrB,EAAMqB,MACZ,IAAK,YACL,IAAK,WAEDT,KAAKmqB,qBAAuBM,EAC5B,MAEJ,IAAK,UACL,IAAK,WAEDzqB,KAAKoqB,wBAA0BK,EAIrC,GAAIA,EAEF,YADAzqB,KAAKqqB,gBAGP,MAAM5c,EAAcrO,EAAMU,cACtBE,KAAK4E,WAAa6I,GAAezN,KAAK4E,SAASpgB,SAASipB,IAG5DzN,KAAKsqB,oBACP,CACA,aAAApJ,GACE3gB,GAAac,GAAGrB,KAAK4E,SAAU0kB,IAAiBlqB,GAASY,KAAKwqB,eAAeprB,GAAO,KACpFmB,GAAac,GAAGrB,KAAK4E,SAAU2kB,IAAgBnqB,GAASY,KAAKwqB,eAAeprB,GAAO,KACnFmB,GAAac,GAAGrB,KAAK4E,SAAU4kB,IAAepqB,GAASY,KAAKwqB,eAAeprB,GAAO,KAClFmB,GAAac,GAAGrB,KAAK4E,SAAU6kB,IAAgBrqB,GAASY,KAAKwqB,eAAeprB,GAAO,IACrF,CACA,aAAAirB,GACEnd,aAAalN,KAAK4gB,UAClB5gB,KAAK4gB,SAAW,IAClB,CAGA,sBAAOnkB,CAAgBqH,GACrB,OAAO9D,KAAKwH,MAAK,WACf,MAAMnd,EAAO6/B,GAAM5kB,oBAAoBtF,KAAM8D,GAC7C,GAAsB,iBAAXA,EAAqB,CAC9B,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQ9D,KACf,CACF,GACF,ECr0IK,SAAS0qB,GAAcruB,GACD,WAAvBhX,SAASuX,WAAyBP,IACjChX,SAASyF,iBAAiB,mBAAoBuR,EACrD,CDy0IAwK,GAAqBqjB,IAMrB/tB,GAAmB+tB,IEpyInBQ,IAzCA,WAC2B,GAAGt4B,MAAM5U,KAChC6H,SAAS+a,iBAAiB,+BAETtd,KAAI,SAAU6nC,GAC/B,OAAO,IAAI,GAAkBA,EAAkB,CAC7CpK,MAAO,CAAE1Q,KAAM,IAAKD,KAAM,MAE9B,GACF,IAiCA8a,IA5BA,WACYrlC,SAASm9B,eAAe,mBAC9B13B,iBAAiB,SAAS,WAC5BzF,SAAS6G,KAAKT,UAAY,EAC1BpG,SAASC,gBAAgBmG,UAAY,CACvC,GACF,IAuBAi/B,IArBA,WACE,IAAIE,EAAMvlC,SAASm9B,eAAe,mBAC9BqI,EAASxlC,SACVylC,uBAAuB,aAAa,GACpCxnC,wBACH1D,OAAOkL,iBAAiB,UAAU,WAC5BkV,KAAK+qB,UAAY/qB,KAAKgrB,SAAWhrB,KAAKgrB,QAAUH,EAAOjtC,OACzDgtC,EAAI7pC,MAAMgxB,QAAU,QAEpB6Y,EAAI7pC,MAAMgxB,QAAU,OAEtB/R,KAAK+qB,UAAY/qB,KAAKgrB,OACxB,GACF,IAUAprC,OAAOqrC,UAAY","sources":["webpack://pydata_sphinx_theme/webpack/bootstrap","webpack://pydata_sphinx_theme/webpack/runtime/define property getters","webpack://pydata_sphinx_theme/webpack/runtime/hasOwnProperty shorthand","webpack://pydata_sphinx_theme/webpack/runtime/make namespace object","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/enums.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/applyStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getBasePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/math.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/userAgent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/contains.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/within.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/expandToHashMap.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/arrow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getVariation.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/computeStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/eventListeners.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/rectToClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/detectOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/flip.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/hide.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/offset.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getAltAxis.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/orderModifiers.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/createPopper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/debounce.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergeByName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper-lite.js","webpack://pydata_sphinx_theme/./node_modules/bootstrap/dist/js/bootstrap.esm.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/mixin.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/bootstrap.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","/*!\n * Bootstrap v5.3.3 (https://getbootstrap.com/)\n * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\nimport * as Popper from '@popperjs/core';\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map();\nconst Data = {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map());\n }\n const instanceMap = elementMap.get(element);\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n return;\n }\n instanceMap.set(key, instance);\n },\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null;\n }\n return null;\n },\n remove(element, key) {\n if (!elementMap.has(element)) {\n return;\n }\n const instanceMap = elementMap.get(element);\n instanceMap.delete(key);\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element);\n }\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1000000;\nconst MILLISECONDS_MULTIPLIER = 1000;\nconst TRANSITION_END = 'transitionend';\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`);\n }\n return selector;\n};\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`;\n }\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n};\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID);\n } while (document.getElementById(prefix));\n return prefix;\n};\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0;\n }\n\n // Get transition-duration of the element\n let {\n transitionDuration,\n transitionDelay\n } = window.getComputedStyle(element);\n const floatTransitionDuration = Number.parseFloat(transitionDuration);\n const floatTransitionDelay = Number.parseFloat(transitionDelay);\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0;\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0];\n transitionDelay = transitionDelay.split(',')[0];\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n};\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END));\n};\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false;\n }\n if (typeof object.jquery !== 'undefined') {\n object = object[0];\n }\n return typeof object.nodeType !== 'undefined';\n};\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object;\n }\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object));\n }\n return null;\n};\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n if (!closedDetails) {\n return elementIsVisible;\n }\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n if (summary === null) {\n return false;\n }\n }\n return elementIsVisible;\n};\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n if (element.classList.contains('disabled')) {\n return true;\n }\n if (typeof element.disabled !== 'undefined') {\n return element.disabled;\n }\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n};\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null;\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode();\n return root instanceof ShadowRoot ? root : null;\n }\n if (element instanceof ShadowRoot) {\n return element;\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null;\n }\n return findShadowRoot(element.parentNode);\n};\nconst noop = () => {};\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight; // eslint-disable-line no-unused-expressions\n};\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery;\n }\n return null;\n};\nconst DOMContentLoadedCallbacks = [];\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback();\n }\n });\n }\n DOMContentLoadedCallbacks.push(callback);\n } else {\n callback();\n }\n};\nconst isRTL = () => document.documentElement.dir === 'rtl';\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery();\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME;\n const JQUERY_NO_CONFLICT = $.fn[name];\n $.fn[name] = plugin.jQueryInterface;\n $.fn[name].Constructor = plugin;\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT;\n return plugin.jQueryInterface;\n };\n }\n });\n};\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;\n};\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback);\n return;\n }\n const durationPadding = 5;\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n let called = false;\n const handler = ({\n target\n }) => {\n if (target !== transitionElement) {\n return;\n }\n called = true;\n transitionElement.removeEventListener(TRANSITION_END, handler);\n execute(callback);\n };\n transitionElement.addEventListener(TRANSITION_END, handler);\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement);\n }\n }, emulatedDuration);\n};\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length;\n let index = list.indexOf(activeElement);\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n }\n index += shouldGetNext ? 1 : -1;\n if (isCycleAllowed) {\n index = (index + listLength) % listLength;\n }\n return list[Math.max(0, Math.min(index, listLength - 1))];\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\nconst stripNameRegex = /\\..*/;\nconst stripUidRegex = /::\\d+$/;\nconst eventRegistry = {}; // Events storage\nlet uidEvent = 1;\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n};\nconst nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n}\nfunction getElementEvents(element) {\n const uid = makeEventUid(element);\n element.uidEvent = uid;\n eventRegistry[uid] = eventRegistry[uid] || {};\n return eventRegistry[uid];\n}\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, {\n delegateTarget: element\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn);\n }\n return fn.apply(element, [event]);\n };\n}\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector);\n for (let {\n target\n } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue;\n }\n hydrateObj(event, {\n delegateTarget: target\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn);\n }\n return fn.apply(target, [event]);\n }\n }\n };\n}\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n}\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string';\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n let typeEvent = getTypeEvent(originalTypeEvent);\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent;\n }\n return [isDelegated, callable, typeEvent];\n}\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n return fn.call(this, event);\n }\n };\n };\n callable = wrapFunction(callable);\n }\n const events = getElementEvents(element);\n const handlers = events[typeEvent] || (events[typeEvent] = {});\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff;\n return;\n }\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n fn.delegationSelector = isDelegated ? handler : null;\n fn.callable = callable;\n fn.oneOff = oneOff;\n fn.uidEvent = uid;\n handlers[uid] = fn;\n element.addEventListener(typeEvent, fn, isDelegated);\n}\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector);\n if (!fn) {\n return;\n }\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n delete events[typeEvent][fn.uidEvent];\n}\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {};\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n}\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '');\n return customEvents[event] || event;\n}\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false);\n },\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true);\n },\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n const inNamespace = typeEvent !== originalTypeEvent;\n const events = getElementEvents(element);\n const storeElementEvent = events[typeEvent] || {};\n const isNamespace = originalTypeEvent.startsWith('.');\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return;\n }\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n return;\n }\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n }\n }\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '');\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n },\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null;\n }\n const $ = getjQuery();\n const typeEvent = getTypeEvent(event);\n const inNamespace = event !== typeEvent;\n let jQueryEvent = null;\n let bubbles = true;\n let nativeDispatch = true;\n let defaultPrevented = false;\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args);\n $(element).trigger(jQueryEvent);\n bubbles = !jQueryEvent.isPropagationStopped();\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n defaultPrevented = jQueryEvent.isDefaultPrevented();\n }\n const evt = hydrateObj(new Event(event, {\n bubbles,\n cancelable: true\n }), args);\n if (defaultPrevented) {\n evt.preventDefault();\n }\n if (nativeDispatch) {\n element.dispatchEvent(evt);\n }\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault();\n }\n return evt;\n }\n};\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value;\n } catch (_unused) {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value;\n }\n });\n }\n }\n return obj;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === Number(value).toString()) {\n return Number(value);\n }\n if (value === '' || value === 'null') {\n return null;\n }\n if (typeof value !== 'string') {\n return value;\n }\n try {\n return JSON.parse(decodeURIComponent(value));\n } catch (_unused) {\n return value;\n }\n}\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n}\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n },\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n },\n getDataAttributes(element) {\n if (!element) {\n return {};\n }\n const attributes = {};\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '');\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n attributes[pureKey] = normalizeData(element.dataset[key]);\n }\n return attributes;\n },\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {};\n }\n static get DefaultType() {\n return {};\n }\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!');\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n return config;\n }\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n };\n }\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property];\n const valueType = isElement(value) ? 'element' : toType(value);\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n }\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3';\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super();\n element = getElement(element);\n if (!element) {\n return;\n }\n this._element = element;\n this._config = this._getConfig(config);\n Data.set(this._element, this.constructor.DATA_KEY, this);\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY);\n EventHandler.off(this._element, this.constructor.EVENT_KEY);\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null;\n }\n }\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated);\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY);\n }\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n }\n static get VERSION() {\n return VERSION;\n }\n static get DATA_KEY() {\n return `bs.${this.NAME}`;\n }\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`;\n }\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target');\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href');\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n return null;\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n }\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n }\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null;\n};\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n },\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector);\n },\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector));\n },\n parents(element, selector) {\n const parents = [];\n let ancestor = element.parentNode.closest(selector);\n while (ancestor) {\n parents.push(ancestor);\n ancestor = ancestor.parentNode.closest(selector);\n }\n return parents;\n },\n prev(element, selector) {\n let previous = element.previousElementSibling;\n while (previous) {\n if (previous.matches(selector)) {\n return [previous];\n }\n previous = previous.previousElementSibling;\n }\n return [];\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling;\n while (next) {\n if (next.matches(selector)) {\n return [next];\n }\n next = next.nextElementSibling;\n }\n return [];\n },\n focusableChildren(element) {\n const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n },\n getSelectorFromElement(element) {\n const selector = getSelector(element);\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null;\n }\n return null;\n },\n getElementFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.findOne(selector) : null;\n },\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.find(selector) : [];\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n const name = component.NAME;\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);\n const instance = component.getOrCreateInstance(target);\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]();\n });\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$f = 'alert';\nconst DATA_KEY$a = 'bs.alert';\nconst EVENT_KEY$b = `.${DATA_KEY$a}`;\nconst EVENT_CLOSE = `close${EVENT_KEY$b}`;\nconst EVENT_CLOSED = `closed${EVENT_KEY$b}`;\nconst CLASS_NAME_FADE$5 = 'fade';\nconst CLASS_NAME_SHOW$8 = 'show';\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$f;\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n if (closeEvent.defaultPrevented) {\n return;\n }\n this._element.classList.remove(CLASS_NAME_SHOW$8);\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n }\n\n // Private\n _destroyElement() {\n this._element.remove();\n EventHandler.trigger(this._element, EVENT_CLOSED);\n this.dispose();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close');\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$e = 'button';\nconst DATA_KEY$9 = 'bs.button';\nconst EVENT_KEY$a = `.${DATA_KEY$9}`;\nconst DATA_API_KEY$6 = '.data-api';\nconst CLASS_NAME_ACTIVE$3 = 'active';\nconst SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\nconst EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$e;\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this);\n if (config === 'toggle') {\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n event.preventDefault();\n const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n const data = Button.getOrCreateInstance(button);\n data.toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$d = 'swipe';\nconst EVENT_KEY$9 = '.bs.swipe';\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\nconst POINTER_TYPE_TOUCH = 'touch';\nconst POINTER_TYPE_PEN = 'pen';\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event';\nconst SWIPE_THRESHOLD = 40;\nconst Default$c = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n};\nconst DefaultType$c = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n};\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super();\n this._element = element;\n if (!element || !Swipe.isSupported()) {\n return;\n }\n this._config = this._getConfig(config);\n this._deltaX = 0;\n this._supportPointerEvents = Boolean(window.PointerEvent);\n this._initEvents();\n }\n\n // Getters\n static get Default() {\n return Default$c;\n }\n static get DefaultType() {\n return DefaultType$c;\n }\n static get NAME() {\n return NAME$d;\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY$9);\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX;\n return;\n }\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX;\n }\n }\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX;\n }\n this._handleSwipe();\n execute(this._config.endCallback);\n }\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n }\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX);\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return;\n }\n const direction = absDeltaX / this._deltaX;\n this._deltaX = 0;\n if (!direction) {\n return;\n }\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n }\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n }\n }\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$c = 'carousel';\nconst DATA_KEY$8 = 'bs.carousel';\nconst EVENT_KEY$8 = `.${DATA_KEY$8}`;\nconst DATA_API_KEY$5 = '.data-api';\nconst ARROW_LEFT_KEY$1 = 'ArrowLeft';\nconst ARROW_RIGHT_KEY$1 = 'ArrowRight';\nconst TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next';\nconst ORDER_PREV = 'prev';\nconst DIRECTION_LEFT = 'left';\nconst DIRECTION_RIGHT = 'right';\nconst EVENT_SLIDE = `slide${EVENT_KEY$8}`;\nconst EVENT_SLID = `slid${EVENT_KEY$8}`;\nconst EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\nconst EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\nconst EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\nconst EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst CLASS_NAME_CAROUSEL = 'carousel';\nconst CLASS_NAME_ACTIVE$2 = 'active';\nconst CLASS_NAME_SLIDE = 'slide';\nconst CLASS_NAME_END = 'carousel-item-end';\nconst CLASS_NAME_START = 'carousel-item-start';\nconst CLASS_NAME_NEXT = 'carousel-item-next';\nconst CLASS_NAME_PREV = 'carousel-item-prev';\nconst SELECTOR_ACTIVE = '.active';\nconst SELECTOR_ITEM = '.carousel-item';\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\nconst SELECTOR_ITEM_IMG = '.carousel-item img';\nconst SELECTOR_INDICATORS = '.carousel-indicators';\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n};\nconst Default$b = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n};\nconst DefaultType$b = {\n interval: '(number|boolean)',\n // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._interval = null;\n this._activeElement = null;\n this._isSliding = false;\n this.touchTimeout = null;\n this._swipeHelper = null;\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n this._addEventListeners();\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$b;\n }\n static get DefaultType() {\n return DefaultType$b;\n }\n static get NAME() {\n return NAME$c;\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT);\n }\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next();\n }\n }\n prev() {\n this._slide(ORDER_PREV);\n }\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element);\n }\n this._clearInterval();\n }\n cycle() {\n this._clearInterval();\n this._updateInterval();\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n }\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n return;\n }\n this.cycle();\n }\n to(index) {\n const items = this._getItems();\n if (index > items.length - 1 || index < 0) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n return;\n }\n const activeIndex = this._getItemIndex(this._getActive());\n if (activeIndex === index) {\n return;\n }\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n this._slide(order, items[index]);\n }\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose();\n }\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval;\n return config;\n }\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n }\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n }\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners();\n }\n }\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n }\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return;\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause();\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout);\n }\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n };\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n };\n this._swipeHelper = new Swipe(this._element, swipeConfig);\n }\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return;\n }\n const direction = KEY_TO_DIRECTION[event.key];\n if (direction) {\n event.preventDefault();\n this._slide(this._directionToOrder(direction));\n }\n }\n _getItemIndex(element) {\n return this._getItems().indexOf(element);\n }\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return;\n }\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n activeIndicator.removeAttribute('aria-current');\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n newActiveIndicator.setAttribute('aria-current', 'true');\n }\n }\n _updateInterval() {\n const element = this._activeElement || this._getActive();\n if (!element) {\n return;\n }\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n this._config.interval = elementInterval || this._config.defaultInterval;\n }\n _slide(order, element = null) {\n if (this._isSliding) {\n return;\n }\n const activeElement = this._getActive();\n const isNext = order === ORDER_NEXT;\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n if (nextElement === activeElement) {\n return;\n }\n const nextElementIndex = this._getItemIndex(nextElement);\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n });\n };\n const slideEvent = triggerEvent(EVENT_SLIDE);\n if (slideEvent.defaultPrevented) {\n return;\n }\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return;\n }\n const isCycling = Boolean(this._interval);\n this.pause();\n this._isSliding = true;\n this._setActiveIndicatorElement(nextElementIndex);\n this._activeElement = nextElement;\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n nextElement.classList.add(orderClassName);\n reflow(nextElement);\n activeElement.classList.add(directionalClassName);\n nextElement.classList.add(directionalClassName);\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName);\n nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n this._isSliding = false;\n triggerEvent(EVENT_SLID);\n };\n this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n if (isCycling) {\n this.cycle();\n }\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE);\n }\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n }\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element);\n }\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n }\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n }\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n }\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config);\n if (typeof config === 'number') {\n data.to(config);\n return;\n }\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return;\n }\n event.preventDefault();\n const carousel = Carousel.getOrCreateInstance(target);\n const slideIndex = this.getAttribute('data-bs-slide-to');\n if (slideIndex) {\n carousel.to(slideIndex);\n carousel._maybeEnableCycle();\n return;\n }\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next();\n carousel._maybeEnableCycle();\n return;\n }\n carousel.prev();\n carousel._maybeEnableCycle();\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel);\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$b = 'collapse';\nconst DATA_KEY$7 = 'bs.collapse';\nconst EVENT_KEY$7 = `.${DATA_KEY$7}`;\nconst DATA_API_KEY$4 = '.data-api';\nconst EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\nconst EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\nconst EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\nconst EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\nconst EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\nconst CLASS_NAME_SHOW$7 = 'show';\nconst CLASS_NAME_COLLAPSE = 'collapse';\nconst CLASS_NAME_COLLAPSING = 'collapsing';\nconst CLASS_NAME_COLLAPSED = 'collapsed';\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\nconst WIDTH = 'width';\nconst HEIGHT = 'height';\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\nconst SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\nconst Default$a = {\n parent: null,\n toggle: true\n};\nconst DefaultType$a = {\n parent: '(null|element)',\n toggle: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isTransitioning = false;\n this._triggerArray = [];\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem);\n const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem);\n }\n }\n this._initializeChildren();\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n }\n if (this._config.toggle) {\n this.toggle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$a;\n }\n static get DefaultType() {\n return DefaultType$a;\n }\n static get NAME() {\n return NAME$b;\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide();\n } else {\n this.show();\n }\n }\n show() {\n if (this._isTransitioning || this._isShown()) {\n return;\n }\n let activeChildren = [];\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n toggle: false\n }));\n }\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n for (const activeInstance of activeChildren) {\n activeInstance.hide();\n }\n const dimension = this._getDimension();\n this._element.classList.remove(CLASS_NAME_COLLAPSE);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.style[dimension] = 0;\n this._addAriaAndCollapsedClass(this._triggerArray, true);\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n this._element.style[dimension] = '';\n EventHandler.trigger(this._element, EVENT_SHOWN$6);\n };\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n const scrollSize = `scroll${capitalizedDimension}`;\n this._queueCallback(complete, this._element, true);\n this._element.style[dimension] = `${this._element[scrollSize]}px`;\n }\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n const dimension = this._getDimension();\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger);\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false);\n }\n }\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE);\n EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n };\n this._element.style[dimension] = '';\n this._queueCallback(complete, this._element, true);\n }\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW$7);\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle); // Coerce string values\n config.parent = getElement(config.parent);\n return config;\n }\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n }\n _initializeChildren() {\n if (!this._config.parent) {\n return;\n }\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element);\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected));\n }\n }\n }\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n }\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return;\n }\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n element.setAttribute('aria-expanded', isOpen);\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {};\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false;\n }\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config);\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n event.preventDefault();\n }\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, {\n toggle: false\n }).toggle();\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$a = 'dropdown';\nconst DATA_KEY$6 = 'bs.dropdown';\nconst EVENT_KEY$6 = `.${DATA_KEY$6}`;\nconst DATA_API_KEY$3 = '.data-api';\nconst ESCAPE_KEY$2 = 'Escape';\nconst TAB_KEY$1 = 'Tab';\nconst ARROW_UP_KEY$1 = 'ArrowUp';\nconst ARROW_DOWN_KEY$1 = 'ArrowDown';\nconst RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\nconst EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\nconst EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\nconst EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\nconst EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst CLASS_NAME_SHOW$6 = 'show';\nconst CLASS_NAME_DROPUP = 'dropup';\nconst CLASS_NAME_DROPEND = 'dropend';\nconst CLASS_NAME_DROPSTART = 'dropstart';\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center';\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\nconst SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\nconst SELECTOR_MENU = '.dropdown-menu';\nconst SELECTOR_NAVBAR = '.navbar';\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav';\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\nconst PLACEMENT_TOPCENTER = 'top';\nconst PLACEMENT_BOTTOMCENTER = 'bottom';\nconst Default$9 = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n};\nconst DefaultType$9 = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n};\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._popper = null;\n this._parent = this._element.parentNode; // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n this._inNavbar = this._detectNavbar();\n }\n\n // Getters\n static get Default() {\n return Default$9;\n }\n static get DefaultType() {\n return DefaultType$9;\n }\n static get NAME() {\n return NAME$a;\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show();\n }\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n if (showEvent.defaultPrevented) {\n return;\n }\n this._createPopper();\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n this._element.focus();\n this._element.setAttribute('aria-expanded', true);\n this._menu.classList.add(CLASS_NAME_SHOW$6);\n this._element.classList.add(CLASS_NAME_SHOW$6);\n EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n }\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n this._completeHide(relatedTarget);\n }\n dispose() {\n if (this._popper) {\n this._popper.destroy();\n }\n super.dispose();\n }\n update() {\n this._inNavbar = this._detectNavbar();\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n if (this._popper) {\n this._popper.destroy();\n }\n this._menu.classList.remove(CLASS_NAME_SHOW$6);\n this._element.classList.remove(CLASS_NAME_SHOW$6);\n this._element.setAttribute('aria-expanded', 'false');\n Manipulator.removeDataAttribute(this._menu, 'popper');\n EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n }\n _getConfig(config) {\n config = super._getConfig(config);\n if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n }\n return config;\n }\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n }\n let referenceElement = this._element;\n if (this._config.reference === 'parent') {\n referenceElement = this._parent;\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference);\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference;\n }\n const popperConfig = this._getPopperConfig();\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig);\n }\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n }\n _getPlacement() {\n const parentDropdown = this._parent;\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER;\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n }\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n }\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null;\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n };\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }];\n }\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _selectMenuItem({\n key,\n target\n }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n if (!items.length) {\n return;\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n return;\n }\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle);\n if (!context || context._config.autoClose === false) {\n continue;\n }\n const composedPath = event.composedPath();\n const isMenuTarget = composedPath.includes(context._menu);\n if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n continue;\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue;\n }\n const relatedTarget = {\n relatedTarget: context._element\n };\n if (event.type === 'click') {\n relatedTarget.clickEvent = event;\n }\n context._completeHide(relatedTarget);\n }\n }\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName);\n const isEscapeEvent = event.key === ESCAPE_KEY$2;\n const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return;\n }\n if (isInput && !isEscapeEvent) {\n return;\n }\n event.preventDefault();\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n const instance = Dropdown.getOrCreateInstance(getToggleButton);\n if (isUpOrDownEvent) {\n event.stopPropagation();\n instance.show();\n instance._selectMenuItem(event);\n return;\n }\n if (instance._isShown()) {\n // else is escape and we check if it is shown\n event.stopPropagation();\n instance.hide();\n getToggleButton.focus();\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n event.preventDefault();\n Dropdown.getOrCreateInstance(this).toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$9 = 'backdrop';\nconst CLASS_NAME_FADE$4 = 'fade';\nconst CLASS_NAME_SHOW$5 = 'show';\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\nconst Default$8 = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true,\n // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n};\nconst DefaultType$8 = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n};\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isAppended = false;\n this._element = null;\n }\n\n // Getters\n static get Default() {\n return Default$8;\n }\n static get DefaultType() {\n return DefaultType$8;\n }\n static get NAME() {\n return NAME$9;\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._append();\n const element = this._getElement();\n if (this._config.isAnimated) {\n reflow(element);\n }\n element.classList.add(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n execute(callback);\n });\n }\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n this.dispose();\n execute(callback);\n });\n }\n dispose() {\n if (!this._isAppended) {\n return;\n }\n EventHandler.off(this._element, EVENT_MOUSEDOWN);\n this._element.remove();\n this._isAppended = false;\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div');\n backdrop.className = this._config.className;\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE$4);\n }\n this._element = backdrop;\n }\n return this._element;\n }\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement);\n return config;\n }\n _append() {\n if (this._isAppended) {\n return;\n }\n const element = this._getElement();\n this._config.rootElement.append(element);\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback);\n });\n this._isAppended = true;\n }\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$8 = 'focustrap';\nconst DATA_KEY$5 = 'bs.focustrap';\nconst EVENT_KEY$5 = `.${DATA_KEY$5}`;\nconst EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\nconst TAB_KEY = 'Tab';\nconst TAB_NAV_FORWARD = 'forward';\nconst TAB_NAV_BACKWARD = 'backward';\nconst Default$7 = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n};\nconst DefaultType$7 = {\n autofocus: 'boolean',\n trapElement: 'element'\n};\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isActive = false;\n this._lastTabNavDirection = null;\n }\n\n // Getters\n static get Default() {\n return Default$7;\n }\n static get DefaultType() {\n return DefaultType$7;\n }\n static get NAME() {\n return NAME$8;\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return;\n }\n if (this._config.autofocus) {\n this._config.trapElement.focus();\n }\n EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n this._isActive = true;\n }\n deactivate() {\n if (!this._isActive) {\n return;\n }\n this._isActive = false;\n EventHandler.off(document, EVENT_KEY$5);\n }\n\n // Private\n _handleFocusin(event) {\n const {\n trapElement\n } = this._config;\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return;\n }\n const elements = SelectorEngine.focusableChildren(trapElement);\n if (elements.length === 0) {\n trapElement.focus();\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus();\n } else {\n elements[0].focus();\n }\n }\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return;\n }\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\nconst SELECTOR_STICKY_CONTENT = '.sticky-top';\nconst PROPERTY_PADDING = 'padding-right';\nconst PROPERTY_MARGIN = 'margin-right';\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body;\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth;\n return Math.abs(window.innerWidth - documentWidth);\n }\n hide() {\n const width = this.getWidth();\n this._disableOverFlow();\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n }\n reset() {\n this._resetElementAttributes(this._element, 'overflow');\n this._resetElementAttributes(this._element, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n }\n isOverflowing() {\n return this.getWidth() > 0;\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow');\n this._element.style.overflow = 'hidden';\n }\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth();\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return;\n }\n this._saveInitialAttribute(element, styleProperty);\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty);\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue);\n }\n }\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty);\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty);\n return;\n }\n Manipulator.removeDataAttribute(element, styleProperty);\n element.style.setProperty(styleProperty, value);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector);\n return;\n }\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel);\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$7 = 'modal';\nconst DATA_KEY$4 = 'bs.modal';\nconst EVENT_KEY$4 = `.${DATA_KEY$4}`;\nconst DATA_API_KEY$2 = '.data-api';\nconst ESCAPE_KEY$1 = 'Escape';\nconst EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\nconst EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\nconst EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\nconst EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\nconst EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\nconst EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\nconst EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\nconst EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\nconst CLASS_NAME_OPEN = 'modal-open';\nconst CLASS_NAME_FADE$3 = 'fade';\nconst CLASS_NAME_SHOW$4 = 'show';\nconst CLASS_NAME_STATIC = 'modal-static';\nconst OPEN_SELECTOR$1 = '.modal.show';\nconst SELECTOR_DIALOG = '.modal-dialog';\nconst SELECTOR_MODAL_BODY = '.modal-body';\nconst SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\nconst Default$6 = {\n backdrop: true,\n focus: true,\n keyboard: true\n};\nconst DefaultType$6 = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._isShown = false;\n this._isTransitioning = false;\n this._scrollBar = new ScrollBarHelper();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$6;\n }\n static get DefaultType() {\n return DefaultType$6;\n }\n static get NAME() {\n return NAME$7;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._isTransitioning = true;\n this._scrollBar.hide();\n document.body.classList.add(CLASS_NAME_OPEN);\n this._adjustDialog();\n this._backdrop.show(() => this._showElement(relatedTarget));\n }\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._isShown = false;\n this._isTransitioning = true;\n this._focustrap.deactivate();\n this._element.classList.remove(CLASS_NAME_SHOW$4);\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n }\n dispose() {\n EventHandler.off(window, EVENT_KEY$4);\n EventHandler.off(this._dialog, EVENT_KEY$4);\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n handleUpdate() {\n this._adjustDialog();\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop),\n // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element);\n }\n this._element.style.display = 'block';\n this._element.removeAttribute('aria-hidden');\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.scrollTop = 0;\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n if (modalBody) {\n modalBody.scrollTop = 0;\n }\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_SHOW$4);\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate();\n }\n this._isTransitioning = false;\n EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n relatedTarget\n });\n };\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n if (event.key !== ESCAPE_KEY$1) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n this._triggerBackdropTransition();\n });\n EventHandler.on(window, EVENT_RESIZE$1, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog();\n }\n });\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return;\n }\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition();\n return;\n }\n if (this._config.backdrop) {\n this.hide();\n }\n });\n });\n }\n _hideModal() {\n this._element.style.display = 'none';\n this._element.setAttribute('aria-hidden', true);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n this._isTransitioning = false;\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN);\n this._resetAdjustments();\n this._scrollBar.reset();\n EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n });\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE$3);\n }\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n if (hideEvent.defaultPrevented) {\n return;\n }\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const initialOverflowY = this._element.style.overflowY;\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return;\n }\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden';\n }\n this._element.classList.add(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY;\n }, this._dialog);\n }, this._dialog);\n this._element.focus();\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const scrollbarWidth = this._scrollBar.getWidth();\n const isBodyOverflowing = scrollbarWidth > 0;\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n }\n _resetAdjustments() {\n this._element.style.paddingLeft = '';\n this._element.style.paddingRight = '';\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](relatedTarget);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$4, () => {\n if (isVisible(this)) {\n this.focus();\n }\n });\n });\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide();\n }\n const data = Modal.getOrCreateInstance(target);\n data.toggle(this);\n});\nenableDismissTrigger(Modal);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$6 = 'offcanvas';\nconst DATA_KEY$3 = 'bs.offcanvas';\nconst EVENT_KEY$3 = `.${DATA_KEY$3}`;\nconst DATA_API_KEY$1 = '.data-api';\nconst EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst ESCAPE_KEY = 'Escape';\nconst CLASS_NAME_SHOW$3 = 'show';\nconst CLASS_NAME_SHOWING$1 = 'showing';\nconst CLASS_NAME_HIDING = 'hiding';\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\nconst OPEN_SELECTOR = '.offcanvas.show';\nconst EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\nconst EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\nconst EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\nconst EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\nconst EVENT_RESIZE = `resize${EVENT_KEY$3}`;\nconst EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\nconst SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\nconst Default$5 = {\n backdrop: true,\n keyboard: true,\n scroll: false\n};\nconst DefaultType$5 = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isShown = false;\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$5;\n }\n static get DefaultType() {\n return DefaultType$5;\n }\n static get NAME() {\n return NAME$6;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._backdrop.show();\n if (!this._config.scroll) {\n new ScrollBarHelper().hide();\n }\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.classList.add(CLASS_NAME_SHOWING$1);\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate();\n }\n this._element.classList.add(CLASS_NAME_SHOW$3);\n this._element.classList.remove(CLASS_NAME_SHOWING$1);\n EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n relatedTarget\n });\n };\n this._queueCallback(completeCallBack, this._element, true);\n }\n hide() {\n if (!this._isShown) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._focustrap.deactivate();\n this._element.blur();\n this._isShown = false;\n this._element.classList.add(CLASS_NAME_HIDING);\n this._backdrop.hide();\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n if (!this._config.scroll) {\n new ScrollBarHelper().reset();\n }\n EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n };\n this._queueCallback(completeCallback, this._element, true);\n }\n dispose() {\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n return;\n }\n this.hide();\n };\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop);\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n });\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$3, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus();\n }\n });\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide();\n }\n const data = Offcanvas.getOrCreateInstance(target);\n data.toggle(this);\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show();\n }\n});\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide();\n }\n }\n});\nenableDismissTrigger(Offcanvas);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\nconst DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n};\n// js-docs-end allow-list\n\nconst uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase();\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));\n }\n return true;\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n};\nfunction sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml;\n }\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml);\n }\n const domParser = new window.DOMParser();\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase();\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove();\n continue;\n }\n const attributeList = [].concat(...element.attributes);\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName);\n }\n }\n }\n return createdDocument.body.innerHTML;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$5 = 'TemplateFactory';\nconst Default$4 = {\n allowList: DefaultAllowlist,\n content: {},\n // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n};\nconst DefaultType$4 = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n};\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n};\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n }\n\n // Getters\n static get Default() {\n return Default$4;\n }\n static get DefaultType() {\n return DefaultType$4;\n }\n static get NAME() {\n return NAME$5;\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n }\n hasContent() {\n return this.getContent().length > 0;\n }\n changeContent(content) {\n this._checkContent(content);\n this._config.content = {\n ...this._config.content,\n ...content\n };\n return this;\n }\n toHtml() {\n const templateWrapper = document.createElement('div');\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector);\n }\n const template = templateWrapper.children[0];\n const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n if (extraClass) {\n template.classList.add(...extraClass.split(' '));\n }\n return template;\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config);\n this._checkContent(config.content);\n }\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({\n selector,\n entry: content\n }, DefaultContentType);\n }\n }\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template);\n if (!templateElement) {\n return;\n }\n content = this._resolvePossibleFunction(content);\n if (!content) {\n templateElement.remove();\n return;\n }\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement);\n return;\n }\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content);\n return;\n }\n templateElement.textContent = content;\n }\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this]);\n }\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = '';\n templateElement.append(element);\n return;\n }\n templateElement.textContent = element.textContent;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$4 = 'tooltip';\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\nconst CLASS_NAME_FADE$2 = 'fade';\nconst CLASS_NAME_MODAL = 'modal';\nconst CLASS_NAME_SHOW$2 = 'show';\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\nconst EVENT_MODAL_HIDE = 'hide.bs.modal';\nconst TRIGGER_HOVER = 'hover';\nconst TRIGGER_FOCUS = 'focus';\nconst TRIGGER_CLICK = 'click';\nconst TRIGGER_MANUAL = 'manual';\nconst EVENT_HIDE$2 = 'hide';\nconst EVENT_HIDDEN$2 = 'hidden';\nconst EVENT_SHOW$2 = 'show';\nconst EVENT_SHOWN$2 = 'shown';\nconst EVENT_INSERTED = 'inserted';\nconst EVENT_CLICK$1 = 'click';\nconst EVENT_FOCUSIN$1 = 'focusin';\nconst EVENT_FOCUSOUT$1 = 'focusout';\nconst EVENT_MOUSEENTER = 'mouseenter';\nconst EVENT_MOUSELEAVE = 'mouseleave';\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n};\nconst Default$3 = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' + '
' + '
' + '
',\n title: '',\n trigger: 'hover focus'\n};\nconst DefaultType$3 = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n};\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n }\n super(element, config);\n\n // Private\n this._isEnabled = true;\n this._timeout = 0;\n this._isHovered = null;\n this._activeTrigger = {};\n this._popper = null;\n this._templateFactory = null;\n this._newContent = null;\n\n // Protected\n this.tip = null;\n this._setListeners();\n if (!this._config.selector) {\n this._fixTitle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$3;\n }\n static get DefaultType() {\n return DefaultType$3;\n }\n static get NAME() {\n return NAME$4;\n }\n\n // Public\n enable() {\n this._isEnabled = true;\n }\n disable() {\n this._isEnabled = false;\n }\n toggleEnabled() {\n this._isEnabled = !this._isEnabled;\n }\n toggle() {\n if (!this._isEnabled) {\n return;\n }\n this._activeTrigger.click = !this._activeTrigger.click;\n if (this._isShown()) {\n this._leave();\n return;\n }\n this._enter();\n }\n dispose() {\n clearTimeout(this._timeout);\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n }\n this._disposePopper();\n super.dispose();\n }\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements');\n }\n if (!(this._isWithContent() && this._isEnabled)) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n const shadowRoot = findShadowRoot(this._element);\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n if (showEvent.defaultPrevented || !isInTheDom) {\n return;\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper();\n const tip = this._getTipElement();\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n const {\n container\n } = this._config;\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip);\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n }\n this._popper = this._createPopper(tip);\n tip.classList.add(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n if (this._isHovered === false) {\n this._leave();\n }\n this._isHovered = false;\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n hide() {\n if (!this._isShown()) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n if (hideEvent.defaultPrevented) {\n return;\n }\n const tip = this._getTipElement();\n tip.classList.remove(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n this._activeTrigger[TRIGGER_CLICK] = false;\n this._activeTrigger[TRIGGER_FOCUS] = false;\n this._activeTrigger[TRIGGER_HOVER] = false;\n this._isHovered = null; // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return;\n }\n if (!this._isHovered) {\n this._disposePopper();\n }\n this._element.removeAttribute('aria-describedby');\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n update() {\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle());\n }\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n }\n return this.tip;\n }\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml();\n\n // TODO: remove this check in v6\n if (!tip) {\n return null;\n }\n tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2);\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n const tipId = getUID(this.constructor.NAME).toString();\n tip.setAttribute('id', tipId);\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE$2);\n }\n return tip;\n }\n setContent(content) {\n this._newContent = content;\n if (this._isShown()) {\n this._disposePopper();\n this.show();\n }\n }\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content);\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n });\n }\n return this._templateFactory;\n }\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n };\n }\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n }\n _isAnimated() {\n return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n }\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n }\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element]);\n const attachment = AttachmentMap[placement.toUpperCase()];\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment));\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element]);\n }\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [{\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }, {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n }, {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n }\n }]\n };\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _setListeners() {\n const triggers = this._config.trigger.split(' ');\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context.toggle();\n });\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n context._enter();\n });\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n context._leave();\n });\n }\n }\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide();\n }\n };\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n }\n _fixTitle() {\n const title = this._element.getAttribute('title');\n if (!title) {\n return;\n }\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title);\n }\n this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title');\n }\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true;\n return;\n }\n this._isHovered = true;\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show();\n }\n }, this._config.delay.show);\n }\n _leave() {\n if (this._isWithActiveTrigger()) {\n return;\n }\n this._isHovered = false;\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide();\n }\n }, this._config.delay.hide);\n }\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout);\n this._timeout = setTimeout(handler, timeout);\n }\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true);\n }\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element);\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute];\n }\n }\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n };\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container);\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n };\n }\n if (typeof config.title === 'number') {\n config.title = config.title.toString();\n }\n if (typeof config.content === 'number') {\n config.content = config.content.toString();\n }\n return config;\n }\n _getDelegateConfig() {\n const config = {};\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value;\n }\n }\n config.selector = false;\n config.trigger = 'manual';\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config;\n }\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy();\n this._popper = null;\n }\n if (this.tip) {\n this.tip.remove();\n this.tip = null;\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$3 = 'popover';\nconst SELECTOR_TITLE = '.popover-header';\nconst SELECTOR_CONTENT = '.popover-body';\nconst Default$2 = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' + '
' + '

' + '
' + '
',\n trigger: 'click'\n};\nconst DefaultType$2 = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n};\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default$2;\n }\n static get DefaultType() {\n return DefaultType$2;\n }\n static get NAME() {\n return NAME$3;\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent();\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n };\n }\n _getContent() {\n return this._resolvePossibleFunction(this._config.content);\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$2 = 'scrollspy';\nconst DATA_KEY$2 = 'bs.scrollspy';\nconst EVENT_KEY$2 = `.${DATA_KEY$2}`;\nconst DATA_API_KEY = '.data-api';\nconst EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\nconst EVENT_CLICK = `click${EVENT_KEY$2}`;\nconst EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\nconst CLASS_NAME_ACTIVE$1 = 'active';\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\nconst SELECTOR_TARGET_LINKS = '[href]';\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\nconst SELECTOR_NAV_LINKS = '.nav-link';\nconst SELECTOR_NAV_ITEMS = '.nav-item';\nconst SELECTOR_LIST_ITEMS = '.list-group-item';\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\nconst SELECTOR_DROPDOWN = '.dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\nconst Default$1 = {\n offset: null,\n // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n};\nconst DefaultType$1 = {\n offset: '(number|null)',\n // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n};\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map();\n this._observableSections = new Map();\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n this._activeTarget = null;\n this._observer = null;\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n };\n this.refresh(); // initialize\n }\n\n // Getters\n static get Default() {\n return Default$1;\n }\n static get DefaultType() {\n return DefaultType$1;\n }\n static get NAME() {\n return NAME$2;\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables();\n this._maybeEnableSmoothScroll();\n if (this._observer) {\n this._observer.disconnect();\n } else {\n this._observer = this._getNewObserver();\n }\n for (const section of this._observableSections.values()) {\n this._observer.observe(section);\n }\n }\n dispose() {\n this._observer.disconnect();\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body;\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n }\n return config;\n }\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return;\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK);\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash);\n if (observableSection) {\n event.preventDefault();\n const root = this._rootElement || window;\n const height = observableSection.offsetTop - this._element.offsetTop;\n if (root.scrollTo) {\n root.scrollTo({\n top: height,\n behavior: 'smooth'\n });\n return;\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height;\n }\n });\n }\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n };\n return new IntersectionObserver(entries => this._observerCallback(entries), options);\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n this._process(targetElement(entry));\n };\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n this._previousScrollData.parentScrollTop = parentScrollTop;\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null;\n this._clearActiveClass(targetElement(entry));\n continue;\n }\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry);\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return;\n }\n continue;\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry);\n }\n }\n }\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map();\n this._observableSections = new Map();\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue;\n }\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor);\n this._observableSections.set(anchor.hash, observableSection);\n }\n }\n }\n _process(target) {\n if (this._activeTarget === target) {\n return;\n }\n this._clearActiveClass(this._config.target);\n this._activeTarget = target;\n target.classList.add(CLASS_NAME_ACTIVE$1);\n this._activateParents(target);\n EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n relatedTarget: target\n });\n }\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n return;\n }\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both