diff --git a/html/.buildinfo b/html/.buildinfo new file mode 100644 index 0000000..9e4a001 --- /dev/null +++ b/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 4d636bbedac67a11c4b09a71c19549c1 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/html/_images/before_rename_urdf_img.png b/html/_images/before_rename_urdf_img.png new file mode 100644 index 0000000..9220ca0 Binary files /dev/null and b/html/_images/before_rename_urdf_img.png differ diff --git a/html/_images/follow_target_mycobot_demo.png b/html/_images/follow_target_mycobot_demo.png new file mode 100644 index 0000000..7ea5f33 Binary files /dev/null and b/html/_images/follow_target_mycobot_demo.png differ diff --git a/html/_images/lula_test_extension.png b/html/_images/lula_test_extension.png new file mode 100644 index 0000000..b515612 Binary files /dev/null and b/html/_images/lula_test_extension.png differ diff --git a/html/_images/robot_model_class.png b/html/_images/robot_model_class.png new file mode 100644 index 0000000..9a17316 Binary files /dev/null and b/html/_images/robot_model_class.png differ diff --git a/html/_images/robot_model_controller.png b/html/_images/robot_model_controller.png new file mode 100644 index 0000000..3552d8b Binary files /dev/null and b/html/_images/robot_model_controller.png differ diff --git a/html/_images/robot_model_yml.png b/html/_images/robot_model_yml.png new file mode 100644 index 0000000..3fd3899 Binary files /dev/null and b/html/_images/robot_model_yml.png differ diff --git a/html/_images/robot_models_add_controller.png b/html/_images/robot_models_add_controller.png new file mode 100644 index 0000000..a7e3155 Binary files /dev/null and b/html/_images/robot_models_add_controller.png differ diff --git a/html/_modules/grutopia/core/datahub/api.html b/html/_modules/grutopia/core/datahub/api.html new file mode 100644 index 0000000..7eb805d --- /dev/null +++ b/html/_modules/grutopia/core/datahub/api.html @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + grutopia.core.datahub.api — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.datahub.api

+from typing import Any, Dict, List
+
+from grutopia.core.datahub.isaac_data import ActionData, IsaacData
+
+
+
[docs]def get_all_obs() -> List[Dict[str, Any]]: + """ + Get all observation data. + + Returns: + List[Dict[str, Any]]: sensor data dict + ``` + """ + return IsaacData.get_obs()
+ + +
[docs]def get_obs_by_id(task_id: int) -> Dict[str, Any]: + """ + Get observation by task_id + + Returns: + Dict[str, Any]: obs data dict + """ + return IsaacData.get_obs_by_id(task_id)
+ + +
[docs]def set_obs_data(obs: List[Dict[str, Any]]) -> None: + """ + Flush observation data. + + Args: + obs (List[Dict[str, Any]]): observation data + + """ + IsaacData.set_obs_data(obs)
+ + +
[docs]def get_actions() -> None | Dict[Any, Any]: + """ + Get all actions + + Returns: + Dict[str, Any]: action data dict + """ + return IsaacData.get_actions()
+ + +
[docs]def send_actions(actions: List[ActionData]): + """ + send actions to datahub + Args: + actions (List[ActionData]): list of [dict of {robot_id: ActionData}] + """ + IsaacData.add_actions(actions)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/datahub/isaac_data.html b/html/_modules/grutopia/core/datahub/isaac_data.html new file mode 100644 index 0000000..727c316 --- /dev/null +++ b/html/_modules/grutopia/core/datahub/isaac_data.html @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + grutopia.core.datahub.isaac_data — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.datahub.isaac_data

+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel
+
+
+class MetaActionData(BaseModel):
+    """
+    action status in grutopia
+    """
+    controller: str
+    data: Any
+
+
+class ActionData(BaseModel):
+    """
+    action status in grutopia
+    """
+    robot: str
+    controllers: List[MetaActionData]
+
+
+class _IsaacData(BaseModel):
+    """
+    isaac status in grutopia
+    """
+    actions: Optional[List[Dict[str, Any]]]
+    obs: Optional[List[Dict[str, Any]]]
+
+
+
[docs]class IsaacData: + """ + isaac status in grutopia + + There are two types of isaac status: + + * Action + * Observation + + structure of isaac status like this:: + + { + actions: { + [ + { + robot_1: { + cap: param, + } + } + ] + }, + observations: { + [ + { + robot_1: { + obs_1: data, + obs_2: data + } + } + ] + } + } + + """ + data = _IsaacData(actions=[], obs=[]) + + def __init__(self) -> None: + pass + + @classmethod + def get_all(cls) -> _IsaacData: + return cls.data + + # Observation + @classmethod + def set_obs_data(cls, obs: List[Dict[str, Any]]) -> None: + cls.data.obs = obs + +
[docs] @classmethod + def get_obs(cls) -> List[Dict[str, Any]]: + """ + Get isaac observation data + + Returns: + isaac observation data list + """ + return cls.data.obs
+ +
[docs] @classmethod + def get_obs_by_id(cls, task_id: int) -> Dict[str, Any]: + """ + Get isaac observation by id + + Args: + task_id: isaac task id + + Returns: + isaac observation data + + """ + return cls.data.obs[task_id]
+ + # Action +
[docs] @classmethod + def add_actions(cls, actions: List[ActionData]): + """ + Add actions + + Args: + actions: action list + + Returns: + + """ + # when add action, return action's index. + cls.data.actions = [] + for action in actions: + cls.data.actions.append({action.robot: {x.controller: x.data for x in action.controllers}}) + return
+ +
[docs] @classmethod + def get_actions(cls) -> None | List[Dict[Any, Any]]: + """ + Get actions + + Returns: + action(dict like {robot_name: {controller_name: param}}) list + """ + return cls.data.actions
+ +
[docs] @classmethod + def get_action_by_id(cls, task_id: int) -> None | Dict[Any, Any]: + """ + Get action by id + + Returns: + action(dict like {robot_name: {controller_name: param}}) + """ + return cls.data.actions[task_id]
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/datahub/web_api.html b/html/_modules/grutopia/core/datahub/web_api.html new file mode 100644 index 0000000..0b82ae4 --- /dev/null +++ b/html/_modules/grutopia/core/datahub/web_api.html @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + grutopia.core.datahub.web_api — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.datahub.web_api

+"""
+Includes web api endpoints
+"""
+from typing import Any, Dict, List
+
+import httpx
+
+from grutopia.core.datahub.isaac_data import ActionData
+
+# constants
+WebBEUrl = 'http://127.0.0.1:9000'  # TODO config this
+GetAllObsPath = WebBEUrl + '/api/stream/get_all_obs'
+GetObsByIdPath = WebBEUrl + '/api/stream/get_obs_by_id/'
+FlushObsUrl = WebBEUrl + '/api/isaac/flush_obs_data'
+SetActionsUrl = WebBEUrl + '/api/isaac/set_action'
+GetAllActionUrl = WebBEUrl + '/api/isaac/get_actions'
+GetActionByIdUrl = WebBEUrl + '/api/isaac/get_action_by_id/'
+
+
+
[docs]def get_all_obs() -> List[Dict[str, Any]] | None: + """ + Get all observation data + Returns: + obs (List[Dict[str, Any]]): List of all observation data + """ + r = httpx.get(GetAllObsPath) + if r.status_code == 200: + return r.json() + return None
+ + +
[docs]def get_obs_by_id(task_id: int) -> Any | None: + """ + Get observation by id + Args: + task_id (int): id of observation data + + Returns: + obs (Any): Observation data + """ + r = httpx.get(GetObsByIdPath + str(task_id)) + if r.status_code == 200: + return r.json()
+ + +
[docs]def set_obs_data(obs: List[Dict[str, Any]]) -> bool: + """ + Set observation data web API + Args: + obs (List[Dict[str, Any]]): isaac observation data + + Returns: + OK if set successfully + """ + r = httpx.post(FlushObsUrl, json=obs, timeout=1) + if r.status_code == 200 and r.json()['msg'] == 'OK': + return True + return False
+ + +# Action +# send get, no poll&callback(all depends on ). +def get_actions(): + r = httpx.get(GetAllActionUrl) + if r.status_code == 200 and r.json()['data'] is not None: + return r.json()['msg'], r.json()['data'] + return None, {} + + +
[docs]def get_actions_by_id(task_id: int): + """ + Get actions by task id(int) + + Args: + task_id(int): id of task + + Returns: + msg: msg str(or None) + data: data + """ + r = httpx.get(GetActionByIdUrl + str(task_id)) + if r.status_code == 200 and r.json()['data'] is not None: + return r.json()['msg'], r.json()['data'] + return None, {}
+ + +
[docs]def send_actions(actions: List[ActionData]) -> bool: + """ + send actions + Args: + actions(List[ActionData]): action data list + + Returns: + Send message successfully or not + """ + r = httpx.post(SetActionsUrl, json=actions) + if r.status_code == 200: + return True + return False
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/datahub/web_ui_api.html b/html/_modules/grutopia/core/datahub/web_ui_api.html new file mode 100644 index 0000000..3b13db9 --- /dev/null +++ b/html/_modules/grutopia/core/datahub/web_ui_api.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + grutopia.core.datahub.web_ui_api — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.datahub.web_ui_api

+"""
+Includes web ui interactive
+"""
+import datetime
+import os
+from typing import Any, Dict, Union
+
+from grutopia.core.datahub.web_api import WebBEUrl
+from grutopia.core.util import AsyncRequest
+
+# constants
+SendChatControlUrl = WebBEUrl + '/api/grutopia/append_chat_control_data'
+SendCOTUrl = WebBEUrl + '/api/grutopia/append_chain_of_thought_data'
+SendLogDataUrl = WebBEUrl + '/api/grutopia/append_log_data'
+GetChatControlUrl = WebBEUrl + '/api/grutopia/getChatList'
+GetLogDataUrl = WebBEUrl + '/api/grutopia/getloglist'
+ClearUrl = WebBEUrl + '/api/grutopia/clear'
+
+WEBUI_HOST = os.getenv('WEBUI_HOST', '127.0.0.1')
+
+DefaultAvatarUrl = f'http://{WEBUI_HOST}:8080/static/avatar_default.jpg'
+
+AvatarUrls = {
+    'user': f'http://{WEBUI_HOST}:8080/static/avatar_00.jpg',
+    'agent': f'http://{WEBUI_HOST}:8080/static/avatar_01.jpg',
+}
+
+
+
[docs]def send_chain_of_thought(cot: str, uuid: str = 'none') -> None: + """ + chain of thought data + + Args: + uuid (str): uuid of chain of thought data, defaults to "none". + cot (str): chain of thought data. + """ + + def cot_format(x): + return {'type': 'text', 'value': x} + + res_data = [{'type': 'time', 'value': datetime.datetime.now().strftime('%H:%M')}] + for i in cot: + res_data.append(cot_format(i)) + AsyncRequest.post(uuid, SendCOTUrl, res_data)
+ + +
[docs]def send_chat_control(nickname: str, text: str, img: str = None, role: str = 'user', uuid: str = 'none') -> None: + """Send a new message to the chatbox. + + Args: + nickname (str): nickname displayed in the chatbox. + text (str): text to send to the chatbox. + img (str, optional): image to send to the chatbox. Defaults to None. + role (str, optional): role name, user or agent. Defaults to "user". + uuid (str, optional): uuid of the message. Defaults to 'none'. + """ + avatar_url = AvatarUrls.get(role, DefaultAvatarUrl) + res_data = { + 'type': role, + 'name': nickname, + 'time': datetime.datetime.now().strftime('%H:%M'), + 'message': text, + 'photo': avatar_url, + 'img': img, + } + AsyncRequest.post(uuid, SendChatControlUrl, res_data)
+ + +
[docs]def send_log_data(log_data: str, + log_type: str = 'message', + user: str = 'Bob', + photo_url: str = DefaultAvatarUrl, + uuid: str = 'none') -> None: + """Send log data. + + Args: + uuid (str): uuid of log, default is none. + log_data (str): log data. + log_type (str): type of log. 'message' or 'user'. + user (str): logger name. default: Bob. + photo_url (str): log photo url path. + + """ + if log_type == 'message': + res_data = {'type': 'message', 'message': log_data} + else: # user + if log_type != 'user': + return + res_data = { + 'type': 'user', + 'name': user, + 'time': datetime.datetime.now().strftime('%H:%M'), + 'message': log_data, + 'photo': photo_url + } + AsyncRequest.post(uuid, SendLogDataUrl, res_data)
+ + +
[docs]def get_log_data(uuid: str = 'none') -> Union[Dict[str, Any], None]: + """ + Get log data. + + Args: + uuid (str): log data uuid. default: none. + + Returns: + log_data (list[dict]): log data. + """ + ok, json_data = AsyncRequest.get(uuid, GetLogDataUrl) + if ok and json_data is not None: + return json_data + return None
+ + +
[docs]def get_chat_control(uuid: str = 'none') -> Union[Dict[str, Any], None]: + """ + Get chat control data. + + Args: + uuid (str): chat control uuid. default: none. + + Returns: + chat_control (List[Dict[str, Any]]): chat control data. + """ + ok, json_data = AsyncRequest.get(uuid, GetChatControlUrl) + if ok and json_data is not None: + return json_data + return None
+ + +
[docs]def clear(uuid: str = 'none'): + """ + Clear all data in webui. + """ + AsyncRequest.post(uuid, ClearUrl, None)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/env.html b/html/_modules/grutopia/core/env.html new file mode 100644 index 0000000..b11a01d --- /dev/null +++ b/html/_modules/grutopia/core/env.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + grutopia.core.env — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.env

+# import json
+from typing import Any, Dict, List
+
+import numpy as np
+
+from grutopia.core.config import SimulatorConfig
+from grutopia.core.util import log
+
+
+
[docs]class BaseEnv: + """ + Env base class. All tasks should inherit from this class(or subclass). + ---------------------------------------------------------------------- + """ + + def __init__(self, config: SimulatorConfig, headless: bool = True, webrtc: bool = False) -> None: + self._simulation_config = None + self._render = None + # Setup Multitask Env Parameters + self.env_map = {} + self.obs_map = {} + + self.config = config.config + self.env_num = config.env_num + self._column_length = int(np.sqrt(self.env_num)) + + # Init Isaac Sim + from omni.isaac.kit import SimulationApp + self.headless = headless + self._simulation_app = SimulationApp({'headless': self.headless, 'anti_aliasing': 0}) + + if webrtc: + from omni.isaac.core.utils.extensions import enable_extension # noqa + + self._simulation_app.set_setting('/app/window/drawMouse', True) + self._simulation_app.set_setting('/app/livestream/proto', 'ws') + self._simulation_app.set_setting('/app/livestream/websocket/framerate_limit', 60) + self._simulation_app.set_setting('/ngx/enabled', False) + enable_extension('omni.services.streamclient.webrtc') + + from grutopia.core import datahub # noqa E402. + from grutopia.core.runner import SimulatorRunner # noqa E402. + + self._runner = SimulatorRunner(config=config) + # self._simulation_config = sim_config + + log.debug(self.config.tasks) + # create tasks + self._runner.add_tasks(self.config.tasks) + return + + @property + def runner(self): + return self._runner + + @property + def is_render(self): + return self._render + + def get_dt(self): + return self._runner.dt + +
[docs] def step(self, actions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + run step with given action(with isaac step) + + Args: + actions (List[Dict[str, Any]]): action(with isaac step) + + Returns: + List[Dict[str, Any]]: observations(with isaac step) + """ + if len(actions) != len(self.config.tasks): + raise AssertionError('len of action list is not equal to len of task list') + _actions = [] + for action_idx, action in enumerate(actions): + _action = {} + for k, v in action.items(): + _action[f'{k}_{action_idx}'] = v + _actions.append(_action) + action_after_reshape = { + self.config.tasks[action_idx].name: action + for action_idx, action in enumerate(_actions) + } + + # log.debug(action_after_reshape) + self._runner.step(action_after_reshape) + observations = self.get_observations() + return observations
+ +
[docs] def reset(self, envs: List[int] = None): + """ + reset the environment(use isaac word reset) + + Args: + envs (List[int]): env need to be reset(default for reset all envs) + """ + if envs is not None: + if len(envs) == 0: + return + log.debug(f'============= reset: {envs} ==============') + # int -> name + self._runner.reset([self.config.tasks[e].name for e in envs]) + return self.get_observations(), {} + self._runner.reset() + return self.get_observations(), {}
+ +
[docs] def get_observations(self) -> List[Dict[str, Any]]: + """ + Get observations from Isaac environment + Returns: + List[Dict[str, Any]]: observations + """ + _obs = self._runner.get_obs() + return _obs
+ + def render(self, mode='human'): + return + +
[docs] def close(self): + """close the environment""" + self._simulation_app.close() + return
+ + @property + def simulation_config(self): + """config of simulation environment""" + return self._simulation_config + + @property + def simulation_app(self): + """simulation app instance""" + return self._simulation_app
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/register/register.html b/html/_modules/grutopia/core/register/register.html new file mode 100644 index 0000000..50949ce --- /dev/null +++ b/html/_modules/grutopia/core/register/register.html @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + grutopia.core.register.register — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.register.register

+import importlib
+import os
+
+from grutopia.core.util import log
+
+ALL_MODULES = []
+MODEL_MODULES = [
+    'controllers',
+    'objects',
+    'metrics',
+    'robots',
+    'sensors',
+    'tasks',
+    'interactions'
+]
+
+DEFAULT_EXTENSION_PATH = os.path.join(os.path.split(os.path.realpath(__file__))[0], '../../../grutopia_extension')
+
+
+def _handle_errors(errors):
+    """
+    Log out and possibly reraise errors during import.
+
+    Args:
+        errors: errors dict to be logged
+    """
+    if not errors:
+        return
+    for name, err in errors:
+        log.warning('Module {} import failed: {}'.format(name, err))
+
+
+
[docs]def import_all_modules_for_register(custom_module_paths=None, extension_path=None): + """ + Import all modules for register. + + Args: + custom_module_paths: custom module paths, e.g. ['xxx.lib1', 'xxx.lib2', 'xxx.lib3'] + extension_path: Extension path(integrated in grutopia_extension as default) + """ + if extension_path is None: + extension_path = DEFAULT_EXTENSION_PATH + for _mod in MODEL_MODULES: + # grutopia_extension's default path + path = os.path.join(extension_path, _mod) + m = [m.split('.py')[0] for m in os.listdir(path) if m.endswith('.py') and m != '__init__.py'] + ALL_MODULES.append((_mod, m)) + modules = [] + for base_dir, mods in ALL_MODULES: + for name in mods: + full_name = 'grutopia_extension.' + base_dir + '.' + name + modules.append(full_name) + if isinstance(custom_module_paths, list): + modules += custom_module_paths + errors = [] + for module in modules: + try: + importlib.import_module(module) + except ImportError as error: + errors.append((module, error)) + _handle_errors(errors)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/robot/controller.html b/html/_modules/grutopia/core/robot/controller.html new file mode 100644 index 0000000..b8824aa --- /dev/null +++ b/html/_modules/grutopia/core/robot/controller.html @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + grutopia.core.robot.controller — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.robot.controller

+# yapf: disable
+from abc import ABC, abstractmethod
+from functools import wraps
+from typing import Any, Dict, List, Union
+
+import numpy as np
+from omni.isaac.core.articulations import ArticulationSubset
+from omni.isaac.core.controllers import BaseController as Base
+from omni.isaac.core.scenes import Scene
+from omni.isaac.core.utils.types import ArticulationAction
+
+from grutopia.core.config.robot import RobotUserConfig
+from grutopia.core.config.robot.params import ControllerParams
+from grutopia.core.robot.robot import BaseRobot
+# yapf: disable
+from grutopia.core.robot.robot_model import ControllerModel, RobotModel
+# yapf: enable
+from grutopia.core.util import log
+
+# yapf: enable
+
+
+
[docs]class BaseController(Base, ABC): + """Base class of controller.""" + controllers = {} + + def __init__(self, config: ControllerModel, robot: BaseRobot, scene: Scene): + """Initialize the controller. + + Args: + config (ControllerModel): merged config (from user config and robot model) of the controller. + robot (BaseRobot): robot owning the controller. + scene (Scene): scene from isaac sim. + + """ + self.scene = scene + if config.name is None: + raise ValueError('must specify controller name.') + super().__init__(config.name) + self._obs = {} + self._robot = robot + self.config = config + self.sub_controllers: List[BaseController] + +
[docs] @abstractmethod + def action_to_control(self, action: Union[np.ndarray, List]) -> ArticulationAction: + """Convert input action (in 1d array format) to joint signals to apply. + + Args: + action (Union[np.ndarray, List]): input control action. + + Returns: + ArticulationAction: joint signals to apply + """ + raise NotImplementedError()
+ +
[docs] def get_obs(self) -> Dict[str, Any]: + """Get observation of controller. + + Returns: + Dict[str, Any]: observation key and value. + """ + obs = {} + for key, obs_ins in self._obs.items(): + obs[key] = obs_ins.get_obs() + return obs
+ +
[docs] @classmethod + def register(cls, name: str): + """Register a controller with its name(decorator). + + Args: + name (str): name of the controller + """ + + def decorator(controller_class): + cls.controllers[name] = controller_class + + @wraps(controller_class) + def wrapped_function(*args, **kwargs): + return controller_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + @property + def robot(self): + return self._robot + + @robot.setter + def robot(self, value): + self._robot = value + +
[docs] def get_joint_subset(self) -> ArticulationSubset: + """Get the joint subset controlled by the controller. + + Returns: + ArticulationSubset: joint subset. + """ + if hasattr(self, 'joint_subset'): + return self.joint_subset + if len(self.sub_controllers) > 0: + return self.sub_controllers[0].get_joint_subset() + raise NotImplementedError('attr joint_subset not found')
+ + +
[docs]def config_inject(user_config: ControllerParams, model: ControllerModel) -> ControllerModel: + """Merge controller config from user config and robot model. + + Args: + user_config (ControllerParams): user config. + model (ControllerModel): controller config from robot model. + + Returns: + ControllerModel: merged controller config. + """ + config = model.dict() + user = user_config.dict() + for k, v in user.items(): + if v is not None: + config[k] = v + conf = ControllerModel(**config) + + return conf
+ + +
[docs]def create_controllers(config: RobotUserConfig, robot_model: RobotModel, robot: BaseRobot, + scene: Scene) -> Dict[str, BaseController]: + """Create all controllers of one robot. + + Args: + config (RobotUserConfig): user config of the robot. + robot_model (RobotModel): model of the robot. + robot (BaseRobot): robot instance. + scene (Scene): scene from isaac sim. + + Returns: + Dict[str, BaseController]: dict of controllers with controller name as key. + """ + controller_map = {} + available_controllers = {a.name: a for a in robot_model.controllers} + + for controller_param in config.controller_params: + controller_name = controller_param.name + if controller_name in available_controllers: + controller_config = config_inject(controller_param, available_controllers[controller_name]) + controller_cls = BaseController.controllers[controller_config.type] + controller_ins: BaseController = controller_cls(config=controller_config, robot=robot, scene=scene) + if controller_config.sub_controllers is not None: + inject_sub_controllers(parent=controller_ins, + configs=controller_config.sub_controllers, + available=available_controllers, + robot=robot, + scene=scene) + else: + log.debug(available_controllers) + raise KeyError(f'{controller_name} not registered in controllers of {config.type}') + + controller_map[controller_name] = controller_ins + log.debug(f'==================== {controller_name} loaded==========================') + + return controller_map
+ + +
[docs]def inject_sub_controllers(parent: BaseController, configs: List[ControllerParams], + available: Dict[str, ControllerModel], robot: BaseRobot, scene: Scene): + """Recursively create and inject sub-controlllers into parent controller. + + Args: + parent (BaseController): parent controller instance. + configs (List[ControllerParams]): user configs of sub-controllers. + available (Dict[str, ControllerModel]): available controllers. + robot (BaseRobot): robot instance. + scene (Scene): scene from isaac sim. + """ + if len(configs) == 0: + return + sub_controllers: List[BaseController] = [] + for config in configs: + controller_name = config.name + if controller_name not in available: + raise KeyError(f'{controller_name} not registered in controllers of {robot.robot_model.type}') + controller_config = config_inject(config, available[controller_name]) + controller_cls = BaseController.controllers[controller_config.type] + controller_ins = controller_cls(config=controller_config, robot=robot, scene=scene) + if controller_config.sub_controllers is not None: + inject_sub_controllers(controller_ins, + configs=controller_config.sub_controllers, + available=available, + robot=robot, + scene=scene) + sub_controllers.append(controller_ins) + + parent.sub_controllers = sub_controllers
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/robot/robot.html b/html/_modules/grutopia/core/robot/robot.html new file mode 100644 index 0000000..aa1534a --- /dev/null +++ b/html/_modules/grutopia/core/robot/robot.html @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + grutopia.core.robot.robot — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.robot.robot

+from functools import wraps
+from typing import Dict, Tuple
+
+import numpy as np
+from omni.isaac.core.prims import RigidPrim
+from omni.isaac.core.robots.robot import Robot as IsaacRobot
+from omni.isaac.core.scenes import Scene
+
+from grutopia.core.config import RobotUserConfig, TaskUserConfig
+from grutopia.core.robot.robot_model import RobotModel, RobotModels
+from grutopia.core.util import log
+
+
+
[docs]class BaseRobot: + """Base class of robot.""" + robots = {} + + def __init__(self, config: RobotUserConfig, robot_model: RobotModel, scene: Scene): + self.name = config.name + self.robot_model = robot_model + self.user_config = config + self.isaac_robot: IsaacRobot | None = None + self.controllers = {} + self.sensors = {} + +
[docs] def set_up_to_scene(self, scene: Scene): + """Set up robot in the scene. + + Args: + scene (Scene): scene to setup. + """ + config = self.user_config + robot_model = self.robot_model + scene.add(self.isaac_robot) + log.debug('self.isaac_robot: ' + str(self.isaac_robot)) + from grutopia.core.robot.controller import BaseController, create_controllers + from grutopia.core.robot.sensor import BaseSensor, create_sensors + + self.controllers: Dict[str, BaseController] = create_controllers(config, robot_model, self, scene) + self.sensors: Dict[str, BaseSensor] = create_sensors(config, robot_model, self, scene)
+ +
[docs] def post_reset(self): + """Set up things that happen after the world resets.""" + pass
+ +
[docs] def apply_action(self, action: dict): + """Apply actions of controllers to robot. + + Args: + action (dict): action dict. + key: controller name. + value: corresponding action array. + """ + raise NotImplementedError()
+ +
[docs] def get_obs(self) -> dict: + """Get observation of robot, including controllers, sensors, and world pose. + + Raises: + NotImplementedError: _description_ + """ + raise NotImplementedError()
+ +
[docs] def get_robot_ik_base(self) -> RigidPrim: + """Get base link of ik controlled parts. + + Returns: + RigidPrim: rigid prim of ik base link. + """ + raise NotImplementedError()
+ +
[docs] def get_robot_base(self) -> RigidPrim: + """ + Get base link of robot. + + Returns: + RigidPrim: rigid prim of robot base link. + """ + raise NotImplementedError()
+ +
[docs] def get_robot_scale(self) -> np.ndarray: + """Get robot scale. + + Returns: + np.ndarray: robot scale in (x, y, z). + """ + return self.isaac_robot.get_local_scale()
+ +
[docs] def get_robot_articulation(self) -> IsaacRobot: + """Get isaac robots instance (articulation). + + Returns: + Robot: robot articulation. + """ + return self.isaac_robot
+ + def get_controllers(self): + return self.controllers + + def get_world_pose(self) -> Tuple[np.ndarray, np.ndarray]: + return self.isaac_robot.get_world_pose() + +
[docs] @classmethod + def register(cls, name: str): + """Register a robot class with its name(decorator). + + Args: + name(str): name of the robot class. + """ + + def decorator(robot_class): + cls.robots[name] = robot_class + + @wraps(robot_class) + def wrapped_function(*args, **kwargs): + return robot_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +
[docs]def create_robots(config: TaskUserConfig, robot_models: RobotModels, scene: Scene) -> Dict[str, BaseRobot]: + """Create robot instances in config. + Args: + config (TaskUserConfig): user config. + robot_models (RobotModels): robot models. + scene (Scene): isaac scene. + + Returns: + Dict[str, BaseRobot]: robot instances dictionary. + """ + robot_map = {} + for robot in config.robots: + if robot.type not in BaseRobot.robots: + raise KeyError(f'unknown robot type "{robot.type}"') + robot_cls = BaseRobot.robots[robot.type] + robot_models = robot_models.robots + r_model = None + for model in robot_models: + if model.type == robot.type: + r_model = model + if r_model is None: + raise KeyError(f'robot model of "{robot.type}" is not found') + robot_ins = robot_cls(robot, r_model, scene) + robot_map[robot.name] = robot_ins + robot_ins.set_up_to_scene(scene) + log.debug(f'===== {robot.name} loaded =====') + return robot_map
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/robot/sensor.html b/html/_modules/grutopia/core/robot/sensor.html new file mode 100644 index 0000000..afe11f7 --- /dev/null +++ b/html/_modules/grutopia/core/robot/sensor.html @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + grutopia.core.robot.sensor — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.robot.sensor

+from abc import ABC, abstractmethod
+from functools import wraps
+from typing import Dict
+
+from grutopia.core.config.robot import RobotUserConfig
+from grutopia.core.config.robot.params import SensorParams
+from grutopia.core.robot.robot import BaseRobot, Scene
+from grutopia.core.robot.robot_model import RobotModel, SensorModel
+from grutopia.core.util import log
+
+
+
[docs]class BaseSensor(ABC): + """Base class of sensor.""" + sensors = {} + + def __init__(self, config: SensorModel, robot: BaseRobot, scene: Scene): + """Initialize the sensor. + + Args: + config (SensorModel): merged config (from user config and robot model) of the sensor. + robot (BaseRobot): robot owning the sensor. + scene (Scene): scene from isaac sim. + """ + if config.name is None: + raise ValueError('must specify sensor name.') + self.name = config.name + self.config = config + self._scene = scene + self._robot = robot + + @abstractmethod + def sensor_init(self): + raise NotImplementedError() + +
[docs] @abstractmethod + def get_data(self) -> Dict: + """Get data from sensor. + + Returns: + Dict: data dict of sensor. + """ + raise NotImplementedError()
+ +
[docs] @classmethod + def register(cls, name: str): + """ + Register a sensor class with the given name(decorator). + Args: + name(str): name of the sensor class. + """ + + def decorator(sensor_class): + cls.sensors[name] = sensor_class + + @wraps(sensor_class) + def wrapped_function(*args, **kwargs): + return sensor_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +
[docs]def config_inject(params: SensorParams, model: SensorModel) -> SensorModel: + """Merge sensor config from user config and robot model. + + Args: + params (SensorParams): user config. + model (SensorModel): sensor config from robot model. + + Returns: + SensorModel: merged sensor config. + """ + if params is None: + return model + config = model.dict() + user = params.dict() + for k, v in user.items(): + if v is not None: + config[k] = v + conf = SensorModel(**config) + + return conf
+ + +
[docs]def create_sensors(config: RobotUserConfig, robot_model: RobotModel, robot: BaseRobot, + scene: Scene) -> Dict[str, BaseSensor]: + """Create all sensors of one robot. + + Args: + config (RobotUserConfig): user config of the robot. + robot_model (RobotModel): model of the robot. + robot (BaseRobot): robot instance. + scene (Scene): scene from isaac sim. + + Returns: + Dict[str, BaseSensor]: dict of sensors with sensor name as key. + """ + sensor_map = {} + if robot_model.sensors is not None: + available_sensors = {a.name: a for a in robot_model.sensors} + for sensor_name, sensor in available_sensors.items(): + if sensor.type not in BaseSensor.sensors: + raise KeyError(f'unknown sensor type "{sensor.type}"') + sensor_cls = BaseSensor.sensors[sensor.type] + # Find if user param exists for this sensor. + param = None + if config.sensor_params is not None: + for p in config.sensor_params: + if p.name == sensor_name: + param = p + break + + sensor_ins = sensor_cls(config=config_inject(param, sensor), robot=robot, name=sensor_name, scene=scene) + sensor_map[sensor_name] = sensor_ins + sensor_ins.sensor_init() + log.debug(f'==================== {sensor_name} loaded==========================') + + return sensor_map
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/scene/object.html b/html/_modules/grutopia/core/scene/object.html new file mode 100644 index 0000000..5fd8c23 --- /dev/null +++ b/html/_modules/grutopia/core/scene/object.html @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + grutopia.core.scene.object — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.scene.object

+from functools import wraps
+
+from omni.isaac.core.scenes import Scene
+
+from grutopia.core.config import Object as ObjectConfig
+
+
+
[docs]class ObjectCommon: + """ + Object common class. + """ + objs = {} + + def __init__(self, config: ObjectConfig): + self._config = config + + def set_up_scene(self, scene: Scene): + raise NotImplementedError + +
[docs] @classmethod + def register(cls, name: str): + """ + Register an object class with the given name(decorator). + + Args: + name(str): name of the object + """ + + def decorator(object_class): + cls.objs[name] = object_class + + @wraps(object_class) + def wrapped_function(*args, **kwargs): + return object_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +
[docs]def create_object(config: ObjectConfig): + """ + Create an object. + Args: + config (ObjectConfig): configuration of the objects + """ + assert config.type in ObjectCommon.objs, 'unknown objects type {}'.format(config.type) + cls = ObjectCommon.objs[config.type] + return cls(config)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/scene/scene/util/usd_op.html b/html/_modules/grutopia/core/scene/scene/util/usd_op.html new file mode 100644 index 0000000..193c1e5 --- /dev/null +++ b/html/_modules/grutopia/core/scene/scene/util/usd_op.html @@ -0,0 +1,608 @@ + + + + + + + + + + + + + + + grutopia.core.scene.scene.util.usd_op — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.scene.scene.util.usd_op

+import typing
+
+from pxr import Gf, Sdf, Usd, UsdGeom
+
+from grutopia.core.scene.scene.util.type import get_xformop_precision, get_xformop_type
+from grutopia.core.util import log
+
+
+
[docs]def add_usd_ref(source_stage: Usd.Stage, dest_stage: Usd.Stage, src_prim_path: str, dest_prim_path: str) -> None: + """ + Add an opened usd into another usd as a reference + set name in dest_prim_path + + Args: + source_stage (Usd.Stage): source stage + dest_stage (Usd.Stage): dest stage + src_prim_path (str): source prim path + dest_prim_path (str): dest prim path + """ + src_root_layer = source_stage.GetRootLayer() + log.debug(src_root_layer.identifier) + source_prim = source_stage.GetPrimAtPath(src_prim_path) + dest_prim = dest_stage.DefinePrim(dest_prim_path, source_prim.GetTypeName()) + dest_prim.GetReferences().AddReference(src_root_layer.identifier) + dest_stage.GetRootLayer().Save()
+ + +
[docs]def get_local_transform_xform(prim: Usd.Prim) -> typing.Tuple[Gf.Vec3d, Gf.Rotation, Gf.Vec3d]: + """ + Get the local transformation of a prim using Xformable. + + Args: + prim: The prim to calculate the local transformation. + Returns: + A tuple of: + - Translation vector. + - Rotation quaternion, i.e. 3d vector plus angle. + - Scale vector. + """ + xform = UsdGeom.Xformable(prim) + local_transformation: Gf.Matrix4d = xform.GetLocalTransformation() + translation: Gf.Vec3d = local_transformation.ExtractTranslation() + rotation: Gf.Rotation = local_transformation.ExtractRotation() + scale: Gf.Vec3d = Gf.Vec3d(*(v.GetLength() for v in local_transformation.ExtractRotationMatrix())) + return translation, rotation, scale
+ + +
[docs]def get_world_transform_xform(prim: Usd.Prim) -> typing.Tuple[Gf.Vec3d, Gf.Rotation, Gf.Vec3d]: + """ + Get the local transformation of a prim using Xformable. + + Args: + prim: The prim to calculate the world transformation. + Returns: + A tuple of: + - Translation vector. + - Rotation quaternion, i.e. 3d vector plus angle. + - Scale vector. + """ + xform = UsdGeom.Xformable(prim) + time = Usd.TimeCode.Default() + world_transform: Gf.Matrix4d = xform.ComputeLocalToWorldTransform(time) + translation: Gf.Vec3d = world_transform.ExtractTranslation() + rotation: Gf.Rotation = world_transform.ExtractRotation() + scale: Gf.Vec3d = Gf.Vec3d(*(v.GetLength() for v in world_transform.ExtractRotationMatrix())) + return translation, rotation, scale
+ + +
[docs]def create_new_usd(new_usd_path: str, default_prim_name: str, default_axis: str = None) -> Usd.Stage: + """ + Create a new usd + + Args: + new_usd_path (str): where to place this new usd + default_prim_name (str): default prim name (root prim path) + default_axis (str): default axis for new usd + """ + stage: Usd.Stage = Usd.Stage.CreateNew(new_usd_path) + default_prim: Usd.Prim = UsdGeom.Xform.Define(stage, Sdf.Path('/' + default_prim_name)).GetPrim() + _set_default_prim(stage, default_prim) + _set_up_axis(stage, default_axis) + stage.GetRootLayer().Save() + return stage
+ + +def _set_up_axis(stage: Usd.Stage, axis_str: str = None) -> None: + """ + Set default axis for a stage + + Args: + stage (Usd.Stage): objects stage + axis_str (str, optional): axis str, 'y' or 'z', set 'z' if None. Defaults to None. + """ + if axis_str == 'y' or axis_str == 'Y': + axis: UsdGeom.Tokens = UsdGeom.Tokens.y + else: + axis: UsdGeom.Tokens = UsdGeom.Tokens.z + UsdGeom.SetStageUpAxis(stage, axis) + + +def _set_default_prim(stage: Usd.Stage, prim: Usd.Prim) -> None: + """ + Set default prim for a stage + + Args: + stage (Usd.Stage): objects stage + prim (Usd.Prim): prim in this stage + """ + stage.SetDefaultPrim(prim) + + +
[docs]def compute_bbox(prim: Usd.Prim) -> Gf.Range3d: + """ + Compute Bounding Box using ComputeWorldBound at UsdGeom.Imageable + + Args: + prim: A prim to compute the bounding box. + Returns: + A range (i.e. bounding box) + """ + imageable: UsdGeom.Imageable = UsdGeom.Imageable(prim) + time = Usd.TimeCode.Default() + bound = imageable.ComputeWorldBound(time, UsdGeom.Tokens.default_) + bound_range = bound.ComputeAlignedBox() + return bound_range
+ + +
[docs]def delete_prim_in_stage(stage: Usd.Stage, prim: Usd.Prim) -> None: + """ + Delete a prim in stage + + Args: + stage (Usd.Stage): objects stage + prim (Usd.Prim): prim to be deleted + """ + stage.RemovePrim(prim.GetPrimPath())
+ + +
[docs]def set_xform_of_prim(prim: Usd.Prim, xform_op: str, set_valve: typing.Any) -> None: + """ + Set xform data of a prim with new data + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be set + set_valve (typing.Any): new data to be set, could be np.array + """ + stage = prim.GetStage() + op_list = prim.GetAttribute('xformOpOrder').Get() + s = None + for i in op_list: + if xform_op == i: + log.debug(prim.GetAttribute(i)) + s = prim.GetAttribute(i) + trans = s.Get() + trans_value = set_valve + data_class = type(trans) + time_code = Usd.TimeCode.Default() + new_data = data_class(*trans_value) + s.Set(new_data, time_code) + stage.Save()
+ + +
[docs]def delete_xform_of_prim(prim: Usd.Prim, xform_op: str) -> None: + """ + Delete xform data of a prim + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be deleted + """ + stage = prim.GetStage() + if prim.HasAttribute(xform_op): + # Clear the attribute from the Prim + prim.GetAttribute(xform_op).Clear() + stage.Save()
+ + +
[docs]def add_xform_of_prim(prim: Usd.Prim, xform_op: str, set_valve: typing.Any) -> None: + """ + Add xform data of a prim with new data + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be set + set_valve (typing.Any): new data to be set, could be Gf.Vec3d, Gf.Rotation + """ + stage = prim.GetStage() + attribute_name = xform_op + attribute_value = set_valve + opType = get_xformop_type(xform_op) + precision = get_xformop_precision('float') + attribute = UsdGeom.Xformable(prim).AddXformOp(opType, precision) + if attribute: + attribute.Set(attribute_value) + # log.debug(f"Attribute {attribute_name} has been set to {attribute_value}.") + else: + log.debug(f'Failed to create attribute named {attribute_name}.') + stage.Save()
+ + +
[docs]def add_xform_of_prim_old(prim: Usd.Prim, xform_op: str, set_valve: typing.Any) -> None: + """ + Add xform data of a prim with new data + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be set + set_valve (typing.Any): new data to be set, could be Gf.Vec3d, Gf.Rotation + """ + stage = prim.GetStage() + attribute_name = xform_op + attribute_value = set_valve + if '3' in type(set_valve).__name__: + attribute_type = Sdf.ValueTypeNames.Float3 + else: + attribute_type = Sdf.ValueTypeNames.Float + attribute = prim.CreateAttribute(attribute_name, attribute_type) + if attribute: + attribute.Set(attribute_value) + # log.debug(f"Attribute {attribute_name} has been set to {attribute_value}.") + else: + log.debug(f'Failed to create attribute named {attribute_name}.') + stage.Save()
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/task/task.html b/html/_modules/grutopia/core/task/task.html new file mode 100644 index 0000000..e8a25e2 --- /dev/null +++ b/html/_modules/grutopia/core/task/task.html @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + grutopia.core.task.task — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.task.task

+# import random
+import traceback
+from abc import ABC, abstractmethod
+from functools import wraps
+from typing import Any, Dict
+
+from omni.isaac.core.scenes.scene import Scene
+from omni.isaac.core.tasks import BaseTask as OmniBaseTask
+from omni.isaac.core.utils.prims import create_prim
+
+from grutopia.core.config import TaskUserConfig
+from grutopia.core.robot import init_robots
+from grutopia.core.scene import create_object, create_scene
+from grutopia.core.task.metric import BaseMetric, create_metric
+from grutopia.core.util import log
+
+
+
[docs]class BaseTask(OmniBaseTask, ABC): + """ + wrap of omniverse isaac sim's base task + + * enable register for auto register task + * contains robots + """ + tasks = {} + + def __init__(self, config: TaskUserConfig, scene: Scene): + self.objects = None + self.robots = None + name = config.name + offset = config.offset + super().__init__(name=name, offset=offset) + self._scene = scene + self.config = config + + self.metrics: dict[str, BaseMetric] = {} + self.steps = 0 + self.work = True + + for metric_config in config.metrics: + self.metrics[metric_config.name] = create_metric(metric_config) + + def load(self): + if self.config.scene_asset_path is not None: + source, prim_path = create_scene(self.config.scene_asset_path, + prim_path_root=f'World/env_{self.config.env_id}/scene') + create_prim(prim_path, + usd_path=source, + scale=self.config.scene_scale, + translation=[self.config.offset[idx] + i for idx, i in enumerate(self.config.scene_position)]) + + self.robots = init_robots(self.config, self._scene) + self.objects = {} + for obj in self.config.objects: + _object = create_object(obj) + _object.set_up_scene(self._scene) + self.objects[obj.name] = _object + log.info(self.robots) + log.info(self.objects) + +
[docs] def set_up_scene(self, scene: Scene) -> None: + self._scene = scene + self.load()
+ +
[docs] def get_observations(self) -> Dict[str, Any]: + """ + Returns current observations from the objects needed for the behavioral layer. + + Return: + Dict[str, Any]: observation of robots in this task + """ + if not self.work: + return {} + obs = {} + for robot_name, robot in self.robots.items(): + try: + obs[robot_name] = robot.get_obs() + except Exception as e: + log.error(self.name) + log.error(e) + traceback.print_exc() + return {} + return obs
+ + def update_metrics(self): + for _, metric in self.metrics.items(): + metric.update() + +
[docs] def calculate_metrics(self) -> dict: + metrics_res = {} + for name, metric in self.metrics.items(): + metrics_res[name] = metric.calc() + + return metrics_res
+ +
[docs] @abstractmethod + def is_done(self) -> bool: + """ + Returns True of the task is done. + + Raises: + NotImplementedError: this must be overridden. + """ + raise NotImplementedError
+ +
[docs] def individual_reset(self): + """ + reload this task individually without reloading whole world. + """ + raise NotImplementedError
+ +
[docs] def pre_step(self, time_step_index: int, simulation_time: float) -> None: + """called before stepping the physics simulation. + + Args: + time_step_index (int): [description] + simulation_time (float): [description] + """ + self.steps += 1 + return
+ +
[docs] def post_reset(self) -> None: + """Calls while doing a .reset() on the world.""" + self.steps = 0 + for robot in self.robots.values(): + robot.post_reset() + return
+ +
[docs] @classmethod + def register(cls, name: str): + """ + Register a task with its name(decorator). + Args: + name(str): name of the task + """ + + def decorator(tasks_class): + cls.tasks[name] = tasks_class + + @wraps(tasks_class) + def wrapped_function(*args, **kwargs): + return tasks_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +def create_task(config: TaskUserConfig, scene: Scene): + task_cls = BaseTask.tasks[config.type] + return task_cls(config, scene) +
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/util/array.html b/html/_modules/grutopia/core/util/array.html new file mode 100644 index 0000000..7cf6fe8 --- /dev/null +++ b/html/_modules/grutopia/core/util/array.html @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + grutopia.core.util.array — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.util.array

+# Copyright (c) 2022-2024, The ORBIT Project Developers.
+# All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+"""Sub-module containing utilities for working with different array backends."""
+
+from typing import Union
+
+import numpy as np
+import torch
+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``).
+"""
+
+
+
[docs]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/html/_modules/grutopia/core/util/configclass.html b/html/_modules/grutopia/core/util/configclass.html new file mode 100644 index 0000000..1a3c1db --- /dev/null +++ b/html/_modules/grutopia/core/util/configclass.html @@ -0,0 +1,799 @@ + + + + + + + + + + + + + + + grutopia.core.util.configclass — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.util.configclass

+# Copyright (c) 2022-2024, The ORBIT 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
+
+
+
[docs]@__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.orbit.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 f + + return _wrap +
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/util/dict.html b/html/_modules/grutopia/core/util/dict.html new file mode 100644 index 0000000..8fc7000 --- /dev/null +++ b/html/_modules/grutopia/core/util/dict.html @@ -0,0 +1,643 @@ + + + + + + + + + + + + + + + grutopia.core.util.dict — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.util.dict

+# Copyright (c) 2022-2024, The ORBIT Project Developers.
+# All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# yapf: disable
+
+"""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.
+"""
+
+
+
[docs]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
+ + +
[docs]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. +""" + + +
[docs]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. +""" + + +
[docs]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
+ + +
[docs]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. + + Reference: + https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth + + 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/html/_modules/grutopia/core/util/omni_usd_util.html b/html/_modules/grutopia/core/util/omni_usd_util.html new file mode 100644 index 0000000..215f94c --- /dev/null +++ b/html/_modules/grutopia/core/util/omni_usd_util.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + grutopia.core.util.omni_usd_util — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.util.omni_usd_util

+import time
+import typing
+
+import carb
+import numpy as np
+import omni.usd
+from pxr import Gf, Usd
+
+
+
[docs]def compute_path_bbox(prim_path: str) -> typing.Tuple[carb.Double3, carb.Double3]: + """ + Compute Bounding Box using omni.usd.UsdContext.compute_path_world_bounding_box + See https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd/omni.usd.UsdContext.html#\ + omni.usd.UsdContext.compute_path_world_bounding_box + + Args: + prim_path: A prim path to compute the bounding box. + Returns: + A range (i.e. bounding box) as a minimum point and maximum point. + """ + return omni.usd.get_context().compute_path_world_bounding_box(prim_path)
+ + +
[docs]def get_pick_position(robot_base_position: np.ndarray, prim_path: str) -> np.ndarray: + """Get the pick position for a manipulator robots to pick an objects at prim_path. + The pick position is simply the nearest top vertex of the objects's bounding box. + + Args: + robot_base_position (np.ndarray): robots base position. + prim_path (str): prim path of objects to pick. + + Returns: + np.ndarray: pick position. + """ + bbox_0, bbox_1 = compute_path_bbox(prim_path) + + x1 = bbox_0[0] + x2 = bbox_1[0] + y1 = bbox_0[1] + y2 = bbox_1[1] + top_z = bbox_0[2] if bbox_0[2] > bbox_1[2] else bbox_1[2] + + top_vertices = [ + np.array([x1, y1, top_z]), + np.array([x1, y2, top_z]), + np.array([x2, y1, top_z]), + np.array([x2, y2, top_z]), + ] + + print('================================ Top vertices: ', top_vertices, ' ====================================') + + pick_position = top_vertices[0] + for vertex in top_vertices: + if np.linalg.norm(robot_base_position - vertex) < np.linalg.norm(robot_base_position - pick_position): + pick_position = vertex + + return pick_position
+ + +
[docs]def get_grabbed_able_xform_paths(root_path: str, prim: Usd.Prim, depth: int = 3) -> typing.List[str]: + """get all prim paths of Xform objects under specified prim. + + Args: + root_path (str): root path of scenes. + prim (Usd.Prim): target prim. + depth (int, optional): expected depth of Xform objects relative to root_path. Defaults to 3. + + Returns: + typing.List[str]: prim paths. + """ + paths = [] + if prim is None: + return paths + print(f'get_grabbed_able_xform_paths: start to traverse {prim.GetPrimPath()}') + relative_prim_path = str(prim.GetPrimPath())[len(root_path):] + if relative_prim_path.count('/') <= depth: + for child in prim.GetChildren(): + if child.GetTypeName() == 'Scope': + paths.extend(get_grabbed_able_xform_paths(root_path, child)) + if child.GetTypeName() == 'Xform': + paths.append(str(child.GetPrimPath())) + + return paths
+ + +
[docs]def get_world_transform_xform(prim: Usd.Prim) -> typing.Tuple[Gf.Vec3d, Gf.Rotation, Gf.Vec3d]: + """ + Get the local transformation of a prim using omni.usd.get_world_transform_matrix(). + See https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd/omni.usd.get_world_transform_matrix.html + Args: + prim: The prim to calculate the world transformation. + Returns: + A tuple of: + - Translation vector. + - Rotation quaternion, i.e. 3d vector plus angle. + - Scale vector. + """ + world_transform: Gf.Matrix4d = omni.usd.get_world_transform_matrix(prim) + translation: Gf.Vec3d = world_transform.ExtractTranslation() + rotation: Gf.Rotation = world_transform.ExtractRotation() + scale: Gf.Vec3d = Gf.Vec3d(*(v.GetLength() for v in world_transform.ExtractRotationMatrix())) + return translation, rotation, scale
+ + +
[docs]def nearest_xform_from_position(stage: Usd.Stage, + xform_paths: typing.List[str], + position: np.ndarray, + threshold: float = 0) -> str: + """get prim path of nearest Xform objects from the target position. + + Args: + stage (Usd.Stage): usd stage. + xform_paths (typing.List[str]): full list of xforms paths. + position (np.ndarray): target position. + threshold (float, optional): max distance. Defaults to 0 (unlimited). + + Returns: + str: prim path of the Xform objects, None if not found. + """ + start = time.time() + if threshold == 0: + threshold = 1000000.0 + min_dist = threshold + nearest_prim_path = None + for path in xform_paths: + prim = stage.GetPrimAtPath(path) + if prim is not None and prim.IsValid(): + pose = get_world_transform_xform(prim) + dist = np.linalg.norm(pose[0] - position) + if dist < min_dist: + min_dist = dist + nearest_prim_path = path + + print(f'nearest_xform_from_position costs: {time.time() - start}') + return nearest_prim_path
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/util/python.html b/html/_modules/grutopia/core/util/python.html new file mode 100644 index 0000000..8c22db9 --- /dev/null +++ b/html/_modules/grutopia/core/util/python.html @@ -0,0 +1,1222 @@ + + + + + + + + + + + + + + + grutopia.core.util.python — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.util.python

+"""
+A set of utility functions for general python usage
+"""
+import inspect
+import re
+from abc import ABCMeta
+from collections.abc import Iterable
+from copy import deepcopy
+from functools import wraps
+from importlib import import_module
+
+import numpy as np
+
+# Global dictionary storing all unique names
+NAMES = set()
+CLASS_NAMES = set()
+
+
+class ClassProperty:
+
+    def __init__(self, f_get):
+        self.f_get = f_get
+
+    def __get__(self, owner_self, owner_cls):
+        return self.f_get(owner_cls)
+
+
+
[docs]def subclass_factory(name, base_classes, __init__=None, **kwargs): + """ + Programmatically generates a new class type with name @name, subclassing from base classes @base_classes, with + corresponding __init__ call @__init__. + + NOTE: If __init__ is None (default), the __init__ call from @base_classes will be used instead. + + cf. https://stackoverflow.com/questions/15247075/how-can-i-dynamically-create-derived-classes-from-a-base-class + + Args: + name (str): Generated class name + base_classes (type, or list of type): Base class(es) to use for generating the subclass + __init__ (None or function): Init call to use for the base class when it is instantiated. If None if specified, + the newly generated class will automatically inherit the __init__ call from @base_classes + **kwargs (any): keyword-mapped parameters to override / set in the child class, where the keys represent + the class / instance attribute to modify and the values represent the functions / value to set + """ + # Standardize base_classes + base_classes = tuple(base_classes if isinstance(base_classes, Iterable) else [base_classes]) + + # Generate the new class + if __init__ is not None: + kwargs['__init__'] = __init__ + return type(name, base_classes, kwargs)
+ + +
[docs]def save_init_info(func): + """ + Decorator to save the init info of an objects to objects._init_info. + + _init_info contains class name and class constructor's input args. + """ + sig = inspect.signature(func) + + @wraps(func) # preserve func name, docstring, arguments list, etc. + def wrapper(self, *args, **kwargs): + values = sig.bind(self, *args, **kwargs) + + # Prevent args of super init from being saved. + if hasattr(self, '_init_info'): + func(*values.args, **values.kwargs) + return + + # Initialize class's self._init_info. + self._init_info = {'class_module': self.__class__.__module__, 'class_name': self.__class__.__name__, 'args': {}} + + # Populate class's self._init_info. + for k, p in sig.parameters.items(): + if k == 'self': + continue + if k in values.arguments: + val = values.arguments[k] + if p.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY): + self._init_info['args'][k] = val + elif p.kind == inspect.Parameter.VAR_KEYWORD: + for kwarg_k, kwarg_val in values.arguments[k].items(): + self._init_info['args'][kwarg_k] = kwarg_val + + # Call the original function. + func(*values.args, **values.kwargs) + + return wrapper
+ + +
[docs]class RecreatableMeta(type): + """ + Simple metaclass that automatically saves __init__ args of the instances it creates. + """ + + def __new__(cls, clsname, bases, clsdict): + if '__init__' in clsdict: + clsdict['__init__'] = save_init_info(clsdict['__init__']) + return super().__new__(cls, clsname, bases, clsdict)
+ + +
[docs]class RecreatableAbcMeta(RecreatableMeta, ABCMeta): + """ + A composite metaclass of both RecreatableMeta and ABCMeta. + + Adding in ABCMeta to resolve metadata conflicts. + """ + + pass
+ + +
[docs]class Recreatable(metaclass=RecreatableAbcMeta): + """ + Simple class that provides an abstract interface automatically saving __init__ args of + the classes inheriting it. + """ + +
[docs] def get_init_info(self): + """ + Grabs relevant initialization information for this class instance. Useful for directly + reloading an objects from this information, using @create_object_from_init_info. + + Returns: + dict: Nested dictionary that contains this objects' initialization information + """ + # Note: self._init_info is procedurally generated via @save_init_info called in metaclass + return self._init_info
+ + +
[docs]def create_object_from_init_info(init_info): + """ + Create a new objects based on given init info. + + Args: + init_info (dict): Nested dictionary that contains an objects's init information. + + Returns: + any: Newly created objects. + """ + module = import_module(init_info['class_module']) + cls = getattr(module, init_info['class_name']) + return cls(**init_info['args'], **init_info.get('kwargs', {}))
+ + +
[docs]def merge_nested_dicts(base_dict, extra_dict, inplace=False, verbose=False): + """ + Iteratively updates @base_dict with values from @extra_dict. Note: This generates a new dictionary! + + Args: + base_dict (dict): Nested base dictionary, which should be updated with all values from @extra_dict + extra_dict (dict): Nested extra dictionary, whose values will overwrite corresponding ones in @base_dict + inplace (bool): Whether to modify @base_dict in place or not + verbose (bool): If True, will print when keys are mismatched + + Returns: + dict: Updated dictionary + """ + # Loop through all keys in @extra_dict and update the corresponding values in @base_dict + base_dict = base_dict if inplace else deepcopy(base_dict) + for k, v in extra_dict.items(): + if k not in base_dict: + base_dict[k] = v + else: + if isinstance(v, dict) and isinstance(base_dict[k], dict): + base_dict[k] = merge_nested_dicts(base_dict[k], v) + else: + not_equal = base_dict[k] != v + if isinstance(not_equal, np.ndarray): + not_equal = not_equal.any() + if not_equal and verbose: + print(f'Different values for key {k}: {base_dict[k]}, {v}\n') + base_dict[k] = np.array(v) if isinstance(v, list) else v + + # Return new dict + return base_dict
+ + +
[docs]def get_class_init_kwargs(cls): + """ + Helper function to return a list of all valid keyword arguments (excluding "self") for the given @cls class. + + Args: + cls (object): Class from which to grab __init__ kwargs + + Returns: + list: All keyword arguments (excluding "self") specified by @cls __init__ constructor method + """ + return list(inspect.signature(cls.__init__).parameters.keys())[1:]
+ + +
[docs]def extract_subset_dict(dic, keys, copy=False): + """ + Helper function to extract a subset of dictionary key-values from a current dictionary. Optionally (deep)copies + the values extracted from the original @dic if @copy is True. + + Args: + dic (dict): Dictionary containing multiple key-values + keys (Iterable): Specific keys to extract from @dic. If the key doesn't exist in @dic, then the key is skipped + copy (bool): If True, will deepcopy all values corresponding to the specified @keys + + Returns: + dict: Extracted subset dictionary containing only the specified @keys and their corresponding values + """ + subset = {k: dic[k] for k in keys if k in dic} + return deepcopy(subset) if copy else subset
+ + +
[docs]def extract_class_init_kwargs_from_dict(cls, dic, copy=False): + """ + Helper function to return a dictionary of key-values that specifically correspond to @cls class's __init__ + constructor method, from @dic which may or may not contain additional, irrelevant kwargs. + Note that @dic may possibly be missing certain kwargs as specified by cls.__init__. No error will be raised. + + Args: + cls (object): Class from which to grab __init__ kwargs that will be be used as filtering keys for @dic + dic (dict): Dictionary containing multiple key-values + copy (bool): If True, will deepcopy all values corresponding to the specified @keys + + Returns: + dict: Extracted subset dictionary possibly containing only the specified keys from cls.__init__ and their + corresponding values + """ + # extract only relevant kwargs for this specific backbone + return extract_subset_dict( + dic=dic, + keys=get_class_init_kwargs(cls), + copy=copy, + )
+ + +
[docs]def assert_valid_key(key, valid_keys, name=None): + """ + Helper function that asserts that @key is in dictionary @valid_keys keys. If not, it will raise an error. + + Args: + key (any): key to check for in dictionary @dic's keys + valid_keys (Iterable): contains keys should be checked with @key + name (str or None): if specified, is the name associated with the key that will be printed out if the + key is not found. If None, default is "value" + """ + if name is None: + name = 'value' + assert key in valid_keys, 'Invalid {} received! Valid options are: {}, got: {}'.format( + name, + valid_keys.keys() if isinstance(valid_keys, dict) else valid_keys, key)
+ + +
[docs]def create_class_from_registry_and_config(cls_name, cls_registry, cfg, cls_type_descriptor): + """ + Helper function to create a class with str type @cls_name, which should be a valid entry in @cls_registry, using + kwargs in dictionary form @cfg to pass to the constructor, with @cls_type_name specified for debugging + + Args: + cls_name (str): Name of the class to create. This should correspond to the actual class type, in string form + cls_registry (dict): Class registry. This should map string names of valid classes to create to the + actual class type itself + cfg (dict): Any keyword arguments to pass to the class constructor + cls_type_descriptor (str): Description of the class type being created. This can be any string and is used + solely for debugging purposes + + Returns: + any: Created class instance + """ + # Make sure the requested class type is valid + assert_valid_key(key=cls_name, valid_keys=cls_registry, name=f'{cls_type_descriptor} type') + + # Grab the kwargs relevant for the specific class + cls = cls_registry[cls_name] + cls_kwargs = extract_class_init_kwargs_from_dict(cls=cls, dic=cfg, copy=False) + + # Create the class + return cls(**cls_kwargs)
+ + +
[docs]def get_uuid(name, n_digits=8): + """ + Helper function to create a unique @n_digits uuid given a unique @name + + Args: + name (str): Name of the objects or class + n_digits (int): Number of digits of the uuid, default is 8 + + Returns: + int: uuid + """ + return abs(hash(name)) % (10**n_digits)
+ + +
[docs]def camel_case_to_snake_case(camel_case_text): + """ + Helper function to convert a camel case text to snake case, e.g. "StrawberrySmoothie" -> "strawberry_smoothie" + + Args: + camel_case_text (str): Text in camel case + + Returns: + str: snake case text + """ + return re.sub(r'(?<!^)(?=[A-Z])', '_', camel_case_text).lower()
+ + +
[docs]def snake_case_to_camel_case(snake_case_text): + """ + Helper function to convert a snake case text to camel case, e.g. "strawberry_smoothie" -> "StrawberrySmoothie" + + Args: + snake_case_text (str): Text in snake case + + Returns: + str: camel case text + """ + return ''.join(item.title() for item in snake_case_text.split('_'))
+ + +
[docs]def meets_minimum_version(test_version, minimum_version): + """ + Verify that @test_version meets the @minimum_version + + Args: + test_version (str): Python package version. Should be, e.g., 0.26.1 + minimum_version (str): Python package version to test against. Should be, e.g., 0.27.2 + + Returns: + bool: Whether @test_version meets @minimum_version + """ + test_nums = [int(num) for num in test_version.split('.')] + minimum_nums = [int(num) for num in minimum_version.split('.')] + assert len(test_nums) == 3 + assert len(minimum_nums) == 3 + + for test_num, minimum_num in zip(test_nums, minimum_nums): + if test_num > minimum_num: + return True + elif test_num < minimum_num: + return False + # Otherwise, we continue through all sub-versions + + # If we get here, that means test_version == threshold_version, so this is a success + return True
+ + +
[docs]class UniquelyNamed: + """ + Simple class that implements a name property, that must be implemented by a subclass. Note that any @Named + entity must be UNIQUE! + """ + + def __init__(self): + global NAMES + # Register this objects, making sure it's name is unique + assert self.name not in NAMES, \ + f'UniquelyNamed objects with name {self.name} already exists!' + NAMES.add(self.name) + + # def __del__(self): + # # Remove this objects name from the registry if it's still there + # self.remove_names(include_all_owned=True) + +
[docs] def remove_names(self, include_all_owned=True, skip_ids=None): + """ + Checks if self.name exists in the global NAMES registry, and deletes it if so. Possibly also iterates through + all owned member variables and checks for their corresponding names if @include_all_owned is True. + + Args: + include_all_owned (bool): If True, will iterate through all owned members of this instance and remove their + names as well, if they are UniquelyNamed + + skip_ids (None or set of int): If specified, will skip over any ids in the specified set that are matched + to any attributes found (this compares id(attr) to @skip_ids). + """ + # Make sure skip_ids is a set so we can pass this into the method, and add the dictionary so we don't + # get infinite recursive loops + skip_ids = set() if skip_ids is None else skip_ids + skip_ids.add(id(self)) + + # Check for this name, possibly remove it if it exists + if self.name in NAMES: + NAMES.remove(self.name) + + # Also possibly iterate through all owned members and check if those are instances of UniquelyNamed + if include_all_owned: + self._remove_names_recursively_from_dict(dic=self.__dict__, skip_ids=skip_ids)
+ + def _remove_names_recursively_from_dict(self, dic, skip_ids=None): + """ + Checks if self.name exists in the global NAMES registry, and deletes it if so + + Args: + skip_ids (None or set): If specified, will skip over any objects in the specified set that are matched + to any attributes found. + """ + # Make sure skip_ids is a set so we can pass this into the method, and add the dictionary so we don't + # get infinite recursive loops + skip_ids = set() if skip_ids is None else skip_ids + skip_ids.add(id(dic)) + + # Loop through all values in the inputted dictionary, and check if any of the values are UniquelyNamed + for name, val in dic.items(): + if id(val) not in skip_ids: + # No need to explicitly add val to skip objects because the methods below handle adding it + if isinstance(val, UniquelyNamed): + val.remove_names(include_all_owned=True, skip_ids=skip_ids) + elif isinstance(val, dict): + # Recursively iterate + self._remove_names_recursively_from_dict(dic=val, skip_ids=skip_ids) + elif hasattr(val, '__dict__'): + # Add the attribute and recursively iterate + skip_ids.add(id(val)) + self._remove_names_recursively_from_dict(dic=val.__dict__, skip_ids=skip_ids) + else: + # Otherwise we just add the value to skip_ids so we don't check it again + skip_ids.add(id(val)) + + @property + def name(self): + """ + Returns: + str: Name of this instance. Must be unique! + """ + raise NotImplementedError
+ + +
[docs]class UniquelyNamedNonInstance: + """ + Identical to UniquelyNamed, but intended for non-instanceable classes + """ + + def __init_subclass__(cls, **kwargs): + global CLASS_NAMES + # Register this objects, making sure it's name is unique + assert cls.name not in CLASS_NAMES, \ + f'UniquelyNamed class with name {cls.name} already exists!' + CLASS_NAMES.add(cls.name) + + @ClassProperty + def name(self): + """ + Returns: + str: Name of this instance. Must be unique! + """ + raise NotImplementedError
+ + +
[docs]class Registerable: + """ + Simple class template that provides an abstract interface for registering classes. + """ + + def __init_subclass__(cls, **kwargs): + """ + Registers all subclasses as part of this registry. This is useful to decouple internal codebase from external + user additions. This way, users can add their custom subclasses by simply extending this class, + and it will automatically be registered internally. This allows users to then specify their classes + directly in string-form in e.g., their config files, without having to manually set the str-to-class mapping + in our code. + """ + cls._register_cls() + + @classmethod + def _register_cls(cls): + """ + Register this class. Can be extended by subclass. + """ + # print(f"registering: {cls.__name__}") + # print(f"registry: {cls._cls_registry}", cls.__name__ not in cls._cls_registry) + # print(f"do not register: {cls._do_not_register_classes}", cls.__name__ not in cls._do_not_register_classes) + # input() + if cls.__name__ not in cls._cls_registry and cls.__name__ not in cls._do_not_register_classes: + cls._cls_registry[cls.__name__] = cls + + @ClassProperty + def _do_not_register_classes(self): + """ + Returns: + set of str: Name(s) of classes that should not be registered. Default is empty set. + Subclasses that shouldn't be added should call super() and then add their own class name to the set + """ + return set() + + @ClassProperty + def _cls_registry(self): + """ + Returns: + dict: Mapping from all registered class names to their classes. This should be a REFERENCE + to some external, global dictionary that will be filled-in at runtime. + """ + raise NotImplementedError()
+ + +
[docs]class Serializable: + """ + Simple class that provides an abstract interface to dump / load states, optionally with serialized functionality + as well. + """ + + @property + def state_size(self): + """ + Returns: + int: Size of this objects's serialized state + """ + raise NotImplementedError() + + def _dump_state(self): + """ + Dumps the state of this objects in dictionary form (can be empty). Should be implemented by subclass. + + Returns: + dict: Keyword-mapped states of this objects + """ + raise NotImplementedError() + +
[docs] def dump_state(self, serialized=False): + """ + Dumps the state of this objects in either dictionary of flattened numerical form. + + Args: + serialized (bool): If True, will return the state of this objects as a 1D numpy array. Otherwise, + will return a (potentially nested) dictionary of states for this objects + + Returns: + dict or n-array: Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array \ + capturing this objects' state, where n is @self.state_size + """ + state = self._dump_state() + return self.serialize(state=state) if serialized else state
+ + def _load_state(self, state): + """ + Load the internal state to this objects as specified by @state. Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to set + """ + raise NotImplementedError() + +
[docs] def load_state(self, state, serialized=False): + """ + Deserializes and loads this objects' state based on @state + + Args: + state (dict or n-array): Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array capturing this objects' state, + where n is @self.state_size + serialized (bool): If True, will interpret @state as a 1D numpy array. Otherwise, + will assume the input is a (potentially nested) dictionary of states for this objects + """ + state = self.deserialize(state=state) if serialized else state + self._load_state(state=state)
+ + def _serialize(self, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + raise NotImplementedError() + +
[docs] def serialize(self, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + # Simply returns self._serialize() for now. this is for future proofing + return self._serialize(state=state)
+ + def _deserialize(self, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + 2-tuple: + - dict: Keyword-mapped states of this objects. Should match structure of output from + self._dump_state() + - int: current index of the flattened state vector that is left off. This is helpful for subclasses + that inherit partial deserializations from parent classes, and need to know where the + deserialization left off before continuing. + """ + raise NotImplementedError + +
[docs] def deserialize(self, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + dict: Keyword-mapped states of these objects. Should match structure of output from + self._dump_state() + """ + # Sanity check the idx with the expected state size + state_dict, idx = self._deserialize(state=state) + assert idx == self.state_size, f'Invalid state deserialization occurred! Expected {self.state_size} total ' \ + f'values to be deserialized, only {idx} were.' + + return state_dict
+ + +
[docs]class SerializableNonInstance: + """ + Identical to Serializable, but intended for non-instance classes + """ + + @ClassProperty + def state_size(self): + """ + Returns: + int: Size of this objects's serialized state + """ + raise NotImplementedError() + + @classmethod + def _dump_state(cls): + """ + Dumps the state of this objects in dictionary form (can be empty). Should be implemented by subclass. + + Returns: + dict: Keyword-mapped states of this objects + """ + raise NotImplementedError() + +
[docs] @classmethod + def dump_state(cls, serialized=False): + """ + Dumps the state of this objects in either dictionary of flattened numerical form. + + Args: + serialized (bool): If True, will return the state of this objects as a 1D numpy array. Otherwise, + will return a (potentially nested) dictionary of states for this objects + + Returns: + dict or n-array: Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array capturing this objects' state, where n is @self.state_size + """ + state = cls._dump_state() + return cls.serialize(state=state) if serialized else state
+ + @classmethod + def _load_state(cls, state): + """ + Load the internal state to this objects as specified by @state. Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of these objects to set + """ + raise NotImplementedError() + +
[docs] @classmethod + def load_state(cls, state, serialized=False): + """ + Deserializes and loads this objects' state based on @state + + Args: + state (dict or n-array): Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array capturing this objects' state, + where n is @self.state_size + serialized (bool): If True, will interpret @state as a 1D numpy array. Otherwise, will assume the input is + a (potentially nested) dictionary of states for this objects + """ + state = cls.deserialize(state=state) if serialized else state + cls._load_state(state=state)
+ + @classmethod + def _serialize(cls, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + raise NotImplementedError() + +
[docs] @classmethod + def serialize(cls, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of these objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + # Simply returns self._serialize() for now. this is for future proofing + return cls._serialize(state=state)
+ + @classmethod + def _deserialize(cls, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + 2-tuple: + - dict: Keyword-mapped states of this objects. Should match structure of output from + self._dump_state() + - int: current index of the flattened state vector that is left off. This is helpful for subclasses + that inherit partial deserializations from parent classes, and need to know where the + deserialization left off before continuing. + """ + raise NotImplementedError + +
[docs] @classmethod + def deserialize(cls, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + dict: Keyword-mapped states of this objects. Should match structure of output from + self._dump_state() + """ + # Sanity check the idx with the expected state size + state_dict, idx = cls._deserialize(state=state) + assert idx == cls.state_size, f'Invalid state deserialization occurred! Expected {cls.state_size} total ' \ + f'values to be deserialized, only {idx} were.' + + return state_dict
+ + +
[docs]class Wrapper: + """ + Base class for all wrapper in OmniGibson + + Args: + obj (any): Arbitrary python objects instance to wrap + """ + + def __init__(self, obj): + # Set the internal attributes -- store wrapped obj + self.wrapped_obj = obj + + @classmethod + def class_name(cls): + return cls.__name__ + + def _warn_double_wrap(self): + """ + Utility function that checks if we're accidentally trying to double wrap an scenes + Raises: + Exception: [Double wrapping scenes] + """ + obj = self.wrapped_obj + while True: + if isinstance(obj, Wrapper): + if obj.class_name() == self.class_name(): + raise Exception('Attempted to double wrap with Wrapper: {}'.format(self.__class__.__name__)) + obj = obj.wrapped_obj + else: + break + + @property + def unwrapped(self): + """ + Grabs unwrapped objects + + Returns: + any: The unwrapped objects instance + """ + return self.wrapped_obj.unwrapped if hasattr(self.wrapped_obj, 'unwrapped') else self.wrapped_obj + + # this method is a fallback option on any methods the original scenes might support + def __getattr__(self, attr): + # If we're querying wrapped_obj, raise an error + if attr == 'wrapped_obj': + raise AttributeError('wrapped_obj attribute not initialized yet!') + + # Sanity check to make sure wrapped obj is not None -- if so, raise error + assert self.wrapped_obj is not None, f'Cannot access attribute {attr} since wrapped_obj is None!' + + # using getattr ensures that both __getattribute__ and __getattr__ (fallback) get called + # (see https://stackoverflow.com/questions/3278077/difference-between-getattr-vs-getattribute) + orig_attr = getattr(self.wrapped_obj, attr) + if callable(orig_attr): + + def hooked(*args, **kwargs): + result = orig_attr(*args, **kwargs) + # prevent wrapped_class from becoming unwrapped + if id(result) == id(self.wrapped_obj): + return self + return result + + return hooked + else: + return orig_attr + + def __setattr__(self, key, value): + # Call setattr on wrapped obj if it has the attribute, otherwise, operate on this objects + if hasattr(self, 'wrapped_obj') and self.wrapped_obj is not None and hasattr(self.wrapped_obj, key): + setattr(self.wrapped_obj, key, value) + else: + super().__setattr__(key, value)
+ + +
[docs]def clear(): + """ + Clear state tied to singleton classes + """ + NAMES.clear() + CLASS_NAMES.clear()
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/grutopia/core/util/string.html b/html/_modules/grutopia/core/util/string.html new file mode 100644 index 0000000..6c04cc7 --- /dev/null +++ b/html/_modules/grutopia/core/util/string.html @@ -0,0 +1,723 @@ + + + + + + + + + + + + + + + grutopia.core.util.string — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for grutopia.core.util.string

+# Copyright (c) 2022-2024, The ORBIT Project Developers.
+# All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# yapf: disable
+
+"""Submodule 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.
+"""
+
+
+
[docs]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)
+ + +
[docs]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. +""" + + +
[docs]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
+ + +
[docs]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}'
+ + +
[docs]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. +""" + + +
[docs]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
+ + +
[docs]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/html/_modules/index.html b/html/_modules/index.html new file mode 100644 index 0000000..60e169e --- /dev/null +++ b/html/_modules/index.html @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + Overview: module code — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ +
    + +
  • + + Docs + > +
  • + + +
  • Overview: module code
  • + + +
  • + +
  • + + +
+ + +
+
+ +
+ Shortcuts +
+
+ +
+ + +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/datahub/api.html b/html/_modules/tao_yuan/core/datahub/api.html new file mode 100644 index 0000000..e47f81a --- /dev/null +++ b/html/_modules/tao_yuan/core/datahub/api.html @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + tao_yuan.core.datahub.api — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.datahub.api

+from typing import Any, Dict, List
+
+from tao_yuan.core.datahub.isaac_data import ActionData, IsaacData
+
+
+
[docs]def get_all_obs() -> List[Dict[str, Any]]: + """ + Get all observation data. + + Returns: + List[Dict[str, Any]]: sensor data dict + ``` + """ + return IsaacData.get_obs()
+ + +
[docs]def get_obs_by_id(task_id: int) -> Dict[str, Any]: + """ + Get observation by task_id + + Returns: + Dict[str, Any]: obs data dict + """ + return IsaacData.get_obs_by_id(task_id)
+ + +
[docs]def set_obs_data(obs: List[Dict[str, Any]]) -> None: + """ + Flush observation data. + + Args: + obs (List[Dict[str, Any]]): observation data + + """ + IsaacData.set_obs_data(obs)
+ + +
[docs]def get_actions() -> None | Dict[Any, Any]: + """ + Get all actions + + Returns: + Dict[str, Any]: action data dict + """ + return IsaacData.get_actions()
+ + +
[docs]def send_actions(actions: List[ActionData]): + """ + send actions to datahub + Args: + actions (List[ActionData]): list of [dict of {robot_id: ActionData}] + """ + IsaacData.add_actions(actions)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/datahub/isaac_data.html b/html/_modules/tao_yuan/core/datahub/isaac_data.html new file mode 100644 index 0000000..23d027d --- /dev/null +++ b/html/_modules/tao_yuan/core/datahub/isaac_data.html @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + tao_yuan.core.datahub.isaac_data — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.datahub.isaac_data

+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel
+
+
+class MetaActionData(BaseModel):
+    """
+    action status in tao_yuan
+    """
+    controller: str
+    data: Any
+
+
+class ActionData(BaseModel):
+    """
+    action status in tao_yuan
+    """
+    robot: str
+    controllers: List[MetaActionData]
+
+
+class _IsaacData(BaseModel):
+    """
+    isaac status in tao_yuan
+    """
+    actions: Optional[List[Dict[str, Any]]]
+    obs: Optional[List[Dict[str, Any]]]
+
+
+
[docs]class IsaacData: + """ + isaac status in tao_yuan + + There are two types of isaac status: + + * Action + * Observation + + structure of isaac status like this:: + + { + actions: { + [ + { + robot_1: { + cap: param, + } + } + ] + }, + observations: { + [ + { + robot_1: { + obs_1: data, + obs_2: data + } + } + ] + } + } + + """ + data = _IsaacData(actions=[], obs=[]) + + def __init__(self) -> None: + pass + + @classmethod + def get_all(cls) -> _IsaacData: + return cls.data + + # Observation + @classmethod + def set_obs_data(cls, obs: List[Dict[str, Any]]) -> None: + cls.data.obs = obs + +
[docs] @classmethod + def get_obs(cls) -> List[Dict[str, Any]]: + """ + Get isaac observation data + + Returns: + isaac observation data list + """ + return cls.data.obs
+ +
[docs] @classmethod + def get_obs_by_id(cls, task_id: int) -> Dict[str, Any]: + """ + Get isaac observation by id + + Args: + task_id: isaac task id + + Returns: + isaac observation data + + """ + return cls.data.obs[task_id]
+ + # Action +
[docs] @classmethod + def add_actions(cls, actions: List[ActionData]): + """ + Add actions + + Args: + actions: action list + + Returns: + + """ + # when add action, return action's index. + cls.data.actions = [] + for action in actions: + cls.data.actions.append({action.robot: {x.controller: x.data for x in action.controllers}}) + return
+ +
[docs] @classmethod + def get_actions(cls) -> None | List[Dict[Any, Any]]: + """ + Get actions + + Returns: + action(dict like {robot_name: {controller_name: param}}) list + """ + return cls.data.actions
+ +
[docs] @classmethod + def get_action_by_id(cls, task_id: int) -> None | Dict[Any, Any]: + """ + Get action by id + + Returns: + action(dict like {robot_name: {controller_name: param}}) + """ + return cls.data.actions[task_id]
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/datahub/web_api.html b/html/_modules/tao_yuan/core/datahub/web_api.html new file mode 100644 index 0000000..0cdd8f6 --- /dev/null +++ b/html/_modules/tao_yuan/core/datahub/web_api.html @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + tao_yuan.core.datahub.web_api — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.datahub.web_api

+"""
+Includes web api endpoints
+"""
+from typing import Any, Dict, List
+
+import httpx
+
+from tao_yuan.core.datahub.isaac_data import ActionData
+
+# constants
+WebBEUrl = 'http://127.0.0.1:9000'  # TODO config this
+GetAllObsPath = WebBEUrl + '/api/stream/get_all_obs'
+GetObsByIdPath = WebBEUrl + '/api/stream/get_obs_by_id/'
+FlushObsUrl = WebBEUrl + '/api/isaac/flush_obs_data'
+SetActionsUrl = WebBEUrl + '/api/isaac/set_action'
+GetAllActionUrl = WebBEUrl + '/api/isaac/get_actions'
+GetActionByIdUrl = WebBEUrl + '/api/isaac/get_action_by_id/'
+
+
+
[docs]def get_all_obs() -> List[Dict[str, Any]] | None: + """ + Get all observation data + Returns: + obs (List[Dict[str, Any]]): List of all observation data + """ + r = httpx.get(GetAllObsPath) + if r.status_code == 200: + return r.json() + return None
+ + +
[docs]def get_obs_by_id(task_id: int) -> Any | None: + """ + Get observation by id + Args: + task_id (int): id of observation data + + Returns: + obs (Any): Observation data + """ + r = httpx.get(GetObsByIdPath + str(task_id)) + if r.status_code == 200: + return r.json()
+ + +
[docs]def set_obs_data(obs: List[Dict[str, Any]]) -> bool: + """ + Set observation data web API + Args: + obs (List[Dict[str, Any]]): isaac observation data + + Returns: + OK if set successfully + """ + r = httpx.post(FlushObsUrl, json=obs, timeout=1) + if r.status_code == 200 and r.json()['msg'] == 'OK': + return True + return False
+ + +# Action +# send get, no poll&callback(all depends on ). +def get_actions(): + r = httpx.get(GetAllActionUrl) + if r.status_code == 200 and r.json()['data'] is not None: + return r.json()['msg'], r.json()['data'] + return None, {} + + +
[docs]def get_actions_by_id(task_id: int): + """ + Get actions by task id(int) + + Args: + task_id(int): id of task + + Returns: + msg: msg str(or None) + data: data + """ + r = httpx.get(GetActionByIdUrl + str(task_id)) + if r.status_code == 200 and r.json()['data'] is not None: + return r.json()['msg'], r.json()['data'] + return None, {}
+ + +
[docs]def send_actions(actions: List[ActionData]) -> bool: + """ + send actions + Args: + actions(List[ActionData]): action data list + + Returns: + + """ + r = httpx.post(SetActionsUrl, json=actions) + if r.status_code == 200: + return True + return False
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/datahub/web_ui_api.html b/html/_modules/tao_yuan/core/datahub/web_ui_api.html new file mode 100644 index 0000000..ce8d7c9 --- /dev/null +++ b/html/_modules/tao_yuan/core/datahub/web_ui_api.html @@ -0,0 +1,511 @@ + + + + + + + + + + + + + + + tao_yuan.core.datahub.web_ui_api — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.datahub.web_ui_api

+"""
+Includes web ui interactive
+"""
+import datetime
+import os
+from typing import Any, Dict, Union
+
+from tao_yuan.core.datahub.web_api import WebBEUrl
+from tao_yuan.core.util import AsyncRequest
+
+# constants
+SendChatControlUrl = WebBEUrl + '/api/tao_yuan/append_chat_control_data'
+SendCOTUrl = WebBEUrl + '/api/tao_yuan/append_chain_of_thought_data'
+SendLogDataUrl = WebBEUrl + '/api/tao_yuan/append_log_data'
+GetChatControlUrl = WebBEUrl + '/api/tao_yuan/getChatList'
+GetLogDataUrl = WebBEUrl + '/api/tao_yuan/getloglist'
+
+WEBUI_HOST = os.getenv('WEBUI_HOST', '127.0.0.1')
+
+DefaultAvatarUrl = f'http://{WEBUI_HOST}:8080/static/avatar_default.jpg'
+
+AvatarUrls = {
+    'user': f'http://{WEBUI_HOST}:8080/static/avatar_00.jpg',
+    'agent': f'http://{WEBUI_HOST}:8080/static/avatar_01.jpg',
+}
+
+
+
[docs]def send_chain_of_thought(cot: str, uuid: str = 'none') -> None: + """ + chain of thought data + + Args: + uuid (str): uuid of chain of thought data, defaults to "none". + cot (str): chain of thought data. + """ + + def cot_format(x): + return {'type': 'text', 'value': x} + + res_data = [{'type': 'time', 'value': datetime.datetime.now().strftime('%H:%M')}] + for i in cot: + res_data.append(cot_format(i)) + AsyncRequest.post(uuid, SendCOTUrl, res_data)
+ + +
[docs]def send_chat_control(nickname: str, text: str, img: str = None, role: str = 'user', uuid: str = 'none') -> None: + """Send a new message to the chatbox. + + Args: + nickname (str): nickname displayed in the chatbox. + text (str): text to send to the chatbox. + img (str, optional): image to send to the chatbox. Defaults to None. + role (str, optional): role name, user or agent. Defaults to "user". + uuid (str, optional): uuid of the message. Defaults to 'none'. + """ + avatar_url = AvatarUrls.get(role, DefaultAvatarUrl) + res_data = { + 'type': role, + 'name': nickname, + 'time': datetime.datetime.now().strftime('%H:%M'), + 'message': text, + 'photo': avatar_url, + 'img': img, + } + AsyncRequest.post(uuid, SendChatControlUrl, res_data)
+ + +
[docs]def send_log_data(log_data: str, + log_type: str = 'message', + user: str = 'Bob', + photo_url: str = DefaultAvatarUrl, + uuid: str = 'none') -> None: + """Send log data. + + Args: + uuid (str): uuid of log, default is none. + log_data (str): log data. + log_type (str): type of log. 'message' or 'user'. + user (str): logger name. default: Bob. + photo_url (str): log photo url path. + + """ + if log_type == 'message': + res_data = {'type': 'message', 'message': log_data} + else: # user + if log_type != 'user': + return + res_data = { + 'type': 'user', + 'name': user, + 'time': datetime.datetime.now().strftime('%H:%M'), + 'message': log_data, + 'photo': photo_url + } + AsyncRequest.post(uuid, SendLogDataUrl, res_data)
+ + +
[docs]def get_log_data(uuid: str = 'none') -> Union[Dict[str, Any], None]: + """ + Get log data. + + Args: + uuid (str): log data uuid. default: none. + + Returns: + log_data (list[dict]): log data. + """ + ok, json_data = AsyncRequest.get(uuid, GetLogDataUrl) + if ok and json_data is not None: + return json_data + return None
+ + +
[docs]def get_chat_control(uuid: str = 'none') -> Union[Dict[str, Any], None]: + """ + Get chat control data. + + Args: + uuid (str): chat control uuid. default: none. + + Returns: + chat_control (List[Dict[str, Any]]): chat control data. + """ + ok, json_data = AsyncRequest.get(uuid, GetChatControlUrl) + if ok and json_data is not None: + return json_data + return None
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/env.html b/html/_modules/tao_yuan/core/env.html new file mode 100644 index 0000000..66cc7f7 --- /dev/null +++ b/html/_modules/tao_yuan/core/env.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + tao_yuan.core.env — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.env

+# import json
+from typing import Any, Dict, List
+
+import numpy as np
+
+from tao_yuan.core.config import SimulatorConfig
+from tao_yuan.core.util import log
+
+
+
[docs]class BaseEnv: + """ + Env base class. All tasks should inherit from this class(or subclass). + ---------------------------------------------------------------------- + """ + + def __init__(self, config: SimulatorConfig, headless: bool = True, webrtc: bool = False) -> None: + self._simulation_config = None + self._render = None + # Setup Multitask Env Parameters + self.env_map = {} + self.obs_map = {} + + self.config = config.config + self.env_num = config.env_num + self._column_length = int(np.sqrt(self.env_num)) + + # Init Isaac Sim + from omni.isaac.kit import SimulationApp + self.headless = headless + self._simulation_app = SimulationApp({'headless': self.headless, 'anti_aliasing': 0}) + + if webrtc: + from omni.isaac.core.utils.extensions import enable_extension # noqa + + self._simulation_app.set_setting('/app/window/drawMouse', True) + self._simulation_app.set_setting('/app/livestream/proto', 'ws') + self._simulation_app.set_setting('/app/livestream/websocket/framerate_limit', 60) + self._simulation_app.set_setting('/ngx/enabled', False) + enable_extension('omni.services.streamclient.webrtc') + + from tao_yuan.core import datahub # noqa E402. + from tao_yuan.core.runner import SimulatorRunner # noqa E402. + + self._runner = SimulatorRunner(config=config) + # self._simulation_config = sim_config + + log.debug(self.config.tasks) + # create tasks + self._runner.add_tasks(self.config.tasks) + return + + @property + def runner(self): + return self._runner + + @property + def is_render(self): + return self._render + + def get_dt(self): + return self._runner.dt + +
[docs] def step(self, actions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + run step with given action(with isaac step) + + Args: + actions (List[Dict[str, Any]]): action(with isaac step) + + Returns: + List[Dict[str, Any]]: observations(with isaac step) + """ + if len(actions) != len(self.config.tasks): + raise AssertionError('len of action list is not equal to len of task list') + _actions = [] + for action_idx, action in enumerate(actions): + _action = {} + for k, v in action.items(): + _action[f'{k}_{action_idx}'] = v + _actions.append(_action) + action_after_reshape = { + self.config.tasks[action_idx].name: action + for action_idx, action in enumerate(_actions) + } + + # log.debug(action_after_reshape) + self._runner.step(action_after_reshape) + observations = self.get_observations() + return observations
+ +
[docs] def reset(self, envs: List[int] = None): + """ + reset the environment(use isaac word reset) + + Args: + envs (List[int]): env need to be reset(default for reset all envs) + """ + if envs is not None: + if len(envs) == 0: + return + log.debug(f'============= reset: {envs} ==============') + # int -> name + self._runner.reset([self.config.tasks[e].name for e in envs]) + return self.get_observations(), {} + self._runner.reset() + return self.get_observations(), {}
+ +
[docs] def get_observations(self) -> List[Dict[str, Any]]: + """ + Get observations from Isaac environment + Returns: + List[Dict[str, Any]]: observations + """ + _obs = self._runner.get_obs() + return _obs
+ + def render(self, mode='human'): + return + +
[docs] def close(self): + """close the environment""" + self._simulation_app.close() + return
+ + @property + def simulation_config(self): + """config of simulation environment""" + return self._simulation_config + + @property + def simulation_app(self): + """simulation app instance""" + return self._simulation_app
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/register/register.html b/html/_modules/tao_yuan/core/register/register.html new file mode 100644 index 0000000..c57543a --- /dev/null +++ b/html/_modules/tao_yuan/core/register/register.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + tao_yuan.core.register.register — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.register.register

+import importlib
+import os
+
+from tao_yuan.core.util import log
+
+ALL_MODULES = []
+MODEL_MODULES = [
+    'controllers',
+    # 'envs',
+    # 'models',
+    'objects',
+    'metrics',
+    # 'observations',
+    # 'planners',
+    'robots',
+    'sensors',
+    'tasks',
+    'interactions'
+]
+
+DEFAULT_EXTENSION_PATH = os.path.join(os.path.split(os.path.realpath(__file__))[0], '../../../ty_extension')
+
+
+def _handle_errors(errors):
+    """
+    Log out and possibly reraise errors during import.
+
+    Args:
+        errors: errors dict to be logged
+    """
+    if not errors:
+        return
+    for name, err in errors:
+        log.warning('Module {} import failed: {}'.format(name, err))
+
+
+
[docs]def import_all_modules_for_register(custom_module_paths=None, extension_path=None): + """ + Import all modules for register. + + Args: + custom_module_paths: custom module paths, e.g. ['xxx.lib1', 'xxx.lib2', 'xxx.lib3'] + extension_path: Extension path(integrated in ty_extension as default) + """ + if extension_path is None: + extension_path = DEFAULT_EXTENSION_PATH + for _mod in MODEL_MODULES: + # ty_extension's default path + path = os.path.join(extension_path, _mod) + m = [m.split('.py')[0] for m in os.listdir(path) if m.endswith('.py') and m != '__init__.py'] + ALL_MODULES.append((_mod, m)) + modules = [] + for base_dir, mods in ALL_MODULES: + for name in mods: + full_name = 'ty_extension.' + base_dir + '.' + name + modules.append(full_name) + if isinstance(custom_module_paths, list): + modules += custom_module_paths + errors = [] + for module in modules: + try: + importlib.import_module(module) + except ImportError as error: + errors.append((module, error)) + _handle_errors(errors)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/robot/controller.html b/html/_modules/tao_yuan/core/robot/controller.html new file mode 100644 index 0000000..e0698b6 --- /dev/null +++ b/html/_modules/tao_yuan/core/robot/controller.html @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + tao_yuan.core.robot.controller — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.robot.controller

+# yapf: disable
+from abc import ABC, abstractmethod
+from functools import wraps
+from typing import Any, Dict, List, Union
+
+import numpy as np
+from omni.isaac.core.articulations import ArticulationSubset
+from omni.isaac.core.controllers import BaseController as Base
+from omni.isaac.core.scenes import Scene
+from omni.isaac.core.utils.types import ArticulationAction
+
+from tao_yuan.core.config.robot import RobotUserConfig
+from tao_yuan.core.config.robot.params import ControllerParams
+from tao_yuan.core.robot.robot import BaseRobot
+# yapf: disable
+from tao_yuan.core.robot.robot_model import ControllerModel, RobotModel
+# yapf: enable
+from tao_yuan.core.util import log
+
+# yapf: enable
+
+
+
[docs]class BaseController(Base, ABC): + """Base class of controller.""" + controllers = {} + + def __init__(self, config: ControllerModel, robot: BaseRobot, scene: Scene): + """Initialize the controller. + + Args: + config (ControllerModel): merged config (from user config and robot model) of the controller. + robot (BaseRobot): robot owning the controller. + scene (Scene): scene from isaac sim. + + """ + self.scene = scene + if config.name is None: + raise ValueError('must specify controller name.') + super().__init__(config.name) + self._obs = {} + self._robot = robot + self.config = config + self.sub_controllers: List[BaseController] + +
[docs] @abstractmethod + def action_to_control(self, action: Union[np.ndarray, List]) -> ArticulationAction: + """Convert input action (in 1d array format) to joint signals to apply. + + Args: + action (Union[np.ndarray, List]): input control action. + + Returns: + ArticulationAction: joint signals to apply + """ + raise NotImplementedError()
+ +
[docs] def get_obs(self) -> Dict[str, Any]: + """Get observation of controller. + + Returns: + Dict[str, Any]: observation key and value. + """ + obs = {} + for key, obs_ins in self._obs.items(): + obs[key] = obs_ins.get_obs() + return obs
+ +
[docs] @classmethod + def register(cls, name: str): + """Register a controller with its name(decorator). + + Args: + name (str): name of the controller + """ + + def decorator(controller_class): + cls.controllers[name] = controller_class + + @wraps(controller_class) + def wrapped_function(*args, **kwargs): + return controller_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + @property + def robot(self): + return self._robot + + @robot.setter + def robot(self, value): + self._robot = value + +
[docs] def get_joint_subset(self) -> ArticulationSubset: + """Get the joint subset controlled by the controller. + + Returns: + ArticulationSubset: joint subset. + """ + if hasattr(self, 'joint_subset'): + return self.joint_subset + if len(self.sub_controllers) > 0: + return self.sub_controllers[0].get_joint_subset() + raise NotImplementedError('attr joint_subset not found')
+ + +
[docs]def config_inject(user_config: ControllerParams, model: ControllerModel) -> ControllerModel: + """Merge controller config from user config and robot model. + + Args: + user_config (ControllerParams): user config. + model (ControllerModel): controller config from robot model. + + Returns: + ControllerModel: merged controller config. + """ + config = model.dict() + user = user_config.dict() + for k, v in user.items(): + if v is not None: + config[k] = v + conf = ControllerModel(**config) + + return conf
+ + +
[docs]def create_controllers(config: RobotUserConfig, robot_model: RobotModel, robot: BaseRobot, + scene: Scene) -> Dict[str, BaseController]: + """Create all controllers of one robot. + + Args: + config (RobotUserConfig): user config of the robot. + robot_model (RobotModel): model of the robot. + robot (BaseRobot): robot instance. + scene (Scene): scene from isaac sim. + + Returns: + Dict[str, BaseController]: dict of controllers with controller name as key. + """ + controller_map = {} + available_controllers = {a.name: a for a in robot_model.controllers} + + for controller_param in config.controller_params: + controller_name = controller_param.name + if controller_name in available_controllers: + controller_config = config_inject(controller_param, available_controllers[controller_name]) + controller_cls = BaseController.controllers[controller_config.type] + controller_ins: BaseController = controller_cls(config=controller_config, robot=robot, scene=scene) + if controller_config.sub_controllers is not None: + inject_sub_controllers(parent=controller_ins, + configs=controller_config.sub_controllers, + available=available_controllers, + robot=robot, + scene=scene) + else: + print(available_controllers) + raise KeyError(f'{controller_name} not registered in controllers of {config.type}') + + controller_map[controller_name] = controller_ins + log.debug(f'==================== {controller_name} loaded==========================') + + return controller_map
+ + +
[docs]def inject_sub_controllers(parent: BaseController, configs: List[ControllerParams], + available: Dict[str, ControllerModel], robot: BaseRobot, scene: Scene): + """Recursively create and inject sub-controlllers into parent controller. + + Args: + parent (BaseController): parent controller instance. + configs (List[ControllerParams]): user configs of sub-controllers. + available (Dict[str, ControllerModel]): available controllers. + robot (BaseRobot): robot instance. + scene (Scene): scene from isaac sim. + """ + if len(configs) == 0: + return + sub_controllers: List[BaseController] = [] + for config in configs: + controller_name = config.name + if controller_name not in available: + raise KeyError(f'{controller_name} not registered in controllers of {robot.robot_model.type}') + controller_config = config_inject(config, available[controller_name]) + controller_cls = BaseController.controllers[controller_config.type] + controller_ins = controller_cls(config=controller_config, robot=robot, scene=scene) + if controller_config.sub_controllers is not None: + inject_sub_controllers(controller_ins, + configs=controller_config.sub_controllers, + available=available, + robot=robot, + scene=scene) + sub_controllers.append(controller_ins) + + parent.sub_controllers = sub_controllers
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/robot/robot.html b/html/_modules/tao_yuan/core/robot/robot.html new file mode 100644 index 0000000..611956d --- /dev/null +++ b/html/_modules/tao_yuan/core/robot/robot.html @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + tao_yuan.core.robot.robot — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.robot.robot

+from functools import wraps
+from typing import Dict, Tuple
+
+import numpy as np
+from omni.isaac.core.prims import RigidPrim
+from omni.isaac.core.robots.robot import Robot as IsaacRobot
+from omni.isaac.core.scenes import Scene
+
+from tao_yuan.core.config import RobotUserConfig, TaskUserConfig
+from tao_yuan.core.robot.robot_model import RobotModel, RobotModels
+from tao_yuan.core.util import log
+
+
+
[docs]class BaseRobot: + """Base class of robot.""" + robots = {} + + def __init__(self, config: RobotUserConfig, robot_model: RobotModel, scene: Scene): + self.name = config.name + self.robot_model = robot_model + self.user_config = config + self.isaac_robot: IsaacRobot | None = None + self.controllers = {} + self.sensors = {} + +
[docs] def set_up_to_scene(self, scene: Scene): + """Set up robot in the scene. + + Args: + scene (Scene): scene to setup. + """ + config = self.user_config + robot_model = self.robot_model + scene.add(self.isaac_robot) + log.debug('self.isaac_robot: ' + str(self.isaac_robot)) + from tao_yuan.core.robot.controller import BaseController, create_controllers + from tao_yuan.core.robot.sensor import BaseSensor, create_sensors + + self.controllers: Dict[str, BaseController] = create_controllers(config, robot_model, self, scene) + self.sensors: Dict[str, BaseSensor] = create_sensors(config, robot_model, self, scene)
+ +
[docs] def post_reset(self): + """Set up things that happen after the world resets.""" + pass
+ +
[docs] def apply_action(self, action: dict): + """Apply actions of controllers to robot. + + Args: + action (dict): action dict. + key: controller name. + value: corresponding action array. + """ + raise NotImplementedError()
+ +
[docs] def get_obs(self) -> dict: + """Get observation of robot, including controllers, sensors, and world pose. + + Raises: + NotImplementedError: _description_ + """ + raise NotImplementedError()
+ +
[docs] def get_robot_ik_base(self) -> RigidPrim: + """Get base link of ik controlled parts. + + Returns: + RigidPrim: rigid prim of ik base link. + """ + raise NotImplementedError()
+ +
[docs] def get_robot_base(self) -> RigidPrim: + """ + Get base link of robot. + + Returns: + RigidPrim: rigid prim of robot base link. + """ + raise NotImplementedError()
+ +
[docs] def get_robot_scale(self) -> np.ndarray: + """Get robot scale. + + Returns: + np.ndarray: robot scale in (x, y, z). + """ + return self.isaac_robot.get_local_scale()
+ +
[docs] def get_robot_articulation(self) -> IsaacRobot: + """Get isaac robots instance (articulation). + + Returns: + Robot: robot articulation. + """ + return self.isaac_robot
+ + def get_controllers(self): + return self.controllers + + def get_world_pose(self) -> Tuple[np.ndarray, np.ndarray]: + return self.isaac_robot.get_world_pose() + +
[docs] @classmethod + def register(cls, name: str): + """Register a robot class with its name(decorator). + + Args: + name(str): name of the robot class. + """ + + def decorator(robot_class): + cls.robots[name] = robot_class + + @wraps(robot_class) + def wrapped_function(*args, **kwargs): + return robot_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +
[docs]def create_robots(config: TaskUserConfig, robot_models: RobotModels, scene: Scene) -> Dict[str, BaseRobot]: + """Create robot instances in config. + Args: + config (TaskUserConfig): user config. + robot_models (RobotModels): robot models. + scene (Scene): isaac scene. + + Returns: + Dict[str, BaseRobot]: robot instances dictionary. + """ + robot_map = {} + for robot in config.robots: + if robot.type not in BaseRobot.robots: + raise KeyError(f'unknown robot type "{robot.type}"') + robot_cls = BaseRobot.robots[robot.type] + robot_models = robot_models.robots + r_model = None + for model in robot_models: + if model.type == robot.type: + r_model = model + if r_model is None: + raise KeyError(f'robot model of "{robot.type}" is not found') + robot_ins = robot_cls(robot, r_model, scene) + robot_map[robot.name] = robot_ins + robot_ins.set_up_to_scene(scene) + log.debug(f'===== {robot.name} loaded =====') + return robot_map
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/robot/sensor.html b/html/_modules/tao_yuan/core/robot/sensor.html new file mode 100644 index 0000000..4853224 --- /dev/null +++ b/html/_modules/tao_yuan/core/robot/sensor.html @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + tao_yuan.core.robot.sensor — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.robot.sensor

+from abc import ABC, abstractmethod
+from functools import wraps
+from typing import Dict
+
+from tao_yuan.core.config.robot import RobotUserConfig
+from tao_yuan.core.config.robot.params import SensorParams
+from tao_yuan.core.robot.robot import BaseRobot, Scene
+from tao_yuan.core.robot.robot_model import RobotModel, SensorModel
+from tao_yuan.core.util import log
+
+
+
[docs]class BaseSensor(ABC): + """Base class of sensor.""" + sensors = {} + + def __init__(self, config: SensorModel, robot: BaseRobot, scene: Scene): + """Initialize the sensor. + + Args: + config (SensorModel): merged config (from user config and robot model) of the sensor. + robot (BaseRobot): robot owning the sensor. + scene (Scene): scene from isaac sim. + """ + if config.name is None: + raise ValueError('must specify sensor name.') + self.name = config.name + self.config = config + self._scene = scene + self._robot = robot + + @abstractmethod + def sensor_init(self): + raise NotImplementedError() + +
[docs] @abstractmethod + def get_data(self) -> Dict: + """Get data from sensor. + + Returns: + Dict: data dict of sensor. + """ + raise NotImplementedError()
+ +
[docs] @classmethod + def register(cls, name: str): + """ + Register a sensor class with the given name(decorator). + Args: + name(str): name of the sensor class. + """ + + def decorator(sensor_class): + cls.sensors[name] = sensor_class + + @wraps(sensor_class) + def wrapped_function(*args, **kwargs): + return sensor_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +
[docs]def config_inject(params: SensorParams, model: SensorModel) -> SensorModel: + """Merge sensor config from user config and robot model. + + Args: + params (SensorParams): user config. + model (SensorModel): sensor config from robot model. + + Returns: + SensorModel: merged sensor config. + """ + if params is None: + return model + config = model.dict() + user = params.dict() + for k, v in user.items(): + if v is not None: + config[k] = v + conf = SensorModel(**config) + + return conf
+ + +
[docs]def create_sensors(config: RobotUserConfig, robot_model: RobotModel, robot: BaseRobot, + scene: Scene) -> Dict[str, BaseSensor]: + """Create all sensors of one robot. + + Args: + config (RobotUserConfig): user config of the robot. + robot_model (RobotModel): model of the robot. + robot (BaseRobot): robot instance. + scene (Scene): scene from isaac sim. + + Returns: + Dict[str, BaseSensor]: dict of sensors with sensor name as key. + """ + sensor_map = {} + if robot_model.sensors is not None: + available_sensors = {a.name: a for a in robot_model.sensors} + for sensor_name, sensor in available_sensors.items(): + if sensor.type not in BaseSensor.sensors: + raise KeyError(f'unknown sensor type "{sensor.type}"') + sensor_cls = BaseSensor.sensors[sensor.type] + # Find if user param exists for this sensor. + param = None + if config.sensor_params is not None: + for p in config.sensor_params: + if p.name == sensor_name: + param = p + break + + sensor_ins = sensor_cls(config=config_inject(param, sensor), robot=robot, name=sensor_name, scene=scene) + sensor_map[sensor_name] = sensor_ins + sensor_ins.sensor_init() + log.debug(f'==================== {sensor_name} loaded==========================') + + return sensor_map
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/runner.html b/html/_modules/tao_yuan/core/runner.html new file mode 100644 index 0000000..f0b94d1 --- /dev/null +++ b/html/_modules/tao_yuan/core/runner.html @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + tao_yuan.core.runner — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.runner

+from typing import List
+
+# import numpy as np
+from omni.isaac.core import World
+from omni.isaac.core.prims.xform_prim import XFormPrim
+from omni.isaac.core.utils.stage import add_reference_to_stage  # noqa F401
+from omni.physx.scripts import utils
+from pxr import Usd  # noqa
+
+# Init
+from tao_yuan.core.config import SimulatorConfig, TaskUserConfig
+from tao_yuan.core.register import import_all_modules_for_register
+from tao_yuan.core.scene import delete_prim_in_stage  # noqa F401
+from tao_yuan.core.scene import create_object, create_scene  # noqa F401
+from tao_yuan.core.task.task import BaseTask, create_task
+from tao_yuan.core.util import log
+from tao_yuan.npc import NPC
+
+
+
[docs]class SimulatorRunner: + + def __init__(self, config: SimulatorConfig): + import_all_modules_for_register() + + self._simulator_config = config.config + physics_dt = self._simulator_config.simulator.physics_dt if self._simulator_config.simulator.physics_dt is not None else None + rendering_dt = self._simulator_config.simulator.rendering_dt if self._simulator_config.simulator.rendering_dt is not None else None + physics_dt = eval(physics_dt) if isinstance(physics_dt, str) else physics_dt + rendering_dt = eval(rendering_dt) if isinstance(rendering_dt, str) else rendering_dt + self.dt = physics_dt + log.debug(f'Simulator physics dt: {self.dt}') + self._world = World(physics_dt=self.dt, rendering_dt=rendering_dt, stage_units_in_meters=1.0) + self._scene = self._world.scene + self._stage = self._world.stage + + # setup scene + prim_path = '/' + if self._simulator_config.env_set.bg_type is None: + self._scene.add_default_ground_plane() + elif self._simulator_config.env_set.bg_type == 'SimpleRoom': + source, prim_path = create_scene('TY-1/assets/scenes/Collected_simple_room/simple_room.usd', + prim_path_root='background') + add_reference_to_stage(source, prim_path) + elif self._simulator_config.env_set.bg_type != 'default': + source, prim_path = create_scene(self._simulator_config.env_set.bg_path, prim_path_root='background') + add_reference_to_stage(source, prim_path) + + self.npc: List[NPC] = [] + for npc_config in config.config.npc: + self.npc.append(NPC(npc_config)) + + self.render_interval = self._simulator_config.simulator.rendering_interval if self._simulator_config.simulator.rendering_interval is not None else 5 + log.info(f'rendering interval: {self.render_interval}') + self.render_trigger = 0 + + @property + def current_tasks(self) -> dict[str, BaseTask]: + return self._world._current_tasks + + def _warm_up(self, steps=10, render=True): + for _ in range(steps): + self._world.step(render=render) + + def add_tasks(self, configs: List[TaskUserConfig]): + for config in configs: + task = create_task(config, self._scene) + self._world.add_task(task) + + self._world.reset() + self._warm_up() + + def step(self, actions: dict, render: bool = True): + for task_name, action_dict in actions.items(): + task = self.current_tasks.get(task_name) + for name, action in action_dict.items(): + if name in task.robots: + task.robots[name].apply_action(action) + self.render_trigger += 1 + render = render and self.render_trigger > self.render_interval + if self.render_trigger > self.render_interval: + self.render_trigger = 0 + self._world.step(render=render) + + obs = self.get_obs() + for npc in self.npc: + try: + npc.feed(obs) + except Exception as e: + log.error(f'fail to feed npc {npc.name} with obs: {e}') + + if render: + return obs + + def get_obs(self): + obs = {} + for task_name, task in self.current_tasks.items(): + obs[task_name] = task.get_observations() + return obs + + def get_current_time_step_index(self) -> int: + return self._world.current_time_step_index + + def reset(self, tasks: List[str] = None): + if tasks is None: + self._world.reset() + return + for task in tasks: + self.current_tasks[task].individual_reset() + + def get_obj(self, name: str) -> XFormPrim: + return self._world.scene.get_object(name) + + def remove_collider(self, prim_path: str): + build = self._world.stage.GetPrimAtPath(prim_path) + if build.IsValid(): + utils.removeCollider(build) + + def add_collider(self, prim_path: str): + build = self._world.stage.GetPrimAtPath(prim_path) + if build.IsValid(): + utils.setCollider(build, approximationShape=None)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/scene/object.html b/html/_modules/tao_yuan/core/scene/object.html new file mode 100644 index 0000000..c22e3f3 --- /dev/null +++ b/html/_modules/tao_yuan/core/scene/object.html @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + tao_yuan.core.scene.object — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.scene.object

+from functools import wraps
+
+from omni.isaac.core.scenes import Scene
+
+from tao_yuan.core.config import Object as ObjectConfig
+
+
+
[docs]class ObjectCommon: + """ + Object common class. + """ + objs = {} + + def __init__(self, config: ObjectConfig): + self._config = config + + def set_up_scene(self, scene: Scene): + raise NotImplementedError + +
[docs] @classmethod + def register(cls, name: str): + """ + Register an object class with the given name(decorator). + + Args: + name(str): name of the object + """ + + def decorator(object_class): + cls.objs[name] = object_class + + @wraps(object_class) + def wrapped_function(*args, **kwargs): + return object_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +
[docs]def create_object(config: ObjectConfig): + """ + Create an object. + Args: + config (ObjectConfig): configuration of the objects + """ + assert config.type in ObjectCommon.objs, 'unknown objects type {}'.format(config.type) + cls = ObjectCommon.objs[config.type] + return cls(config)
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/scene/scene/util/usd_op.html b/html/_modules/tao_yuan/core/scene/scene/util/usd_op.html new file mode 100644 index 0000000..a326c28 --- /dev/null +++ b/html/_modules/tao_yuan/core/scene/scene/util/usd_op.html @@ -0,0 +1,608 @@ + + + + + + + + + + + + + + + tao_yuan.core.scene.scene.util.usd_op — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.scene.scene.util.usd_op

+import typing
+
+from pxr import Gf, Sdf, Usd, UsdGeom
+
+from tao_yuan.core.scene.scene.util.type import get_xformop_precision, get_xformop_type
+from tao_yuan.core.util import log
+
+
+
[docs]def add_usd_ref(source_stage: Usd.Stage, dest_stage: Usd.Stage, src_prim_path: str, dest_prim_path: str) -> None: + """ + Add an opened usd into another usd as a reference + set name in dest_prim_path + + Args: + source_stage (Usd.Stage): source stage + dest_stage (Usd.Stage): dest stage + src_prim_path (str): source prim path + dest_prim_path (str): dest prim path + """ + src_root_layer = source_stage.GetRootLayer() + log.debug(src_root_layer.identifier) + source_prim = source_stage.GetPrimAtPath(src_prim_path) + dest_prim = dest_stage.DefinePrim(dest_prim_path, source_prim.GetTypeName()) + dest_prim.GetReferences().AddReference(src_root_layer.identifier) + dest_stage.GetRootLayer().Save()
+ + +
[docs]def get_local_transform_xform(prim: Usd.Prim) -> typing.Tuple[Gf.Vec3d, Gf.Rotation, Gf.Vec3d]: + """ + Get the local transformation of a prim using Xformable. + + Args: + prim: The prim to calculate the local transformation. + Returns: + A tuple of: + - Translation vector. + - Rotation quaternion, i.e. 3d vector plus angle. + - Scale vector. + """ + xform = UsdGeom.Xformable(prim) + local_transformation: Gf.Matrix4d = xform.GetLocalTransformation() + translation: Gf.Vec3d = local_transformation.ExtractTranslation() + rotation: Gf.Rotation = local_transformation.ExtractRotation() + scale: Gf.Vec3d = Gf.Vec3d(*(v.GetLength() for v in local_transformation.ExtractRotationMatrix())) + return translation, rotation, scale
+ + +
[docs]def get_world_transform_xform(prim: Usd.Prim) -> typing.Tuple[Gf.Vec3d, Gf.Rotation, Gf.Vec3d]: + """ + Get the local transformation of a prim using Xformable. + + Args: + prim: The prim to calculate the world transformation. + Returns: + A tuple of: + - Translation vector. + - Rotation quaternion, i.e. 3d vector plus angle. + - Scale vector. + """ + xform = UsdGeom.Xformable(prim) + time = Usd.TimeCode.Default() + world_transform: Gf.Matrix4d = xform.ComputeLocalToWorldTransform(time) + translation: Gf.Vec3d = world_transform.ExtractTranslation() + rotation: Gf.Rotation = world_transform.ExtractRotation() + scale: Gf.Vec3d = Gf.Vec3d(*(v.GetLength() for v in world_transform.ExtractRotationMatrix())) + return translation, rotation, scale
+ + +
[docs]def create_new_usd(new_usd_path: str, default_prim_name: str, default_axis: str = None) -> Usd.Stage: + """ + Create a new usd + + Args: + new_usd_path (str): where to place this new usd + default_prim_name (str): default prim name (root prim path) + default_axis (str): default axis for new usd + """ + stage: Usd.Stage = Usd.Stage.CreateNew(new_usd_path) + default_prim: Usd.Prim = UsdGeom.Xform.Define(stage, Sdf.Path('/' + default_prim_name)).GetPrim() + _set_default_prim(stage, default_prim) + _set_up_axis(stage, default_axis) + stage.GetRootLayer().Save() + return stage
+ + +def _set_up_axis(stage: Usd.Stage, axis_str: str = None) -> None: + """ + Set default axis for a stage + + Args: + stage (Usd.Stage): objects stage + axis_str (str, optional): axis str, 'y' or 'z', set 'z' if None. Defaults to None. + """ + if axis_str == 'y' or axis_str == 'Y': + axis: UsdGeom.Tokens = UsdGeom.Tokens.y + else: + axis: UsdGeom.Tokens = UsdGeom.Tokens.z + UsdGeom.SetStageUpAxis(stage, axis) + + +def _set_default_prim(stage: Usd.Stage, prim: Usd.Prim) -> None: + """ + Set default prim for a stage + + Args: + stage (Usd.Stage): objects stage + prim (Usd.Prim): prim in this stage + """ + stage.SetDefaultPrim(prim) + + +
[docs]def compute_bbox(prim: Usd.Prim) -> Gf.Range3d: + """ + Compute Bounding Box using ComputeWorldBound at UsdGeom.Imageable + + Args: + prim: A prim to compute the bounding box. + Returns: + A range (i.e. bounding box) + """ + imageable: UsdGeom.Imageable = UsdGeom.Imageable(prim) + time = Usd.TimeCode.Default() + bound = imageable.ComputeWorldBound(time, UsdGeom.Tokens.default_) + bound_range = bound.ComputeAlignedBox() + return bound_range
+ + +
[docs]def delete_prim_in_stage(stage: Usd.Stage, prim: Usd.Prim) -> None: + """ + Delete a prim in stage + + Args: + stage (Usd.Stage): objects stage + prim (Usd.Prim): prim to be deleted + """ + stage.RemovePrim(prim.GetPrimPath())
+ + +
[docs]def set_xform_of_prim(prim: Usd.Prim, xform_op: str, set_valve: typing.Any) -> None: + """ + Set xform data of a prim with new data + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be set + set_valve (typing.Any): new data to be set, could be np.array + """ + stage = prim.GetStage() + op_list = prim.GetAttribute('xformOpOrder').Get() + s = None + for i in op_list: + if xform_op == i: + log.debug(prim.GetAttribute(i)) + s = prim.GetAttribute(i) + trans = s.Get() + trans_value = set_valve + data_class = type(trans) + time_code = Usd.TimeCode.Default() + new_data = data_class(*trans_value) + s.Set(new_data, time_code) + stage.Save()
+ + +
[docs]def delete_xform_of_prim(prim: Usd.Prim, xform_op: str) -> None: + """ + Delete xform data of a prim + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be deleted + """ + stage = prim.GetStage() + if prim.HasAttribute(xform_op): + # Clear the attribute from the Prim + prim.GetAttribute(xform_op).Clear() + stage.Save()
+ + +
[docs]def add_xform_of_prim(prim: Usd.Prim, xform_op: str, set_valve: typing.Any) -> None: + """ + Add xform data of a prim with new data + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be set + set_valve (typing.Any): new data to be set, could be Gf.Vec3d, Gf.Rotation + """ + stage = prim.GetStage() + attribute_name = xform_op + attribute_value = set_valve + opType = get_xformop_type(xform_op) + precision = get_xformop_precision('float') + attribute = UsdGeom.Xformable(prim).AddXformOp(opType, precision) + if attribute: + attribute.Set(attribute_value) + # log.debug(f"Attribute {attribute_name} has been set to {attribute_value}.") + else: + log.debug(f'Failed to create attribute named {attribute_name}.') + stage.Save()
+ + +
[docs]def add_xform_of_prim_old(prim: Usd.Prim, xform_op: str, set_valve: typing.Any) -> None: + """ + Add xform data of a prim with new data + + Args: + prim (Usd.Prim): objects prim + xform_op (str): which op to be set + set_valve (typing.Any): new data to be set, could be Gf.Vec3d, Gf.Rotation + """ + stage = prim.GetStage() + attribute_name = xform_op + attribute_value = set_valve + if '3' in type(set_valve).__name__: + attribute_type = Sdf.ValueTypeNames.Float3 + else: + attribute_type = Sdf.ValueTypeNames.Float + attribute = prim.CreateAttribute(attribute_name, attribute_type) + if attribute: + attribute.Set(attribute_value) + # log.debug(f"Attribute {attribute_name} has been set to {attribute_value}.") + else: + log.debug(f'Failed to create attribute named {attribute_name}.') + stage.Save()
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/task/task.html b/html/_modules/tao_yuan/core/task/task.html new file mode 100644 index 0000000..094a0a7 --- /dev/null +++ b/html/_modules/tao_yuan/core/task/task.html @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + tao_yuan.core.task.task — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.task.task

+# import random
+from abc import ABC, abstractmethod
+from functools import wraps
+from typing import Any, Dict
+
+from omni.isaac.core.scenes.scene import Scene
+from omni.isaac.core.tasks import BaseTask as OmniBaseTask
+from omni.isaac.core.utils.prims import create_prim
+
+from tao_yuan.core.config import TaskUserConfig
+from tao_yuan.core.robot import init_robots
+from tao_yuan.core.scene import create_object, create_scene
+from tao_yuan.core.task.metric import BaseMetric, create_metric
+from tao_yuan.core.util import log
+
+
+
[docs]class BaseTask(OmniBaseTask, ABC): + """ + wrap of omniverse isaac sim's base task + + * enable register for auto register task + * contains robots + """ + tasks = {} + + def __init__(self, config: TaskUserConfig, scene: Scene): + self.objects = None + self.robots = None + name = config.name + offset = config.offset + super().__init__(name=name, offset=offset) + self._scene = scene + self.config = config + + self.metrics: dict[str, BaseMetric] = {} + self.steps = 0 + self.work = True + + for metric_config in config.metrics: + self.metrics[metric_config.name] = create_metric(metric_config) + + def load(self): + if self.config.scene_asset_path == 'default': + source, prim_path = create_scene('TY-1/assets/scenes/Collected_simple_room/simple_room.usd', + prim_path_root=f'World/env_{self.config.env_id}/scene') + + create_prim(prim_path, + usd_path=source, + scale=self.config.scene_scale, + translation=[self.config.offset[idx] + i for idx, i in enumerate(self.config.scene_position)]) + elif self.config.scene_asset_path is not None: + source, prim_path = create_scene(self.config.scene_asset_path, + prim_path_root=f'World/env_{self.config.env_id}/scene') + create_prim(prim_path, + usd_path=source, + scale=self.config.scene_scale, + translation=[self.config.offset[idx] + i for idx, i in enumerate(self.config.scene_position)]) + + self.robots = init_robots(self.config, self._scene) + self.objects = {} + for obj in self.config.objects: + _object = create_object(obj) + _object.set_up_scene(self._scene) + self.objects[obj.name] = _object + log.info(self.robots) + log.info(self.objects) + +
[docs] def set_up_scene(self, scene: Scene) -> None: + self._scene = scene + self.load()
+ +
[docs] def get_observations(self) -> Dict[str, Any]: + """ + Returns current observations from the objects needed for the behavioral layer. + + Return: + Dict[str, Any]: observation of robots in this task + """ + if not self.work: + return {} + obs = {} + for robot_name, robot in self.robots.items(): + try: + obs[robot_name] = robot.get_obs() + except Exception as e: + log.error(self.name) + log.error(e) + return {} + return obs
+ + def update_metrics(self): + for _, metric in self.metrics.items(): + metric.update() + +
[docs] def calculate_metrics(self) -> dict: + metrics_res = {} + for name, metric in self.metrics.items(): + metrics_res[name] = metric.calc() + + return metrics_res
+ +
[docs] @abstractmethod + def is_done(self) -> bool: + """ + Returns True of the task is done. + + Raises: + NotImplementedError: this must be overridden. + """ + raise NotImplementedError
+ +
[docs] def individual_reset(self): + """ + reload this task individually without reloading whole world. + """ + raise NotImplementedError
+ +
[docs] def pre_step(self, time_step_index: int, simulation_time: float) -> None: + """called before stepping the physics simulation. + + Args: + time_step_index (int): [description] + simulation_time (float): [description] + """ + self.steps += 1 + return
+ +
[docs] def post_reset(self) -> None: + """Calls while doing a .reset() on the world.""" + self.steps = 0 + for robot in self.robots.values(): + robot.post_reset() + return
+ +
[docs] @classmethod + def register(cls, name: str): + """ + Register a task with its name(decorator). + Args: + name(str): name of the task + """ + + def decorator(tasks_class): + cls.tasks[name] = tasks_class + + @wraps(tasks_class) + def wrapped_function(*args, **kwargs): + return tasks_class(*args, **kwargs) + + return wrapped_function + + return decorator
+ + +def create_task(config: TaskUserConfig, scene: Scene): + print(BaseTask.tasks) + task_cls = BaseTask.tasks[config.type] + return task_cls(config, scene) +
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/util/array.html b/html/_modules/tao_yuan/core/util/array.html new file mode 100644 index 0000000..4126ea1 --- /dev/null +++ b/html/_modules/tao_yuan/core/util/array.html @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.array — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.array

+# Copyright (c) 2022-2024, The ORBIT Project Developers.
+# All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+"""Sub-module containing utilities for working with different array backends."""
+
+from typing import Union
+
+import numpy as np
+import torch
+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``).
+"""
+
+
+
[docs]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/html/_modules/tao_yuan/core/util/assets.html b/html/_modules/tao_yuan/core/util/assets.html new file mode 100644 index 0000000..bdc169a --- /dev/null +++ b/html/_modules/tao_yuan/core/util/assets.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.assets — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.assets

+# Copyright (c) 2022-2024, The ORBIT 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
+import omni.isaac.core.utils.nucleus as nucleus_utils
+
+# get assets root path
+# note: we check only once at the start of the module to prevent multiple checks on the Nucleus Server
+NUCLEUS_ASSET_ROOT_DIR = nucleus_utils.get_assets_root_path()
+"""Path to the root directory on the Nucleus Server.
+
+This is resolved using Isaac Sim's Nucleus API. If the Nucleus Server is not running, then this
+will be set to None. The path is resolved using the following steps:
+
+1. Based on simulation parameter: ``/persistent/isaac/asset_root/default``.
+2. Iterating over all the connected Nucleus Servers and checking for the first server that has the
+   the connected status.
+3. Based on simulation parameter: ``/persistent/isaac/asset_root/cloud``.
+"""
+
+# check nucleus connection
+if NUCLEUS_ASSET_ROOT_DIR is None:
+    msg = (
+        'Unable to perform Nucleus login on Omniverse. Assets root path is not set.\n'
+        '\tPlease check: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/overview.html#omniverse-nucleus')
+    carb.log_error(msg)
+    raise RuntimeError(msg)
+
+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."""
+
+ISAAC_ORBIT_NUCLEUS_DIR = f'{ISAAC_NUCLEUS_DIR}/Samples/Orbit'
+"""Path to the ``Isaac/Samples/Orbit`` directory on the NVIDIA Nucleus Server."""
+
+
+
[docs]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
+ + +
[docs]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}')
+ + +
[docs]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/html/_modules/tao_yuan/core/util/configclass.html b/html/_modules/tao_yuan/core/util/configclass.html new file mode 100644 index 0000000..056dfd8 --- /dev/null +++ b/html/_modules/tao_yuan/core/util/configclass.html @@ -0,0 +1,799 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.configclass — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.configclass

+# Copyright (c) 2022-2024, The ORBIT 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
+
+
+
[docs]@__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.orbit.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 f + + return _wrap +
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/util/dict.html b/html/_modules/tao_yuan/core/util/dict.html new file mode 100644 index 0000000..33ecda3 --- /dev/null +++ b/html/_modules/tao_yuan/core/util/dict.html @@ -0,0 +1,643 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.dict — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.dict

+# Copyright (c) 2022-2024, The ORBIT Project Developers.
+# All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# yapf: disable
+
+"""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.
+"""
+
+
+
[docs]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
+ + +
[docs]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. +""" + + +
[docs]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. +""" + + +
[docs]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
+ + +
[docs]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. + + Reference: + https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth + + 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/html/_modules/tao_yuan/core/util/omni_usd_util.html b/html/_modules/tao_yuan/core/util/omni_usd_util.html new file mode 100644 index 0000000..074da42 --- /dev/null +++ b/html/_modules/tao_yuan/core/util/omni_usd_util.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.omni_usd_util — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.omni_usd_util

+import time
+import typing
+
+import carb
+import numpy as np
+import omni.usd
+from pxr import Gf, Usd
+
+
+
[docs]def compute_path_bbox(prim_path: str) -> typing.Tuple[carb.Double3, carb.Double3]: + """ + Compute Bounding Box using omni.usd.UsdContext.compute_path_world_bounding_box + See https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd/omni.usd.UsdContext.html#\ + omni.usd.UsdContext.compute_path_world_bounding_box + + Args: + prim_path: A prim path to compute the bounding box. + Returns: + A range (i.e. bounding box) as a minimum point and maximum point. + """ + return omni.usd.get_context().compute_path_world_bounding_box(prim_path)
+ + +
[docs]def get_pick_position(robot_base_position: np.ndarray, prim_path: str) -> np.ndarray: + """Get the pick position for a manipulator robots to pick an objects at prim_path. + The pick position is simply the nearest top vertex of the objects's bounding box. + + Args: + robot_base_position (np.ndarray): robots base position. + prim_path (str): prim path of objects to pick. + + Returns: + np.ndarray: pick position. + """ + bbox_0, bbox_1 = compute_path_bbox(prim_path) + + x1 = bbox_0[0] + x2 = bbox_1[0] + y1 = bbox_0[1] + y2 = bbox_1[1] + top_z = bbox_0[2] if bbox_0[2] > bbox_1[2] else bbox_1[2] + + top_vertices = [ + np.array([x1, y1, top_z]), + np.array([x1, y2, top_z]), + np.array([x2, y1, top_z]), + np.array([x2, y2, top_z]), + ] + + print('================================ Top vertices: ', top_vertices, ' ====================================') + + pick_position = top_vertices[0] + for vertex in top_vertices: + if np.linalg.norm(robot_base_position - vertex) < np.linalg.norm(robot_base_position - pick_position): + pick_position = vertex + + return pick_position
+ + +
[docs]def get_grabbed_able_xform_paths(root_path: str, prim: Usd.Prim, depth: int = 3) -> typing.List[str]: + """get all prim paths of Xform objects under specified prim. + + Args: + root_path (str): root path of scenes. + prim (Usd.Prim): target prim. + depth (int, optional): expected depth of Xform objects relative to root_path. Defaults to 3. + + Returns: + typing.List[str]: prim paths. + """ + paths = [] + if prim is None: + return paths + print(f'get_grabbed_able_xform_paths: start to traverse {prim.GetPrimPath()}') + relative_prim_path = str(prim.GetPrimPath())[len(root_path):] + if relative_prim_path.count('/') <= depth: + for child in prim.GetChildren(): + if child.GetTypeName() == 'Scope': + paths.extend(get_grabbed_able_xform_paths(root_path, child)) + if child.GetTypeName() == 'Xform': + paths.append(str(child.GetPrimPath())) + + return paths
+ + +
[docs]def get_world_transform_xform(prim: Usd.Prim) -> typing.Tuple[Gf.Vec3d, Gf.Rotation, Gf.Vec3d]: + """ + Get the local transformation of a prim using omni.usd.get_world_transform_matrix(). + See https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd/omni.usd.get_world_transform_matrix.html + Args: + prim: The prim to calculate the world transformation. + Returns: + A tuple of: + - Translation vector. + - Rotation quaternion, i.e. 3d vector plus angle. + - Scale vector. + """ + world_transform: Gf.Matrix4d = omni.usd.get_world_transform_matrix(prim) + translation: Gf.Vec3d = world_transform.ExtractTranslation() + rotation: Gf.Rotation = world_transform.ExtractRotation() + scale: Gf.Vec3d = Gf.Vec3d(*(v.GetLength() for v in world_transform.ExtractRotationMatrix())) + return translation, rotation, scale
+ + +
[docs]def nearest_xform_from_position(stage: Usd.Stage, + xform_paths: typing.List[str], + position: np.ndarray, + threshold: float = 0) -> str: + """get prim path of nearest Xform objects from the target position. + + Args: + stage (Usd.Stage): usd stage. + xform_paths (typing.List[str]): full list of xforms paths. + position (np.ndarray): target position. + threshold (float, optional): max distance. Defaults to 0 (unlimited). + + Returns: + str: prim path of the Xform objects, None if not found. + """ + start = time.time() + if threshold == 0: + threshold = 1000000.0 + min_dist = threshold + nearest_prim_path = None + for path in xform_paths: + prim = stage.GetPrimAtPath(path) + if prim is not None and prim.IsValid(): + pose = get_world_transform_xform(prim) + dist = np.linalg.norm(pose[0] - position) + if dist < min_dist: + min_dist = dist + nearest_prim_path = path + + print(f'nearest_xform_from_position costs: {time.time() - start}') + return nearest_prim_path
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/util/python.html b/html/_modules/tao_yuan/core/util/python.html new file mode 100644 index 0000000..539b045 --- /dev/null +++ b/html/_modules/tao_yuan/core/util/python.html @@ -0,0 +1,1222 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.python — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.python

+"""
+A set of utility functions for general python usage
+"""
+import inspect
+import re
+from abc import ABCMeta
+from collections.abc import Iterable
+from copy import deepcopy
+from functools import wraps
+from importlib import import_module
+
+import numpy as np
+
+# Global dictionary storing all unique names
+NAMES = set()
+CLASS_NAMES = set()
+
+
+class ClassProperty:
+
+    def __init__(self, f_get):
+        self.f_get = f_get
+
+    def __get__(self, owner_self, owner_cls):
+        return self.f_get(owner_cls)
+
+
+
[docs]def subclass_factory(name, base_classes, __init__=None, **kwargs): + """ + Programmatically generates a new class type with name @name, subclassing from base classes @base_classes, with + corresponding __init__ call @__init__. + + NOTE: If __init__ is None (default), the __init__ call from @base_classes will be used instead. + + cf. https://stackoverflow.com/questions/15247075/how-can-i-dynamically-create-derived-classes-from-a-base-class + + Args: + name (str): Generated class name + base_classes (type, or list of type): Base class(es) to use for generating the subclass + __init__ (None or function): Init call to use for the base class when it is instantiated. If None if specified, + the newly generated class will automatically inherit the __init__ call from @base_classes + **kwargs (any): keyword-mapped parameters to override / set in the child class, where the keys represent + the class / instance attribute to modify and the values represent the functions / value to set + """ + # Standardize base_classes + base_classes = tuple(base_classes if isinstance(base_classes, Iterable) else [base_classes]) + + # Generate the new class + if __init__ is not None: + kwargs['__init__'] = __init__ + return type(name, base_classes, kwargs)
+ + +
[docs]def save_init_info(func): + """ + Decorator to save the init info of an objects to objects._init_info. + + _init_info contains class name and class constructor's input args. + """ + sig = inspect.signature(func) + + @wraps(func) # preserve func name, docstring, arguments list, etc. + def wrapper(self, *args, **kwargs): + values = sig.bind(self, *args, **kwargs) + + # Prevent args of super init from being saved. + if hasattr(self, '_init_info'): + func(*values.args, **values.kwargs) + return + + # Initialize class's self._init_info. + self._init_info = {'class_module': self.__class__.__module__, 'class_name': self.__class__.__name__, 'args': {}} + + # Populate class's self._init_info. + for k, p in sig.parameters.items(): + if k == 'self': + continue + if k in values.arguments: + val = values.arguments[k] + if p.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY): + self._init_info['args'][k] = val + elif p.kind == inspect.Parameter.VAR_KEYWORD: + for kwarg_k, kwarg_val in values.arguments[k].items(): + self._init_info['args'][kwarg_k] = kwarg_val + + # Call the original function. + func(*values.args, **values.kwargs) + + return wrapper
+ + +
[docs]class RecreatableMeta(type): + """ + Simple metaclass that automatically saves __init__ args of the instances it creates. + """ + + def __new__(cls, clsname, bases, clsdict): + if '__init__' in clsdict: + clsdict['__init__'] = save_init_info(clsdict['__init__']) + return super().__new__(cls, clsname, bases, clsdict)
+ + +
[docs]class RecreatableAbcMeta(RecreatableMeta, ABCMeta): + """ + A composite metaclass of both RecreatableMeta and ABCMeta. + + Adding in ABCMeta to resolve metadata conflicts. + """ + + pass
+ + +
[docs]class Recreatable(metaclass=RecreatableAbcMeta): + """ + Simple class that provides an abstract interface automatically saving __init__ args of + the classes inheriting it. + """ + +
[docs] def get_init_info(self): + """ + Grabs relevant initialization information for this class instance. Useful for directly + reloading an objects from this information, using @create_object_from_init_info. + + Returns: + dict: Nested dictionary that contains this objects' initialization information + """ + # Note: self._init_info is procedurally generated via @save_init_info called in metaclass + return self._init_info
+ + +
[docs]def create_object_from_init_info(init_info): + """ + Create a new objects based on given init info. + + Args: + init_info (dict): Nested dictionary that contains an objects's init information. + + Returns: + any: Newly created objects. + """ + module = import_module(init_info['class_module']) + cls = getattr(module, init_info['class_name']) + return cls(**init_info['args'], **init_info.get('kwargs', {}))
+ + +
[docs]def merge_nested_dicts(base_dict, extra_dict, inplace=False, verbose=False): + """ + Iteratively updates @base_dict with values from @extra_dict. Note: This generates a new dictionary! + + Args: + base_dict (dict): Nested base dictionary, which should be updated with all values from @extra_dict + extra_dict (dict): Nested extra dictionary, whose values will overwrite corresponding ones in @base_dict + inplace (bool): Whether to modify @base_dict in place or not + verbose (bool): If True, will print when keys are mismatched + + Returns: + dict: Updated dictionary + """ + # Loop through all keys in @extra_dict and update the corresponding values in @base_dict + base_dict = base_dict if inplace else deepcopy(base_dict) + for k, v in extra_dict.items(): + if k not in base_dict: + base_dict[k] = v + else: + if isinstance(v, dict) and isinstance(base_dict[k], dict): + base_dict[k] = merge_nested_dicts(base_dict[k], v) + else: + not_equal = base_dict[k] != v + if isinstance(not_equal, np.ndarray): + not_equal = not_equal.any() + if not_equal and verbose: + print(f'Different values for key {k}: {base_dict[k]}, {v}\n') + base_dict[k] = np.array(v) if isinstance(v, list) else v + + # Return new dict + return base_dict
+ + +
[docs]def get_class_init_kwargs(cls): + """ + Helper function to return a list of all valid keyword arguments (excluding "self") for the given @cls class. + + Args: + cls (object): Class from which to grab __init__ kwargs + + Returns: + list: All keyword arguments (excluding "self") specified by @cls __init__ constructor method + """ + return list(inspect.signature(cls.__init__).parameters.keys())[1:]
+ + +
[docs]def extract_subset_dict(dic, keys, copy=False): + """ + Helper function to extract a subset of dictionary key-values from a current dictionary. Optionally (deep)copies + the values extracted from the original @dic if @copy is True. + + Args: + dic (dict): Dictionary containing multiple key-values + keys (Iterable): Specific keys to extract from @dic. If the key doesn't exist in @dic, then the key is skipped + copy (bool): If True, will deepcopy all values corresponding to the specified @keys + + Returns: + dict: Extracted subset dictionary containing only the specified @keys and their corresponding values + """ + subset = {k: dic[k] for k in keys if k in dic} + return deepcopy(subset) if copy else subset
+ + +
[docs]def extract_class_init_kwargs_from_dict(cls, dic, copy=False): + """ + Helper function to return a dictionary of key-values that specifically correspond to @cls class's __init__ + constructor method, from @dic which may or may not contain additional, irrelevant kwargs. + Note that @dic may possibly be missing certain kwargs as specified by cls.__init__. No error will be raised. + + Args: + cls (object): Class from which to grab __init__ kwargs that will be be used as filtering keys for @dic + dic (dict): Dictionary containing multiple key-values + copy (bool): If True, will deepcopy all values corresponding to the specified @keys + + Returns: + dict: Extracted subset dictionary possibly containing only the specified keys from cls.__init__ and their + corresponding values + """ + # extract only relevant kwargs for this specific backbone + return extract_subset_dict( + dic=dic, + keys=get_class_init_kwargs(cls), + copy=copy, + )
+ + +
[docs]def assert_valid_key(key, valid_keys, name=None): + """ + Helper function that asserts that @key is in dictionary @valid_keys keys. If not, it will raise an error. + + Args: + key (any): key to check for in dictionary @dic's keys + valid_keys (Iterable): contains keys should be checked with @key + name (str or None): if specified, is the name associated with the key that will be printed out if the + key is not found. If None, default is "value" + """ + if name is None: + name = 'value' + assert key in valid_keys, 'Invalid {} received! Valid options are: {}, got: {}'.format( + name, + valid_keys.keys() if isinstance(valid_keys, dict) else valid_keys, key)
+ + +
[docs]def create_class_from_registry_and_config(cls_name, cls_registry, cfg, cls_type_descriptor): + """ + Helper function to create a class with str type @cls_name, which should be a valid entry in @cls_registry, using + kwargs in dictionary form @cfg to pass to the constructor, with @cls_type_name specified for debugging + + Args: + cls_name (str): Name of the class to create. This should correspond to the actual class type, in string form + cls_registry (dict): Class registry. This should map string names of valid classes to create to the + actual class type itself + cfg (dict): Any keyword arguments to pass to the class constructor + cls_type_descriptor (str): Description of the class type being created. This can be any string and is used + solely for debugging purposes + + Returns: + any: Created class instance + """ + # Make sure the requested class type is valid + assert_valid_key(key=cls_name, valid_keys=cls_registry, name=f'{cls_type_descriptor} type') + + # Grab the kwargs relevant for the specific class + cls = cls_registry[cls_name] + cls_kwargs = extract_class_init_kwargs_from_dict(cls=cls, dic=cfg, copy=False) + + # Create the class + return cls(**cls_kwargs)
+ + +
[docs]def get_uuid(name, n_digits=8): + """ + Helper function to create a unique @n_digits uuid given a unique @name + + Args: + name (str): Name of the objects or class + n_digits (int): Number of digits of the uuid, default is 8 + + Returns: + int: uuid + """ + return abs(hash(name)) % (10**n_digits)
+ + +
[docs]def camel_case_to_snake_case(camel_case_text): + """ + Helper function to convert a camel case text to snake case, e.g. "StrawberrySmoothie" -> "strawberry_smoothie" + + Args: + camel_case_text (str): Text in camel case + + Returns: + str: snake case text + """ + return re.sub(r'(?<!^)(?=[A-Z])', '_', camel_case_text).lower()
+ + +
[docs]def snake_case_to_camel_case(snake_case_text): + """ + Helper function to convert a snake case text to camel case, e.g. "strawberry_smoothie" -> "StrawberrySmoothie" + + Args: + snake_case_text (str): Text in snake case + + Returns: + str: camel case text + """ + return ''.join(item.title() for item in snake_case_text.split('_'))
+ + +
[docs]def meets_minimum_version(test_version, minimum_version): + """ + Verify that @test_version meets the @minimum_version + + Args: + test_version (str): Python package version. Should be, e.g., 0.26.1 + minimum_version (str): Python package version to test against. Should be, e.g., 0.27.2 + + Returns: + bool: Whether @test_version meets @minimum_version + """ + test_nums = [int(num) for num in test_version.split('.')] + minimum_nums = [int(num) for num in minimum_version.split('.')] + assert len(test_nums) == 3 + assert len(minimum_nums) == 3 + + for test_num, minimum_num in zip(test_nums, minimum_nums): + if test_num > minimum_num: + return True + elif test_num < minimum_num: + return False + # Otherwise, we continue through all sub-versions + + # If we get here, that means test_version == threshold_version, so this is a success + return True
+ + +
[docs]class UniquelyNamed: + """ + Simple class that implements a name property, that must be implemented by a subclass. Note that any @Named + entity must be UNIQUE! + """ + + def __init__(self): + global NAMES + # Register this objects, making sure it's name is unique + assert self.name not in NAMES, \ + f'UniquelyNamed objects with name {self.name} already exists!' + NAMES.add(self.name) + + # def __del__(self): + # # Remove this objects name from the registry if it's still there + # self.remove_names(include_all_owned=True) + +
[docs] def remove_names(self, include_all_owned=True, skip_ids=None): + """ + Checks if self.name exists in the global NAMES registry, and deletes it if so. Possibly also iterates through + all owned member variables and checks for their corresponding names if @include_all_owned is True. + + Args: + include_all_owned (bool): If True, will iterate through all owned members of this instance and remove their + names as well, if they are UniquelyNamed + + skip_ids (None or set of int): If specified, will skip over any ids in the specified set that are matched + to any attributes found (this compares id(attr) to @skip_ids). + """ + # Make sure skip_ids is a set so we can pass this into the method, and add the dictionary so we don't + # get infinite recursive loops + skip_ids = set() if skip_ids is None else skip_ids + skip_ids.add(id(self)) + + # Check for this name, possibly remove it if it exists + if self.name in NAMES: + NAMES.remove(self.name) + + # Also possibly iterate through all owned members and check if those are instances of UniquelyNamed + if include_all_owned: + self._remove_names_recursively_from_dict(dic=self.__dict__, skip_ids=skip_ids)
+ + def _remove_names_recursively_from_dict(self, dic, skip_ids=None): + """ + Checks if self.name exists in the global NAMES registry, and deletes it if so + + Args: + skip_ids (None or set): If specified, will skip over any objects in the specified set that are matched + to any attributes found. + """ + # Make sure skip_ids is a set so we can pass this into the method, and add the dictionary so we don't + # get infinite recursive loops + skip_ids = set() if skip_ids is None else skip_ids + skip_ids.add(id(dic)) + + # Loop through all values in the inputted dictionary, and check if any of the values are UniquelyNamed + for name, val in dic.items(): + if id(val) not in skip_ids: + # No need to explicitly add val to skip objects because the methods below handle adding it + if isinstance(val, UniquelyNamed): + val.remove_names(include_all_owned=True, skip_ids=skip_ids) + elif isinstance(val, dict): + # Recursively iterate + self._remove_names_recursively_from_dict(dic=val, skip_ids=skip_ids) + elif hasattr(val, '__dict__'): + # Add the attribute and recursively iterate + skip_ids.add(id(val)) + self._remove_names_recursively_from_dict(dic=val.__dict__, skip_ids=skip_ids) + else: + # Otherwise we just add the value to skip_ids so we don't check it again + skip_ids.add(id(val)) + + @property + def name(self): + """ + Returns: + str: Name of this instance. Must be unique! + """ + raise NotImplementedError
+ + +
[docs]class UniquelyNamedNonInstance: + """ + Identical to UniquelyNamed, but intended for non-instanceable classes + """ + + def __init_subclass__(cls, **kwargs): + global CLASS_NAMES + # Register this objects, making sure it's name is unique + assert cls.name not in CLASS_NAMES, \ + f'UniquelyNamed class with name {cls.name} already exists!' + CLASS_NAMES.add(cls.name) + + @ClassProperty + def name(self): + """ + Returns: + str: Name of this instance. Must be unique! + """ + raise NotImplementedError
+ + +
[docs]class Registerable: + """ + Simple class template that provides an abstract interface for registering classes. + """ + + def __init_subclass__(cls, **kwargs): + """ + Registers all subclasses as part of this registry. This is useful to decouple internal codebase from external + user additions. This way, users can add their custom subclasses by simply extending this class, + and it will automatically be registered internally. This allows users to then specify their classes + directly in string-form in e.g., their config files, without having to manually set the str-to-class mapping + in our code. + """ + cls._register_cls() + + @classmethod + def _register_cls(cls): + """ + Register this class. Can be extended by subclass. + """ + # print(f"registering: {cls.__name__}") + # print(f"registry: {cls._cls_registry}", cls.__name__ not in cls._cls_registry) + # print(f"do not register: {cls._do_not_register_classes}", cls.__name__ not in cls._do_not_register_classes) + # input() + if cls.__name__ not in cls._cls_registry and cls.__name__ not in cls._do_not_register_classes: + cls._cls_registry[cls.__name__] = cls + + @ClassProperty + def _do_not_register_classes(self): + """ + Returns: + set of str: Name(s) of classes that should not be registered. Default is empty set. + Subclasses that shouldn't be added should call super() and then add their own class name to the set + """ + return set() + + @ClassProperty + def _cls_registry(self): + """ + Returns: + dict: Mapping from all registered class names to their classes. This should be a REFERENCE + to some external, global dictionary that will be filled-in at runtime. + """ + raise NotImplementedError()
+ + +
[docs]class Serializable: + """ + Simple class that provides an abstract interface to dump / load states, optionally with serialized functionality + as well. + """ + + @property + def state_size(self): + """ + Returns: + int: Size of this objects's serialized state + """ + raise NotImplementedError() + + def _dump_state(self): + """ + Dumps the state of this objects in dictionary form (can be empty). Should be implemented by subclass. + + Returns: + dict: Keyword-mapped states of this objects + """ + raise NotImplementedError() + +
[docs] def dump_state(self, serialized=False): + """ + Dumps the state of this objects in either dictionary of flattened numerical form. + + Args: + serialized (bool): If True, will return the state of this objects as a 1D numpy array. Otherwise, + will return a (potentially nested) dictionary of states for this objects + + Returns: + dict or n-array: Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array \ + capturing this objects' state, where n is @self.state_size + """ + state = self._dump_state() + return self.serialize(state=state) if serialized else state
+ + def _load_state(self, state): + """ + Load the internal state to this objects as specified by @state. Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to set + """ + raise NotImplementedError() + +
[docs] def load_state(self, state, serialized=False): + """ + Deserializes and loads this objects' state based on @state + + Args: + state (dict or n-array): Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array capturing this objects' state, + where n is @self.state_size + serialized (bool): If True, will interpret @state as a 1D numpy array. Otherwise, + will assume the input is a (potentially nested) dictionary of states for this objects + """ + state = self.deserialize(state=state) if serialized else state + self._load_state(state=state)
+ + def _serialize(self, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + raise NotImplementedError() + +
[docs] def serialize(self, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + # Simply returns self._serialize() for now. this is for future proofing + return self._serialize(state=state)
+ + def _deserialize(self, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + 2-tuple: + - dict: Keyword-mapped states of this objects. Should match structure of output from + self._dump_state() + - int: current index of the flattened state vector that is left off. This is helpful for subclasses + that inherit partial deserializations from parent classes, and need to know where the + deserialization left off before continuing. + """ + raise NotImplementedError + +
[docs] def deserialize(self, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + dict: Keyword-mapped states of these objects. Should match structure of output from + self._dump_state() + """ + # Sanity check the idx with the expected state size + state_dict, idx = self._deserialize(state=state) + assert idx == self.state_size, f'Invalid state deserialization occurred! Expected {self.state_size} total ' \ + f'values to be deserialized, only {idx} were.' + + return state_dict
+ + +
[docs]class SerializableNonInstance: + """ + Identical to Serializable, but intended for non-instance classes + """ + + @ClassProperty + def state_size(self): + """ + Returns: + int: Size of this objects's serialized state + """ + raise NotImplementedError() + + @classmethod + def _dump_state(cls): + """ + Dumps the state of this objects in dictionary form (can be empty). Should be implemented by subclass. + + Returns: + dict: Keyword-mapped states of this objects + """ + raise NotImplementedError() + +
[docs] @classmethod + def dump_state(cls, serialized=False): + """ + Dumps the state of this objects in either dictionary of flattened numerical form. + + Args: + serialized (bool): If True, will return the state of this objects as a 1D numpy array. Otherwise, + will return a (potentially nested) dictionary of states for this objects + + Returns: + dict or n-array: Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array capturing this objects' state, where n is @self.state_size + """ + state = cls._dump_state() + return cls.serialize(state=state) if serialized else state
+ + @classmethod + def _load_state(cls, state): + """ + Load the internal state to this objects as specified by @state. Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of these objects to set + """ + raise NotImplementedError() + +
[docs] @classmethod + def load_state(cls, state, serialized=False): + """ + Deserializes and loads this objects' state based on @state + + Args: + state (dict or n-array): Either: + - Keyword-mapped states of these objects, or + - encoded + serialized, 1D numerical np.array capturing this objects' state, + where n is @self.state_size + serialized (bool): If True, will interpret @state as a 1D numpy array. Otherwise, will assume the input is + a (potentially nested) dictionary of states for this objects + """ + state = cls.deserialize(state=state) if serialized else state + cls._load_state(state=state)
+ + @classmethod + def _serialize(cls, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of this objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + raise NotImplementedError() + +
[docs] @classmethod + def serialize(cls, state): + """ + Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. + Should be implemented by subclass. + + Args: + state (dict): Keyword-mapped states of these objects to encode. Should match structure of output from + self._dump_state() + + Returns: + n-array: encoded + serialized, 1D numerical np.array capturing this objects's state + """ + # Simply returns self._serialize() for now. this is for future proofing + return cls._serialize(state=state)
+ + @classmethod + def _deserialize(cls, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + 2-tuple: + - dict: Keyword-mapped states of this objects. Should match structure of output from + self._dump_state() + - int: current index of the flattened state vector that is left off. This is helpful for subclasses + that inherit partial deserializations from parent classes, and need to know where the + deserialization left off before continuing. + """ + raise NotImplementedError + +
[docs] @classmethod + def deserialize(cls, state): + """ + De-serializes flattened 1D numpy array @state into nested dictionary state. + Should be implemented by subclass. + + Args: + state (n-array): encoded + serialized, 1D numerical np.array capturing this objects's state + + Returns: + dict: Keyword-mapped states of this objects. Should match structure of output from + self._dump_state() + """ + # Sanity check the idx with the expected state size + state_dict, idx = cls._deserialize(state=state) + assert idx == cls.state_size, f'Invalid state deserialization occurred! Expected {cls.state_size} total ' \ + f'values to be deserialized, only {idx} were.' + + return state_dict
+ + +
[docs]class Wrapper: + """ + Base class for all wrapper in OmniGibson + + Args: + obj (any): Arbitrary python objects instance to wrap + """ + + def __init__(self, obj): + # Set the internal attributes -- store wrapped obj + self.wrapped_obj = obj + + @classmethod + def class_name(cls): + return cls.__name__ + + def _warn_double_wrap(self): + """ + Utility function that checks if we're accidentally trying to double wrap an scenes + Raises: + Exception: [Double wrapping scenes] + """ + obj = self.wrapped_obj + while True: + if isinstance(obj, Wrapper): + if obj.class_name() == self.class_name(): + raise Exception('Attempted to double wrap with Wrapper: {}'.format(self.__class__.__name__)) + obj = obj.wrapped_obj + else: + break + + @property + def unwrapped(self): + """ + Grabs unwrapped objects + + Returns: + any: The unwrapped objects instance + """ + return self.wrapped_obj.unwrapped if hasattr(self.wrapped_obj, 'unwrapped') else self.wrapped_obj + + # this method is a fallback option on any methods the original scenes might support + def __getattr__(self, attr): + # If we're querying wrapped_obj, raise an error + if attr == 'wrapped_obj': + raise AttributeError('wrapped_obj attribute not initialized yet!') + + # Sanity check to make sure wrapped obj is not None -- if so, raise error + assert self.wrapped_obj is not None, f'Cannot access attribute {attr} since wrapped_obj is None!' + + # using getattr ensures that both __getattribute__ and __getattr__ (fallback) get called + # (see https://stackoverflow.com/questions/3278077/difference-between-getattr-vs-getattribute) + orig_attr = getattr(self.wrapped_obj, attr) + if callable(orig_attr): + + def hooked(*args, **kwargs): + result = orig_attr(*args, **kwargs) + # prevent wrapped_class from becoming unwrapped + if id(result) == id(self.wrapped_obj): + return self + return result + + return hooked + else: + return orig_attr + + def __setattr__(self, key, value): + # Call setattr on wrapped obj if it has the attribute, otherwise, operate on this objects + if hasattr(self, 'wrapped_obj') and self.wrapped_obj is not None and hasattr(self.wrapped_obj, key): + setattr(self.wrapped_obj, key, value) + else: + super().__setattr__(key, value)
+ + +
[docs]def clear(): + """ + Clear state tied to singleton classes + """ + NAMES.clear() + CLASS_NAMES.clear()
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/_modules/tao_yuan/core/util/string.html b/html/_modules/tao_yuan/core/util/string.html new file mode 100644 index 0000000..53dad9b --- /dev/null +++ b/html/_modules/tao_yuan/core/util/string.html @@ -0,0 +1,723 @@ + + + + + + + + + + + + + + + tao_yuan.core.util.string — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +

Source code for tao_yuan.core.util.string

+# Copyright (c) 2022-2024, The ORBIT Project Developers.
+# All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# yapf: disable
+
+"""Submodule 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.
+"""
+
+
+
[docs]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)
+ + +
[docs]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. +""" + + +
[docs]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
+ + +
[docs]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}'
+ + +
[docs]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. +""" + + +
[docs]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
+ + +
[docs]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/html/_sources/advanced_tutorials/how-to-add-controller.md.txt b/html/_sources/advanced_tutorials/how-to-add-controller.md.txt new file mode 100644 index 0000000..214ad40 --- /dev/null +++ b/html/_sources/advanced_tutorials/how-to-add-controller.md.txt @@ -0,0 +1,66 @@ +# How to add custom controller + +> This tutorial will show you how to add a controller for a robot + +Before this tutorial, you should read: +- [how to add robot](./how-to-add-robot.md) + +## 1. Add `grutopia.core.robot.controller` + +Take this "ChatboxController" as an example, + +```python +from datetime import datetime +from typing import Any, Dict, List, Union + +import numpy as np +from omni.isaac.core.scenes import Scene +from omni.isaac.core.utils.types import ArticulationAction + +from grutopia.core.datahub.model_data import LogData, ModelData +from grutopia.core.robot.controller import BaseController +from grutopia.core.robot.robot import BaseRobot +from grutopia.core.robot.robot_model import ControllerModel + + +@BaseController.register('ChatboxController') +class ChatboxController(BaseController): + + def __init__(self, config: ControllerModel, name: str, robot: BaseRobot, scene: Scene) -> None: + config = ControllerModel(name=name, type='chat') + super().__init__(config, robot, scene) + self._user_config = None + self.counter = 1 + + def action_to_control(self, action: Union[np.ndarray, List]) -> ArticulationAction: + # TODO Check input(like [np.array('I am the sentence', agent_avatar_data)]) + return self.forward(str(action[0])) + + def forward(self, chat: str) -> ArticulationAction: + # TODO Set chat action + return ArticulationAction() + + def get_obs(self) -> Dict[str, Any]: + return {} +``` + +## 2. Register at `robot_models` + +Like this. + +![alt text](../_static/image/robot_models_add_controller.png) + +## 3. Write a demo + +```python +... +while env.simulation_app.is_running(): + ... + env_actions = [{ + h1: { + "web_chat": np.array(['你好']) + } + }] + obs = env.step(actions=env_actions) +env.simulation_app.close() +``` diff --git a/html/_sources/advanced_tutorials/how-to-add-robot.md.txt b/html/_sources/advanced_tutorials/how-to-add-robot.md.txt new file mode 100644 index 0000000..c02c9d1 --- /dev/null +++ b/html/_sources/advanced_tutorials/how-to-add-robot.md.txt @@ -0,0 +1,133 @@ +# How to add custom robot + +> This tutorial will show you how to add a robot + +## 1. Add isaac sim robot + +> Assuming you already have an usd file of a robot, and it has drivable joints. + +Create a file in `grutopia_extension/robots`, named `demo_robot.py`. Inherit the robot class from isaac. + +```Python +from omni.isaac.core.robots.robot import Robot as IsaacRobot +from omni.isaac.core.utils.stage import add_reference_to_stage + + +class DemoRobot(IsaacRobot): + + def __init__(self, + prim_path: str, + usd_path: str, + name: str, + position: np.ndarray = None, + orientation: np.ndarray = None, + scale: np.ndarray = None): + add_reference_to_stage(prim_path=prim_path, usd_path=os.path.abspath(usd_path)) + super().__init__(prim_path=prim_path, name=name, position=position, orientation=orientation, scale=scale) + # Set robot-specific parameters/attributes here +``` + +## 2. Wrap with `grutopia.core.robot.robot` + +```Python +from omni.isaac.core.scenes import Scene + +from grutopia.core.config.robot import RobotUserConfig as Config +from grutopia.core.robot.robot import BaseRobot +from grutopia.core.robot.robot_model import RobotModel +from grutopia.core.util import log + + +# Register this robot to grutopia.core +@BaseRobot.register('DemoRobot') +class DemoRobotWrapper(BaseRobot): + + def __init__(self, config: Config, robot_model: RobotModel, scene: Scene): + super().__init__(config, robot_model, scene) + self._sensor_config = robot_model.sensors + self._gains = robot_model.gains + self._start_position = np.array(config.position) if config.position is not None else None + self._start_orientation = np.array(config.orientation) if config.orientation is not None else None + + log.debug(f'demo_robot {config.name}: position : ' + str(self._start_position)) + log.debug(f'demo_robot {config.name}: orientation : ' + str(self._start_orientation)) + + usd_path = robot_model.usd_path + if usd_path.startswith('/Isaac'): + usd_path = get_assets_root_path() + usd_path + + log.debug(f'demo_robot {config.name}: usd_path : ' + str(usd_path)) + log.debug(f'demo_robot {config.name}: config.prim_path : ' + str(config.prim_path)) + + # Wrap the robot class here. + self.isaac_robot = DemoRobot( + prim_path=config.prim_path, + name=config.name, + position=self._start_position, + orientation=self._start_orientation, + usd_path=usd_path, + ) + + self._robot_scale = np.array([1.0, 1.0, 1.0]) + if config.scale is not None: + self._robot_scale = np.array(config.scale) + self.isaac_robot.set_local_scale(self._robot_scale) + + # Add the attr you want here. + + ... + + def apply_action(self, action: dict): + """ + Args: + action (dict): inputs for controllers. + """ + for controller_name, controller_action in action.items(): + if controller_name not in self.controllers: + log.warn(f'unknown controller {controller_name} in action') + continue + controller = self.controllers[controller_name] + control = controller.action_to_control(controller_action) + self.isaac_robot.apply_actuator_model(control, controller_name, self.joint_subset) + + def get_obs(self): + """ + Set the observation you need here. + """ + + # custom + position, orientation = self._robot_base.get_world_pose() + obs = { + 'position': position, + 'orientation': orientation, + } + + # common + for c_obs_name, controller_obs in self.controllers.items(): + obs[c_obs_name] = controller_obs.get_obs() + for sensor_name, sensor_obs in self.sensors.items(): + obs[sensor_name] = sensor_obs.get_data() + return obs +``` + +* And there are many other functions in `grutopia.core.robot.robot`, FYI. + +## 3. Register at `robot_models` + +Add you robot model at `grutopia_extension/robots/robot_models.yaml` + +```yaml +- type: "DemoRobotWrapper" + usd_path: "..." + controllers: + - name: "..." + type: "..." +``` + +## 4. Add controllers and sensors + +See [how to add controller](./how-to-add-controller.md) and [how to add sensor](./how-to-add-sensor.md) + +## 5. Write a demo + +See [how to add controller](./how-to-add-controller.md) and [how to add sensor](./how-to-add-sensor.md) diff --git a/html/_sources/advanced_tutorials/how-to-add-sensor.md.txt b/html/_sources/advanced_tutorials/how-to-add-sensor.md.txt new file mode 100644 index 0000000..12eb1b7 --- /dev/null +++ b/html/_sources/advanced_tutorials/how-to-add-sensor.md.txt @@ -0,0 +1,100 @@ +# How to add custom sensor + +> This tutorial will show you how to add a sensor for a robot + +Before this tutorial, you should read: +- [how to add robot](./how-to-add-robot.md) + +## 1. Create with `grutopia.core.robot.sensor` + +The sensors in `grutopia` are not just tensor. They are interfaces for robots to passively receive all kinds of +information. + +The only thing we should matter is: implement `BaseSensor` from `grutopia.core.robot.sensor` + +Camera sensor FYI + +```Python +from typing import Dict + +from omni.isaac.sensor import Camera as i_Camera + +from grutopia.core.config.robot import RobotUserConfig +from grutopia.core.robot.robot import BaseRobot, Scene +from grutopia.core.robot.robot_model import SensorModel +from grutopia.core.robot.sensor import BaseSensor +from grutopia.core.util import log + + +@BaseSensor.register('Camera') +class Camera(BaseSensor): + """ + wrap of isaac sim's Camera class + """ + + def __init__(self, + robot_user_config: RobotUserConfig, + sensor_config: SensorModel, + robot: BaseRobot, + name: str = None, + scene: Scene = None): + super().__init__(robot_user_config, sensor_config, robot, name) + self.param = None + if self.robot_user_config.sensor_params is not None: + self.param = [p for p in self.robot_user_config.sensor_params if p.name == self.name][0] + self._camera = self.create_camera() + + def create_camera(self) -> i_Camera: + size = (1280, 720) + if self.param is not None: + size = self.param.size + + prim_path = self.robot_user_config.prim_path + '/' + self.sensor_config.prim_path + log.debug('camera_prim_path: ' + prim_path) + log.debug('name : ' + '_'.join([self.robot_user_config.name, self.sensor_config.name])) + return i_Camera(prim_path=prim_path, resolution=size) + + def sensor_init(self) -> None: + if self.param is not None: + if self.param.switch: + self._camera.initialize() + self._camera.add_distance_to_image_plane_to_frame() + self._camera.add_semantic_segmentation_to_frame() + self._camera.add_instance_segmentation_to_frame() + self._camera.add_instance_id_segmentation_to_frame() + self._camera.add_bounding_box_2d_tight_to_frame() + + def get_data(self) -> Dict: + if self.param is not None: + if self.param.switch: + rgba = self._camera.get_rgba() + depth = self._camera.get_depth() + frame = self._camera.get_current_frame() + return {'rgba': rgba, 'depth': depth, 'frame': frame} + return {} +``` + +## 2. Register at `robot_models` + +Add sensor for robots in `grutopia_extension/robots/robot_models.yaml`. + +```yaml +robots: + - type: "HumanoidRobot" + ... + sensors: + - name: "camera" + prim_path: "relative/prim/path/to/camera" # relative path + type: "Camera" # map to key in `register` +``` + +## 3. Write a demo + +In simulation_app's step loop: + +```Python + ... + obs = env.step(actions) + photo = obs['robot_name_in_config']['camera']['frame']['rgba'] # `camera` is sensor name in model + ... +``` diff --git a/html/_sources/advanced_tutorials/how-to-add-task.md.txt b/html/_sources/advanced_tutorials/how-to-add-task.md.txt new file mode 100644 index 0000000..5868794 --- /dev/null +++ b/html/_sources/advanced_tutorials/how-to-add-task.md.txt @@ -0,0 +1,11 @@ +# how to add custom task + +> Wrap a task as you wish + +## When I need a custom task + +> WIP + +## How to wrap the Task + +> WIP \ No newline at end of file diff --git a/html/_sources/api/datahub.rst.txt b/html/_sources/api/datahub.rst.txt new file mode 100644 index 0000000..4170f24 --- /dev/null +++ b/html/_sources/api/datahub.rst.txt @@ -0,0 +1,28 @@ +grutopia.core.datahub +=================================== + +datahub +------- + +.. autoclass:: grutopia.core.datahub.IsaacData + :members: + + +local api +--------- + +.. automodule:: grutopia.core.datahub.api + :members: + +.. automodule:: grutopia.core.datahub.model_data + :members: + + +web api +------- + +.. automodule:: grutopia.core.datahub.web_ui_api + :members: + +.. automodule:: grutopia.core.datahub.web_api + :members: diff --git a/html/_sources/api/env.rst.txt b/html/_sources/api/env.rst.txt new file mode 100644 index 0000000..7e27d0d --- /dev/null +++ b/html/_sources/api/env.rst.txt @@ -0,0 +1,14 @@ +grutopia.core.env +================= + +env +--- + +.. autoclass:: grutopia.core.env.BaseEnv + :members: + +runner +------ + +.. autoclass:: grutopia.core.runner.SimulatorRunner + :members: diff --git a/html/_sources/api/register.rst.txt b/html/_sources/api/register.rst.txt new file mode 100644 index 0000000..518d3e5 --- /dev/null +++ b/html/_sources/api/register.rst.txt @@ -0,0 +1,8 @@ +grutopia.core.register +=================================== + +register +-------- + +.. automodule:: grutopia.core.register.register + :members: diff --git a/html/_sources/api/robot.rst.txt b/html/_sources/api/robot.rst.txt new file mode 100644 index 0000000..78b34f8 --- /dev/null +++ b/html/_sources/api/robot.rst.txt @@ -0,0 +1,22 @@ +grutopia.core.robot +=================================== + +robot +----- + +.. automodule:: grutopia.core.robot.robot + :members: + + +controller +---------- + +.. automodule:: grutopia.core.robot.controller + :members: + + +sensor +------ + +.. automodule:: grutopia.core.robot.sensor + :members: diff --git a/html/_sources/api/scene.rst.txt b/html/_sources/api/scene.rst.txt new file mode 100644 index 0000000..f0b52a9 --- /dev/null +++ b/html/_sources/api/scene.rst.txt @@ -0,0 +1,17 @@ +grutopia.core.scene +=================================== + + +object +------ + +.. automodule:: grutopia.core.scene.object + :members: + + +usd_op +------ + + +.. automodule:: grutopia.core.scene.scene.util.usd_op + :members: diff --git a/html/_sources/api/task.rst.txt b/html/_sources/api/task.rst.txt new file mode 100644 index 0000000..6f28cbc --- /dev/null +++ b/html/_sources/api/task.rst.txt @@ -0,0 +1,16 @@ +grutopia.core.task +================== + +task +---- + +.. automodule:: grutopia.core.task.task + :members: + + +.. + metric + ------ + + .. automodule:: grutopia.core.task.metric + :members: diff --git a/html/_sources/api/util.rst.txt b/html/_sources/api/util.rst.txt new file mode 100644 index 0000000..51947d9 --- /dev/null +++ b/html/_sources/api/util.rst.txt @@ -0,0 +1,57 @@ +grutopia.core.util +================== + +array +----- + +.. automodule:: grutopia.core.util.array + :members: + + +assets +------ + +.. automodule:: grutopia.core.util.assets + :members: + + +configclass +----------- + +.. automodule:: grutopia.core.util.configclass + :members: + + +dict +---- + +.. automodule:: grutopia.core.util.dict + :members: + + +math +---- + +.. automodule:: grutopia.core.util.math + :members: + + +omni_usd_util +------------- + +.. automodule:: grutopia.core.util.omni_usd_util + :members: + + +python +------ + +.. automodule:: grutopia.core.util.python + :members: + + +string +------ + +.. automodule:: grutopia.core.util.string + :members: diff --git a/html/_sources/get_started/30-min-to-get-started.md.txt b/html/_sources/get_started/30-min-to-get-started.md.txt new file mode 100644 index 0000000..11a1a4f --- /dev/null +++ b/html/_sources/get_started/30-min-to-get-started.md.txt @@ -0,0 +1,567 @@ +# 30 minutes to get started + +In this tutorial, we'll add a MyCobot280Pi to + +## Get robot resources + +### 1. URDF and usd + +we can get full resources [here](https://github.com/elephantrobotics/mycobot_ros/tree/noetic/mycobot_description/urdf/mycobot_280_pi) + +if you are not familiar to `ROS`/`ROS2`, you need to rename the source path in `URDF` file + +![img.png](../_static/image/before_rename_urdf_img.png) + +my resource path is `/home/apx103/Desktop/mycobot_280_pi`, so change meshes' `filename` attr like this: + +```xml + + + + + + +``` + +### 2. import URDF and gen usd file + +follow this [instruction](https://docs.omniverse.nvidia.com/isaacsim/latest/advanced_tutorials/tutorial_advanced_import_urdf.html) to get usd like this: + +```log +. +├── materials +│ ├── joint2.png +│ ├── joint3.png +│ ├── joint4.png +│ ├── joint5.png +│ ├── joint6.png +│ └── joint7.png +└── mycobot_280pi_with_camera_flange.usd +``` + +then follow this [instruction](https://docs.omniverse.nvidia.com/isaacsim/latest/advanced_tutorials/tutorial_motion_generation_robot_description_editor.html) to get robot description + +my description is like this: + +```yaml + +# The robot description defines the generalized coordinates and how to map those +# to the underlying URDF dofs. + +api_version: 1.0 + +# Defines the generalized coordinates. Each generalized coordinate is assumed +# to have an entry in the URDF. +# Lula will only use these joints to control the robot position. +cspace: + - joint2_to_joint1 + - joint3_to_joint2 + - joint4_to_joint3 + - joint5_to_joint4 + - joint6_to_joint5 + - joint6output_to_joint6 + +root_link: g_base + +default_q: [ + 0.0,-0.0,-0.0,-0.0,0.0,-0.0 +] + +# Most dimensions of the cspace have a direct corresponding element +# in the URDF. This list of rules defines how unspecified coordinates +# should be extracted or how values in the URDF should be overwritten. + +cspace_to_urdf_rules: + +# Lula uses collision spheres to define the robot geometry in order to avoid +# collisions with external obstacles. If no spheres are specified, Lula will +# not be able to avoid obstacles. + +collision_spheres: + - joint1: + - "center": [0.0, 0.0, 0.039] + "radius": 0.035 + - joint2: + - "center": [0.0, 0.0, 0.0] + "radius": 0.02 + - "center": [0.0, 0.0, -0.045] + "radius": 0.02 + - "center": [0.0, 0.0, -0.011] + "radius": 0.02 + - "center": [0.0, 0.0, -0.023] + "radius": 0.02 + - "center": [0.0, 0.0, -0.034] + "radius": 0.02 + - joint4: + - "center": [0.0, 0.0, 0.0] + "radius": 0.02 + - "center": [-0.094, -0.0, -0.0] + "radius": 0.02 + - "center": [-0.016, -0.0, -0.0] + "radius": 0.02 + - "center": [-0.031, -0.0, -0.0] + "radius": 0.02 + - "center": [-0.047, -0.0, -0.0] + "radius": 0.02 + - "center": [-0.063, -0.0, -0.0] + "radius": 0.02 + - "center": [-0.078, -0.0, -0.0] + "radius": 0.02 + - joint3: + - "center": [-0.0, -0.0, 0.064] + "radius": 0.02 + - "center": [-0.107, -0.0, 0.064] + "radius": 0.02 + - "center": [-0.018, -0.0, 0.064] + "radius": 0.02 + - "center": [-0.036, -0.0, 0.064] + "radius": 0.02 + - "center": [-0.053, -0.0, 0.064] + "radius": 0.02 + - "center": [-0.071, -0.0, 0.064] + "radius": 0.02 + - "center": [-0.089, -0.0, 0.064] + "radius": 0.02 + - joint5: + - "center": [0.0, 0.0, 0.0] + "radius": 0.02 + - joint6: + - "center": [0.0, 0.0, 0.0] + "radius": 0.02 +``` + +### 3. Test files + +> Test whether these files work + +open `lula test extension` in isaac sim to test files we get before + +![img.png](../_static/image/lula_test_extension.png) + +we only need to test `lula Kunematics Solver`. And then we know: + +1. robot(manipulator) can get solve using `lula Kunematics Solver`. +2. description file of robot(manipulator) is work. + +## Add Robot + +### What we need to do + +In this example, we just add a IK controller to set the position(angle included) of manipulator's end effector. + +### move resources to `assets` + +we need to move resources to `assets/robots/mycobot_280_pi_with_camera_flange` like: + +```log +. +├── materials +│ ├── joint2.png +│ ├── joint3.png +│ ├── joint4.png +│ ├── joint5.png +│ ├── joint6.png +│ └── joint7.png +├── mycobot_280pi_with_camera_flange.urdf +├── mycobot_280pi_with_camera_flange.usd +└── robot_descriptor.yaml +``` + +### Add Robot Class + +1. At first, we need to define a Robot class based on `omni.isaac.core.robots.robot.Robot`. It wraps `end effector` and `articulation`. + + ```python + import os + import numpy as np + from omni.isaac.core.prims import RigidPrim + from omni.isaac.core.robots.robot import Robot as IsaacRobot + from omni.isaac.core.scenes import Scene + from omni.isaac.core.utils.nucleus import get_assets_root_path + from omni.isaac.core.utils.stage import add_reference_to_stage + + from tao_yuan.core.config.robot import RobotUserConfig as Config + from tao_yuan.core.robot.robot import BaseRobot + from tao_yuan.core.robot.robot_model import RobotModel + from tao_yuan.core.util import log + + + + class MyCobot280(IsaacRobot): + def __init__(self, + prim_path: str, + usd_path: str, + name: str, + position: np.ndarray = None, + orientation: np.ndarray = None, + scale: np.ndarray = None): + add_reference_to_stage( + prim_path=prim_path, usd_path=os.path.abspath(usd_path)) + super().__init__( + prim_path=prim_path, + name=name, + position=position, + orientation=orientation, + scale=scale) + self._end_effector_prim_path = prim_path + '/joint6_flange' + self._end_effector = RigidPrim( + prim_path=self._end_effector_prim_path, + name=name + '_end_effector', + ) + + @property + def end_effector_prim_path(self): + return self._end_effector_prim_path + + @property + def end_effector(self): + return self._end_effector + ``` + +2. Then we need to wrap this Robot with `tao_yuan.core.robot.BaseRobot` + + ```python + @BaseRobot.regester('MyCobot280PiRobot') + class MyCobot280PiRobot(BaseRobot): + def __init__(self, config: Config, robot_model: RobotModel, scene: Scene): + super().__init__(config, robot_model, scene) + self._sensor_config = robot_model.sensors + self._start_position = np.array( + config.position) if config.position is not None else None + self._start_orientation = np.array( + config.orientation) if config.orientation is not None else None + + log.debug(f'mycobot_280_pi {config.name}: position : ' + + str(self._start_position)) + log.debug(f'mycobot_280_pi {config.name}: orientation : ' + + str(self._start_orientation)) + + usd_path = robot_model.usd_path + if usd_path.startswith('/Isaac'): + usd_path = get_assets_root_path() + usd_path + print(usd_path) + + log.debug(f'mycobot_280_pi {config.name}: usd_path : ' + + str(usd_path)) + log.debug(f'mycobot_280_pi {config.name}: config.prim_path : ' + + str(config.prim_path)) + self.isaac_robot = MyCobot280( + prim_path=config.prim_path, + name=config.name, + position=self._start_position, + orientation=self._start_orientation, + usd_path=usd_path, + ) + + self._robot_scale = np.array([1.0, 1.0, 1.0]) + if config.scale is not None: + self._robot_scale = np.array(config.scale) + self.isaac_robot.set_local_scale(self._robot_scale) + + self._robot_ik_base = RigidPrim( + prim_path=config.prim_path + '/joint1', + name=config.name + '_ik_base_link', + ) + + self._robot_base = RigidPrim( + prim_path=config.prim_path + '/g_base', + name=config.name + '_base_link') + + def get_robot_scale(self): + return self._robot_scale + + def get_robot_ik_base(self): + return self._robot_ik_base + + def get_world_pose(self): + return self._robot_base.get_world_pose() + + def apply_action(self, action: dict): + """ + Args: + action (dict): inputs for controllers. + """ + for controller_name, controller_action in action.items(): + if controller_name not in self.controllers: + log.warn(f'unknown controller {controller_name} in action') + continue + controller = self.controllers[controller_name] + control = controller.action_to_control(controller_action) + self.isaac_robot.apply_action(control) + + def get_obs(self): + """Add obs you need here.""" + position, orientation = self._robot_base.get_world_pose() + + # custom + obs = { + 'position': position, + 'orientation': orientation, + } + + eef_world_pose = self.isaac_robot.end_effector.get_world_pose() + obs['eef_position'] = eef_world_pose[0] + obs['eef_orientation'] = eef_world_pose[1] + + # common + obs.update(super().get_obs()) + return obs + ``` +### Add Controller + +In this example, we use a framework integrated controller `ty_extension.controllers.ik_controller` to solve IK for our robot. + +```Python +# yapf: disable +from typing import Dict, List, Tuple + +import numpy as np +from omni.isaac.core.articulations import Articulation +from omni.isaac.core.scenes import Scene +from omni.isaac.core.utils.numpy.rotations import rot_matrices_to_quats +from omni.isaac.core.utils.types import ArticulationAction +from omni.isaac.motion_generation import ArticulationKinematicsSolver, LulaKinematicsSolver + +from tao_yuan.core.robot.controller import BaseController +from tao_yuan.core.robot.robot import BaseRobot +from tao_yuan.core.robot.robot_model import ControllerModel + +# yapf: enable + + +@BaseController.register('InverseKinematicsController') +class InverseKinematicsController(BaseController): + + def __init__(self, config: ControllerModel, robot: BaseRobot, scene: Scene): + super().__init__(config=config, robot=robot, scene=scene) + self._kinematics_solver = KinematicsSolver( + robot_articulation=robot.isaac_robot, + robot_description_path=config.robot_description_path, + robot_urdf_path=config.robot_urdf_path, + end_effector_frame_name=config.end_effector_frame_name, + ) + self.joint_subset = self._kinematics_solver.get_joints_subset() + if config.reference: + assert config.reference in ['world', 'robot', 'arm_base'], \ + f'unknown ik controller reference {config.reference}' + self._reference = config.reference + else: + self._reference = 'world' + + self.success = False + self.last_action = None + self.threshold = 0.01 if config.threshold is None else config.threshold + + self._ik_base = robot.get_robot_ik_base() + self._robot_scale = robot.get_robot_scale() + if self._reference == 'robot': + # The local pose of ik base is assumed not to change during simulation for ik controlled parts. + # However, the world pose won't change even its base link has moved for some robots like ridgeback franka, + # so the ik base pose returned by get_local_pose may change during simulation, which is unexpected. + # So the initial local pose of ik base is saved at first and used during the whole simulation. + self._ik_base_local_pose = self._ik_base.get_local_pose() + + def get_ik_base_world_pose(self) -> Tuple[np.ndarray, np.ndarray]: + if self._reference == 'robot': + ik_base_pose = self._ik_base_local_pose + elif self._reference == 'arm_base': + # Robot base is always at the origin. + ik_base_pose = (np.array([0, 0, 0]), np.array([1, 0, 0, 0])) + else: + ik_base_pose = self._ik_base.get_world_pose() + return ik_base_pose + + def forward(self, eef_target_position: np.ndarray, + eef_target_orientation: np.ndarray) -> Tuple[ArticulationAction, bool]: + self.last_action = [eef_target_position, eef_target_orientation] + + if eef_target_position is None: + # Keep joint positions to lock pose. + subset = self._kinematics_solver.get_joints_subset() + return subset.make_articulation_action(joint_positions=subset.get_joint_positions(), + joint_velocities=subset.get_joint_velocities()), True + + ik_base_pose = self.get_ik_base_world_pose() + self._kinematics_solver.set_robot_base_pose(robot_position=ik_base_pose[0] / self._robot_scale, + robot_orientation=ik_base_pose[1]) + return self._kinematics_solver.compute_inverse_kinematics( + target_position=eef_target_position / self._robot_scale, + target_orientation=eef_target_orientation, + ) + + def action_to_control(self, action: List | np.ndarray): + """ + Args: + action (np.ndarray): n-element 1d array containing: + 0. eef_target_position + 1. eef_target_orientation + """ + assert len(action) == 2, 'action must contain 2 elements' + assert self._kinematics_solver is not None, 'kinematics solver is not initialized' + + eef_target_position = None if action[0] is None else np.array(action[0]) + eef_target_orientation = None if action[1] is None else np.array(action[1]) + + result, self.success = self.forward( + eef_target_position=eef_target_position, + eef_target_orientation=eef_target_orientation, + ) + return result + + def get_obs(self) -> Dict[str, np.ndarray]: + """Compute the pose of the robot end effector using the simulated robot's current joint positions + + Returns: + Dict[str, np.ndarray]: + - eef_position: eef position + - eef_orientation: eef orientation quats + - success: if solver converged successfully + - finished: applied action has been finished + """ + ik_base_pose = self.get_ik_base_world_pose() + self._kinematics_solver.set_robot_base_pose(robot_position=ik_base_pose[0] / self._robot_scale, + robot_orientation=ik_base_pose[1]) + pos, ori = self._kinematics_solver.compute_end_effector_pose() + + finished = False + if self.last_action is not None: + if self.last_action[0] is not None: + dist_from_goal = np.linalg.norm(pos - self.last_action[0]) + if dist_from_goal < self.threshold * self.robot.get_robot_scale()[0]: + finished = True + + return { + 'eef_position': pos * self._robot_scale, + 'eef_orientation': rot_matrices_to_quats(ori), + 'success': self.success, + 'finished': finished, + } + + +class KinematicsSolver(ArticulationKinematicsSolver): + """Kinematics Solver for robot. This class loads a LulaKinematicsSovler object + + Args: + robot_description_path (str): path to a robot description yaml file \ + describing the cspace of the robot and other relevant parameters + robot_urdf_path (str): path to a URDF file describing the robot + end_effector_frame_name (str): The name of the end effector. + """ + + def __init__(self, robot_articulation: Articulation, robot_description_path: str, robot_urdf_path: str, + end_effector_frame_name: str): + self._kinematics = LulaKinematicsSolver(robot_description_path, robot_urdf_path) + + ArticulationKinematicsSolver.__init__(self, robot_articulation, self._kinematics, end_effector_frame_name) + + if hasattr(self._kinematics, 'set_max_iterations'): + self._kinematics.set_max_iterations(150) + else: + self._kinematics.ccd_max_iterations = 150 + + return + + def set_robot_base_pose(self, robot_position: np.array, robot_orientation: np.array): + return self._kinematics.set_robot_base_pose(robot_position=robot_position, robot_orientation=robot_orientation) + +``` + +### Add config + +After add robot and add controller, we need register our robot to `ty_extension/robots/robot_models.yaml` as follows + +```yaml +- type: "MyCobot280PiRobot" + usd_path: "TY-1/assets/robots/mycobot_280pi_with_camera_flange/mycobot_280pi_with_camera_flange.usd" + controllers: + - name: "ik_controller" + type: "InverseKinematicsController" + robot_description_path: "TY-1/assets/robots/mycobot_280pi_with_camera_flange/robot_descriptor.yaml" + robot_urdf_path: "TY-1/assets/robots/mycobot_280pi_with_camera_flange/mycobot_280pi_with_camera_flange.urdf" + end_effector_frame_name: "joint6_flange" + threshold: 0.01 +``` +this file combine robot with controllers and sensors, and setup some param that app users have not deed to know. + +## Test + +to test a new robot in our framework. we need to create two files: + +1. run file: define running process. +2. config file: define scene\robots\objects and any well load to isaac sim dynamically. + +### `run file` + +```python +from tao_yuan.core.config import SimulatorConfig +from tao_yuan.core.env import BaseEnv + +file_path = './TY-1/demo/configs/follow_target_mycobot.yaml' +sim_config = SimulatorConfig(file_path) + +# env = BaseEnv(sim_config) +env = BaseEnv(sim_config, headless=False) + +while env.simulation_app.is_running(): + actions = [] + for task in env.config.tasks: + target_cube_pose = env.runner.get_obj(task.objects[0].name).get_world_pose() + action = { + 'mycobot': { + 'ik_controller': target_cube_pose, + }, + } + actions.append(action) + observations = env.step(actions) + +env.simulation_app.close() +``` + +### `config` + +```yaml +simulator: + physics_dt: 0.01666 # 1 / 60 + rendering_dt: 0.01666 # 1 / 60 + +env: + bg_type: null + +render: + render: true + +tasks: +- type: "SingleInferenceTask" + name: "mycobot280pi_follow_cube" + env_num: 1 + offset_size: 1.0 + robots: + - name: mycobot + prim_path: "/mycobot" + type: "MyCobot280PiRobot" + position: [.0, .0, .0] + scale: [1, 1, 1] + controller_params: + - name: "ik_controller" + - name: "rmp_controller" + + objects: + - name: target_cube + type: VisualCube + prim_path: /target_cube + position: [0.08, 0.1, 0.3] + scale: [0.05015, 0.05015, 0.05015] + color: [0, 0, 1.0] +``` + +### Run Test + +Run test file at root path of isaac sim like: + +```shell +python ./TY-1/demo/follow_target_mycobot280.py +``` + +we get a follow target mycobot demo + +![img.png](../_static/image/follow_target_mycobot_demo.png) diff --git a/html/_sources/get_started/installation.md.txt b/html/_sources/get_started/installation.md.txt new file mode 100644 index 0000000..2d3d33a --- /dev/null +++ b/html/_sources/get_started/installation.md.txt @@ -0,0 +1,135 @@ +# Installation + +## Prerequisites + +- OS: Ubuntu 20.04+ +- RAM: 32GB+ +- GPU: NVIDIA RTX 2070+ +- NVIDIA Driver: 525.85+ + +## Install from source (Linux) + +Make sure you have [Isaac Sim 2023.1.1](https://docs.omniverse.nvidia.com/isaacsim/latest/installation/install_workstation.html) installed. + +[Conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html) is required to install from source. + +1. Navigate to Isaac Sim root path (default path in Ubuntu is `$HOME/.local/share/ov/pkg/isaac_sim-2023.1.1`) and clone the repository. + + ```bash + $ cd PATH/TO/ISAAC_SIM/ROOT + $ git clone git@github.com:OpenRobotLab/GRUtopia.git + ``` + +1. Download [dataset](https://openxlab.org.cn/datasets/OpenRobotLab/GRScenes/cli/main) and save it to the `assets` directory under GRUtopia root path. + + The file structure should be like: + + ``` + GRUtopia + ├── assets + │   ├── objects + │   ├── policy + │   ├── robots + │   └── scenes + ├── demo + │   ├── configs + │   ├── h1_city.py + │   ├── h1_locomotion.py + │   └── h1_npc.py + ... + ``` + +1. Navigate to GRUtopia root path and configure the conda environment. + + ```bash + $ cd PATH/TO/GRUTOPIA/ROOT + + # Conda environment will be created and configured automatically with prompt. + $ ./setup_conda.sh + + $ cd .. && conda activate grutopia # or your conda env name + ``` + +1. Verify the Installation. + + Run at the root path of Isaac Sim: + + ```bash + $ cd PATH/TO/ISAAC_SIM/ROOT + $ python ./GRUtopia/demo/h1_locomotion.py # start simulation + ``` + +## Install with Docker (Linux) + +Make sure you have [Docker](https://docs.docker.com/get-docker/) installed. + +1. Clone the GRUtopia repository to any desired location. + + ```bash + $ git clone git@github.com:OpenRobotLab/GRUtopia.git + ``` + +1. Download [dataset](https://openxlab.org.cn/datasets/OpenRobotLab/GRScenes/cli/main) and save it to the `assets` directory under GRUtopia root path. + + The file structure should be like: + + ``` + GRUtopia + ├── assets + │   ├── objects + │   ├── policy + │   ├── robots + │   └── scenes + ├── demo + │   ├── configs + │   ├── h1_city.py + │   ├── h1_locomotion.py + │   └── h1_npc.py + ... + ``` + +1. Pull the Isaac Sim image (`docker login` is required, please refer to [NGC Documents](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/isaac-sim)). + + ```bash + $ docker pull nvcr.io/nvidia/isaac-sim:2023.1.1 + ``` +1. Build docker image. + + ```bash + $ cd PATH/TO/GRUTOPIA/ROOT + + $ docker build -t grutopia:0.0.1 . + ``` + +1. Start docker container. + + ```bash + $ cd PATH/TO/GRUTOPIA/ROOT + + $ export CACHE_ROOT=$HOME/docker # set cache root path + $ export WEBUI_HOST=127.0.0.1 # set webui listen address, default to 127.0.0.1 + + $ docker run --name grutopia -it --rm --gpus all --network host \ + -e "ACCEPT_EULA=Y" \ + -e "PRIVACY_CONSENT=Y" \ + -e "WEBUI_HOST=${WEBUI_HOST}" \ + -v ${PWD}:/isaac-sim/GRUtopia \ + -v ${CACHE_ROOT}/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/ov:/root/.cache/ov:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/pip:/root/.cache/pip:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \ + -v ${CACHE_ROOT}/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \ + -v ${CACHE_ROOT}/isaac-sim/data:/root/.local/share/ov/data:rw \ + -v ${CACHE_ROOT}/isaac-sim/documents:/root/Documents:rw \ + grutopia:0.0.1 + ``` + +1. Verify the Installation. + + Run inside container: + + ```bash + # run inside container + $ python ./GRUtopia/demo/h1_locomotion.py # start simulation + ``` diff --git a/html/_sources/get_started/wander-with-keyboard.md.txt b/html/_sources/get_started/wander-with-keyboard.md.txt new file mode 100644 index 0000000..06e41fc --- /dev/null +++ b/html/_sources/get_started/wander-with-keyboard.md.txt @@ -0,0 +1,49 @@ +# Wander with keyboard + +> This tutorial guides you to wander with keyboard as h1 robot. + +## Wander in house + +```bash +# decompress the house scene +$ cd PATH/TO/GRUTOPIA/ROOT +$ cd assets/scenes/ +$ unzip demo_house.zip +# start simulation +$ cd ../../.. +$ python ./GRUtopia/demo/h1_house.py +``` + +You can control the h1 robot with keyboard command: + +- W: Move Forward +- S: Move Backward +- A: Move Left +- D: Move Right +- Q: Turn Left +- E: Turn Right + +You can change camera view to perspective/first-person/third-person camera. + +## Wander in city + +```bash +# decompress the city scene +$ cd PATH/TO/GRUTOPIA/ROOT +$ cd assets/scenes/ +$ unzip demo_city.zip +# start simulation +$ cd ../../.. +$ python ./GRUtopia/demo/h1_city.py +``` + +You can control the h1 robot with keyboard command: + +- W: Move Forward +- S: Move Backward +- A: Move Left +- D: Move Right +- Q: Turn Left +- E: Turn Right + +You can change camera view to perspective/first-person/third-person camera. diff --git a/html/_sources/get_started/webui.md.txt b/html/_sources/get_started/webui.md.txt new file mode 100644 index 0000000..eb1b2d3 --- /dev/null +++ b/html/_sources/get_started/webui.md.txt @@ -0,0 +1,65 @@ +# Interact with NPC through WebUI + +> This tutorial guides you to interact with NPC through WebUI. + +> Currently WebUI is only supported with docker. Make sure you have built the docker image following the instruction of [installation with docker](./installation.md#install-with-docker-linux). + +## Start WebUI process + +Start docker container and start WebUI process within the container. + +```bash +cd PATH/TO/GRUTOPIA/ROOT + +$ export CACHE_ROOT=$HOME/docker # set cache root path +$ export WEBUI_HOST=127.0.0.1 # set WebUI listen address, default to 127.0.0.1 + +$ docker run --name grutopia -it --rm --gpus all --network host \ + -e "ACCEPT_EULA=Y" \ + -e "PRIVACY_CONSENT=Y" \ + -e "WEBUI_HOST=${WEBUI_HOST}" \ + -v ${PWD}:/isaac-sim/GRUtopia \ + -v ${CACHE_ROOT}/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/ov:/root/.cache/ov:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/pip:/root/.cache/pip:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \ + -v ${CACHE_ROOT}/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \ + -v ${CACHE_ROOT}/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \ + -v ${CACHE_ROOT}/isaac-sim/data:/root/.local/share/ov/data:rw \ + -v ${CACHE_ROOT}/isaac-sim/documents:/root/Documents:rw \ + grutopia:0.0.1 + +# run inside container +$ ./webui_start.sh # start WebUI process +``` + +Now you can access WebUI from `http://${WEBUI_HOST}:8080`. + +## Start simulation + +GPT-4o is used as npc by default so an openai api key is required. + +Run inside container: + +```bash +# run inside container +$ sed -i 's/YOUR_OPENAI_API_KEY/{YOUR_OPENAI_API_KEY}/g' GRUtopia/demo/config/h1_npc.yaml # set openai api key +$ python GRUtopia/demo/h1_npc.py # start simulation +``` + +Now the simulation is available through WebRTC in the WebUI page. + +You can control the h1 robot with keyboard command: + +- W: Move Forward +- S: Move Backward +- A: Move Left +- D: Move Right +- Q: Turn Left +- E: Turn Right + +And you can talk to npc as agent in the chatbox. The left side of the screen will display Isaac Sim's window, where you can switch to the robot's camera view. The right side features the chat window, where you can interact with the NPC. Ensure your questions are related to the scene, the robot's view, or its position, as unrelated queries might not yield useful responses. Replies will appear in the chat window within a few seconds. During this time, you can continue moving the robot or ask additional questions, which will be answered sequentially. + +Note that the NPC might not always provide accurate answers due to design limitations. + +Occasionally, unexpected responses from the LLM or code errors may cause issues. Check the error logs or contact us for support in resolving these problems. diff --git a/html/_sources/index.rst.txt b/html/_sources/index.rst.txt new file mode 100644 index 0000000..e80f576 --- /dev/null +++ b/html/_sources/index.rst.txt @@ -0,0 +1,63 @@ +.. GRUtopia documentation master file, created by + sphinx-quickstart on Fri Mar 8 17:31:29 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to GRUtopia's documentation! +================================ + +.. toctree:: + :maxdepth: 1 + :caption: Introduction + + + introduction/introduction.md + +.. toctree:: + :maxdepth: 1 + :caption: Get Started + + + get_started/installation.md + get_started/wander-with-keyboard.md + get_started/webui.md + +.. toctree:: + :maxdepth: 1 + :caption: Tutorials + + + tutorials/how-to-use-npc.md + tutorials/how-to-use-robot.md + tutorials/how-to-use-controller.md + tutorials/how-to-use-sensor.md + +.. toctree:: + :maxdepth: 1 + :caption: Advanced Tutorials + + + advanced_tutorials/how-to-add-robot.md + advanced_tutorials/how-to-add-controller.md + advanced_tutorials/how-to-add-sensor.md + +.. toctree:: + :maxdepth: 1 + :caption: API Reference + + + grutopia.core.env + grutopia.core.datahub + grutopia.core.register + grutopia.core.robot + grutopia.core.scene + grutopia.core.task + grutopia.core.util + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/html/_sources/introduction/introduction.md.txt b/html/_sources/introduction/introduction.md.txt new file mode 100644 index 0000000..74beec9 --- /dev/null +++ b/html/_sources/introduction/introduction.md.txt @@ -0,0 +1,13 @@ +# Introduction + +Current embodied intelligence research urgently needs to overcome the problem of disconnection between high-level perceptual planning and low-level motion control. By constructing a highly realistic simulation environment, not only can it enhance the robot's perception and behavior planning capabilities but also promote the development of multi-module collaborative control strategies, ultimately steadily advancing towards the goal of general-purpose embodied robots. (Research Needs) + +High-level studies are typically conducted on static datasets or simulation platforms, which often cannot provide environments with both visual realism and physical realism at the same time, limiting the transferability of research results to real-world application scenarios. At the same time, the development of large-model technology has opened up new paths for improving the perception and behavior planning abilities of robots, making the realization of the goal of universal robots no longer distant. (Industry Status) + +To address these challenges, the OpenRobotLab team of Shanghai AI Lab proposes the GRUtopia Embodied Intelligence Simulation Platform. The platform features: + +1. A large-scale scene dataset covering various application scenarios, capable of providing rich and realistic visual and physical environments for embodied research; +2. An API library and extensive toolkit containing mainstream robotic control algorithms, enabling plug-and-play functionality with just one line of code to achieve a realistic control process, reproducing all kinds of actual situations likely encountered during planning processes; +3. The toolkit also provides functions like algorithm migration and strategy training. + +Additionally, there is a task generation system for embodied tasks driven by large models and an NPC interaction system, marking the first time automated embodied task generation and multimodal interactive NPCs have been achieved. This offers infinite training tasks for developing generalized agents and also serves as a foundation for studying embodied behavior interpretability and human-machine interactions. (Achievement Definition) diff --git a/html/_sources/tutorials/how-to-run-rl.md.txt b/html/_sources/tutorials/how-to-run-rl.md.txt new file mode 100644 index 0000000..6204e96 --- /dev/null +++ b/html/_sources/tutorials/how-to-run-rl.md.txt @@ -0,0 +1,3 @@ +# how to run rl on TY-1 + +> WIP diff --git a/html/_sources/tutorials/how-to-use-controller.md.txt b/html/_sources/tutorials/how-to-use-controller.md.txt new file mode 100644 index 0000000..1cd01d5 --- /dev/null +++ b/html/_sources/tutorials/how-to-use-controller.md.txt @@ -0,0 +1,36 @@ +# How to use controller + +> This tutorial will show you how to use an existed controller for a robot + +## What is `Controller` + +Controllers usually control joints of robot. Also, They're the entries of robot actions. To make robot move, rab, or +even speak, chat online, we use controllers. + +## Which controllers are our robots supported + +Check `grutopia_extension/robots/robot_models.yaml`, + +![img.png](../_static/image/robot_model_controller.png) + +This is all controller robot `HumanoidRobot` can use. + +## How to use a controller + +Use them in isaac simulation_app's step loops. + +for example: + +```Python +while env.simulation_app.is_running(): + actions = [{ + h1: { + "move_to_point": np.array([.0, .0, .0]), + }, + }] + obs = env.step(actions=env_actions) + ... +env.simulation_app.close() +``` + +for more usage, please read source code~ diff --git a/html/_sources/tutorials/how-to-use-npc.md.txt b/html/_sources/tutorials/how-to-use-npc.md.txt new file mode 100644 index 0000000..8fdd6c2 --- /dev/null +++ b/html/_sources/tutorials/how-to-use-npc.md.txt @@ -0,0 +1,65 @@ +# Customize NPC with your algorithm + +## Set configuration in task yaml file. + +If you're using openai api, you can refer to [Web Demo](../get_started/webui.md). + +If you are using other llm api service, you can set your api endpoint. + +```yaml +npc: +- name: "npc_name" + api_base_url: "api_endpoint" + # other configurations +``` + +You can change the schema of NPC configuration in `grutopia.core.config.npc`. + +## Customize your prompt and implement LLM caller + +Our system message and in-context example are defined in `grutopia.npc.prompt`. And the LLM inference process are in `grutopia.npc.llm_caller`. You can customize them according to your own needs and algorithms. + + +## Reimplement `feed` method in `grutopia.npc.base` + +Reimplement the `feed` method of `NPC` class in `grutopia.npc.base`. + +In `feed` function, observation in `dict` type is processed and fed into the llm caller, new responses from llm caller are sent back to the robot. + +Base `feed` method FYI. + +```python +def feed(self, obs: dict): + """feed npc with observation. + + Args: + obs (dict): full observation of the world, with hierarchy of + obs + task_name: + robot_name: + position + orientation + controller_0 + controller_1 + ... + sensor_0 + sensor_1 + ... + """ + for task_obs in obs.values(): + for robot_obs in task_obs.values(): + chat = robot_obs.get('web_chat', None) + if chat is not None and chat['chat_control']: + # process observation + position = robot_obs.get('position', None) + orientation = robot_obs.get('orientation', None) + bbox_label_data_from_camera = robot_obs['camera']['frame']['bounding_box_2d_tight'] + bbox = bbox_label_data['data'] + idToLabels = bbox_label_data['info']['idToLabels'] + # feed processed observation into llm caller + # pick response from llm caller and send back to robot + # details omitted + +``` + +Then you can launch the web demo and chat with your NPC. diff --git a/html/_sources/tutorials/how-to-use-robot.md.txt b/html/_sources/tutorials/how-to-use-robot.md.txt new file mode 100644 index 0000000..b4c1594 --- /dev/null +++ b/html/_sources/tutorials/how-to-use-robot.md.txt @@ -0,0 +1,67 @@ +# How to use robot + +> This tutorial will show you how to use an existing robot. + +## All available robots + +See `grutopia_extension/robots/robot_models.yaml`. + +![img.png](../_static/image/robot_model_yml.png) + +## Usage + +### Add robot to config + +Add a robot to config: + +```yaml +simulator: + physics_dt: 1/240 + rendering_dt: 1/240 + +env: + bg_type: null + +render: + render: true + +tasks: +- type: "SingleInferenceTask" + name: "h1_locomotion" + env_num: 1 + offset_size: 1.0 + robots: # Add robots here + - name: h1 + prim_path: "/World/h1" + type: "HumanoidRobot" + position: [.0, .0, 1.05] + scale: [1, 1, 1] +``` + +Done. + +### Run demo + +try this demo: + +```python +from grutopia.core.config import SimulatorConfig +from grutopia.core.env import BaseEnv + +file_path = f'{path/to/your/config}' +sim_config = SimulatorConfig(file_path) + +env = BaseEnv(sim_config, headless=False) +import numpy as np + +while env.simulation_app.is_running(): + + obs = env.step(actions=env_actions) +env.simulation_app.close() +``` + +It runs, but the robot doesn't move. + +We need to add controller for this robot to make it move. + +See [how to use controller](./how-to-use-controller.md) diff --git a/html/_sources/tutorials/how-to-use-sensor.md.txt b/html/_sources/tutorials/how-to-use-sensor.md.txt new file mode 100644 index 0000000..48c98d8 --- /dev/null +++ b/html/_sources/tutorials/how-to-use-sensor.md.txt @@ -0,0 +1,38 @@ +# How to use sensor + +> This tutorial will show you how to use an existed sensors of robot + + + +## Which sensors are our robots supported + +In `grutopia/core/robot/robot_model.py`, We know `Sensors` is under `RobotModel`. + +![img.png](../_static/image/robot_model_class.png) + +Check `grutopia_extension/robots/robot_models.yaml`. We find + +```yaml +robots: + - type: "HumanoidRobot" + ... + sensors: + - name: "camera" # <- this is sensor name + prim_path: "relative/prim/path/to/camera" + type: "Camera" +``` + +## How to use a sensor + +When we run `demo/h1_locomotion.py`, observation from sensors can be got from `obs` (obs = env.step(actions=env_actions)) + +Use them in isaac simulation_app's step loops. + +```Python +while env.simulation_app.is_running(): + ... + obs = env.step(actions) + photo = obs['robot_name_in_config']['camera']['rgba'] # here get `camera` data + ... +env.simulation_app.close() +``` diff --git a/html/_sources/tutorials/how-to-use-task.md.txt b/html/_sources/tutorials/how-to-use-task.md.txt new file mode 100644 index 0000000..4c59c14 --- /dev/null +++ b/html/_sources/tutorials/how-to-use-task.md.txt @@ -0,0 +1,7 @@ +# how to use task + +> This tutorial will show you how to use task + +## What is `Task` + +WIP diff --git a/html/_sources/tutorials/how-to-use-web-ui.md.txt b/html/_sources/tutorials/how-to-use-web-ui.md.txt new file mode 100644 index 0000000..262d111 --- /dev/null +++ b/html/_sources/tutorials/how-to-use-web-ui.md.txt @@ -0,0 +1,67 @@ +# how to use web ui + +> This tutorial will show you how to use webui + +> We only suggest use webui with docker + +## Build docker image + +```bash +cd ${path_to_source_code} + +docker build -t tao_yuan:0.0.1 . +``` + +## Start a WebUI demo + +Start docker container. + +```bash +cd ${path_to_source_code} + +docker run --name isaac-sim --entrypoint bash -it --rm --gpus all \ + -e "ACCEPT_EULA=Y" \ + -e "PRIVACY_CONSENT=Y" \ + -v ./TY-1:/isaac-sim/TY-1 \ + -v ./TY-1/test/.test_scripts:/isaac-sim/run_scripts \ + -v ~/docker/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \ + -v ~/docker/isaac-sim/cache/ov:/root/.cache/ov:rw \ + -v ~/docker/isaac-sim/cache/pip:/root/.cache/pip:rw \ + -v ~/docker/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \ + -v ~/docker/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \ + -v ~/docker/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \ + -v ~/docker/isaac-sim/data:/root/.local/share/ov/data:rw \ + -v ~/docker/isaac-sim/documents:/root/Documents:rw \ + tao_yuan:0.0.1 +``` + +Then start demo + +```bash +# in container +./docker_start.sh ./TY-1/ +``` + +## How to write a WebUI Case + +### 1. Enable WebRTC + +```Python +import numpy as np + +from tao_yuan.core.config import SimulatorConfig +from tao_yuan.core.env import BaseEnv + +sim_config = SimulatorConfig('/path/to/your/config.yml') # noqa +env = BaseEnv(sim_config, headless=True, webrtc=True) # Set `webrtc` +``` + +### 2. Run loop + +```Python +while env.simulation_app.is_running(): + # Before step <> + obs = env.step(actions=[]) + # After step <> +env.simulation_app.close() +``` diff --git a/html/_sources/tutorials/infra.md.txt b/html/_sources/tutorials/infra.md.txt new file mode 100644 index 0000000..dce3af1 --- /dev/null +++ b/html/_sources/tutorials/infra.md.txt @@ -0,0 +1,3 @@ +# infrastructure + +> In this tutorial we will introduce you the infrastructure of this Project \ No newline at end of file diff --git a/html/_static/basic.css b/html/_static/basic.css new file mode 100644 index 0000000..7577acb --- /dev/null +++ b/html/_static/basic.css @@ -0,0 +1,903 @@ +/* + * 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: 230px; + 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/html/_static/check-solid.svg b/html/_static/check-solid.svg new file mode 100644 index 0000000..92fad4b --- /dev/null +++ b/html/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/html/_static/clipboard.min.js b/html/_static/clipboard.min.js new file mode 100644 index 0000000..54b3c46 --- /dev/null +++ b/html/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * 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/html/_static/copybutton.css b/html/_static/copybutton.css new file mode 100644 index 0000000..f1916ec --- /dev/null +++ b/html/_static/copybutton.css @@ -0,0 +1,94 @@ +/* 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/html/_static/copybutton.js b/html/_static/copybutton.js new file mode 100644 index 0000000..19f1fbd --- /dev/null +++ b/html/_static/copybutton.js @@ -0,0 +1,248 @@ +// 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, '>>> |\\.\\.\\. ', true, 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/html/_static/copybutton_funcs.js b/html/_static/copybutton_funcs.js new file mode 100644 index 0000000..dbe1aaa --- /dev/null +++ b/html/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +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/html/_static/css/readthedocs.css b/html/_static/css/readthedocs.css new file mode 100644 index 0000000..3649c86 --- /dev/null +++ b/html/_static/css/readthedocs.css @@ -0,0 +1,6 @@ +.header-logo { + background-image: url("../image/logo.png"); + background-size: 180px 40px; + height: 40px; + width: 180px; +} diff --git a/html/_static/css/theme.css b/html/_static/css/theme.css new file mode 100644 index 0000000..cdd377e --- /dev/null +++ b/html/_static/css/theme.css @@ -0,0 +1,12303 @@ +@charset "UTF-8"; +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #6c757d; + --gray-dark: #343a40; + --primary: #007bff; + --secondary: #6c757d; + --success: #28a745; + --info: #17a2b8; + --warning: #ffc107; + --danger: #dc3545; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +*, +*::before, +*::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +@-ms-viewport { + width: device-width; +} +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +.highlight > pre { + line-height: 1.325rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} +a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; +} + +label { + display: inline-block; + margin-bottom: .5rem; +} + +button { + border-radius: 0; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.2; + color: inherit; +} + +h1, .h1 { + font-size: 2.5rem; +} + +h2, .h2 { + font-size: 2rem; +} + +h3, .h3 { + font-size: 1.75rem; +} + +h4, .h4 { + font-size: 1.5rem; +} + +h5, .h5 { + font-size: 1.25rem; +} + +h6, .h6 { + font-size: 1rem; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; +} + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +small, +.small { + font-size: 80%; + font-weight: 400; +} + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} + +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} + +.blockquote-footer { + display: block; + font-size: 80%; + color: #6c757d; +} +.blockquote-footer::before { + content: "\2014 \00A0"; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 90%; + color: #6c757d; +} + +code, +kbd, +pre, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +code { + font-size: 87.5%; + color: #e83e8c; + word-break: break-word; +} +a > code { + color: inherit; +} + +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; +} + +pre { + display: block; + font-size: 87.5%; + color: #212529; +} +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; +} + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; +} + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; +} + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; +} + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; +} + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; +} + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; +} + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; +} + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; +} + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; +} + +.order-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; +} + +.order-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; +} + +.order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.3333333333%; +} + +.offset-2 { + margin-left: 16.6666666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.3333333333%; +} + +.offset-5 { + margin-left: 41.6666666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.3333333333%; +} + +.offset-8 { + margin-left: 66.6666666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.3333333333%; +} + +.offset-11 { + margin-left: 91.6666666667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-sm-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-sm-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-sm-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-sm-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-sm-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-sm-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-sm-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-sm-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-sm-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-sm-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-sm-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-sm-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-sm-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-sm-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-sm-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-sm-0 { + margin-left: 0; + } + + .offset-sm-1 { + margin-left: 8.3333333333%; + } + + .offset-sm-2 { + margin-left: 16.6666666667%; + } + + .offset-sm-3 { + margin-left: 25%; + } + + .offset-sm-4 { + margin-left: 33.3333333333%; + } + + .offset-sm-5 { + margin-left: 41.6666666667%; + } + + .offset-sm-6 { + margin-left: 50%; + } + + .offset-sm-7 { + margin-left: 58.3333333333%; + } + + .offset-sm-8 { + margin-left: 66.6666666667%; + } + + .offset-sm-9 { + margin-left: 75%; + } + + .offset-sm-10 { + margin-left: 83.3333333333%; + } + + .offset-sm-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-md-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-md-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-md-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-md-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-md-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-md-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-md-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-md-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-md-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-md-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-md-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-md-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-md-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-md-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-md-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-md-0 { + margin-left: 0; + } + + .offset-md-1 { + margin-left: 8.3333333333%; + } + + .offset-md-2 { + margin-left: 16.6666666667%; + } + + .offset-md-3 { + margin-left: 25%; + } + + .offset-md-4 { + margin-left: 33.3333333333%; + } + + .offset-md-5 { + margin-left: 41.6666666667%; + } + + .offset-md-6 { + margin-left: 50%; + } + + .offset-md-7 { + margin-left: 58.3333333333%; + } + + .offset-md-8 { + margin-left: 66.6666666667%; + } + + .offset-md-9 { + margin-left: 75%; + } + + .offset-md-10 { + margin-left: 83.3333333333%; + } + + .offset-md-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-lg-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-lg-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-lg-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-lg-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-lg-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-lg-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-lg-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-lg-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-lg-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-lg-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-lg-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-lg-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-lg-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-lg-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-lg-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-lg-0 { + margin-left: 0; + } + + .offset-lg-1 { + margin-left: 8.3333333333%; + } + + .offset-lg-2 { + margin-left: 16.6666666667%; + } + + .offset-lg-3 { + margin-left: 25%; + } + + .offset-lg-4 { + margin-left: 33.3333333333%; + } + + .offset-lg-5 { + margin-left: 41.6666666667%; + } + + .offset-lg-6 { + margin-left: 50%; + } + + .offset-lg-7 { + margin-left: 58.3333333333%; + } + + .offset-lg-8 { + margin-left: 66.6666666667%; + } + + .offset-lg-9 { + margin-left: 75%; + } + + .offset-lg-10 { + margin-left: 83.3333333333%; + } + + .offset-lg-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + + .order-xl-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + + .order-xl-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + + .order-xl-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + + .order-xl-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + + .order-xl-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + + .order-xl-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + + .order-xl-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + + .order-xl-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + + .order-xl-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + + .order-xl-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + + .order-xl-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + + .order-xl-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + + .order-xl-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + + .order-xl-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + + .order-xl-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + + .offset-xl-0 { + margin-left: 0; + } + + .offset-xl-1 { + margin-left: 8.3333333333%; + } + + .offset-xl-2 { + margin-left: 16.6666666667%; + } + + .offset-xl-3 { + margin-left: 25%; + } + + .offset-xl-4 { + margin-left: 33.3333333333%; + } + + .offset-xl-5 { + margin-left: 41.6666666667%; + } + + .offset-xl-6 { + margin-left: 50%; + } + + .offset-xl-7 { + margin-left: 58.3333333333%; + } + + .offset-xl-8 { + margin-left: 66.6666666667%; + } + + .offset-xl-9 { + margin-left: 75%; + } + + .offset-xl-10 { + margin-left: 83.3333333333%; + } + + .offset-xl-11 { + margin-left: 91.6666666667%; + } +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; + background-color: transparent; +} +.table th, +.table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; +} +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; +} +.table tbody + tbody { + border-top: 2px solid #dee2e6; +} +.table .table { + background-color: #fff; +} + +.table-sm th, +.table-sm td { + padding: 0.3rem; +} + +.table-bordered { + border: 1px solid #dee2e6; +} +.table-bordered th, +.table-bordered td { + border: 1px solid #dee2e6; +} +.table-bordered thead th, +.table-bordered thead td { + border-bottom-width: 2px; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} + +.table-hover tbody tr:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #b8daff; +} + +.table-hover .table-primary:hover { + background-color: #9fcdff; +} +.table-hover .table-primary:hover > td, +.table-hover .table-primary:hover > th { + background-color: #9fcdff; +} + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #d6d8db; +} + +.table-hover .table-secondary:hover { + background-color: #c8cbcf; +} +.table-hover .table-secondary:hover > td, +.table-hover .table-secondary:hover > th { + background-color: #c8cbcf; +} + +.table-success, +.table-success > th, +.table-success > td { + background-color: #c3e6cb; +} + +.table-hover .table-success:hover { + background-color: #b1dfbb; +} +.table-hover .table-success:hover > td, +.table-hover .table-success:hover > th { + background-color: #b1dfbb; +} + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; +} + +.table-hover .table-info:hover { + background-color: #abdde5; +} +.table-hover .table-info:hover > td, +.table-hover .table-info:hover > th { + background-color: #abdde5; +} + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #ffeeba; +} + +.table-hover .table-warning:hover { + background-color: #ffe8a1; +} +.table-hover .table-warning:hover > td, +.table-hover .table-warning:hover > th { + background-color: #ffe8a1; +} + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f5c6cb; +} + +.table-hover .table-danger:hover { + background-color: #f1b0b7; +} +.table-hover .table-danger:hover > td, +.table-hover .table-danger:hover > th { + background-color: #f1b0b7; +} + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; +} + +.table-hover .table-light:hover { + background-color: #ececf6; +} +.table-hover .table-light:hover > td, +.table-hover .table-light:hover > th { + background-color: #ececf6; +} + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; +} + +.table-hover .table-dark:hover { + background-color: #b9bbbe; +} +.table-hover .table-dark:hover > td, +.table-hover .table-dark:hover > th { + background-color: #b9bbbe; +} + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} +.table-hover .table-active:hover > td, +.table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} + +.table .thead-dark th { + color: #fff; + background-color: #212529; + border-color: #32383e; +} +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.table-dark { + color: #fff; + background-color: #212529; +} +.table-dark th, +.table-dark td, +.table-dark thead th { + border-color: #32383e; +} +.table-dark.table-bordered { + border: 0; +} +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} +.table-dark.table-hover tbody tr:hover { + background-color: rgba(255, 255, 255, 0.075); +} + +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-sm > .table-bordered { + border: 0; + } +} +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-md > .table-bordered { + border: 0; + } +} +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-lg > .table-bordered { + border: 0; + } +} +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-xl > .table-bordered { + border: 0; + } +} +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; +} +.table-responsive > .table-bordered { + border: 0; +} + +.form-control { + display: block; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.form-control::-webkit-input-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control::-moz-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control::-ms-input-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control::placeholder { + color: #6c757d; + opacity: 1; +} +.form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} + +select.form-control:not([size]):not([multiple]) { + height: calc(2.25rem + 2px); +} +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.form-control-file, +.form-control-range { + display: block; + width: 100%; +} + +.col-form-label { + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; +} + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5; +} + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; +} + +.form-control-plaintext { + display: block; + width: 100%; + padding-top: 0.375rem; + padding-bottom: 0.375rem; + margin-bottom: 0; + line-height: 1.5; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; +} +.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control, +.input-group-sm > .input-group-prepend > .form-control-plaintext.input-group-text, +.input-group-sm > .input-group-append > .form-control-plaintext.input-group-text, +.input-group-sm > .input-group-prepend > .form-control-plaintext.btn, +.input-group-sm > .input-group-append > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control, +.input-group-lg > .input-group-prepend > .form-control-plaintext.input-group-text, +.input-group-lg > .input-group-append > .form-control-plaintext.input-group-text, +.input-group-lg > .input-group-prepend > .form-control-plaintext.btn, +.input-group-lg > .input-group-append > .form-control-plaintext.btn { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm, .input-group-sm > .form-control, +.input-group-sm > .input-group-prepend > .input-group-text, +.input-group-sm > .input-group-append > .input-group-text, +.input-group-sm > .input-group-prepend > .btn, +.input-group-sm > .input-group-append > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]), +.input-group-sm > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), +.input-group-sm > .input-group-append > select.input-group-text:not([size]):not([multiple]), +.input-group-sm > .input-group-prepend > select.btn:not([size]):not([multiple]), +.input-group-sm > .input-group-append > select.btn:not([size]):not([multiple]) { + height: calc(1.8125rem + 2px); +} + +.form-control-lg, .input-group-lg > .form-control, +.input-group-lg > .input-group-prepend > .input-group-text, +.input-group-lg > .input-group-append > .input-group-text, +.input-group-lg > .input-group-prepend > .btn, +.input-group-lg > .input-group-append > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]), +.input-group-lg > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), +.input-group-lg > .input-group-append > select.input-group-text:not([size]):not([multiple]), +.input-group-lg > .input-group-prepend > select.btn:not([size]):not([multiple]), +.input-group-lg > .input-group-append > select.btn:not([size]):not([multiple]) { + height: calc(2.875rem + 2px); +} + +.form-group { + margin-bottom: 1rem; +} + +.form-text { + display: block; + margin-top: 0.25rem; +} + +.form-row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; +} +.form-row > .col, +.form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; +} + +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; +} + +.form-check-input { + position: absolute; + margin-top: 0.3rem; + margin-left: -1.25rem; +} +.form-check-input:disabled ~ .form-check-label { + color: #6c757d; +} + +.form-check-label { + margin-bottom: 0; +} + +.form-check-inline { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding-left: 0; + margin-right: 0.75rem; +} +.form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: 0.3125rem; + margin-left: 0; +} + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #28a745; +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(40, 167, 69, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:valid, .form-control.is-valid, +.was-validated .custom-select:valid, +.custom-select.is-valid { + border-color: #28a745; +} +.was-validated .form-control:valid:focus, .form-control.is-valid:focus, +.was-validated .custom-select:valid:focus, +.custom-select.is-valid:focus { + border-color: #28a745; + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} +.was-validated .form-control:valid ~ .valid-feedback, +.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, +.form-control.is-valid ~ .valid-tooltip, +.was-validated .custom-select:valid ~ .valid-feedback, +.was-validated .custom-select:valid ~ .valid-tooltip, +.custom-select.is-valid ~ .valid-feedback, +.custom-select.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: #28a745; +} +.was-validated .form-check-input:valid ~ .valid-feedback, +.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, +.form-check-input.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { + color: #28a745; +} +.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { + background-color: #71dd8a; +} +.was-validated .custom-control-input:valid ~ .valid-feedback, +.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, +.custom-control-input.is-valid ~ .valid-tooltip { + display: block; +} +.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { + background-color: #34ce57; +} +.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { + border-color: #28a745; +} +.was-validated .custom-file-input:valid ~ .custom-file-label::before, .custom-file-input.is-valid ~ .custom-file-label::before { + border-color: inherit; +} +.was-validated .custom-file-input:valid ~ .valid-feedback, +.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, +.custom-file-input.is-valid ~ .valid-tooltip { + display: block; +} +.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #dc3545; +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(220, 53, 69, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:invalid, .form-control.is-invalid, +.was-validated .custom-select:invalid, +.custom-select.is-invalid { + border-color: #dc3545; +} +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, +.was-validated .custom-select:invalid:focus, +.custom-select.is-invalid:focus { + border-color: #dc3545; + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} +.was-validated .form-control:invalid ~ .invalid-feedback, +.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, +.form-control.is-invalid ~ .invalid-tooltip, +.was-validated .custom-select:invalid ~ .invalid-feedback, +.was-validated .custom-select:invalid ~ .invalid-tooltip, +.custom-select.is-invalid ~ .invalid-feedback, +.custom-select.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: #dc3545; +} +.was-validated .form-check-input:invalid ~ .invalid-feedback, +.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, +.form-check-input.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { + color: #dc3545; +} +.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { + background-color: #efa2a9; +} +.was-validated .custom-control-input:invalid ~ .invalid-feedback, +.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, +.custom-control-input.is-invalid ~ .invalid-tooltip { + display: block; +} +.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { + background-color: #e4606d; +} +.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { + border-color: #dc3545; +} +.was-validated .custom-file-input:invalid ~ .custom-file-label::before, .custom-file-input.is-invalid ~ .custom-file-label::before { + border-color: inherit; +} +.was-validated .custom-file-input:invalid ~ .invalid-feedback, +.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, +.custom-file-input.is-invalid ~ .invalid-tooltip { + display: block; +} +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.form-inline { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +.form-inline .form-check { + width: 100%; +} +@media (min-width: 576px) { + .form-inline label { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0; + } + .form-inline .form-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-plaintext { + display: inline-block; + } + .form-inline .input-group { + width: auto; + } + .form-inline .form-check { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + padding-left: 0; + } + .form-inline .form-check-input { + position: relative; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; + } + .form-inline .custom-control { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + } + .form-inline .custom-control-label { + margin-bottom: 0; + } +} + +.btn { + display: inline-block; + font-weight: 400; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} +.btn:hover, .btn:focus { + text-decoration: none; +} +.btn:focus, .btn.focus { + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.btn.disabled, .btn:disabled { + opacity: 0.65; +} +.btn:not(:disabled):not(.disabled) { + cursor: pointer; +} +.btn:not(:disabled):not(.disabled):active, .btn:not(:disabled):not(.disabled).active { + background-image: none; +} + +a.btn.disabled, +fieldset:disabled a.btn { + pointer-events: none; +} + +.btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-primary:hover { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; +} +.btn-primary:focus, .btn-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} +.btn-primary.disabled, .btn-primary:disabled { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #0062cc; + border-color: #005cbf; +} +.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-secondary { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-secondary:hover { + color: #fff; + background-color: #5a6268; + border-color: #545b62; +} +.btn-secondary:focus, .btn-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} +.btn-secondary.disabled, .btn-secondary:disabled { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #545b62; + border-color: #4e555b; +} +.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} + +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34; +} +.btn-success:focus, .btn-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} +.btn-success.disabled, .btn-success:disabled { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #1e7e34; + border-color: #1c7430; +} +.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; +} +.btn-info:focus, .btn-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} +.btn-info.disabled, .btn-info:disabled { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f; +} +.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-warning { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-warning:hover { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; +} +.btn-warning:focus, .btn-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} +.btn-warning.disabled, .btn-warning:disabled { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { + color: #212529; + background-color: #d39e00; + border-color: #c69500; +} +.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; +} +.btn-danger:focus, .btn-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} +.btn-danger.disabled, .btn-danger:disabled { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #bd2130; + border-color: #b21f2d; +} +.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; +} +.btn-light:focus, .btn-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} +.btn-light.disabled, .btn-light:disabled { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df; +} +.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, .show > .btn-light.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} +.btn-dark:focus, .btn-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} +.btn-dark.disabled, .btn-dark:disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, .show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; +} +.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-dark.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-primary { + color: #007bff; + background-color: transparent; + background-image: none; + border-color: #007bff; +} +.btn-outline-primary:hover { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-outline-primary:focus, .btn-outline-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #007bff; + background-color: transparent; +} +.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-outline-secondary { + color: #6c757d; + background-color: transparent; + background-image: none; + border-color: #6c757d; +} +.btn-outline-secondary:hover { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-outline-secondary:focus, .btn-outline-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #6c757d; + background-color: transparent; +} +.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} + +.btn-outline-success { + color: #28a745; + background-color: transparent; + background-image: none; + border-color: #28a745; +} +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-outline-success:focus, .btn-outline-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent; +} +.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-info { + color: #17a2b8; + background-color: transparent; + background-image: none; + border-color: #17a2b8; +} +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-outline-info:focus, .btn-outline-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; +} +.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-warning { + color: #ffc107; + background-color: transparent; + background-image: none; + border-color: #ffc107; +} +.btn-outline-warning:hover { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-outline-warning:focus, .btn-outline-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent; +} +.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-danger { + color: #dc3545; + background-color: transparent; + background-image: none; + border-color: #dc3545; +} +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-outline-danger:focus, .btn-outline-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; +} +.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-light { + color: #f8f9fa; + background-color: transparent; + background-image: none; + border-color: #f8f9fa; +} +.btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-outline-light:focus, .btn-outline-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} +.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-dark { + color: #343a40; + background-color: transparent; + background-image: none; + border-color: #343a40; +} +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-outline-dark:focus, .btn-outline-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} +.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-link { + font-weight: 400; + color: #007bff; + background-color: transparent; +} +.btn-link:hover { + color: #0056b3; + text-decoration: underline; + background-color: transparent; + border-color: transparent; +} +.btn-link:focus, .btn-link.focus { + text-decoration: underline; + border-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link:disabled, .btn-link.disabled { + color: #6c757d; +} + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 0.5rem; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.show { + opacity: 1; +} + +.collapse { + display: none; +} +.collapse.show { + display: block; +} + +tr.collapse.show { + display: table-row; +} + +tbody.collapse.show { + display: table-row-group; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.dropup .dropdown-menu { + margin-top: 0; + margin-bottom: 0.125rem; +} +.dropup .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropright .dropdown-menu { + margin-top: 0; + margin-left: 0.125rem; +} +.dropright .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} +.dropright .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropright .dropdown-toggle::after { + vertical-align: 0; +} + +.dropleft .dropdown-menu { + margin-top: 0; + margin-right: 0.125rem; +} +.dropleft .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} +.dropleft .dropdown-toggle::after { + display: none; +} +.dropleft .dropdown-toggle::before { + display: inline-block; + width: 0; + height: 0; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} +.dropleft .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropleft .dropdown-toggle::before { + vertical-align: 0; +} + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; +} +.dropdown-item:hover, .dropdown-item:focus { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa; +} +.dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #007bff; +} +.dropdown-item.disabled, .dropdown-item:disabled { + color: #6c757d; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #6c757d; + white-space: nowrap; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover { + z-index: 1; +} +.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 1; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group, +.btn-group-vertical .btn + .btn, +.btn-group-vertical .btn + .btn-group, +.btn-group-vertical .btn-group + .btn, +.btn-group-vertical .btn-group + .btn-group { + margin-left: -1px; +} + +.btn-toolbar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} +.btn-toolbar .input-group { + width: auto; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} +.dropdown-toggle-split::after { + margin-left: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group-vertical { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} +.btn-group-vertical .btn, +.btn-group-vertical .btn-group { + width: 100%; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:not(:first-child), +.btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.btn-group-toggle > .btn, +.btn-group-toggle > .btn-group > .btn { + margin-bottom: 0; +} +.btn-group-toggle > .btn input[type="radio"], +.btn-group-toggle > .btn input[type="checkbox"], +.btn-group-toggle > .btn-group > .btn input[type="radio"], +.btn-group-toggle > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + +.input-group { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; +} +.input-group > .form-control, +.input-group > .custom-select, +.input-group > .custom-file { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; +} +.input-group > .form-control:focus, +.input-group > .custom-select:focus, +.input-group > .custom-file:focus { + z-index: 3; +} +.input-group > .form-control + .form-control, +.input-group > .form-control + .custom-select, +.input-group > .form-control + .custom-file, +.input-group > .custom-select + .form-control, +.input-group > .custom-select + .custom-select, +.input-group > .custom-select + .custom-file, +.input-group > .custom-file + .form-control, +.input-group > .custom-file + .custom-select, +.input-group > .custom-file + .custom-file { + margin-left: -1px; +} +.input-group > .form-control:not(:last-child), +.input-group > .custom-select:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group > .form-control:not(:first-child), +.input-group > .custom-select:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group > .custom-file { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +.input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label::before { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group > .custom-file:not(:first-child) .custom-file-label, .input-group > .custom-file:not(:first-child) .custom-file-label::before { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.input-group-prepend, +.input-group-append { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +.input-group-prepend .btn, +.input-group-append .btn { + position: relative; + z-index: 2; +} +.input-group-prepend .btn + .btn, +.input-group-prepend .btn + .input-group-text, +.input-group-prepend .input-group-text + .input-group-text, +.input-group-prepend .input-group-text + .btn, +.input-group-append .btn + .btn, +.input-group-append .btn + .input-group-text, +.input-group-append .input-group-text + .input-group-text, +.input-group-append .input-group-text + .btn { + margin-left: -1px; +} + +.input-group-prepend { + margin-right: -1px; +} + +.input-group-append { + margin-left: -1px; +} + +.input-group-text { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} +.input-group-text input[type="radio"], +.input-group-text input[type="checkbox"] { + margin-top: 0; +} + +.input-group > .input-group-prepend > .btn, +.input-group > .input-group-prepend > .input-group-text, +.input-group > .input-group-append:not(:last-child) > .btn, +.input-group > .input-group-append:not(:last-child) > .input-group-text, +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.custom-control { + position: relative; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem; +} + +.custom-control-inline { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + margin-right: 1rem; +} + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; +} +.custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + background-color: #007bff; +} +.custom-control-input:focus ~ .custom-control-label::before { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-control-input:active ~ .custom-control-label::before { + color: #fff; + background-color: #b3d7ff; +} +.custom-control-input:disabled ~ .custom-control-label { + color: #6c757d; +} +.custom-control-input:disabled ~ .custom-control-label::before { + background-color: #e9ecef; +} + +.custom-control-label { + margin-bottom: 0; +} +.custom-control-label::before { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #dee2e6; +} +.custom-control-label::after { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background-repeat: no-repeat; + background-position: center center; + background-size: 50% 50%; +} + +.custom-checkbox .custom-control-label::before { + border-radius: 0.25rem; +} +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before { + background-color: #007bff; +} +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); +} +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { + background-color: #007bff; +} +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); +} +.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} +.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} + +.custom-radio .custom-control-label::before { + border-radius: 50%; +} +.custom-radio .custom-control-input:checked ~ .custom-control-label::before { + background-color: #007bff; +} +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); +} +.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} + +.custom-select { + display: inline-block; + width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.custom-select:focus { + border-color: #80bdff; + outline: 0; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(128, 189, 255, 0.5); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(128, 189, 255, 0.5); +} +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} +.custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; +} +.custom-select:disabled { + color: #6c757d; + background-color: #e9ecef; +} +.custom-select::-ms-expand { + opacity: 0; +} + +.custom-select-sm { + height: calc(1.8125rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; +} + +.custom-select-lg { + height: calc(2.875rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 125%; +} + +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(2.25rem + 2px); + margin-bottom: 0; +} + +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(2.25rem + 2px); + margin: 0; + opacity: 0; +} +.custom-file-input:focus ~ .custom-file-control { + border-color: #80bdff; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-file-input:focus ~ .custom-file-control::before { + border-color: #80bdff; +} +.custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse"; +} + +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(2.25rem + 2px); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} +.custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: calc(calc(2.25rem + 2px) - 1px * 2); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: 1px solid #ced4da; + border-radius: 0 0.25rem 0.25rem 0; +} + +.nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: 0.5rem 1rem; +} +.nav-link:hover, .nav-link:focus { + text-decoration: none; +} +.nav-link.disabled { + color: #6c757d; +} + +.nav-tabs { + border-bottom: 1px solid #dee2e6; +} +.nav-tabs .nav-item { + margin-bottom: -1px; +} +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} +.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + border-color: #e9ecef #e9ecef #dee2e6; +} +.nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent; +} +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + border-radius: 0.25rem; +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #007bff; +} + +.nav-fill .nav-item { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; +} + +.nav-justified .nav-item { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center; +} + +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} + +.navbar { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0.5rem 1rem; +} +.navbar > .container, +.navbar > .container-fluid { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; +} +.navbar-brand:hover, .navbar-brand:focus { + text-decoration: none; +} + +.navbar-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; +} +.navbar-nav .dropdown-menu { + position: static; + float: none; +} + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; +} +.navbar-toggler:hover, .navbar-toggler:focus { + text-decoration: none; +} +.navbar-toggler:not(:disabled):not(.disabled) { + cursor: pointer; +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; +} + +@media (max-width: 575.98px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 576px) { + .navbar-expand-sm { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-sm .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} +@media (max-width: 767.98px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 768px) { + .navbar-expand-md { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-md .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 992px) { + .navbar-expand-lg { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-lg .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} +@media (max-width: 1199.98px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 1200px) { + .navbar-expand-xl { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-xl .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} +.navbar-expand { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} +.navbar-expand > .container, +.navbar-expand > .container-fluid { + padding-right: 0; + padding-left: 0; +} +.navbar-expand .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; +} +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} +.navbar-expand .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; +} +.navbar-expand .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; +} +.navbar-expand > .container, +.navbar-expand > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} +.navbar-expand .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; +} +.navbar-expand .navbar-toggler { + display: none; +} +.navbar-expand .dropup .dropdown-menu { + top: auto; + bottom: 100%; +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} +.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { + color: rgba(0, 0, 0, 0.7); +} +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} +.navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-dark .navbar-brand { + color: #fff; +} +.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { + color: #fff; +} +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); +} +.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { + color: rgba(255, 255, 255, 0.75); +} +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; +} +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); +} +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); +} +.navbar-dark .navbar-text a { + color: #fff; +} +.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { + color: #fff; +} + +.card { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; +} +.card > hr { + margin-right: 0; + margin-left: 0; +} +.card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.card-body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1.25rem; +} + +.card-title { + margin-bottom: 0.75rem; +} + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link:hover { + text-decoration: none; +} +.card-link + .card-link { + margin-left: 1.25rem; +} + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} +.card-header + .list-group .list-group-item:first-child { + border-top: 0; +} + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); +} +.card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); +} + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; +} + +.card-img { + width: 100%; + border-radius: calc(0.25rem - 1px); +} + +.card-img-top { + width: 100%; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + +.card-img-bottom { + width: 100%; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); +} + +.card-deck { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} +.card-deck .card { + margin-bottom: 15px; +} +@media (min-width: 576px) { + .card-deck { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; + } +} + +.card-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} +.card-group > .card { + margin-bottom: 15px; +} +@media (min-width: 576px) { + .card-group { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + } + .card-group > .card { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:first-child .card-img-top, + .card-group > .card:first-child .card-header { + border-top-right-radius: 0; + } + .card-group > .card:first-child .card-img-bottom, + .card-group > .card:first-child .card-footer { + border-bottom-right-radius: 0; + } + .card-group > .card:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:last-child .card-img-top, + .card-group > .card:last-child .card-header { + border-top-left-radius: 0; + } + .card-group > .card:last-child .card-img-bottom, + .card-group > .card:last-child .card-footer { + border-bottom-left-radius: 0; + } + .card-group > .card:only-child { + border-radius: 0.25rem; + } + .card-group > .card:only-child .card-img-top, + .card-group > .card:only-child .card-header { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + .card-group > .card:only-child .card-img-bottom, + .card-group > .card:only-child .card-footer { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) { + border-radius: 0; + } + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { + border-radius: 0; + } +} + +.card-columns .card { + margin-bottom: 0.75rem; +} +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + -moz-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + .card-columns .card { + display: inline-block; + width: 100%; + } +} + +.breadcrumb { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + padding-left: 0.5rem; + color: #6c757d; + content: "/"; +} +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; +} +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; +} +.breadcrumb-item.active { + color: #6c757d; +} + +.pagination { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; +} + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #007bff; + background-color: #fff; + border: 1px solid #dee2e6; +} +.page-link:hover { + color: #0056b3; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6; +} +.page-link:focus { + z-index: 2; + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.page-link:not(:disabled):not(.disabled) { + cursor: pointer; +} + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} +.page-item.active .page-link { + z-index: 1; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6; +} + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; +} +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} + +.badge-primary { + color: #fff; + background-color: #007bff; +} +.badge-primary[href]:hover, .badge-primary[href]:focus { + color: #fff; + text-decoration: none; + background-color: #0062cc; +} + +.badge-secondary { + color: #fff; + background-color: #6c757d; +} +.badge-secondary[href]:hover, .badge-secondary[href]:focus { + color: #fff; + text-decoration: none; + background-color: #545b62; +} + +.badge-success { + color: #fff; + background-color: #28a745; +} +.badge-success[href]:hover, .badge-success[href]:focus { + color: #fff; + text-decoration: none; + background-color: #1e7e34; +} + +.badge-info { + color: #fff; + background-color: #17a2b8; +} +.badge-info[href]:hover, .badge-info[href]:focus { + color: #fff; + text-decoration: none; + background-color: #117a8b; +} + +.badge-warning { + color: #212529; + background-color: #ffc107; +} +.badge-warning[href]:hover, .badge-warning[href]:focus { + color: #212529; + text-decoration: none; + background-color: #d39e00; +} + +.badge-danger { + color: #fff; + background-color: #dc3545; +} +.badge-danger[href]:hover, .badge-danger[href]:focus { + color: #fff; + text-decoration: none; + background-color: #bd2130; +} + +.badge-light { + color: #212529; + background-color: #f8f9fa; +} +.badge-light[href]:hover, .badge-light[href]:focus { + color: #212529; + text-decoration: none; + background-color: #dae0e5; +} + +.badge-dark { + color: #fff; + background-color: #343a40; +} +.badge-dark[href]:hover, .badge-dark[href]:focus { + color: #fff; + text-decoration: none; + background-color: #1d2124; +} + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; +} +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; + } +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; +} + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; +} + +.alert-dismissible { + padding-right: 4rem; +} +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; +} + +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} +.alert-primary hr { + border-top-color: #9fcdff; +} +.alert-primary .alert-link { + color: #002752; +} + +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; +} +.alert-secondary hr { + border-top-color: #c8cbcf; +} +.alert-secondary .alert-link { + color: #202326; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} +.alert-success hr { + border-top-color: #b1dfbb; +} +.alert-success .alert-link { + color: #0b2e13; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} +.alert-info hr { + border-top-color: #abdde5; +} +.alert-info .alert-link { + color: #062c33; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} +.alert-warning hr { + border-top-color: #ffe8a1; +} +.alert-warning .alert-link { + color: #533f03; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} +.alert-danger hr { + border-top-color: #f1b0b7; +} +.alert-danger .alert-link { + color: #491217; +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} +.alert-light hr { + border-top-color: #ececf6; +} +.alert-light .alert-link { + color: #686868; +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} +.alert-dark hr { + border-top-color: #b9bbbe; +} +.alert-dark .alert-link { + color: #040505; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} +.progress { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.progress-bar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + color: #fff; + text-align: center; + background-color: #007bff; + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; +} + +.progress-bar-animated { + -webkit-animation: progress-bar-stripes 1s linear infinite; + animation: progress-bar-stripes 1s linear infinite; +} + +.media { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.media-body { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.list-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; +} + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; +} +.list-group-item-action:hover, .list-group-item-action:focus { + color: #495057; + text-decoration: none; + background-color: #f8f9fa; +} +.list-group-item-action:active { + color: #212529; + background-color: #e9ecef; +} + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); +} +.list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} +.list-group-item:hover, .list-group-item:focus { + z-index: 1; + text-decoration: none; +} +.list-group-item.disabled, .list-group-item:disabled { + color: #6c757d; + background-color: #fff; +} +.list-group-item.active { + z-index: 2; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; +} +.list-group-flush:last-child .list-group-item:last-child { + border-bottom: 0; +} + +.list-group-item-primary { + color: #004085; + background-color: #b8daff; +} +.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { + color: #004085; + background-color: #9fcdff; +} +.list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #004085; + border-color: #004085; +} + +.list-group-item-secondary { + color: #383d41; + background-color: #d6d8db; +} +.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { + color: #383d41; + background-color: #c8cbcf; +} +.list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #383d41; + border-color: #383d41; +} + +.list-group-item-success { + color: #155724; + background-color: #c3e6cb; +} +.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { + color: #155724; + background-color: #b1dfbb; +} +.list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #155724; + border-color: #155724; +} + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; +} +.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { + color: #0c5460; + background-color: #abdde5; +} +.list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; +} + +.list-group-item-warning { + color: #856404; + background-color: #ffeeba; +} +.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { + color: #856404; + background-color: #ffe8a1; +} +.list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #856404; + border-color: #856404; +} + +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb; +} +.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { + color: #721c24; + background-color: #f1b0b7; +} +.list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #721c24; + border-color: #721c24; +} + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; +} +.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { + color: #818182; + background-color: #ececf6; +} +.list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #818182; + border-color: #818182; +} + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; +} +.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { + color: #1b1e21; + background-color: #b9bbbe; +} +.list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; +} + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; +} +.close:hover, .close:focus { + color: #000; + text-decoration: none; + opacity: .75; +} +.close:not(:disabled):not(.disabled) { + cursor: pointer; +} + +button.close { + padding: 0; + background-color: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + outline: 0; +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.show .modal-dialog { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-dialog-centered { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-height: calc(100% - (0.5rem * 2)); +} + +.modal-content { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop.show { + opacity: 0.5; +} + +.modal-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid #e9ecef; + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; +} +.modal-header .close { + padding: 1rem; + margin: -1rem -1rem -1rem auto; +} + +.modal-title { + margin-bottom: 0; + line-height: 1.5; +} + +.modal-body { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem; +} + +.modal-footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 1rem; + border-top: 1px solid #e9ecef; +} +.modal-footer > :not(:first-child) { + margin-left: .25rem; +} +.modal-footer > :not(:last-child) { + margin-right: .25rem; +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; + } + + .modal-dialog-centered { + min-height: calc(100% - (1.75rem * 2)); + } + + .modal-sm { + max-width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + max-width: 800px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; +} +.tooltip.show { + opacity: 0.9; +} +.tooltip .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; +} +.tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { + padding: 0.4rem 0; +} +.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; +} +.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { + top: 0; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; +} + +.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { + padding: 0 0.4rem; +} +.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; +} +.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { + right: 0; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; +} + +.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { + padding: 0.4rem 0; +} +.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; +} +.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + bottom: 0; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; +} + +.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { + padding: 0 0.4rem; +} +.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; +} +.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { + left: 0; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; +} + +.tooltip-inner { + max-width: 200px; + padding: 0.25rem 0.5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; +} +.popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; + margin: 0 0.3rem; +} +.popover .arrow::before, .popover .arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-popover-top, .bs-popover-auto[x-placement^="top"] { + margin-bottom: 0.5rem; +} +.bs-popover-top .arrow, .bs-popover-auto[x-placement^="top"] .arrow { + bottom: calc((0.5rem + 1px) * -1); +} +.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before, +.bs-popover-top .arrow::after, +.bs-popover-auto[x-placement^="top"] .arrow::after { + border-width: 0.5rem 0.5rem 0; +} +.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before { + bottom: 0; + border-top-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-top .arrow::after, .bs-popover-auto[x-placement^="top"] .arrow::after { + bottom: 1px; + border-top-color: #fff; +} + +.bs-popover-right, .bs-popover-auto[x-placement^="right"] { + margin-left: 0.5rem; +} +.bs-popover-right .arrow, .bs-popover-auto[x-placement^="right"] .arrow { + left: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} +.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before, +.bs-popover-right .arrow::after, +.bs-popover-auto[x-placement^="right"] .arrow::after { + border-width: 0.5rem 0.5rem 0.5rem 0; +} +.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before { + left: 0; + border-right-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-right .arrow::after, .bs-popover-auto[x-placement^="right"] .arrow::after { + left: 1px; + border-right-color: #fff; +} + +.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { + margin-top: 0.5rem; +} +.bs-popover-bottom .arrow, .bs-popover-auto[x-placement^="bottom"] .arrow { + top: calc((0.5rem + 1px) * -1); +} +.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before, +.bs-popover-bottom .arrow::after, +.bs-popover-auto[x-placement^="bottom"] .arrow::after { + border-width: 0 0.5rem 0.5rem 0.5rem; +} +.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before { + top: 0; + border-bottom-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-bottom .arrow::after, .bs-popover-auto[x-placement^="bottom"] .arrow::after { + top: 1px; + border-bottom-color: #fff; +} +.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7; +} + +.bs-popover-left, .bs-popover-auto[x-placement^="left"] { + margin-right: 0.5rem; +} +.bs-popover-left .arrow, .bs-popover-auto[x-placement^="left"] .arrow { + right: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} +.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before, +.bs-popover-left .arrow::after, +.bs-popover-auto[x-placement^="left"] .arrow::after { + border-width: 0.5rem 0 0.5rem 0.5rem; +} +.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before { + right: 0; + border-left-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-left .arrow::after, .bs-popover-auto[x-placement^="left"] .arrow::after { + right: 1px; + border-left-color: #fff; +} + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + color: inherit; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} +.popover-header:empty { + display: none; +} + +.popover-body { + padding: 0.5rem 0.75rem; + color: #212529; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-item { + position: relative; + display: none; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + -webkit-transition: -webkit-transform 0.6s ease; + transition: -webkit-transform 0.6s ease; + transition: transform 0.6s ease; + transition: transform 0.6s ease, -webkit-transform 0.6s ease; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; +} + +.carousel-item-next.carousel-item-left, +.carousel-item-prev.carousel-item-right { + -webkit-transform: translateX(0); + transform: translateX(0); +} +@supports (transform-style: preserve-3d) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.carousel-item-next, +.active.carousel-item-right { + -webkit-transform: translateX(100%); + transform: translateX(100%); +} +@supports (transform-style: preserve-3d) { + .carousel-item-next, + .active.carousel-item-right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.carousel-item-prev, +.active.carousel-item-left { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); +} +@supports (transform-style: preserve-3d) { + .carousel-item-prev, + .active.carousel-item-left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; +} +.carousel-control-prev:hover, .carousel-control-prev:focus, +.carousel-control-next:hover, +.carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: transparent no-repeat center center; + background-size: 100% 100%; +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} +.carousel-indicators li { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + background-color: rgba(255, 255, 255, 0.5); +} +.carousel-indicators li::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} +.carousel-indicators li::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} +.carousel-indicators .active { + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.bg-primary { + background-color: #007bff !important; +} + +a.bg-primary:hover, a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #0062cc !important; +} + +.bg-secondary { + background-color: #6c757d !important; +} + +a.bg-secondary:hover, a.bg-secondary:focus, +button.bg-secondary:hover, +button.bg-secondary:focus { + background-color: #545b62 !important; +} + +.bg-success { + background-color: #28a745 !important; +} + +a.bg-success:hover, a.bg-success:focus, +button.bg-success:hover, +button.bg-success:focus { + background-color: #1e7e34 !important; +} + +.bg-info { + background-color: #17a2b8 !important; +} + +a.bg-info:hover, a.bg-info:focus, +button.bg-info:hover, +button.bg-info:focus { + background-color: #117a8b !important; +} + +.bg-warning { + background-color: #ffc107 !important; +} + +a.bg-warning:hover, a.bg-warning:focus, +button.bg-warning:hover, +button.bg-warning:focus { + background-color: #d39e00 !important; +} + +.bg-danger { + background-color: #dc3545 !important; +} + +a.bg-danger:hover, a.bg-danger:focus, +button.bg-danger:hover, +button.bg-danger:focus { + background-color: #bd2130 !important; +} + +.bg-light { + background-color: #f8f9fa !important; +} + +a.bg-light:hover, a.bg-light:focus, +button.bg-light:hover, +button.bg-light:focus { + background-color: #dae0e5 !important; +} + +.bg-dark { + background-color: #343a40 !important; +} + +a.bg-dark:hover, a.bg-dark:focus, +button.bg-dark:hover, +button.bg-dark:focus { + background-color: #1d2124 !important; +} + +.bg-white { + background-color: #fff !important; +} + +.bg-transparent { + background-color: transparent !important; +} + +.border { + border: 1px solid #dee2e6 !important; +} + +.border-top { + border-top: 1px solid #dee2e6 !important; +} + +.border-right { + border-right: 1px solid #dee2e6 !important; +} + +.border-bottom { + border-bottom: 1px solid #dee2e6 !important; +} + +.border-left { + border-left: 1px solid #dee2e6 !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-right-0 { + border-right: 0 !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-left-0 { + border-left: 0 !important; +} + +.border-primary { + border-color: #007bff !important; +} + +.border-secondary { + border-color: #6c757d !important; +} + +.border-success { + border-color: #28a745 !important; +} + +.border-info { + border-color: #17a2b8 !important; +} + +.border-warning { + border-color: #ffc107 !important; +} + +.border-danger { + border-color: #dc3545 !important; +} + +.border-light { + border-color: #f8f9fa !important; +} + +.border-dark { + border-color: #343a40 !important; +} + +.border-white { + border-color: #fff !important; +} + +.rounded { + border-radius: 0.25rem !important; +} + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; +} + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-0 { + border-radius: 0 !important; +} + +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + + .d-sm-inline { + display: inline !important; + } + + .d-sm-inline-block { + display: inline-block !important; + } + + .d-sm-block { + display: block !important; + } + + .d-sm-table { + display: table !important; + } + + .d-sm-table-row { + display: table-row !important; + } + + .d-sm-table-cell { + display: table-cell !important; + } + + .d-sm-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .d-sm-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + + .d-md-inline { + display: inline !important; + } + + .d-md-inline-block { + display: inline-block !important; + } + + .d-md-block { + display: block !important; + } + + .d-md-table { + display: table !important; + } + + .d-md-table-row { + display: table-row !important; + } + + .d-md-table-cell { + display: table-cell !important; + } + + .d-md-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .d-md-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + + .d-lg-inline { + display: inline !important; + } + + .d-lg-inline-block { + display: inline-block !important; + } + + .d-lg-block { + display: block !important; + } + + .d-lg-table { + display: table !important; + } + + .d-lg-table-row { + display: table-row !important; + } + + .d-lg-table-cell { + display: table-cell !important; + } + + .d-lg-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .d-lg-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + + .d-xl-inline { + display: inline !important; + } + + .d-xl-inline-block { + display: inline-block !important; + } + + .d-xl-block { + display: block !important; + } + + .d-xl-table { + display: table !important; + } + + .d-xl-table-row { + display: table-row !important; + } + + .d-xl-table-cell { + display: table-cell !important; + } + + .d-xl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .d-xl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media print { + .d-print-none { + display: none !important; + } + + .d-print-inline { + display: inline !important; + } + + .d-print-inline-block { + display: inline-block !important; + } + + .d-print-block { + display: block !important; + } + + .d-print-table { + display: table !important; + } + + .d-print-table-row { + display: table-row !important; + } + + .d-print-table-cell { + display: table-cell !important; + } + + .d-print-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .d-print-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} +.embed-responsive::before { + display: block; + content: ""; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.embed-responsive-21by9::before { + padding-top: 42.8571428571%; +} + +.embed-responsive-16by9::before { + padding-top: 56.25%; +} + +.embed-responsive-4by3::before { + padding-top: 75%; +} + +.embed-responsive-1by1::before { + padding-top: 100%; +} + +.flex-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + + .flex-sm-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + + .flex-sm-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + + .flex-sm-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + + .justify-content-sm-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + + .justify-content-sm-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + + .justify-content-sm-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + + .justify-content-sm-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + + .align-items-sm-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + + .align-items-sm-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + + .align-items-sm-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + + .align-items-sm-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + + .align-items-sm-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 768px) { + .flex-md-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + + .flex-md-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + + .flex-md-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + + .flex-md-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + + .justify-content-md-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + + .justify-content-md-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + + .justify-content-md-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + + .justify-content-md-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + + .align-items-md-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + + .align-items-md-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + + .align-items-md-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + + .align-items-md-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + + .align-items-md-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 992px) { + .flex-lg-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + + .flex-lg-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + + .flex-lg-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + + .flex-lg-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + + .justify-content-lg-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + + .justify-content-lg-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + + .justify-content-lg-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + + .justify-content-lg-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + + .align-items-lg-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + + .align-items-lg-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + + .align-items-lg-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + + .align-items-lg-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + + .align-items-lg-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 1200px) { + .flex-xl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + + .flex-xl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + + .flex-xl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + + .flex-xl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + + .justify-content-xl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + + .justify-content-xl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + + .justify-content-xl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + + .justify-content-xl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + + .align-items-xl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + + .align-items-xl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + + .align-items-xl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + + .align-items-xl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + + .align-items-xl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +.float-left { + float: left !important; +} + +.float-right { + float: right !important; +} + +.float-none { + float: none !important; +} + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; + } + + .float-sm-right { + float: right !important; + } + + .float-sm-none { + float: none !important; + } +} +@media (min-width: 768px) { + .float-md-left { + float: left !important; + } + + .float-md-right { + float: right !important; + } + + .float-md-none { + float: none !important; + } +} +@media (min-width: 992px) { + .float-lg-left { + float: left !important; + } + + .float-lg-right { + float: right !important; + } + + .float-lg-none { + float: none !important; + } +} +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; + } + + .float-xl-right { + float: right !important; + } + + .float-xl-none { + float: none !important; + } +} +.position-static { + position: static !important; +} + +.position-relative { + position: relative !important; +} + +.position-absolute { + position: absolute !important; +} + +.position-fixed { + position: fixed !important; +} + +.position-sticky { + position: sticky !important; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +@supports (position: sticky) { + .sticky-top { + position: sticky; + top: 0; + z-index: 1020; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; + -webkit-clip-path: none; + clip-path: none; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.m-0 { + margin: 0 !important; +} + +.mt-0, +.my-0 { + margin-top: 0 !important; +} + +.mr-0, +.mx-0 { + margin-right: 0 !important; +} + +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} + +.ml-0, +.mx-0 { + margin-left: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3, +.my-3 { + margin-top: 1rem !important; +} + +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} + +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5, +.my-5 { + margin-top: 3rem !important; +} + +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} + +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0, +.py-0 { + padding-top: 0 !important; +} + +.pr-0, +.px-0 { + padding-right: 0 !important; +} + +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} + +.pl-0, +.px-0 { + padding-left: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3, +.py-3 { + padding-top: 1rem !important; +} + +.pr-3, +.px-3 { + padding-right: 1rem !important; +} + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} + +.pl-3, +.px-3 { + padding-left: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5, +.py-5 { + padding-top: 3rem !important; +} + +.pr-5, +.px-5 { + padding-right: 3rem !important; +} + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} + +.pl-5, +.px-5 { + padding-left: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto, +.my-auto { + margin-top: auto !important; +} + +.mr-auto, +.mx-auto { + margin-right: auto !important; +} + +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} + +.ml-auto, +.mx-auto { + margin-left: auto !important; +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + + .m-sm-1 { + margin: 0.25rem !important; + } + + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + + .m-sm-2 { + margin: 0.5rem !important; + } + + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + + .m-sm-3 { + margin: 1rem !important; + } + + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + + .m-sm-4 { + margin: 1.5rem !important; + } + + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + + .m-sm-5 { + margin: 3rem !important; + } + + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + + .p-sm-0 { + padding: 0 !important; + } + + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + + .p-sm-1 { + padding: 0.25rem !important; + } + + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + + .p-sm-2 { + padding: 0.5rem !important; + } + + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + + .p-sm-3 { + padding: 1rem !important; + } + + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + + .p-sm-4 { + padding: 1.5rem !important; + } + + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + + .p-sm-5 { + padding: 3rem !important; + } + + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + + .m-sm-auto { + margin: auto !important; + } + + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + + .m-md-1 { + margin: 0.25rem !important; + } + + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + + .m-md-2 { + margin: 0.5rem !important; + } + + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + + .m-md-3 { + margin: 1rem !important; + } + + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + + .m-md-4 { + margin: 1.5rem !important; + } + + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + + .m-md-5 { + margin: 3rem !important; + } + + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + + .p-md-0 { + padding: 0 !important; + } + + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + + .p-md-1 { + padding: 0.25rem !important; + } + + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + + .p-md-2 { + padding: 0.5rem !important; + } + + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + + .p-md-3 { + padding: 1rem !important; + } + + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + + .p-md-4 { + padding: 1.5rem !important; + } + + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + + .p-md-5 { + padding: 3rem !important; + } + + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + + .m-md-auto { + margin: auto !important; + } + + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + + .m-lg-1 { + margin: 0.25rem !important; + } + + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + + .m-lg-2 { + margin: 0.5rem !important; + } + + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + + .m-lg-3 { + margin: 1rem !important; + } + + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + + .m-lg-4 { + margin: 1.5rem !important; + } + + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + + .m-lg-5 { + margin: 3rem !important; + } + + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + + .p-lg-0 { + padding: 0 !important; + } + + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + + .p-lg-1 { + padding: 0.25rem !important; + } + + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + + .p-lg-2 { + padding: 0.5rem !important; + } + + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + + .p-lg-3 { + padding: 1rem !important; + } + + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + + .p-lg-4 { + padding: 1.5rem !important; + } + + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + + .p-lg-5 { + padding: 3rem !important; + } + + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + + .m-lg-auto { + margin: auto !important; + } + + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + + .m-xl-1 { + margin: 0.25rem !important; + } + + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + + .m-xl-2 { + margin: 0.5rem !important; + } + + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + + .m-xl-3 { + margin: 1rem !important; + } + + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + + .m-xl-4 { + margin: 1.5rem !important; + } + + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + + .m-xl-5 { + margin: 3rem !important; + } + + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + + .p-xl-0 { + padding: 0 !important; + } + + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + + .p-xl-1 { + padding: 0.25rem !important; + } + + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + + .p-xl-2 { + padding: 0.5rem !important; + } + + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + + .p-xl-3 { + padding: 1rem !important; + } + + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + + .p-xl-4 { + padding: 1.5rem !important; + } + + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + + .p-xl-5 { + padding: 3rem !important; + } + + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + + .m-xl-auto { + margin: auto !important; + } + + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} +.text-justify { + text-align: justify !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-left { + text-align: left !important; +} + +.text-right { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; + } + + .text-sm-right { + text-align: right !important; + } + + .text-sm-center { + text-align: center !important; + } +} +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; + } + + .text-md-right { + text-align: right !important; + } + + .text-md-center { + text-align: center !important; + } +} +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; + } + + .text-lg-right { + text-align: right !important; + } + + .text-lg-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; + } + + .text-xl-right { + text-align: right !important; + } + + .text-xl-center { + text-align: center !important; + } +} +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.font-weight-light { + font-weight: 300 !important; +} + +.font-weight-normal { + font-weight: 400 !important; +} + +.font-weight-bold { + font-weight: 700 !important; +} + +.font-italic { + font-style: italic !important; +} + +.text-white { + color: #fff !important; +} + +.text-primary { + color: #007bff !important; +} + +a.text-primary:hover, a.text-primary:focus { + color: #0062cc !important; +} + +.text-secondary { + color: #6c757d !important; +} + +a.text-secondary:hover, a.text-secondary:focus { + color: #545b62 !important; +} + +.text-success { + color: #28a745 !important; +} + +a.text-success:hover, a.text-success:focus { + color: #1e7e34 !important; +} + +.text-info { + color: #17a2b8 !important; +} + +a.text-info:hover, a.text-info:focus { + color: #117a8b !important; +} + +.text-warning { + color: #ffc107 !important; +} + +a.text-warning:hover, a.text-warning:focus { + color: #d39e00 !important; +} + +.text-danger { + color: #dc3545 !important; +} + +a.text-danger:hover, a.text-danger:focus { + color: #bd2130 !important; +} + +.text-light { + color: #f8f9fa !important; +} + +a.text-light:hover, a.text-light:focus { + color: #dae0e5 !important; +} + +.text-dark { + color: #343a40 !important; +} + +a.text-dark:hover, a.text-dark:focus { + color: #1d2124 !important; +} + +.text-muted { + color: #6c757d !important; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + + a:not(.btn) { + text-decoration: underline; + } + + abbr[title]::after { + content: " (" attr(title) ")"; + } + + pre { + white-space: pre-wrap !important; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + tr, + img { + page-break-inside: avoid; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + @page { + size: a3; + } + body { + min-width: 992px !important; + } + + .container { + min-width: 992px !important; + } + + .navbar { + display: none; + } + + .badge { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +/*Github syntax highlighting theme via Rouge*/ +.highlight table td { + padding: 5px; +} + +.highlight table pre { + margin: 0; +} + +.highlight .cm { + color: #999988; + font-style: italic; +} + +.highlight .cp { + color: #999999; + font-weight: bold; +} + +.highlight .c1 { + color: #999988; + font-style: italic; +} + +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} + +.highlight .c, .highlight .cd { + color: #999988; + font-style: italic; +} + +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} + +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} + +.highlight .ge { + color: #000000; + font-style: italic; +} + +.highlight .gr { + color: #aa0000; +} + +.highlight .gh { + color: #999999; +} + +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} + +.highlight .go { + color: #888888; +} + +.highlight .gp { + color: #2980b9; +} + +.highlight .gs { + font-weight: bold; +} + +.highlight .gu { + color: #aaaaaa; +} + +.highlight .gt { + color: #aa0000; +} + +.highlight .kc { + color: #000000; + font-weight: bold; +} + +.highlight .kd { + color: #000000; + font-weight: bold; +} + +.highlight .kn { + color: #000000; + font-weight: bold; +} + +.highlight .kp { + color: #000000; + font-weight: bold; +} + +.highlight .kr { + color: #000000; + font-weight: bold; +} + +.highlight .kt { + color: #445588; + font-weight: bold; +} + +.highlight .k, .highlight .kv { + color: #000000; + font-weight: bold; +} + +.highlight .mf { + color: #009999; +} + +.highlight .mh { + color: #009999; +} + +.highlight .il { + color: #009999; +} + +.highlight .mi { + color: #009999; +} + +.highlight .mo { + color: #009999; +} + +.highlight .m, .highlight .mb, .highlight .mx { + color: #009999; +} + +.highlight .sb { + color: #d14; +} + +.highlight .sc { + color: #d14; +} + +.highlight .sd { + color: #d14; +} + +.highlight .s2 { + color: #d14; +} + +.highlight .se { + color: #d14; +} + +.highlight .sh { + color: #d14; +} + +.highlight .si { + color: #d14; +} + +.highlight .sx { + color: #d14; +} + +.highlight .sr { + color: #009926; +} + +.highlight .s1 { + color: #d14; +} + +.highlight .ss { + color: #990073; +} + +.highlight .s { + color: #d14; +} + +.highlight .na { + color: #008080; +} + +.highlight .bp { + color: #525252; +} + +.highlight .nb { + color: #0086B3; +} + +.highlight .nc { + color: #445588; + font-weight: bold; +} + +.highlight .no { + color: #008080; +} + +.highlight .nd { + color: #3c5d5d; + font-weight: bold; +} + +.highlight .ni { + color: #800080; +} + +.highlight .ne { + color: #990000; + font-weight: bold; +} + +.highlight .nf { + color: #990000; + font-weight: bold; +} + +.highlight .nl { + color: #990000; + font-weight: bold; +} + +.highlight .nn { + color: #2980b9; +} + +.highlight .nt { + color: #000080; +} + +.highlight .vc { + color: #008080; +} + +.highlight .vg { + color: #008080; +} + +.highlight .vi { + color: #008080; +} + +.highlight .nv { + color: #008080; +} + +.highlight .ow { + color: #000000; + font-weight: bold; +} + +.highlight .o { + color: #000000; + font-weight: bold; +} + +.highlight .n { + color: #000000; + font-weight: bold; +} + +.highlight .p { + color: #000000; + font-weight: bold; +} + +.highlight .w { + color: #bbbbbb; +} + +.highlight { + background-color: #f8f8f8; +} + +@font-face { + font-family: FreightSans; + font-weight: 700; + font-style: normal; + src: url("../fonts/FreightSans/freight-sans-bold.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-bold.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 700; + font-style: italic; + src: url("../fonts/FreightSans/freight-sans-bold-italic.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-bold-italic.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 500; + font-style: normal; + src: url("../fonts/FreightSans/freight-sans-medium.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-medium.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 500; + font-style: italic; + src: url("../fonts/FreightSans/freight-sans-medium-italic.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-medium-italic.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 100; + font-style: normal; + src: url("../fonts/FreightSans/freight-sans-light.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-light.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 100; + font-style: italic; + src: url("../fonts/FreightSans/freight-sans-light-italic.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-light-italic.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 400; + font-style: italic; + src: url("../fonts/FreightSans/freight-sans-book-italic.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-book-italic.woff") format("woff"); +} +@font-face { + font-family: FreightSans; + font-weight: 400; + font-style: normal; + src: url("../fonts/FreightSans/freight-sans-book.woff2") format("woff2"), url("../fonts/FreightSans/freight-sans-book.woff") format("woff"); +} +@font-face { + font-family: IBMPlexMono; + font-weight: 600; + font-style: normal; + unicode-range: u+0020-007f; + src: local("IBMPlexMono-SemiBold"), url("../fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff2") format("woff2"), url("../fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff") format("woff"); +} +@font-face { + font-family: IBMPlexMono; + font-weight: 500; + font-style: normal; + unicode-range: u+0020-007f; + src: local("IBMPlexMono-Medium"), url("../fonts/IBMPlexMono/IBMPlexMono-Medium.woff2") format("woff2"), url("../fonts/IBMPlexMono/IBMPlexMono-Medium.woff") format("woff"); +} +@font-face { + font-family: IBMPlexMono; + font-weight: 400; + font-style: normal; + unicode-range: u+0020-007f; + src: local("IBMPlexMono-Regular"), url("../fonts/IBMPlexMono/IBMPlexMono-Regular.woff2") format("woff2"), url("../fonts/IBMPlexMono/IBMPlexMono-Regular.woff") format("woff"); +} +@font-face { + font-family: IBMPlexMono; + font-weight: 300; + font-style: normal; + unicode-range: u+0020-007f; + src: local("IBMPlexMono-Light"), url("../fonts/IBMPlexMono/IBMPlexMono-Light.woff2") format("woff2"), url("../fonts/IBMPlexMono/IBMPlexMono-Light.woff") format("woff"); +} +html { + position: relative; + min-height: 100%; + font-size: 12px; +} +@media screen and (min-width: 768px) { + html { + font-size: 16px; + } +} + +* { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +body { + font-family: FreightSans, Helvetica Neue, Helvetica, Arial, sans-serif; +} + +a:link, +a:visited, +a:hover { + text-decoration: none; + color: #2980b9; +} + +a.with-right-arrow, .btn.with-right-arrow { + padding-right: 1.375rem; + position: relative; + background-image: url("../images/chevron-right-blue.svg"); + background-size: 6px 13px; + background-position: center right 5px; + background-repeat: no-repeat; +} +@media screen and (min-width: 768px) { + a.with-right-arrow, .btn.with-right-arrow { + background-size: 8px 14px; + background-position: center right 12px; + padding-right: 2rem; + } +} + +::-webkit-input-placeholder { + color: #2980b9; +} + +::-moz-placeholder { + color: #2980b9; +} + +:-ms-input-placeholder { + color: #2980b9; +} + +:-moz-placeholder { + color: #2980b9; +} + +.email-subscribe-form input.email { + color: #2980b9; + border: none; + border-bottom: 1px solid #939393; + width: 100%; + background-color: transparent; + outline: none; + font-size: 1.125rem; + letter-spacing: 0.25px; + line-height: 2.25rem; +} +.email-subscribe-form input[type="submit"] { + position: absolute; + right: 0; + top: 10px; + height: 15px; + width: 15px; + background-image: url("../images/arrow-right-with-tail.svg"); + background-color: transparent; + background-repeat: no-repeat; + background-size: 15px 15px; + background-position: center center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 0; +} + +.email-subscribe-form-fields-wrapper { + position: relative; +} + +.anchorjs-link { + color: #6c6c6d !important; +} +@media screen and (min-width: 768px) { + .anchorjs-link:hover { + color: inherit; + text-decoration: none !important; + } +} + +.pytorch-article #table-of-contents { + display: none; +} + +code, kbd, pre, samp { + font-family: IBMPlexMono,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; +} +code span, kbd span, pre span, samp span { + font-family: IBMPlexMono,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; +} + +pre { + padding: 1.125rem; +} +pre code { + font-size: 0.875rem; +} +pre.highlight { + background-color: #f3f4f7; + line-height: 1.3125rem; +} + +code.highlighter-rouge { + color: #6c6c6d; + background-color: #f3f4f7; + padding: 2px 6px; +} + +a:link code.highlighter-rouge, +a:visited code.highlighter-rouge, +a:hover code.highlighter-rouge { + color: #4974D1; +} +a:link.has-code, +a:visited.has-code, +a:hover.has-code { + color: #4974D1; +} + +p code, +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { + font-size: 78.5%; +} + +pre { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + +.header-holder { + height: 68px; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + left: 0; + margin-left: auto; + margin-right: auto; + position: fixed; + right: 0; + top: 0; + width: 100%; + z-index: 9999; + background-color: #ffffff; + border-bottom: 1px solid #e2e2e2; +} +@media screen and (min-width: 1100px) { + .header-holder { + height: 90px; + } +} + +.header-container { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +.header-container:before, .header-container:after { + content: ""; + display: table; +} +.header-container:after { + clear: both; +} +.header-container { + *zoom: 1; +} +@media screen and (min-width: 1100px) { + .header-container { + display: block; + } +} + +.header-logo { + height: 30px; + width: 115px; + background-image: url("../images/logo.png"); + background-repeat: no-repeat; + background-size: 115px 30px; + display: block; + float: left; + z-index: 10; +} +@media screen and (min-width: 1100px) { + .header-logo { + background-size: 154px 40px; + position: absolute; + height: 40px; + width: 154px; + top: -4px; + float: none; + } +} + +.main-menu-open-button { + background-image: url("../images/icon-menu-dots.svg"); + background-position: center center; + background-size: 25px 7px; + background-repeat: no-repeat; + width: 25px; + height: 17px; + position: absolute; + right: 0; + top: 4px; +} +@media screen and (min-width: 1100px) { + .main-menu-open-button { + display: none; + } +} + +.header-holder .main-menu { + display: none; +} +@media screen and (min-width: 1100px) { + .header-holder .main-menu { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + } +} +.header-holder .main-menu ul { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin: 0; +} +.header-holder .main-menu ul li { + display: inline-block; + margin-right: 40px; + position: relative; +} +.header-holder .main-menu ul li.active:after { + content: "•"; + bottom: -24px; + color: #2980b9; + font-size: 1.375rem; + left: 0; + position: absolute; + right: 0; + text-align: center; +} +.header-holder .main-menu ul li.active a { + color: #2980b9; +} +.header-holder .main-menu ul li.docs-active:after { + content: "•"; + bottom: -24px; + color: #2980b9; + font-size: 1.375rem; + left: -24px; + position: absolute; + right: 0; + text-align: center; +} +.header-holder .main-menu ul li:last-of-type { + margin-right: 0; +} +.header-holder .main-menu ul li a { + color: #ffffff; + font-size: 1.3rem; + letter-spacing: 0; + line-height: 2.125rem; + text-align: center; + text-decoration: none; +} +@media screen and (min-width: 1100px) { + .header-holder .main-menu ul li a:hover { + color: #2980b9; + } +} + +.mobile-main-menu { + display: none; +} +.mobile-main-menu.open { + background-color: #262626; + display: block; + height: 100%; + left: 0; + margin-left: auto; + margin-right: auto; + min-height: 100%; + position: fixed; + right: 0; + top: 0; + width: 100%; + z-index: 99999; +} + +.mobile-main-menu .container-fluid { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + height: 68px; + position: relative; +} +.mobile-main-menu .container-fluid:before, .mobile-main-menu .container-fluid:after { + content: ""; + display: table; +} +.mobile-main-menu .container-fluid:after { + clear: both; +} +.mobile-main-menu .container-fluid { + *zoom: 1; +} + +.mobile-main-menu.open ul { + list-style-type: none; + padding: 0; +} +.mobile-main-menu.open ul li a, .mobile-main-menu.open .resources-mobile-menu-title { + font-size: 2rem; + color: #ffffff; + letter-spacing: 0; + line-height: 4rem; + text-decoration: none; +} +.mobile-main-menu.open ul li.active a { + color: #2980b9; +} + +.main-menu-close-button { + background-image: url("../images/icon-close.svg"); + background-position: center center; + background-repeat: no-repeat; + background-size: 24px 24px; + height: 24px; + position: absolute; + right: 0; + width: 24px; + top: -4px; +} + +.mobile-main-menu-header-container { + position: relative; +} + +.mobile-main-menu-links-container { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding-left: 2.8125rem; + height: 90vh; + margin-top: -25px; + padding-top: 50%; + overflow-y: scroll; +} +.mobile-main-menu-links-container .main-menu { + height: 100vh; +} + +.mobile-main-menu-links-container ul.resources-mobile-menu-items li { + padding-left: 15px; +} + +.site-footer { + padding: 2.5rem 0; + width: 100%; + background: #000000; + background-size: 100%; + margin-left: 0; + margin-right: 0; + position: relative; + z-index: 201; +} +@media screen and (min-width: 768px) { + .site-footer { + padding: 5rem 0; + } +} +.site-footer p { + color: #ffffff; +} +.site-footer ul { + list-style-type: none; + padding-left: 0; + margin-bottom: 0; +} +.site-footer ul li { + font-size: 1.125rem; + line-height: 2rem; + color: #A0A0A1; + padding-bottom: 0.375rem; +} +.site-footer ul li.list-title { + padding-bottom: 0.75rem; + color: #ffffff; +} +.site-footer a:link, +.site-footer a:visited { + color: inherit; +} +@media screen and (min-width: 768px) { + .site-footer a:hover { + color: #2980b9; + } +} + +.docs-tutorials-resources { + background-color: #262626; + color: #ffffff; + padding-top: 2.5rem; + padding-bottom: 2.5rem; + position: relative; + z-index: 201; +} +@media screen and (min-width: 768px) { + .docs-tutorials-resources { + padding-top: 5rem; + padding-bottom: 5rem; + } +} +.docs-tutorials-resources p { + color: #929292; + font-size: 1.125rem; +} +.docs-tutorials-resources h2 { + font-size: 1.5rem; + letter-spacing: -0.25px; + text-transform: none; + margin-bottom: 0.25rem; +} +@media screen and (min-width: 768px) { + .docs-tutorials-resources h2 { + margin-bottom: 1.25rem; + } +} +.docs-tutorials-resources .col-md-4 { + margin-bottom: 2rem; + text-align: center; +} +@media screen and (min-width: 768px) { + .docs-tutorials-resources .col-md-4 { + margin-bottom: 0; + } +} +.docs-tutorials-resources .with-right-arrow { + margin-left: 12px; +} +.docs-tutorials-resources .with-right-arrow:hover { + background-image: url("../images/chevron-right-white.svg"); +} +.docs-tutorials-resources p { + font-size: 1rem; + line-height: 1.5rem; + letter-spacing: 0.22px; + color: #939393; + margin-bottom: 0; +} +@media screen and (min-width: 768px) { + .docs-tutorials-resources p { + margin-bottom: 1.25rem; + } +} +.docs-tutorials-resources a { + font-size: 1.125rem; + color: #2980b9; +} +.docs-tutorials-resources a:hover { + color: #ffffff; +} + +.footer-container { + position: relative; +} + +@media screen and (min-width: 768px) { + .footer-logo-wrapper { + position: absolute; + top: 0; + left: 30px; + } +} + +.footer-logo { + background-image: url("../images/logo-icon.svg"); + background-position: center; + background-repeat: no-repeat; + background-size: 20px 24px; + display: block; + height: 24px; + margin-bottom: 2.8125rem; + width: 20px; +} +@media screen and (min-width: 768px) { + .footer-logo { + background-size: 29px 36px; + height: 36px; + margin-bottom: 0; + margin-bottom: 0; + width: 29px; + } +} + +.footer-links-wrapper { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} +@media screen and (min-width: 768px) { + .footer-links-wrapper { + -ms-flex-wrap: initial; + flex-wrap: initial; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + } +} + +.footer-links-col { + margin-bottom: 3.75rem; + width: 50%; +} +@media screen and (min-width: 768px) { + .footer-links-col { + margin-bottom: 0; + width: 14%; + margin-right: 23px; + } + .footer-links-col.follow-us-col { + width: 18%; + margin-right: 0; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + .footer-links-col { + width: 18%; + margin-right: 30px; + } +} + +.footer-social-icons { + margin: 8.5625rem 0 2.5rem 0; +} +.footer-social-icons a { + height: 32px; + width: 32px; + display: inline-block; + background-color: #CCCDD1; + border-radius: 50%; + margin-right: 5px; +} +.footer-social-icons a.facebook { + background-image: url("../images/logo-facebook-dark.svg"); + background-position: center center; + background-size: 9px 18px; + background-repeat: no-repeat; +} +.footer-social-icons a.twitter { + background-image: url("../images/logo-twitter-dark.svg"); + background-position: center center; + background-size: 17px 17px; + background-repeat: no-repeat; +} +.footer-social-icons a.youtube { + background-image: url("../images/logo-youtube-dark.svg"); + background-position: center center; + background-repeat: no-repeat; +} + +.site-footer .mc-field-group { + margin-top: -2px; +} + +article.pytorch-article { + max-width: 920px; + margin: 0 auto; +} +article.pytorch-article h2, +article.pytorch-article h3, +article.pytorch-article h4, +article.pytorch-article h5, +article.pytorch-article h6 { + margin: 1.375rem 0; + color: #262626; +} +article.pytorch-article h2 { + font-size: 1.625rem; + letter-spacing: 1.33px; + line-height: 2rem; + text-transform: none; +} +article.pytorch-article h3 { + font-size: 1.5rem; + letter-spacing: -0.25px; + line-height: 1.875rem; + text-transform: none; +} +article.pytorch-article h4, +article.pytorch-article h5, +article.pytorch-article h6 { + font-size: 1.125rem; + letter-spacing: -0.19px; + line-height: 1.875rem; +} +article.pytorch-article p { + margin-bottom: 1.125rem; +} +article.pytorch-article p, +article.pytorch-article ul li, +article.pytorch-article ol li, +article.pytorch-article dl dt, +article.pytorch-article dl dd, +article.pytorch-article blockquote { + font-size: 1rem; + line-height: 1.375rem; + color: #262626; + letter-spacing: 0.01px; + font-weight: 500; +} +article.pytorch-article table { + /* margin-bottom: 2.5rem; */ + width: 100%; +} +article.pytorch-article table thead { + border-bottom: 1px solid #cacaca; +} +article.pytorch-article table th { + padding: 0.625rem; + color: #262626; +} +article.pytorch-article table td { + padding: 0.3125rem; +} +article.pytorch-article table tr th:first-of-type, +article.pytorch-article table tr td:first-of-type { + padding-left: 0; +} +article.pytorch-article table.docutils.field-list th.field-name { + padding: 0.3125rem; + padding-left: 0; +} +article.pytorch-article table.docutils.field-list td.field-body { + padding: 0.3125rem; +} +article.pytorch-article table.docutils.field-list td.field-body p:last-of-type { + margin-bottom: 0; +} +article.pytorch-article ul, +article.pytorch-article ol { + margin: 1.5rem 0 3.125rem 0; +} +@media screen and (min-width: 768px) { + article.pytorch-article ul, + article.pytorch-article ol { + padding-left: 6.25rem; + } +} +article.pytorch-article ul li, +article.pytorch-article ol li { + margin-bottom: 0.625rem; +} +article.pytorch-article dl { + margin-bottom: 1.5rem; +} +article.pytorch-article dl dt { + margin-bottom: 0.75rem; +} +article.pytorch-article pre { + margin-bottom: 2.5rem; +} +article.pytorch-article hr { + margin-top: 4.6875rem; + margin-bottom: 4.6875rem; +} +article.pytorch-article blockquote { + margin: 0 auto; + margin-bottom: 2.5rem; + width: 65%; +} +article.pytorch-article img { + max-width: 100%; +} + +html { + height: 100%; +} +@media screen and (min-width: 768px) { + html { + font-size: 16px; + } +} + +body { + background: #ffffff; + height: 100%; + margin: 0; +} +body.no-scroll { + height: 100%; + overflow: hidden; +} + +p { + margin-top: 0; + margin-bottom: 1.125rem; +} +p a:link, +p a:visited, +p a:hover { + color: #2980b9; + text-decoration: none; +} +@media screen and (min-width: 768px) { + p a:hover { + text-decoration: underline; + } +} +p a:link, +p a:visited, +p a:hover { + color: #2980b9; +} + +.wy-breadcrumbs li a { + color: #2980b9; +} + +ul.pytorch-breadcrumbs { + padding-left: 0; + list-style-type: none; +} +ul.pytorch-breadcrumbs li { + display: inline-block; + font-size: 0.875rem; +} +ul.pytorch-breadcrumbs a { + color: #2980b9; + text-decoration: none; +} + +.table-of-contents-link-wrapper { + display: block; + margin-top: 0; + padding: 1.25rem 1.875rem; + background-color: #f3f4f7; + position: relative; + color: #262626; + font-size: 1.25rem; +} +.table-of-contents-link-wrapper.is-open .toggle-table-of-contents { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); +} +@media screen and (min-width: 1100px) { + .table-of-contents-link-wrapper { + display: none; + } +} + +.toggle-table-of-contents { + background-image: url("../images/chevron-down-grey.svg"); + background-position: center center; + background-repeat: no-repeat; + background-size: 18px 18px; + height: 100%; + position: absolute; + right: 21px; + width: 30px; + top: 0; +} + +.tutorials-header .main-menu ul li a { + color: #262626; +} +.tutorials-header .main-menu-open-button { + background-image: url("../images/icon-menu-dots-dark.svg"); +} + +.rst-content footer .rating-hr.hr-top { + margin-bottom: -0.0625rem; +} +.rst-content footer .rating-hr.hr-bottom { + margin-top: -0.0625rem; +} +.rst-content footer .rating-container { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + font-size: 1.125rem; +} +.rst-content footer .rating-container .rating-prompt, .rst-content footer .rating-container .was-helpful-thank-you { + padding: 0.625rem 1.25rem 0.625rem 1.25rem; +} +.rst-content footer .rating-container .was-helpful-thank-you { + display: none; +} +.rst-content footer .rating-container .rating-prompt.yes-link, .rst-content footer .rating-container .rating-prompt.no-link { + color: #2980b9; + cursor: pointer; +} +.rst-content footer .rating-container .rating-prompt.yes-link:hover, .rst-content footer .rating-container .rating-prompt.no-link:hover { + background-color: #2980b9; + color: #ffffff; +} +.rst-content footer .rating-container .stars-outer { + display: inline-block; + position: relative; + font-family: FontAwesome; + padding: 0.625rem 1.25rem 0.625rem 1.25rem; +} +.rst-content footer .rating-container .stars-outer i { + cursor: pointer; +} +.rst-content footer .rating-container .stars-outer .star-fill { + color: #2980b9; +} +.rst-content footer div[role="contentinfo"] { + padding-top: 2.5rem; +} +.rst-content footer div[role="contentinfo"] p { + margin-bottom: 0; +} + +h1 { + font-size: 2rem; + letter-spacing: 1.78px; + line-height: 2.5rem; + text-transform: uppercase; + margin: 1.375rem 0; +} + +span.pre { + color: #6c6c6d; + background-color: #f3f4f7; + padding: 2px 0px; +} + +pre { + padding: 1.375rem; +} + +.highlight .c1 { + color: #6c6c6d; +} + +.headerlink { + display: none !important; +} + +a:link.has-code, +a:hover.has-code, +a:visited.has-code { + color: #4974D1; +} +a:link.has-code span, +a:hover.has-code span, +a:visited.has-code span { + color: #4974D1; +} + +article.pytorch-article ul, +article.pytorch-article ol { + padding-left: 1.875rem; + margin: 0; +} +article.pytorch-article ul li, +article.pytorch-article ol li { + margin: 0; + line-height: 1.75rem; +} +article.pytorch-article ul p, +article.pytorch-article ol p { + line-height: 1.75rem; + margin-bottom: 0; +} +article.pytorch-article ul ul, +article.pytorch-article ul ol, +article.pytorch-article ol ul, +article.pytorch-article ol ol { + margin: 0; +} +article.pytorch-article h1, +article.pytorch-article h2, +article.pytorch-article h3, +article.pytorch-article h4, +article.pytorch-article h5, +article.pytorch-article h6 { + font-weight: normal; +} +article.pytorch-article h1 a, +article.pytorch-article h2 a, +article.pytorch-article h3 a, +article.pytorch-article h4 a, +article.pytorch-article h5 a, +article.pytorch-article h6 a { + color: #262626; +} +article.pytorch-article p.caption { + margin-top: 1.25rem; +} + +article.pytorch-article .section:first-of-type h1:first-of-type { + margin-top: 0; +} + +article.pytorch-article .sphx-glr-thumbcontainer { + margin: 0; + border: 1px solid #d6d7d8; + border-radius: 0; + width: 45%; + text-align: center; + margin-bottom: 5%; +} +@media screen and (max-width: 1100px) { + article.pytorch-article .sphx-glr-thumbcontainer:nth-child(odd) { + margin-left: 0; + margin-right: 2.5%; + } + article.pytorch-article .sphx-glr-thumbcontainer:nth-child(even) { + margin-right: 0; + margin-left: 2.5%; + } + article.pytorch-article .sphx-glr-thumbcontainer .figure { + width: 40%; + } +} +@media screen and (min-width: 1101px) { + article.pytorch-article .sphx-glr-thumbcontainer { + margin-right: 3%; + margin-bottom: 3%; + width: 30%; + } +} +article.pytorch-article .sphx-glr-thumbcontainer .caption-text a { + font-size: 1rem; + color: #262626; + letter-spacing: 0; + line-height: 1.5rem; + text-decoration: none; +} +article.pytorch-article .sphx-glr-thumbcontainer:hover { + -webkit-box-shadow: none; + box-shadow: none; + border-bottom-color: #ffffff; +} +article.pytorch-article .sphx-glr-thumbcontainer:hover .figure:before { + bottom: 100%; +} +article.pytorch-article .sphx-glr-thumbcontainer .figure { + width: 80%; +} +article.pytorch-article .sphx-glr-thumbcontainer .figure:before { + content: ""; + display: block; + position: absolute; + top: 0; + bottom: 35%; + left: 0; + right: 0; + background: #8A94B3; + opacity: 0.10; +} +article.pytorch-article .sphx-glr-thumbcontainer .figure a.reference.internal { + text-align: left; +} +@media screen and (min-width: 768px) { + article.pytorch-article .sphx-glr-thumbcontainer:after { + content: ""; + display: block; + width: 0; + height: 1px; + position: absolute; + bottom: 0; + left: 0; + background-color: #2980b9; + -webkit-transition: width .250s ease-in-out; + transition: width .250s ease-in-out; + } + article.pytorch-article .sphx-glr-thumbcontainer:hover:after { + width: 100%; + } +} +@media screen and (min-width: 768px) { + article.pytorch-article .sphx-glr-thumbcontainer:after { + background-color: #2980b9; + } +} + +article.pytorch-article .section :not(dt) > code { + color: #262626; + border-top: solid 2px #ffffff; + background-color: #ffffff; + border-bottom: solid 2px #ffffff; + padding: 0px 3px; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} +article.pytorch-article .section :not(dt) > code .pre { + outline: 0px; + padding: 0px; +} +article.pytorch-article .function dt, article.pytorch-article .attribute dt, article.pytorch-article .class .attribute dt, article.pytorch-article .class dt { + position: relative; + background: #f3f4f7; + padding: 0.5rem; + border-left: 3px solid #2980b9; + word-wrap: break-word; + padding-right: 100px; +} +article.pytorch-article .function dt em.property, article.pytorch-article .attribute dt em.property, article.pytorch-article .class dt em.property { + font-family: inherit; +} +article.pytorch-article .function dt em, article.pytorch-article .attribute dt em, article.pytorch-article .class .attribute dt em, article.pytorch-article .class dt em, article.pytorch-article .function dt .sig-paren, article.pytorch-article .attribute dt .sig-paren, article.pytorch-article .class dt .sig-paren { + font-family: IBMPlexMono,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; + font-size: 87.5%; +} +article.pytorch-article .function dt a, article.pytorch-article .attribute dt a, article.pytorch-article .class .attribute dt a, article.pytorch-article .class dt a { + right: 30px; + padding-right: 0; + top: 50%; + -webkit-transform: perspective(1px) translateY(-50%); + transform: perspective(1px) translateY(-50%); +} +article.pytorch-article .function dt:hover .viewcode-link, article.pytorch-article .attribute dt:hover .viewcode-link, article.pytorch-article .class dt:hover .viewcode-link { + color: #2980b9; +} +article.pytorch-article .function .anchorjs-link, article.pytorch-article .attribute .anchorjs-link, article.pytorch-article .class .anchorjs-link { + display: inline; + position: absolute; + right: 8px; + font-size: 1.5625rem !important; + padding-left: 0; +} +article.pytorch-article .function dt > code, article.pytorch-article .attribute dt > code, article.pytorch-article .class .attribute dt > code, article.pytorch-article .class dt > code { + color: #262626; + border-top: solid 2px #f3f4f7; + background-color: #f3f4f7; + border-bottom: solid 2px #f3f4f7; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} +article.pytorch-article .function .viewcode-link, article.pytorch-article .attribute .viewcode-link, article.pytorch-article .class .viewcode-link { + padding-left: 0.6rem; + position: absolute; + font-size: 0.875rem; + color: #979797; + letter-spacing: 0; + line-height: 1.5rem; + text-transform: uppercase; +} +article.pytorch-article .function dd, article.pytorch-article .attribute dd, article.pytorch-article .class .attribute dd, article.pytorch-article .class dd { + padding-left: 3.75rem; +} +article.pytorch-article .function dd p, article.pytorch-article .attribute dd p, article.pytorch-article .class .attribute dd p, article.pytorch-article .class dd p { + color: #262626; +} +article.pytorch-article .function table tbody tr th.field-name, article.pytorch-article .attribute table tbody tr th.field-name, article.pytorch-article .class table tbody tr th.field-name { + white-space: nowrap; + color: #262626; + width: 20%; +} +@media screen and (min-width: 768px) { + article.pytorch-article .function table tbody tr th.field-name, article.pytorch-article .attribute table tbody tr th.field-name, article.pytorch-article .class table tbody tr th.field-name { + width: 15%; + } +} +article.pytorch-article .function table tbody tr td.field-body, article.pytorch-article .attribute table tbody tr td.field-body, article.pytorch-article .class table tbody tr td.field-body { + padding: 0.625rem; + width: 80%; + color: #262626; +} +@media screen and (min-width: 768px) { + article.pytorch-article .function table tbody tr td.field-body, article.pytorch-article .attribute table tbody tr td.field-body, article.pytorch-article .class table tbody tr td.field-body { + width: 85%; + } +} +@media screen and (min-width: 1600px) { + article.pytorch-article .function table tbody tr td.field-body, article.pytorch-article .attribute table tbody tr td.field-body, article.pytorch-article .class table tbody tr td.field-body { + padding-left: 1.25rem; + } +} +article.pytorch-article .function table tbody tr td.field-body p, article.pytorch-article .attribute table tbody tr td.field-body p, article.pytorch-article .class table tbody tr td.field-body p { + padding-left: 0px; +} +article.pytorch-article .function table tbody tr td.field-body p:last-of-type, article.pytorch-article .attribute table tbody tr td.field-body p:last-of-type, article.pytorch-article .class table tbody tr td.field-body p:last-of-type { + margin-bottom: 0; +} +article.pytorch-article .function table tbody tr td.field-body ol, article.pytorch-article .attribute table tbody tr td.field-body ol, article.pytorch-article .class table tbody tr td.field-body ol, article.pytorch-article .function table tbody tr td.field-body ul, article.pytorch-article .attribute table tbody tr td.field-body ul, article.pytorch-article .class table tbody tr td.field-body ul { + padding-left: 1rem; + padding-bottom: 0; +} +article.pytorch-article .function table.docutils.field-list, article.pytorch-article .attribute table.docutils.field-list, article.pytorch-article .class table.docutils.field-list { + margin-bottom: 0.75rem; +} +article.pytorch-article .attribute .has-code { + float: none; +} +article.pytorch-article .class dt { + border-left: none; + border-top: 3px solid #2980b9; + padding-left: 4em; +} +article.pytorch-article .class dt em.property { + position: absolute; + left: 0.5rem; +} +article.pytorch-article .class dd .docutils dt { + padding-left: 0.5rem; +} +article.pytorch-article .class em.property { + text-transform: uppercase; + font-style: normal; + color: #2980b9; + font-size: 1rem; + letter-spacing: 0; + padding-right: 0.75rem; +} +article.pytorch-article .class dl dt em.property { + position: static; + left: 0; + padding-right: 0; +} +article.pytorch-article .class .method dt, +article.pytorch-article .class .staticmethod dt { + border-left: 3px solid #2980b9; + border-top: none; +} +article.pytorch-article .class .method dt, +article.pytorch-article .class .staticmethod dt { + padding-left: 0.5rem; +} +article.pytorch-article .class .attribute dt { + border-top: none; +} +article.pytorch-article .class .attribute dt em.property { + position: relative; + left: 0; +} +/* article.pytorch-article table { + table-layout: fixed; +} */ + +article.pytorch-article .note, +article.pytorch-article .warning, +article.pytorch-article .tip, +article.pytorch-article .seealso, +article.pytorch-article .hint, +article.pytorch-article .important, +article.pytorch-article .caution, +article.pytorch-article .danger, +article.pytorch-article .attention, +article.pytorch-article .error { + background: #f3f4f7; + margin-top: 1.875rem; + margin-bottom: 1.125rem; +} +article.pytorch-article .note .admonition-title, +article.pytorch-article .warning .admonition-title, +article.pytorch-article .tip .admonition-title, +article.pytorch-article .seealso .admonition-title, +article.pytorch-article .hint .admonition-title, +article.pytorch-article .important .admonition-title, +article.pytorch-article .caution .admonition-title, +article.pytorch-article .danger .admonition-title, +article.pytorch-article .attention .admonition-title, +article.pytorch-article .error .admonition-title { + color: #ffffff; + letter-spacing: 1px; + text-transform: uppercase; + margin-bottom: 1.125rem; + padding: 3px 0 3px 1.375rem; + position: relative; + font-size: 0.875rem; +} +article.pytorch-article .note .admonition-title:before, +article.pytorch-article .warning .admonition-title:before, +article.pytorch-article .tip .admonition-title:before, +article.pytorch-article .seealso .admonition-title:before, +article.pytorch-article .hint .admonition-title:before, +article.pytorch-article .important .admonition-title:before, +article.pytorch-article .caution .admonition-title:before, +article.pytorch-article .danger .admonition-title:before, +article.pytorch-article .attention .admonition-title:before, +article.pytorch-article .error .admonition-title:before { + content: "\2022"; + position: absolute; + left: 9px; + color: #ffffff; + top: 2px; +} +article.pytorch-article .note p:nth-child(n + 2), +article.pytorch-article .warning p:nth-child(n + 2), +article.pytorch-article .tip p:nth-child(n + 2), +article.pytorch-article .seealso p:nth-child(n + 2), +article.pytorch-article .hint p:nth-child(n + 2), +article.pytorch-article .important p:nth-child(n + 2), +article.pytorch-article .caution p:nth-child(n + 2), +article.pytorch-article .danger p:nth-child(n + 2), +article.pytorch-article .attention p:nth-child(n + 2), +article.pytorch-article .error p:nth-child(n + 2) { + padding: 0 1.375rem; +} +article.pytorch-article .note table, +article.pytorch-article .warning table, +article.pytorch-article .tip table, +article.pytorch-article .seealso table, +article.pytorch-article .hint table, +article.pytorch-article .important table, +article.pytorch-article .caution table, +article.pytorch-article .danger table, +article.pytorch-article .attention table, +article.pytorch-article .error table { + margin: 0 2rem; + width: auto; +} +article.pytorch-article .note :not(dt) > code, +article.pytorch-article .warning :not(dt) > code, +article.pytorch-article .tip :not(dt) > code, +article.pytorch-article .seealso :not(dt) > code, +article.pytorch-article .hint :not(dt) > code, +article.pytorch-article .important :not(dt) > code, +article.pytorch-article .caution :not(dt) > code, +article.pytorch-article .danger :not(dt) > code, +article.pytorch-article .attention :not(dt) > code, +article.pytorch-article .error :not(dt) > code { + border-top: solid 2px #ffffff; + background-color: #ffffff; + border-bottom: solid 2px #ffffff; + padding: 0px 3px; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; + outline: 1px solid #e9e9e9; +} +article.pytorch-article .note :not(dt) > code .pre, +article.pytorch-article .warning :not(dt) > code .pre, +article.pytorch-article .tip :not(dt) > code .pre, +article.pytorch-article .seealso :not(dt) > code .pre, +article.pytorch-article .hint :not(dt) > code .pre, +article.pytorch-article .important :not(dt) > code .pre, +article.pytorch-article .caution :not(dt) > code .pre, +article.pytorch-article .danger :not(dt) > code .pre, +article.pytorch-article .attention :not(dt) > code .pre, +article.pytorch-article .error :not(dt) > code .pre { + outline: 0px; + padding: 0px; +} +article.pytorch-article .note pre, +article.pytorch-article .warning pre, +article.pytorch-article .tip pre, +article.pytorch-article .seealso pre, +article.pytorch-article .hint pre, +article.pytorch-article .important pre, +article.pytorch-article .caution pre, +article.pytorch-article .danger pre, +article.pytorch-article .attention pre, +article.pytorch-article .error pre { + margin-bottom: 0; +} +article.pytorch-article .note .highlight, +article.pytorch-article .warning .highlight, +article.pytorch-article .tip .highlight, +article.pytorch-article .seealso .highlight, +article.pytorch-article .hint .highlight, +article.pytorch-article .important .highlight, +article.pytorch-article .caution .highlight, +article.pytorch-article .danger .highlight, +article.pytorch-article .attention .highlight, +article.pytorch-article .error .highlight { + margin: 0 2rem 1.125rem 2rem; +} +article.pytorch-article .note ul, +article.pytorch-article .note ol, +article.pytorch-article .warning ul, +article.pytorch-article .warning ol, +article.pytorch-article .tip ul, +article.pytorch-article .tip ol, +article.pytorch-article .seealso ul, +article.pytorch-article .seealso ol, +article.pytorch-article .hint ul, +article.pytorch-article .hint ol, +article.pytorch-article .important ul, +article.pytorch-article .important ol, +article.pytorch-article .caution ul, +article.pytorch-article .caution ol, +article.pytorch-article .danger ul, +article.pytorch-article .danger ol, +article.pytorch-article .attention ul, +article.pytorch-article .attention ol, +article.pytorch-article .error ul, +article.pytorch-article .error ol { + padding-left: 3.25rem; +} +article.pytorch-article .note ul li, +article.pytorch-article .note ol li, +article.pytorch-article .warning ul li, +article.pytorch-article .warning ol li, +article.pytorch-article .tip ul li, +article.pytorch-article .tip ol li, +article.pytorch-article .seealso ul li, +article.pytorch-article .seealso ol li, +article.pytorch-article .hint ul li, +article.pytorch-article .hint ol li, +article.pytorch-article .important ul li, +article.pytorch-article .important ol li, +article.pytorch-article .caution ul li, +article.pytorch-article .caution ol li, +article.pytorch-article .danger ul li, +article.pytorch-article .danger ol li, +article.pytorch-article .attention ul li, +article.pytorch-article .attention ol li, +article.pytorch-article .error ul li, +article.pytorch-article .error ol li { + color: #262626; +} +article.pytorch-article .note p, +article.pytorch-article .warning p, +article.pytorch-article .tip p, +article.pytorch-article .seealso p, +article.pytorch-article .hint p, +article.pytorch-article .important p, +article.pytorch-article .caution p, +article.pytorch-article .danger p, +article.pytorch-article .attention p, +article.pytorch-article .error p { + margin-top: 1.125rem; +} +article.pytorch-article .note .admonition-title { + background: #54c7ec; +} +article.pytorch-article .warning .admonition-title { + background: #e94f3b; +} +article.pytorch-article .tip .admonition-title { + background: #6bcebb; +} +article.pytorch-article .seealso .admonition-title { + background: #6bcebb; +} +article.pytorch-article .hint .admonition-title { + background: #a2cdde; +} +article.pytorch-article .important .admonition-title { + background: #5890ff; +} +article.pytorch-article .caution .admonition-title { + background: #f7923a; +} +article.pytorch-article .danger .admonition-title { + background: #db2c49; +} +article.pytorch-article .attention .admonition-title { + background: #f5a623; +} +article.pytorch-article .error .admonition-title { + background: #cc2f90; +} +article.pytorch-article .sphx-glr-download-link-note.admonition.note, +article.pytorch-article .reference.download.internal, article.pytorch-article .sphx-glr-signature { + display: none; +} +article.pytorch-article .admonition >:last-child { + margin-bottom: 0; + padding-bottom: 1.125rem !important; +} + +.pytorch-article div.sphx-glr-download a { + background-color: #f3f4f7; + background-image: url("../images/arrow-down-blue.svg"); + background-repeat: no-repeat; + background-position: left 10px center; + background-size: 15px 15px; + border-radius: 0; + border: none; + display: block; + text-align: left; + padding: 0.9375rem 3.125rem; + position: relative; + margin: 1.25rem auto; +} +@media screen and (min-width: 768px) { + .pytorch-article div.sphx-glr-download a:after { + content: ""; + display: block; + width: 0; + height: 1px; + position: absolute; + bottom: 0; + left: 0; + background-color: #2980b9; + -webkit-transition: width .250s ease-in-out; + transition: width .250s ease-in-out; + } + .pytorch-article div.sphx-glr-download a:hover:after { + width: 100%; + } +} +@media screen and (min-width: 768px) { + .pytorch-article div.sphx-glr-download a:after { + background-color: #2980b9; + } +} +@media screen and (min-width: 768px) { + .pytorch-article div.sphx-glr-download a { + background-position: left 20px center; + } +} +.pytorch-article div.sphx-glr-download a:hover { + -webkit-box-shadow: none; + box-shadow: none; + text-decoration: none; + background-image: url("../images/arrow-down-blue.svg"); + background-color: #f3f4f7; +} +.pytorch-article div.sphx-glr-download a span.pre { + background-color: transparent; + font-size: 1.125rem; + padding: 0; + color: #262626; +} +.pytorch-article div.sphx-glr-download a code, .pytorch-article div.sphx-glr-download a kbd, .pytorch-article div.sphx-glr-download a pre, .pytorch-article div.sphx-glr-download a samp, .pytorch-article div.sphx-glr-download a span.pre { + font-family: FreightSans, Helvetica Neue, Helvetica, Arial, sans-serif; +} + +.pytorch-article p.sphx-glr-script-out { + margin-bottom: 1.125rem; +} + +.pytorch-article div.sphx-glr-script-out { + margin-bottom: 2.5rem; +} +.pytorch-article div.sphx-glr-script-out .highlight { + margin-left: 0; + margin-top: 0; +} +.pytorch-article div.sphx-glr-script-out .highlight pre { + background-color: #fdede9; + padding: 1.5625rem; + color: #837b79; +} +.pytorch-article div.sphx-glr-script-out + p { + margin-top: unset; +} + +article.pytorch-article .wy-table-responsive { + overflow: auto; + margin-bottom: 2.5rem; +} + +article.pytorch-article .wy-table-responsive table { + border: none; + /* border-color: #ffffff !important; */ +} +article.pytorch-article .wy-table-responsive table thead tr { + border-bottom: 2px solid #6c6c6d; +} +article.pytorch-article .wy-table-responsive table thead th { + /* text-align: center; */ + line-height: 1.75rem; + padding-left: 0.9375rem; + padding-right: 0.9375rem; +} +article.pytorch-article .wy-table-responsive table tbody .row-odd { + background-color: #f3f4f7; +} +article.pytorch-article .wy-table-responsive table tbody td { + color: #6c6c6d; + white-space: normal; + padding: 0.9375rem; + font-size: 1rem; + line-height: 1.375rem; + border-bottom: #e4e4e4 1px solid; +} +article.pytorch-article .wy-table-responsive table tbody td .pre { + background: #ffffff; + color: #2980b9; + font-size: 87.5%; +} +article.pytorch-article .wy-table-responsive table tbody td code { + font-size: 87.5%; +} + +a[rel~="prev"], a[rel~="next"] { + padding: 0.375rem 0 0 0; +} + +img.next-page, +img.previous-page { + width: 8px; + height: 10px; + position: relative; + top: -1px; +} + +img.previous-page { + -webkit-transform: scaleX(-1); + transform: scaleX(-1); +} + +.rst-footer-buttons { + margin-top: 1.875rem; + margin-bottom: 1.875rem; +} +.rst-footer-buttons .btn:focus, +.rst-footer-buttons .btn.focus { + -webkit-box-shadow: none; + box-shadow: none; +} + +article.pytorch-article blockquote { + margin-left: 3.75rem; + color: #6c6c6d; +} + +article.pytorch-article .caption { + color: #6c6c6d; + letter-spacing: 0.25px; + line-height: 2.125rem; +} + +article.pytorch-article .math { + color: #262626; + width: auto; + text-align: center; +} +article.pytorch-article .math img { + width: auto; +} + +.pytorch-breadcrumbs-wrapper { + width: 100%; +} +@media screen and (min-width: 1101px) { + .pytorch-breadcrumbs-wrapper { + float: left; + margin-left: 3%; + width: 75%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-breadcrumbs-wrapper { + width: 850px; + margin-left: 1.875rem; + } +} +.pytorch-breadcrumbs-wrapper .pytorch-breadcrumbs-aside { + padding-left: 20px; + float: right; + margin-top: 5px; + display: block; +} + +.pytorch-article .container { + padding-left: 0; + padding-right: 0; + max-width: none; +} + +a:link, +a:visited, +a:hover { + color: #2980b9; +} + +::-webkit-input-placeholder { + color: #2980b9; +} + +::-moz-placeholder { + color: #2980b9; +} + +:-ms-input-placeholder { + color: #2980b9; +} + +:-moz-placeholder { + color: #2980b9; +} + +@media screen and (min-width: 768px) { + .site-footer a:hover { + color: #2980b9; + } +} + +.docs-tutorials-resources a { + color: #2980b9; +} + +.header-holder { + position: relative; + z-index: 201; +} + +.header-holder .main-menu ul li.active:after { + color: #2980b9; +} +.header-holder .main-menu ul li.active a { + color: #2980b9; +} +@media screen and (min-width: 1100px) { + .header-holder .main-menu ul li a:hover { + color: #2980b9; + } +} + +.mobile-main-menu.open ul li.active a { + color: #2980b9; +} + +.version { + padding-bottom: 1rem; +} + +.pytorch-call-to-action-links { + padding-top: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +@media screen and (min-width: 768px) { + .pytorch-call-to-action-links { + padding-top: 2.5rem; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + .pytorch-call-to-action-links { + padding-top: 0; + } +} +@media (min-width: 1100px) and (max-width: 1239px) { + .pytorch-call-to-action-links { + padding-top: 2.5rem; + } +} +.pytorch-call-to-action-links #tutorial-type { + display: none; +} +.pytorch-call-to-action-links .call-to-action-img, .pytorch-call-to-action-links .call-to-action-notebook-img { + height: 1.375rem; + width: 1.375rem; + margin-right: 10px; +} +.pytorch-call-to-action-links .call-to-action-notebook-img { + height: 1rem; +} +.pytorch-call-to-action-links a { + padding-right: 1.25rem; + color: #000000; + cursor: pointer; +} +.pytorch-call-to-action-links a:hover { + color: #2980b9; +} +.pytorch-call-to-action-links a .call-to-action-desktop-view { + display: none; +} +@media screen and (min-width: 768px) { + .pytorch-call-to-action-links a .call-to-action-desktop-view { + display: block; + } +} +.pytorch-call-to-action-links a .call-to-action-mobile-view { + display: block; +} +@media screen and (min-width: 768px) { + .pytorch-call-to-action-links a .call-to-action-mobile-view { + display: none; + } +} +.pytorch-call-to-action-links a #google-colab-link, .pytorch-call-to-action-links a #download-notebook-link, +.pytorch-call-to-action-links a #github-view-link { + padding-bottom: 0.625rem; + border-bottom: 1px solid #f3f4f7; + padding-right: 2.5rem; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +.pytorch-call-to-action-links a #google-colab-link:hover, .pytorch-call-to-action-links a #download-notebook-link:hover, +.pytorch-call-to-action-links a #github-view-link:hover { + border-bottom-color: #2980b9; + color: #2980b9; +} + +#tutorial-cards-container #tutorial-cards { + width: 100%; +} +#tutorial-cards-container .tutorials-nav { + padding-left: 0; + padding-right: 0; + padding-bottom: 0; +} +#tutorial-cards-container .tutorials-hr { + margin-top: 1rem; + margin-bottom: 1rem; +} +#tutorial-cards-container .card.tutorials-card { + border-radius: 0; + border-color: #f3f4f7; + height: 98px; + margin-bottom: 1.25rem; + margin-bottom: 1.875rem; + overflow: scroll; + background-color: #f3f4f7; + cursor: pointer; +} +@media screen and (min-width: 1240px) { + #tutorial-cards-container .card.tutorials-card { + height: 200px; + overflow: inherit; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + #tutorial-cards-container .card.tutorials-card { + height: 200px; + overflow: scroll; + } +} +#tutorial-cards-container .card.tutorials-card .tutorials-image { + position: absolute; + top: 0px; + right: 0px; + height: 96px; + width: 96px; + opacity: 0.5; +} +#tutorial-cards-container .card.tutorials-card .tutorials-image img { + height: 100%; + width: 100%; +} +@media screen and (min-width: 768px) { + #tutorial-cards-container .card.tutorials-card .tutorials-image { + height: 100%; + width: 25%; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + #tutorial-cards-container .card.tutorials-card .tutorials-image { + height: 100%; + width: 198px; + } +} +#tutorial-cards-container .card.tutorials-card .tutorials-image:before { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1; + background: #000000; + opacity: .075; +} +#tutorial-cards-container .card.tutorials-card .card-title-container { + width: 70%; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; +} +@media screen and (min-width: 768px) { + #tutorial-cards-container .card.tutorials-card .card-title-container { + width: 75%; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + #tutorial-cards-container .card.tutorials-card .card-title-container { + width: 70%; + } +} +#tutorial-cards-container .card.tutorials-card .card-title-container h4 { + margin-bottom: 1.125rem; + margin-top: 0; + font-size: 1.5rem; +} +#tutorial-cards-container .card.tutorials-card p.card-summary, #tutorial-cards-container .card.tutorials-card p.tags { + font-size: 0.9375rem; + line-height: 1.5rem; + margin-bottom: 0; + color: #6c6c6d; + font-weight: 400; + width: 70%; +} +@media screen and (min-width: 768px) { + #tutorial-cards-container .card.tutorials-card p.card-summary, #tutorial-cards-container .card.tutorials-card p.tags { + width: 75%; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + #tutorial-cards-container .card.tutorials-card p.card-summary, #tutorial-cards-container .card.tutorials-card p.tags { + width: 70%; + } +} +#tutorial-cards-container .card.tutorials-card p.tags { + margin-top: 30px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} +#tutorial-cards-container .card.tutorials-card h4 { + color: #262626; + margin-bottom: 1.125rem; +} +#tutorial-cards-container .card.tutorials-card a { + height: 100%; +} +@media screen and (min-width: 768px) { + #tutorial-cards-container .card.tutorials-card a { + min-height: 190px; + } +} +@media (min-width: 768px) and (max-width: 1239px) { + #tutorial-cards-container .card.tutorials-card a { + min-height: 234px; + } +} +@media screen and (min-width: 768px) { + #tutorial-cards-container .card.tutorials-card:after { + content: ""; + display: block; + width: 0; + height: 1px; + position: absolute; + bottom: 0; + left: 0; + background-color: #2980b9; + -webkit-transition: width .250s ease-in-out; + transition: width .250s ease-in-out; + } + #tutorial-cards-container .card.tutorials-card:hover:after { + width: 100%; + } +} +#tutorial-cards-container .card.tutorials-card:hover { + background-color: #ffffff; + border: 1px solid #e2e2e2; + border-bottom: none; +} +#tutorial-cards-container .card.tutorials-card:hover p.card-summary { + color: #262626; +} +#tutorial-cards-container .card.tutorials-card:hover .tutorials-image { + opacity: unset; +} +#tutorial-cards-container .tutorial-tags-container { + width: 75%; +} +#tutorial-cards-container .tutorial-tags-container.active { + width: 0; +} +#tutorial-cards-container .tutorial-filter-menu ul { + list-style-type: none; + padding-left: 1.25rem; +} +#tutorial-cards-container .tutorial-filter-menu ul li { + padding-right: 1.25rem; + word-break: break-all; +} +#tutorial-cards-container .tutorial-filter-menu ul li a { + color: #979797; +} +#tutorial-cards-container .tutorial-filter-menu ul li a:hover { + color: #2980b9; +} +#tutorial-cards-container .tutorial-filter { + cursor: pointer; +} +#tutorial-cards-container .filter-btn { + color: #979797; + border: 1px solid #979797; + display: inline-block; + text-align: center; + white-space: nowrap; + vertical-align: middle; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + margin-bottom: 5px; +} +#tutorial-cards-container .filter-btn:hover { + border: 1px solid #2980b9; + color: #2980b9; +} +#tutorial-cards-container .filter-btn.selected { + background-color: #2980b9; + border: 1px solid #2980b9; + color: #ffffff; +} +#tutorial-cards-container .all-tag-selected { + background-color: #979797; + color: #ffffff; +} +#tutorial-cards-container .all-tag-selected:hover { + border-color: #979797; + color: #ffffff; +} +#tutorial-cards-container .pagination .page { + border: 1px solid #dee2e6; + padding: 0.5rem 0.75rem; +} +#tutorial-cards-container .pagination .active .page { + background-color: #dee2e6; +} + +article.pytorch-article .tutorials-callout-container { + padding-bottom: 50px; +} +article.pytorch-article .tutorials-callout-container .col-md-6 { + padding-bottom: 10px; +} +article.pytorch-article .tutorials-callout-container .text-container { + padding: 10px 0px 30px 0px; + padding-bottom: 10px; +} +article.pytorch-article .tutorials-callout-container .text-container .body-paragraph { + color: #666666; + font-weight: 300; + font-size: 1.125rem; + line-height: 1.875rem; +} +article.pytorch-article .tutorials-callout-container .btn.callout-button { + font-size: 1.125rem; + border-radius: 0; + border: none; + background-color: #f3f4f7; + color: #6c6c6d; + font-weight: 400; + position: relative; + letter-spacing: 0.25px; +} +@media screen and (min-width: 768px) { + article.pytorch-article .tutorials-callout-container .btn.callout-button:after { + content: ""; + display: block; + width: 0; + height: 1px; + position: absolute; + bottom: 0; + left: 0; + background-color: #2980b9; + -webkit-transition: width .250s ease-in-out; + transition: width .250s ease-in-out; + } + article.pytorch-article .tutorials-callout-container .btn.callout-button:hover:after { + width: 100%; + } +} +article.pytorch-article .tutorials-callout-container .btn.callout-button a { + color: inherit; +} + +.pytorch-container { + margin: 0 auto; + padding: 0 1.875rem; + width: auto; + position: relative; +} +@media screen and (min-width: 1100px) { + .pytorch-container { + padding: 0; + } +} +@media screen and (min-width: 1101px) { + .pytorch-container { + margin-left: 25%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-container { + margin-left: 350px; + } +} +.pytorch-container:before, .pytorch-container:after { + content: ""; + display: table; +} +.pytorch-container:after { + clear: both; +} +.pytorch-container { + *zoom: 1; +} + +.pytorch-content-wrap { + background-color: #ffffff; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + position: relative; + padding-top: 0; +} +.pytorch-content-wrap:before, .pytorch-content-wrap:after { + content: ""; + display: table; +} +.pytorch-content-wrap:after { + clear: both; +} +.pytorch-content-wrap { + *zoom: 1; +} +@media screen and (min-width: 1101px) { + .pytorch-content-wrap { + padding-top: 45px; + float: left; + width: 100%; + display: block; + } +} +@media screen and (min-width: 1600px) { + .pytorch-content-wrap { + width: 100%; + } +} + +.pytorch-content { + background: #ffffff; + width: 100%; + max-width: 700px; + position: relative; +} + +.pytorch-content-left { + min-height: 100vh; + margin-top: 2.5rem; + width: 100%; +} +@media screen and (min-width: 1101px) { + .pytorch-content-left { + margin-top: 0; + margin-left: 3%; + width: 75%; + float: left; + } +} +@media screen and (min-width: 1600px) { + .pytorch-content-left { + width: 850px; + margin-left: 30px; + } +} +.pytorch-content-left .main-content { + padding-top: 0.9375rem; +} +.pytorch-content-left .main-content ul.simple { + padding-bottom: 1.25rem; +} +.pytorch-content-left .main-content .note:nth-child(1), .pytorch-content-left .main-content .warning:nth-child(1) { + margin-top: 0; +} + +.pytorch-content-right { + display: none; + position: relative; + overflow-x: hidden; + overflow-y: hidden; + z-index: 0; +} +@media screen and (min-width: 1101px) { + .pytorch-content-right { + display: block; + margin-left: 0; + width: 19%; + float: left; + height: 100%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-content-right { + width: 280px; + } +} + +@media screen and (min-width: 1101px) { + .pytorch-side-scroll { + position: relative; + overflow-x: hidden; + overflow-y: visible; + height: 100%; + } +} + +.pytorch-menu-vertical { + padding: 1.25rem 1.875rem 2.5rem 1.875rem; +} +@media screen and (min-width: 1101px) { + .pytorch-menu-vertical { + display: block; + padding-top: 0; + padding-right: 13.5%; + padding-bottom: 5.625rem; + } +} +@media screen and (min-width: 1600px) { + .pytorch-menu-vertical { + padding-left: 0; + padding-right: 1.5625rem; + } +} + +.pytorch-left-menu { + display: none; + background-color: #f3f4f7; + color: #262626; + overflow: scroll; +} +@media screen and (min-width: 1101px) { + .pytorch-left-menu { + display: block; + overflow-x: hidden; + overflow-y: hidden; + padding-bottom: 110px; + padding: 0 1.875rem 0 0; + width: 25%; + z-index: 200; + float: left; + } + .pytorch-left-menu.make-fixed { + position: fixed; + top: 0; + bottom: 0; + left: 0; + float: none; + } +} +@media screen and (min-width: 1600px) { + .pytorch-left-menu { + padding: 0 0 0 1.875rem; + width: 350px; + } +} + +.expand-menu, .hide-menu { + color: #6c6c6d; + padding-left: 10px; + cursor: pointer; +} + +.collapse { + display: none; +} + +.left-nav-top-caption { + padding-top: 1rem; +} + +.pytorch-left-menu p.caption { + color: #262626; + display: block; + font-size: 1rem; + line-height: 1.375rem; + margin-bottom: 1rem; + text-transform: none; + white-space: normal; +} + +.pytorch-left-menu-search { + margin-bottom: 2.5rem; +} +@media screen and (min-width: 1101px) { + .pytorch-left-menu-search { + margin: 1.25rem 0.625rem 1.875rem 0; + } +} + +.pytorch-left-menu-search ::-webkit-input-placeholder { + color: #262626; +} +.pytorch-left-menu-search ::-moz-placeholder { + color: #262626; +} +.pytorch-left-menu-search :-ms-input-placeholder { + color: #262626; +} +.pytorch-left-menu-search ::-ms-input-placeholder { + color: #262626; +} +.pytorch-left-menu-search ::placeholder { + color: #262626; +} + +.pytorch-left-menu-search input[type=text] { + border-radius: 0; + padding: 0.5rem 0.75rem; + border-color: #ffffff; + color: #262626; + border-style: solid; + font-size: 1rem; + width: 100%; + background-color: #f3f4f7; + background-image: url("../images/search-icon.svg"); + background-repeat: no-repeat; + background-size: 18px 18px; + background-position: 12px 10px; + padding-left: 40px; + background-color: #ffffff; +} +.pytorch-left-menu-search input[type=text]:focus { + outline: 0; +} + +@media screen and (min-width: 1101px) { + .pytorch-left-menu .pytorch-side-scroll { + width: 120%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-left-menu .pytorch-side-scroll { + width: 340px; + } +} + +.pytorch-right-menu { + min-height: 100px; + overflow-x: hidden; + overflow-y: hidden; + left: 0; + z-index: 200; + padding-top: 0; + position: relative; +} +@media screen and (min-width: 1101px) { + .pytorch-right-menu { + width: 100%; + } + .pytorch-right-menu.scrolling-fixed { + position: fixed; + top: 45px; + left: 83.5%; + width: 14%; + } + .pytorch-right-menu.scrolling-absolute { + position: absolute; + left: 0; + } +} +@media screen and (min-width: 1600px) { + .pytorch-right-menu { + left: 0; + width: 100%; + } + .pytorch-right-menu.scrolling-fixed { + position: fixed; + top: 45px; + left: 1230px; + } + .pytorch-right-menu.scrolling-absolute { + position: absolute; + left: 0; + } +} + +.pytorch-left-menu ul, +.pytorch-right-menu ul { + list-style-type: none; + padding-left: 0; + margin-bottom: 2.5rem; +} +.pytorch-left-menu > ul, +.pytorch-right-menu > ul { + margin-bottom: 2.5rem; +} +.pytorch-left-menu a:link, +.pytorch-left-menu a:visited, +.pytorch-left-menu a:hover, +.pytorch-right-menu a:link, +.pytorch-right-menu a:visited, +.pytorch-right-menu a:hover { + color: #6c6c6d; + font-size: 0.875rem; + line-height: 1rem; + padding: 0; + text-decoration: none; +} +.pytorch-left-menu a:link.reference.internal, +.pytorch-left-menu a:visited.reference.internal, +.pytorch-left-menu a:hover.reference.internal, +.pytorch-right-menu a:link.reference.internal, +.pytorch-right-menu a:visited.reference.internal, +.pytorch-right-menu a:hover.reference.internal { + margin-bottom: 0.3125rem; + position: relative; +} +.pytorch-left-menu li code, +.pytorch-right-menu li code { + border: none; + background: inherit; + color: inherit; + padding-left: 0; + padding-right: 0; +} +.pytorch-left-menu li span.toctree-expand, +.pytorch-right-menu li span.toctree-expand { + display: block; + float: left; + margin-left: -1.2em; + font-size: 0.8em; + line-height: 1.6em; +} +.pytorch-left-menu li.on a, .pytorch-left-menu li.current > a, +.pytorch-right-menu li.on a, +.pytorch-right-menu li.current > a { + position: relative; + border: none; +} +.pytorch-left-menu li.on a span.toctree-expand, .pytorch-left-menu li.current > a span.toctree-expand, +.pytorch-right-menu li.on a span.toctree-expand, +.pytorch-right-menu li.current > a span.toctree-expand { + display: block; + font-size: 0.8em; + line-height: 1.6em; +} +.pytorch-left-menu li.toctree-l1.current > a, +.pytorch-right-menu li.toctree-l1.current > a { + color: #2980b9; +} +.pytorch-left-menu li.toctree-l1.current > a:before, +.pytorch-right-menu li.toctree-l1.current > a:before { + content: "\2022"; + display: inline-block; + position: absolute; + left: -15px; + top: -10%; + font-size: 1.375rem; + color: #2980b9; +} +@media screen and (min-width: 1101px) { + .pytorch-left-menu li.toctree-l1.current > a:before, + .pytorch-right-menu li.toctree-l1.current > a:before { + left: -20px; + } +} +.pytorch-left-menu li.toctree-l1.current li.toctree-l2 > ul, .pytorch-left-menu li.toctree-l2.current li.toctree-l3 > ul, +.pytorch-right-menu li.toctree-l1.current li.toctree-l2 > ul, +.pytorch-right-menu li.toctree-l2.current li.toctree-l3 > ul { + display: none; +} +.pytorch-left-menu li.toctree-l1.current li.toctree-l2.current > ul, .pytorch-left-menu li.toctree-l2.current li.toctree-l3.current > ul, +.pytorch-right-menu li.toctree-l1.current li.toctree-l2.current > ul, +.pytorch-right-menu li.toctree-l2.current li.toctree-l3.current > ul { + display: block; +} +.pytorch-left-menu li.toctree-l2.current li.toctree-l3 > a, +.pytorch-right-menu li.toctree-l2.current li.toctree-l3 > a { + display: block; +} +.pytorch-left-menu li.toctree-l3, +.pytorch-right-menu li.toctree-l3 { + font-size: 0.9em; +} +.pytorch-left-menu li.toctree-l3.current li.toctree-l4 > a, +.pytorch-right-menu li.toctree-l3.current li.toctree-l4 > a { + display: block; +} +.pytorch-left-menu li.toctree-l4, +.pytorch-right-menu li.toctree-l4 { + font-size: 0.9em; +} +.pytorch-left-menu li.current ul, +.pytorch-right-menu li.current ul { + display: block; +} +.pytorch-left-menu li ul, +.pytorch-right-menu li ul { + margin-bottom: 0; + display: none; +} +.pytorch-left-menu li ul li a, +.pytorch-right-menu li ul li a { + margin-bottom: 0; +} +.pytorch-left-menu a, +.pytorch-right-menu a { + display: inline-block; + position: relative; +} +.pytorch-left-menu a:hover, +.pytorch-right-menu a:hover { + cursor: pointer; +} +.pytorch-left-menu a:active, +.pytorch-right-menu a:active { + cursor: pointer; +} + +.pytorch-left-menu ul { + padding-left: 0; +} + +.pytorch-right-menu a:link, +.pytorch-right-menu a:visited, +.pytorch-right-menu a:hover { + color: #6c6c6d; +} +.pytorch-right-menu a:link span.pre, +.pytorch-right-menu a:visited span.pre, +.pytorch-right-menu a:hover span.pre { + color: #6c6c6d; +} +.pytorch-right-menu a.reference.internal.expanded:before { + content: "-"; + font-family: monospace; + position: absolute; + left: -12px; +} +.pytorch-right-menu a.reference.internal.not-expanded:before { + content: "+"; + font-family: monospace; + position: absolute; + left: -12px; +} +.pytorch-right-menu li.active > a { + color: #2980b9; +} +.pytorch-right-menu li.active > a span.pre, .pytorch-right-menu li.active > a:before { + color: #2980b9; +} +.pytorch-right-menu li.active > a:after { + content: "\2022"; + color: #2980b9; + display: inline-block; + font-size: 1.375rem; + left: -17px; + position: absolute; + top: 1px; +} +.pytorch-right-menu .pytorch-side-scroll > ul > li > ul > li { + margin-bottom: 0; +} +.pytorch-right-menu ul ul { + padding-left: 0; +} +.pytorch-right-menu ul ul li { + padding-left: 0px; +} +.pytorch-right-menu ul ul li a.reference.internal { + padding-left: 0; +} +.pytorch-right-menu ul ul li ul { + display: none; + padding-left: 10px; +} +.pytorch-right-menu ul ul li li a.reference.internal { + padding-left: 0; +} +.pytorch-right-menu li ul { + display: block; +} + +.pytorch-right-menu .pytorch-side-scroll { + padding-top: 20px; +} +@media screen and (min-width: 1101px) { + .pytorch-right-menu .pytorch-side-scroll { + width: 100%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-right-menu .pytorch-side-scroll { + width: 100%; + } +} +.pytorch-right-menu .pytorch-side-scroll > ul { + padding-left: 10%; + padding-right: 10%; + margin-bottom: 0; +} +@media screen and (min-width: 1600px) { + .pytorch-right-menu .pytorch-side-scroll > ul { + padding-left: 25px; + } +} +.pytorch-right-menu .pytorch-side-scroll > ul > li > a.reference.internal { + color: #262626; + font-weight: 500; +} +.pytorch-right-menu .pytorch-side-scroll ul li { + position: relative; +} + +#pytorch-right-menu .side-scroll-highlight { + color: #2980b9; +} + +.header-container { + max-width: none; + margin-top: 4px; +} +@media screen and (min-width: 1101px) { + .header-container { + margin-top: 0; + } +} +@media screen and (min-width: 1600px) { + .header-container { + margin-top: 0; + } +} + +.container-fluid.header-holder { + padding-right: 0; + padding-left: 0; +} + +.header-holder .container { + max-width: none; + padding-right: 1.875rem; + padding-left: 1.875rem; +} +@media screen and (min-width: 1101px) { + .header-holder .container { + padding-right: 1.875rem; + padding-left: 1.875rem; + } +} + +.header-holder .main-menu { + -webkit-box-pack: unset; + -ms-flex-pack: unset; + justify-content: unset; + position: relative; +} +@media screen and (min-width: 1101px) { + .header-holder .main-menu ul { + padding-left: 0; + margin-left: 26%; + } +} +@media screen and (min-width: 1600px) { + .header-holder .main-menu ul { + padding-left: 38px; + margin-left: 310px; + } +} + +.pytorch-page-level-bar { + display: none; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #ffffff; + border-bottom: 1px solid #e2e2e2; + width: 100%; + z-index: 201; +} +@media screen and (min-width: 1101px) { + .pytorch-page-level-bar { + left: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + height: 45px; + padding-left: 0; + width: 100%; + position: absolute; + z-index: 1; + } + .pytorch-page-level-bar.left-menu-is-fixed { + position: fixed; + top: 0; + left: 25%; + padding-left: 0; + right: 0; + width: 75%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-page-level-bar { + left: 0; + right: 0; + width: auto; + z-index: 1; + } + .pytorch-page-level-bar.left-menu-is-fixed { + left: 350px; + right: 0; + width: auto; + } +} +.pytorch-page-level-bar ul, .pytorch-page-level-bar li { + margin: 0; +} + +.pytorch-shortcuts-wrapper { + display: none; +} +@media screen and (min-width: 1101px) { + .pytorch-shortcuts-wrapper { + font-size: 0.875rem; + float: left; + margin-left: 2%; + } +} +@media screen and (min-width: 1600px) { + .pytorch-shortcuts-wrapper { + margin-left: 1.875rem; + } +} + + +.main-menu ul li .resources-dropdown a { + cursor: pointer; +} +.main-menu ul li .dropdown-menu { + border-radius: 0; + padding: 0; +} +.main-menu ul li .dropdown-menu .dropdown-item { + color: #6c6c6d; + border-bottom: 1px solid #e2e2e2; +} +.main-menu ul li .dropdown-menu .dropdown-item:last-of-type { + border-bottom-color: transparent; +} +.main-menu ul li .dropdown-menu .dropdown-item:hover { + background-color: #2980b9; +} +.main-menu ul li .dropdown-menu .dropdown-item p { + font-size: 1rem; + color: #979797; +} +.main-menu ul li .dropdown-menu a.dropdown-item:hover { + color: #ffffff; +} +.main-menu ul li .dropdown-menu a.dropdown-item:hover p { + color: #ffffff; +} + +.resources-dropdown-menu { + left: -75px; + width: 226px; + display: none; + position: absolute; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #ffffff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.resources-dropdown:hover .resources-dropdown-menu { + display: block; +} + +.main-menu ul li .resources-dropdown-menu { + border-radius: 0; + padding: 0; +} +.main-menu ul li.active:hover .resources-dropdown-menu { + display: block; +} + +.main-menu ul li .resources-dropdown-menu .dropdown-item { + color: #6c6c6d; + border-bottom: 1px solid #e2e2e2; +} + +.resources-dropdown .with-down-blue-arrow { + padding-right: 2rem; + position: relative; + background: url("../images/chevron-down-blue.svg"); + background-size: 14px 18px; + background-position: top 7px right 10px; + background-repeat: no-repeat; +} + +.with-down-arrow { + padding-right: 2rem; + position: relative; + background-image: url("../images/chevron-down-black.svg"); + background-size: 14px 18px; + background-position: top 7px right 10px; + background-repeat: no-repeat; +} +.with-down-arrow:hover { + background-image: url("../images/chevron-down-blue.svg"); + background-repeat: no-repeat; +} + +.header-holder .main-menu ul li .resources-dropdown .doc-dropdown-option { + padding-top: 1rem; +} + +.header-holder .main-menu ul li a.nav-dropdown-item { + display: block; + font-size: 1rem; + line-height: 1.3125rem; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #979797; + text-align: center; + background-color: transparent; + border-bottom: 1px solid #e2e2e2; +} +.header-holder .main-menu ul li a.nav-dropdown-item:last-of-type { + border-bottom-color: transparent; +} +.header-holder .main-menu ul li a.nav-dropdown-item:hover { + background-color: #2980b9; + color: white; +} +.header-holder .main-menu ul li a.nav-dropdown-item .dropdown-title { + font-size: 1.125rem; + color: #6c6c6d; + letter-spacing: 0; + line-height: 34px; +} + +.header-holder .main-menu ul li a.nav-dropdown-item:hover .dropdown-title { + background-color: #2980b9; + color: white; +} + +.MathJax { + font-size: 100% !important; +} + +/*# sourceMappingURL=theme.css.map */ diff --git a/html/_static/doctools.js b/html/_static/doctools.js new file mode 100644 index 0000000..d06a71d --- /dev/null +++ b/html/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * 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/html/_static/documentation_options.js b/html/_static/documentation_options.js new file mode 100644 index 0000000..a6faed8 --- /dev/null +++ b/html/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: 'v0.1.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/html/_static/file.png b/html/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/html/_static/file.png differ diff --git a/html/_static/fonts/FreightSans/freight-sans-bold-italic.woff b/html/_static/fonts/FreightSans/freight-sans-bold-italic.woff new file mode 100644 index 0000000..e317248 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-bold-italic.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-bold-italic.woff2 b/html/_static/fonts/FreightSans/freight-sans-bold-italic.woff2 new file mode 100644 index 0000000..cec2dc9 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-bold-italic.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-bold.woff b/html/_static/fonts/FreightSans/freight-sans-bold.woff new file mode 100644 index 0000000..de46625 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-bold.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-bold.woff2 b/html/_static/fonts/FreightSans/freight-sans-bold.woff2 new file mode 100644 index 0000000..dc05cd8 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-bold.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-book-italic.woff b/html/_static/fonts/FreightSans/freight-sans-book-italic.woff new file mode 100644 index 0000000..a50e503 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-book-italic.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-book-italic.woff2 b/html/_static/fonts/FreightSans/freight-sans-book-italic.woff2 new file mode 100644 index 0000000..fe284db Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-book-italic.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-book.woff b/html/_static/fonts/FreightSans/freight-sans-book.woff new file mode 100644 index 0000000..6ab8775 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-book.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-book.woff2 b/html/_static/fonts/FreightSans/freight-sans-book.woff2 new file mode 100644 index 0000000..2688739 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-book.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-light-italic.woff b/html/_static/fonts/FreightSans/freight-sans-light-italic.woff new file mode 100644 index 0000000..beda58d Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-light-italic.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-light-italic.woff2 b/html/_static/fonts/FreightSans/freight-sans-light-italic.woff2 new file mode 100644 index 0000000..e2fa013 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-light-italic.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-light.woff b/html/_static/fonts/FreightSans/freight-sans-light.woff new file mode 100644 index 0000000..226a0bf Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-light.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-light.woff2 b/html/_static/fonts/FreightSans/freight-sans-light.woff2 new file mode 100644 index 0000000..6d8ff2c Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-light.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-medium-italic.woff b/html/_static/fonts/FreightSans/freight-sans-medium-italic.woff new file mode 100644 index 0000000..a42115d Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-medium-italic.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-medium-italic.woff2 b/html/_static/fonts/FreightSans/freight-sans-medium-italic.woff2 new file mode 100644 index 0000000..16a7713 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-medium-italic.woff2 differ diff --git a/html/_static/fonts/FreightSans/freight-sans-medium.woff b/html/_static/fonts/FreightSans/freight-sans-medium.woff new file mode 100644 index 0000000..5ea3453 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-medium.woff differ diff --git a/html/_static/fonts/FreightSans/freight-sans-medium.woff2 b/html/_static/fonts/FreightSans/freight-sans-medium.woff2 new file mode 100644 index 0000000..c58b6a5 Binary files /dev/null and b/html/_static/fonts/FreightSans/freight-sans-medium.woff2 differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-Light.woff b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Light.woff new file mode 100644 index 0000000..cf37a5c Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Light.woff differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-Light.woff2 b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Light.woff2 new file mode 100644 index 0000000..955a6ea Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Light.woff2 differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-Medium.woff b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Medium.woff new file mode 100644 index 0000000..fc65a67 Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Medium.woff differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-Medium.woff2 b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Medium.woff2 new file mode 100644 index 0000000..c352e40 Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Medium.woff2 differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-Regular.woff b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Regular.woff new file mode 100644 index 0000000..7d63d89 Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Regular.woff differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-Regular.woff2 b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Regular.woff2 new file mode 100644 index 0000000..d0d7ded Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-Regular.woff2 differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff b/html/_static/fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff new file mode 100644 index 0000000..1da7753 Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff differ diff --git a/html/_static/fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff2 b/html/_static/fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff2 new file mode 100644 index 0000000..79dffdb Binary files /dev/null and b/html/_static/fonts/IBMPlexMono/IBMPlexMono-SemiBold.woff2 differ diff --git a/html/_static/image/before_rename_urdf_img.png b/html/_static/image/before_rename_urdf_img.png new file mode 100644 index 0000000..9220ca0 Binary files /dev/null and b/html/_static/image/before_rename_urdf_img.png differ diff --git a/html/_static/image/follow_target_mycobot_demo.png b/html/_static/image/follow_target_mycobot_demo.png new file mode 100644 index 0000000..7ea5f33 Binary files /dev/null and b/html/_static/image/follow_target_mycobot_demo.png differ diff --git a/html/_static/image/logo.png b/html/_static/image/logo.png new file mode 100644 index 0000000..a34a065 Binary files /dev/null and b/html/_static/image/logo.png differ diff --git a/html/_static/image/logo192.png b/html/_static/image/logo192.png new file mode 100644 index 0000000..137360b Binary files /dev/null and b/html/_static/image/logo192.png differ diff --git a/html/_static/image/lula_test_extension.png b/html/_static/image/lula_test_extension.png new file mode 100644 index 0000000..b515612 Binary files /dev/null and b/html/_static/image/lula_test_extension.png differ diff --git a/html/_static/image/robot_model_class.png b/html/_static/image/robot_model_class.png new file mode 100644 index 0000000..9a17316 Binary files /dev/null and b/html/_static/image/robot_model_class.png differ diff --git a/html/_static/image/robot_model_controller.png b/html/_static/image/robot_model_controller.png new file mode 100644 index 0000000..3552d8b Binary files /dev/null and b/html/_static/image/robot_model_controller.png differ diff --git a/html/_static/image/robot_model_yml.png b/html/_static/image/robot_model_yml.png new file mode 100644 index 0000000..3fd3899 Binary files /dev/null and b/html/_static/image/robot_model_yml.png differ diff --git a/html/_static/image/robot_models_add_controller.png b/html/_static/image/robot_models_add_controller.png new file mode 100644 index 0000000..a7e3155 Binary files /dev/null and b/html/_static/image/robot_models_add_controller.png differ diff --git a/html/_static/images/arrow-down-blue.svg b/html/_static/images/arrow-down-blue.svg new file mode 100644 index 0000000..82fe329 --- /dev/null +++ b/html/_static/images/arrow-down-blue.svg @@ -0,0 +1,19 @@ + + + + Group 5 + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/_static/images/arrow-right-with-tail.svg b/html/_static/images/arrow-right-with-tail.svg new file mode 100644 index 0000000..09fc3d7 --- /dev/null +++ b/html/_static/images/arrow-right-with-tail.svg @@ -0,0 +1,19 @@ + + + + Page 1 + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/_static/images/chevron-down-black.svg b/html/_static/images/chevron-down-black.svg new file mode 100644 index 0000000..097bc07 --- /dev/null +++ b/html/_static/images/chevron-down-black.svg @@ -0,0 +1,16 @@ + + + Created with Sketch. + + + + + + + + + + + + + diff --git a/html/_static/images/chevron-down-blue.svg b/html/_static/images/chevron-down-blue.svg new file mode 100644 index 0000000..e721e0a --- /dev/null +++ b/html/_static/images/chevron-down-blue.svg @@ -0,0 +1,16 @@ + + + Created with Sketch. + + + + + + + + + + + + + diff --git a/html/_static/images/chevron-down-grey.svg b/html/_static/images/chevron-down-grey.svg new file mode 100644 index 0000000..82d6514 --- /dev/null +++ b/html/_static/images/chevron-down-grey.svg @@ -0,0 +1,18 @@ + + + + +Created with Sketch. + + + + + + + + + + + + diff --git a/html/_static/images/chevron-down-white.svg b/html/_static/images/chevron-down-white.svg new file mode 100644 index 0000000..e6c94e2 --- /dev/null +++ b/html/_static/images/chevron-down-white.svg @@ -0,0 +1,16 @@ + + + Created with Sketch. + + + + + + + + + + + + + diff --git a/html/_static/images/chevron-right-blue.svg b/html/_static/images/chevron-right-blue.svg new file mode 100644 index 0000000..2f57a1d --- /dev/null +++ b/html/_static/images/chevron-right-blue.svg @@ -0,0 +1,17 @@ + + + + +Page 1 +Created with Sketch. + + + + + + + + + + diff --git a/html/_static/images/chevron-right-white.svg b/html/_static/images/chevron-right-white.svg new file mode 100644 index 0000000..dd9e77f --- /dev/null +++ b/html/_static/images/chevron-right-white.svg @@ -0,0 +1,17 @@ + + + + +Page 1 +Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/html/_static/images/home-footer-background.jpg b/html/_static/images/home-footer-background.jpg new file mode 100644 index 0000000..b307bb5 Binary files /dev/null and b/html/_static/images/home-footer-background.jpg differ diff --git a/html/_static/images/icon-close.svg b/html/_static/images/icon-close.svg new file mode 100644 index 0000000..348964e --- /dev/null +++ b/html/_static/images/icon-close.svg @@ -0,0 +1,21 @@ + + + + Page 1 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/_static/images/icon-menu-dots-dark.svg b/html/_static/images/icon-menu-dots-dark.svg new file mode 100644 index 0000000..fa2ad04 --- /dev/null +++ b/html/_static/images/icon-menu-dots-dark.svg @@ -0,0 +1,42 @@ + + + + Page 1 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/_static/images/logo-dark.svg b/html/_static/images/logo-dark.svg new file mode 100644 index 0000000..9b4c1a5 --- /dev/null +++ b/html/_static/images/logo-dark.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/html/_static/images/logo-facebook-dark.svg b/html/_static/images/logo-facebook-dark.svg new file mode 100644 index 0000000..cff1791 --- /dev/null +++ b/html/_static/images/logo-facebook-dark.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/html/_static/images/logo-icon.svg b/html/_static/images/logo-icon.svg new file mode 100644 index 0000000..575f682 --- /dev/null +++ b/html/_static/images/logo-icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/html/_static/images/logo-twitter-dark.svg b/html/_static/images/logo-twitter-dark.svg new file mode 100644 index 0000000..1572570 --- /dev/null +++ b/html/_static/images/logo-twitter-dark.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/html/_static/images/logo-youtube-dark.svg b/html/_static/images/logo-youtube-dark.svg new file mode 100644 index 0000000..e3cfedd --- /dev/null +++ b/html/_static/images/logo-youtube-dark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/html/_static/images/logo.png b/html/_static/images/logo.png new file mode 100644 index 0000000..a34a065 Binary files /dev/null and b/html/_static/images/logo.png differ diff --git a/html/_static/images/logo.svg b/html/_static/images/logo.svg new file mode 100644 index 0000000..f8d44b9 --- /dev/null +++ b/html/_static/images/logo.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/html/_static/images/logo192.png b/html/_static/images/logo192.png new file mode 100644 index 0000000..137360b Binary files /dev/null and b/html/_static/images/logo192.png differ diff --git a/html/_static/images/pytorch-colab.svg b/html/_static/images/pytorch-colab.svg new file mode 100644 index 0000000..2ab15e2 --- /dev/null +++ b/html/_static/images/pytorch-colab.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/html/_static/images/pytorch-download.svg b/html/_static/images/pytorch-download.svg new file mode 100644 index 0000000..cc37d63 --- /dev/null +++ b/html/_static/images/pytorch-download.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/html/_static/images/pytorch-github.svg b/html/_static/images/pytorch-github.svg new file mode 100644 index 0000000..2c2570d --- /dev/null +++ b/html/_static/images/pytorch-github.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/html/_static/images/pytorch-x.svg b/html/_static/images/pytorch-x.svg new file mode 100644 index 0000000..74856ea --- /dev/null +++ b/html/_static/images/pytorch-x.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/html/_static/images/search-icon.svg b/html/_static/images/search-icon.svg new file mode 100644 index 0000000..9d7d999 --- /dev/null +++ b/html/_static/images/search-icon.svg @@ -0,0 +1,19 @@ + + + + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/html/_static/images/view-page-source-icon.svg b/html/_static/images/view-page-source-icon.svg new file mode 100644 index 0000000..752a9ec --- /dev/null +++ b/html/_static/images/view-page-source-icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/html/_static/js/modernizr.min.js b/html/_static/js/modernizr.min.js new file mode 100644 index 0000000..f65d479 --- /dev/null +++ b/html/_static/js/modernizr.min.js @@ -0,0 +1,4 @@ +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load + */ +;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + + closest: function (el, selector) { + var matchesFn; + + // find vendor prefix + ['matches', 'webkitMatchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector'].some(function (fn) { + if (typeof document.body[fn] == 'function') { + matchesFn = fn; + return true; + } + return false; + }); + + var parent; + + // traverse parents + while (el) { + parent = el.parentElement; + if (parent && parent[matchesFn](selector)) { + return parent; + } + el = parent; + } + + return null; + }, + + // Modified from https://stackoverflow.com/a/18953277 + offset: function (elem) { + if (!elem) { + return; + } + + rect = elem.getBoundingClientRect(); + + // Make sure element is not hidden (display: none) or disconnected + if (rect.width || rect.height || elem.getClientRects().length) { + var doc = elem.ownerDocument; + var docElem = doc.documentElement; + + return { + top: rect.top + window.pageYOffset - docElem.clientTop, + left: rect.left + window.pageXOffset - docElem.clientLeft + }; + } + }, + + headersHeight: function () { + if (document.getElementById("pytorch-left-menu").classList.contains("make-fixed")) { + return document.getElementById("pytorch-page-level-bar").offsetHeight; + } else { + return document.getElementById("header-holder").offsetHeight + + document.getElementById("pytorch-page-level-bar").offsetHeight; + } + }, + + windowHeight: function () { + return window.innerHeight || + document.documentElement.clientHeight || + document.body.clientHeight; + } + } + + }, {}], 2: [function (require, module, exports) { + var cookieBanner = { + init: function () { + cookieBanner.bind(); + + var cookieExists = cookieBanner.cookieExists(); + + if (!cookieExists) { + cookieBanner.setCookie(); + cookieBanner.showCookieNotice(); + } + }, + + bind: function () { + $(".close-button").on("click", cookieBanner.hideCookieNotice); + }, + + cookieExists: function () { + var cookie = localStorage.getItem("returningPytorchUser"); + + if (cookie) { + return true; + } else { + return false; + } + }, + + setCookie: function () { + localStorage.setItem("returningPytorchUser", true); + }, + + showCookieNotice: function () { + $(".cookie-banner-wrapper").addClass("is-visible"); + }, + + hideCookieNotice: function () { + $(".cookie-banner-wrapper").removeClass("is-visible"); + } + }; + + $(function () { + cookieBanner.init(); + }); + + }, {}], 3: [function (require, module, exports) { + window.filterTags = { + bind: function () { + var options = { + valueNames: [{ data: ["tags"] }], + page: "6", + pagination: true + }; + + var tutorialList = new List("tutorial-cards", options); + + function filterSelectedTags(cardTags, selectedTags) { + return cardTags.some(function (tag) { + return selectedTags.some(function (selectedTag) { + return selectedTag == tag; + }); + }); + } + + function updateList() { + var selectedTags = []; + + $(".selected").each(function () { + selectedTags.push($(this).data("tag")); + }); + + tutorialList.filter(function (item) { + var cardTags; + + if (item.values().tags == null) { + cardTags = [""]; + } else { + cardTags = item.values().tags.split(","); + } + + if (selectedTags.length == 0) { + return true; + } else { + return filterSelectedTags(cardTags, selectedTags); + } + }); + } + + $(".filter-btn").on("click", function () { + if ($(this).data("tag") == "all") { + $(this).addClass("all-tag-selected"); + $(".filter").removeClass("selected"); + } else { + $(this).toggleClass("selected"); + $("[data-tag='all']").removeClass("all-tag-selected"); + } + + // If no tags are selected then highlight the 'All' tag + + if (!$(".selected")[0]) { + $("[data-tag='all']").addClass("all-tag-selected"); + } + + updateList(); + }); + } + }; + + }, {}], 4: [function (require, module, exports) { + // Modified from https://stackoverflow.com/a/32396543 + window.highlightNavigation = { + navigationListItems: document.querySelectorAll("#pytorch-right-menu li"), + sections: document.querySelectorAll(".pytorch-article .section"), + sectionIdTonavigationLink: {}, + + bind: function () { + if (!sideMenus.displayRightMenu) { + return; + }; + + for (var i = 0; i < highlightNavigation.sections.length; i++) { + var id = highlightNavigation.sections[i].id; + highlightNavigation.sectionIdTonavigationLink[id] = + document.querySelectorAll('#pytorch-right-menu li a[href="#' + id + '"]')[0]; + } + + $(window).scroll(utilities.throttle(highlightNavigation.highlight, 100)); + }, + + highlight: function () { + var rightMenu = document.getElementById("pytorch-right-menu"); + + // If right menu is not on the screen don't bother + if (rightMenu.offsetWidth === 0 && rightMenu.offsetHeight === 0) { + return; + } + + var scrollPosition = utilities.scrollTop(); + var OFFSET_TOP_PADDING = 25; + var offset = document.getElementById("header-holder").offsetHeight + + document.getElementById("pytorch-page-level-bar").offsetHeight + + OFFSET_TOP_PADDING; + + var sections = highlightNavigation.sections; + + for (var i = (sections.length - 1); i >= 0; i--) { + var currentSection = sections[i]; + var sectionTop = utilities.offset(currentSection).top; + + if (scrollPosition >= sectionTop - offset) { + var navigationLink = highlightNavigation.sectionIdTonavigationLink[currentSection.id]; + var navigationListItem = utilities.closest(navigationLink, "li"); + + if (navigationListItem && !navigationListItem.classList.contains("active")) { + for (var i = 0; i < highlightNavigation.navigationListItems.length; i++) { + var el = highlightNavigation.navigationListItems[i]; + if (el.classList.contains("active")) { + el.classList.remove("active"); + } + } + + navigationListItem.classList.add("active"); + + // Scroll to active item. Not a requested feature but we could revive it. Needs work. + + // var menuTop = $("#pytorch-right-menu").position().top; + // var itemTop = navigationListItem.getBoundingClientRect().top; + // var TOP_PADDING = 20 + // var newActiveTop = $("#pytorch-side-scroll-right").scrollTop() + itemTop - menuTop - TOP_PADDING; + + // $("#pytorch-side-scroll-right").animate({ + // scrollTop: newActiveTop + // }, 100); + } + + break; + } + } + } + }; + + }, {}], 5: [function (require, module, exports) { + window.mainMenuDropdown = { + bind: function () { + $("[data-toggle='ecosystem-dropdown']").on("click", function () { + toggleDropdown($(this).attr("data-toggle")); + }); + + $("[data-toggle='resources-dropdown']").on("click", function () { + toggleDropdown($(this).attr("data-toggle")); + }); + + function toggleDropdown(menuToggle) { + var showMenuClass = "show-menu"; + var menuClass = "." + menuToggle + "-menu"; + + if ($(menuClass).hasClass(showMenuClass)) { + $(menuClass).removeClass(showMenuClass); + } else { + $("[data-toggle=" + menuToggle + "].show-menu").removeClass( + showMenuClass + ); + $(menuClass).addClass(showMenuClass); + } + } + } + }; + + }, {}], 6: [function (require, module, exports) { + window.mobileMenu = { + bind: function () { + $("[data-behavior='open-mobile-menu']").on('click', function (e) { + e.preventDefault(); + $(".mobile-main-menu").addClass("open"); + $("body").addClass('no-scroll'); + + mobileMenu.listenForResize(); + }); + + $("[data-behavior='close-mobile-menu']").on('click', function (e) { + e.preventDefault(); + mobileMenu.close(); + }); + }, + + listenForResize: function () { + $(window).on('resize.ForMobileMenu', function () { + if ($(this).width() > 768) { + mobileMenu.close(); + } + }); + }, + + close: function () { + $(".mobile-main-menu").removeClass("open"); + $("body").removeClass('no-scroll'); + $(window).off('resize.ForMobileMenu'); + } + }; + + }, {}], 7: [function (require, module, exports) { + window.mobileTOC = { + bind: function () { + $("[data-behavior='toggle-table-of-contents']").on("click", function (e) { + e.preventDefault(); + + var $parent = $(this).parent(); + + if ($parent.hasClass("is-open")) { + $parent.removeClass("is-open"); + $(".pytorch-left-menu").slideUp(200, function () { + $(this).css({ display: "" }); + }); + } else { + $parent.addClass("is-open"); + $(".pytorch-left-menu").slideDown(200); + } + }); + } + } + + }, {}], 8: [function (require, module, exports) { + window.pytorchAnchors = { + bind: function () { + // Replace Sphinx-generated anchors with anchorjs ones + $(".headerlink").text(""); + + window.anchors.add(".pytorch-article .headerlink"); + + $(".anchorjs-link").each(function () { + var $headerLink = $(this).closest(".headerlink"); + var href = $headerLink.attr("href"); + var clone = this.outerHTML; + + $clone = $(clone).attr("href", href); + $headerLink.before($clone); + $headerLink.remove(); + }); + } + }; + + }, {}], 9: [function (require, module, exports) { + // Modified from https://stackoverflow.com/a/13067009 + // Going for a JS solution to scrolling to an anchor so we can benefit from + // less hacky css and smooth scrolling. + + window.scrollToAnchor = { + bind: function () { + var document = window.document; + var history = window.history; + var location = window.location + var HISTORY_SUPPORT = !!(history && history.pushState); + + var anchorScrolls = { + ANCHOR_REGEX: /^#[^ ]+$/, + offsetHeightPx: function () { + var OFFSET_HEIGHT_PADDING = 20; + // TODO: this is a little janky. We should try to not rely on JS for this + return utilities.headersHeight() + OFFSET_HEIGHT_PADDING; + }, + + /** + * Establish events, and fix initial scroll position if a hash is provided. + */ + init: function () { + this.scrollToCurrent(); + // This interferes with clicks below it, causing a double fire + // $(window).on('hashchange', $.proxy(this, 'scrollToCurrent')); + $('body').on('click', 'a', $.proxy(this, 'delegateAnchors')); + $('body').on('click', '#pytorch-right-menu li span', $.proxy(this, 'delegateSpans')); + }, + + /** + * Return the offset amount to deduct from the normal scroll position. + * Modify as appropriate to allow for dynamic calculations + */ + getFixedOffset: function () { + return this.offsetHeightPx(); + }, + + /** + * If the provided href is an anchor which resolves to an element on the + * page, scroll to it. + * @param {String} href + * @return {Boolean} - Was the href an anchor. + */ + scrollIfAnchor: function (href, pushToHistory) { + var match, anchorOffset; + + if (!this.ANCHOR_REGEX.test(href)) { + return false; + } + + match = document.getElementById(href.slice(1)); + + if (match) { + var anchorOffset = $(match).offset().top - this.getFixedOffset(); + + $('html, body').scrollTop(anchorOffset); + + // Add the state to history as-per normal anchor links + if (HISTORY_SUPPORT && pushToHistory) { + history.pushState({}, document.title, location.pathname + href); + } + } + + return !!match; + }, + + /** + * Attempt to scroll to the current location's hash. + */ + scrollToCurrent: function (e) { + if (this.scrollIfAnchor(window.location.hash) && e) { + e.preventDefault(); + } + }, + + delegateSpans: function (e) { + var elem = utilities.closest(e.target, "a"); + + if (this.scrollIfAnchor(elem.getAttribute('href'), true)) { + e.preventDefault(); + } + }, + + /** + * If the click event's target was an anchor, fix the scroll position. + */ + delegateAnchors: function (e) { + var elem = e.target; + + if (this.scrollIfAnchor(elem.getAttribute('href'), true)) { + e.preventDefault(); + } + } + }; + + $(document).ready($.proxy(anchorScrolls, 'init')); + } + }; + + }, {}], 10: [function (require, module, exports) { + window.sideMenus = { + rightMenuIsOnScreen: function () { + return document.getElementById("pytorch-content-right").offsetParent !== null; + }, + + isFixedToBottom: false, + + bind: function () { + sideMenus.handleLeftMenu(); + + var rightMenuLinks = document.querySelectorAll("#pytorch-right-menu li"); + var rightMenuHasLinks = rightMenuLinks.length > 1; + + if (!rightMenuHasLinks) { + for (var i = 0; i < rightMenuLinks.length; i++) { + rightMenuLinks[i].style.display = "none"; + } + } + + if (rightMenuHasLinks) { + // Don't show the Shortcuts menu title text unless there are menu items + document.getElementById("pytorch-shortcuts-wrapper").style.display = "block"; + + // We are hiding the titles of the pages in the right side menu but there are a few + // pages that include other pages in the right side menu (see 'torch.nn' in the docs) + // so if we exclude those it looks confusing. Here we add a 'title-link' class to these + // links so we can exclude them from normal right side menu link operations + var titleLinks = document.querySelectorAll( + "#pytorch-right-menu #pytorch-side-scroll-right \ + > ul > li > a.reference.internal" + ); + + for (var i = 0; i < titleLinks.length; i++) { + var link = titleLinks[i]; + + link.classList.add("title-link"); + + if ( + link.nextElementSibling && + link.nextElementSibling.tagName === "UL" && + link.nextElementSibling.children.length > 0 + ) { + link.classList.add("has-children"); + } + } + + // Add + expansion signifiers to normal right menu links that have sub menus + var menuLinks = document.querySelectorAll( + "#pytorch-right-menu ul li ul li a.reference.internal" + ); + + for (var i = 0; i < menuLinks.length; i++) { + if ( + menuLinks[i].nextElementSibling && + menuLinks[i].nextElementSibling.tagName === "UL" + ) { + menuLinks[i].classList.add("not-expanded"); + } + } + + // If a hash is present on page load recursively expand menu items leading to selected item + var linkWithHash = + document.querySelector( + "#pytorch-right-menu a[href=\"" + window.location.hash + "\"]" + ); + + if (linkWithHash) { + // Expand immediate sibling list if present + if ( + linkWithHash.nextElementSibling && + linkWithHash.nextElementSibling.tagName === "UL" && + linkWithHash.nextElementSibling.children.length > 0 + ) { + linkWithHash.nextElementSibling.style.display = "block"; + linkWithHash.classList.add("expanded"); + } + + // Expand ancestor lists if any + sideMenus.expandClosestUnexpandedParentList(linkWithHash); + } + + // Bind click events on right menu links + $("#pytorch-right-menu a.reference.internal").on("click", function () { + if (this.classList.contains("expanded")) { + this.nextElementSibling.style.display = "none"; + this.classList.remove("expanded"); + this.classList.add("not-expanded"); + } else if (this.classList.contains("not-expanded")) { + this.nextElementSibling.style.display = "block"; + this.classList.remove("not-expanded"); + this.classList.add("expanded"); + } + }); + + sideMenus.handleRightMenu(); + } + + $(window).on('resize scroll', function (e) { + sideMenus.handleNavBar(); + + sideMenus.handleLeftMenu(); + + if (sideMenus.rightMenuIsOnScreen()) { + sideMenus.handleRightMenu(); + } + }); + }, + + leftMenuIsFixed: function () { + return document.getElementById("pytorch-left-menu").classList.contains("make-fixed"); + }, + + handleNavBar: function () { + var mainHeaderHeight = document.getElementById('header-holder').offsetHeight; + + // If we are scrolled past the main navigation header fix the sub menu bar to top of page + if (utilities.scrollTop() >= mainHeaderHeight) { + document.getElementById("pytorch-left-menu").classList.add("make-fixed"); + document.getElementById("pytorch-page-level-bar").classList.add("left-menu-is-fixed"); + } else { + document.getElementById("pytorch-left-menu").classList.remove("make-fixed"); + document.getElementById("pytorch-page-level-bar").classList.remove("left-menu-is-fixed"); + } + }, + + expandClosestUnexpandedParentList: function (el) { + var closestParentList = utilities.closest(el, "ul"); + + if (closestParentList) { + var closestParentLink = closestParentList.previousElementSibling; + var closestParentLinkExists = closestParentLink && + closestParentLink.tagName === "A" && + closestParentLink.classList.contains("reference"); + + if (closestParentLinkExists) { + // Don't add expansion class to any title links + if (closestParentLink.classList.contains("title-link")) { + return; + } + + closestParentList.style.display = "block"; + closestParentLink.classList.remove("not-expanded"); + closestParentLink.classList.add("expanded"); + sideMenus.expandClosestUnexpandedParentList(closestParentLink); + } + } + }, + + handleLeftMenu: function () { + var windowHeight = utilities.windowHeight(); + var topOfFooterRelativeToWindow = document.getElementById("docs-tutorials-resources").getBoundingClientRect().top; + + if (topOfFooterRelativeToWindow >= windowHeight) { + document.getElementById("pytorch-left-menu").style.height = "100%"; + } else { + var howManyPixelsOfTheFooterAreInTheWindow = windowHeight - topOfFooterRelativeToWindow; + var leftMenuDifference = howManyPixelsOfTheFooterAreInTheWindow; + document.getElementById("pytorch-left-menu").style.height = (windowHeight - leftMenuDifference) + "px"; + } + }, + + handleRightMenu: function () { + var rightMenuWrapper = document.getElementById("pytorch-content-right"); + var rightMenu = document.getElementById("pytorch-right-menu"); + var rightMenuList = rightMenu.getElementsByTagName("ul")[0]; + var article = document.getElementById("pytorch-article"); + var articleHeight = article.offsetHeight; + var articleBottom = utilities.offset(article).top + articleHeight; + var mainHeaderHeight = document.getElementById('header-holder').offsetHeight; + + if (utilities.scrollTop() < mainHeaderHeight) { + rightMenuWrapper.style.height = "100%"; + rightMenu.style.top = 0; + rightMenu.classList.remove("scrolling-fixed"); + rightMenu.classList.remove("scrolling-absolute"); + } else { + if (rightMenu.classList.contains("scrolling-fixed")) { + var rightMenuBottom = + utilities.offset(rightMenuList).top + rightMenuList.offsetHeight; + + if (rightMenuBottom >= articleBottom) { + rightMenuWrapper.style.height = articleHeight + mainHeaderHeight + "px"; + rightMenu.style.top = utilities.scrollTop() - mainHeaderHeight + "px"; + rightMenu.classList.add("scrolling-absolute"); + rightMenu.classList.remove("scrolling-fixed"); + } + } else { + rightMenuWrapper.style.height = articleHeight + mainHeaderHeight + "px"; + rightMenu.style.top = + articleBottom - mainHeaderHeight - rightMenuList.offsetHeight + "px"; + rightMenu.classList.add("scrolling-absolute"); + } + + if (utilities.scrollTop() < articleBottom - rightMenuList.offsetHeight) { + rightMenuWrapper.style.height = "100%"; + rightMenu.style.top = ""; + rightMenu.classList.remove("scrolling-absolute"); + rightMenu.classList.add("scrolling-fixed"); + } + } + + var rightMenuSideScroll = document.getElementById("pytorch-side-scroll-right"); + var sideScrollFromWindowTop = rightMenuSideScroll.getBoundingClientRect().top; + + rightMenuSideScroll.style.height = utilities.windowHeight() - sideScrollFromWindowTop + "px"; + } + }; + + }, {}], "pytorch-sphinx-theme": [function (require, module, exports) { + var jQuery = (typeof (window) != 'undefined') ? window.jQuery : require('jquery'); + + // Sphinx theme nav state + function ThemeNav() { + + var nav = { + navBar: null, + win: null, + winScroll: false, + winResize: false, + linkScroll: false, + winPosition: 0, + winHeight: null, + docHeight: null, + isRunning: false + }; + + nav.enable = function (withStickyNav) { + var self = this; + + // TODO this can likely be removed once the theme javascript is broken + // out from the RTD assets. This just ensures old projects that are + // calling `enable()` get the sticky menu on by default. All other cals + // to `enable` should include an argument for enabling the sticky menu. + if (typeof (withStickyNav) == 'undefined') { + withStickyNav = true; + } + + if (self.isRunning) { + // Only allow enabling nav logic once + return; + } + + self.isRunning = true; + jQuery(function ($) { + self.init($); + + self.reset(); + self.win.on('hashchange', self.reset); + + if (withStickyNav) { + // Set scroll monitor + self.win.on('scroll', function () { + if (!self.linkScroll) { + if (!self.winScroll) { + self.winScroll = true; + requestAnimationFrame(function () { self.onScroll(); }); + } + } + }); + } + + // Set resize monitor + self.win.on('resize', function () { + if (!self.winResize) { + self.winResize = true; + requestAnimationFrame(function () { self.onResize(); }); + } + }); + + self.onResize(); + }); + + }; + + // TODO remove this with a split in theme and Read the Docs JS logic as + // well, it's only here to support 0.3.0 installs of our theme. + nav.enableSticky = function () { + this.enable(true); + }; + + nav.init = function ($) { + var doc = $(document), + self = this; + + this.navBar = $('div.pytorch-side-scroll:first'); + this.win = $(window); + + // Set up javascript UX bits + $(document) + // Shift nav in mobile when clicking the menu. + .on('click', "[data-toggle='pytorch-left-menu-nav-top']", function () { + $("[data-toggle='wy-nav-shift']").toggleClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + }) + + // Nav menu link click operations + .on('click', ".pytorch-menu-vertical .current ul li a", function () { + var target = $(this); + // Close menu when you click a link. + $("[data-toggle='wy-nav-shift']").removeClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + // Handle dynamic display of l3 and l4 nav lists + self.toggleCurrent(target); + self.hashChange(); + }) + .on('click', "[data-toggle='rst-current-version']", function () { + $("[data-toggle='rst-versions']").toggleClass("shift-up"); + }) + + // Make tables responsive + $("table.docutils:not(.field-list,.footnote,.citation)") + .wrap("
"); + + // Add extra class to responsive tables that contain + // footnotes or citations so that we can target them for styling + $("table.docutils.footnote") + .wrap("
"); + $("table.docutils.citation") + .wrap("
"); + + // Add expand links to all parents of nested ul + $('.pytorch-menu-vertical ul').not('.simple').siblings('a').each(function () { + var link = $(this); + expand = $(''); + expand.on('click', function (ev) { + self.toggleCurrent(link); + ev.stopPropagation(); + return false; + }); + link.prepend(expand); + }); + }; + + nav.reset = function () { + // Get anchor from URL and open up nested nav + var anchor = encodeURI(window.location.hash) || '#'; + + try { + var vmenu = $('.pytorch-menu-vertical'); + var link = vmenu.find('[href="' + anchor + '"]'); + if (link.length === 0) { + // this link was not found in the sidebar. + // Find associated id element, then its closest section + // in the document and try with that one. + var id_elt = $('.document [id="' + anchor.substring(1) + '"]'); + var closest_section = id_elt.closest('div.section'); + link = vmenu.find('[href="#' + closest_section.attr("id") + '"]'); + if (link.length === 0) { + // still not found in the sidebar. fall back to main section + link = vmenu.find('[href="#"]'); + } + } + // If we found a matching link then reset current and re-apply + // otherwise retain the existing match + if (link.length > 0) { + $('.pytorch-menu-vertical .current').removeClass('current'); + link.addClass('current'); + link.closest('li.toctree-l1').addClass('current'); + link.closest('li.toctree-l1').parent().addClass('current'); + link.closest('li.toctree-l1').addClass('current'); + link.closest('li.toctree-l2').addClass('current'); + link.closest('li.toctree-l3').addClass('current'); + link.closest('li.toctree-l4').addClass('current'); + } + } + catch (err) { + console.log("Error expanding nav for anchor", err); + } + + }; + + nav.onScroll = function () { + // this.winScroll = false; + // var newWinPosition = this.win.scrollTop(), + // winBottom = newWinPosition + this.winHeight, + // navPosition = this.navBar.scrollTop(), + // newNavPosition = navPosition + (newWinPosition - this.winPosition); + // if (newWinPosition < 0 || winBottom > this.docHeight) { + // return; + // } + // this.navBar.scrollTop(newNavPosition); + // this.winPosition = newWinPosition; + }; + + nav.onResize = function () { + this.winResize = false; + this.winHeight = this.win.height(); + this.docHeight = $(document).height(); + }; + + nav.hashChange = function () { + this.linkScroll = true; + this.win.one('hashchange', function () { + this.linkScroll = false; + }); + }; + + nav.toggleCurrent = function (elem) { + var parent_li = elem.closest('li'); + parent_li.siblings('li.current').removeClass('current'); + parent_li.siblings().find('li.current').removeClass('current'); + parent_li.find('> ul li.current').removeClass('current'); + parent_li.toggleClass('current'); + } + + return nav; + }; + + module.exports.ThemeNav = ThemeNav(); + + if (typeof (window) != 'undefined') { + window.SphinxRtdTheme = { + Navigation: module.exports.ThemeNav, + // TODO remove this once static assets are split up between the theme + // and Read the Docs. For now, this patches 0.3.0 to be backwards + // compatible with a pre-0.3.0 layout.html + StickyNav: module.exports.ThemeNav, + }; + } + + + // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel + // https://gist.github.com/paulirish/1579671 + // MIT license + + (function () { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] + || window[vendors[x] + 'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + }()); + + $(".sphx-glr-thumbcontainer").removeAttr("tooltip"); + $("table").removeAttr("border"); + + // This code replaces the default sphinx gallery download buttons + // with the 3 download buttons at the top of the page + + var downloadNote = $(".sphx-glr-download-link-note.admonition.note"); + if (downloadNote.length >= 1) { + var tutorialUrlArray = $("#tutorial-type").text().split('/'); + tutorialUrlArray[0] = tutorialUrlArray[0] + "_source" + + var githubLink = "https://github.com/pytorch/tutorials/blob/master/" + tutorialUrlArray.join("/") + ".py", + notebookLink = $(".reference.download")[1].href, + notebookDownloadPath = notebookLink.split('_downloads')[1], + colabLink = "https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads" + notebookDownloadPath; + + $("#google-colab-link").wrap(""); + $("#download-notebook-link").wrap(""); + $("#github-view-link").wrap(""); + } else { + $(".pytorch-call-to-action-links").hide(); + } + + //This code handles the Expand/Hide toggle for the Docs/Tutorials left nav items + + $(document).ready(function () { + var caption = "#pytorch-left-menu p.caption"; + var collapseAdded = $(this).not("checked"); + $(caption).each(function () { + var menuName = this.innerText.replace(/[\[\]+-]/gi, "").trim(); + $(this).find("span").addClass("checked"); + if (collapsedSections.includes(menuName) == true && collapseAdded && sessionStorage.getItem(menuName) !== "expand" || sessionStorage.getItem(menuName) == "collapse") { + $(this.firstChild).after("[ + ]"); + $(this.firstChild).after("[ - ]"); + $(this).next("ul").hide(); + } else if (collapsedSections.includes(menuName) == false && collapseAdded || sessionStorage.getItem(menuName) == "expand") { + $(this.firstChild).after("[ + ]"); + $(this.firstChild).after("[ - ]"); + } + }); + + $(".expand-menu").on("click", function () { + $(this).prev(".hide-menu").toggle(); + $(this).parent().next("ul").toggle(); + var menuName = $(this).parent().text().replace(/[[\[\]+-]/gi, "").trim(); + if (sessionStorage.getItem(menuName) == "collapse") { + sessionStorage.removeItem(menuName); + } + sessionStorage.setItem(menuName, "expand"); + toggleList(this); + }); + + $(".hide-menu").on("click", function () { + $(this).next(".expand-menu").toggle(); + $(this).parent().next("ul").toggle(); + var menuName = $(this).parent().text().replace(/[\[\]+-]/gi, "").trim(); + if (sessionStorage.getItem(menuName) == "expand") { + sessionStorage.removeItem(menuName); + } + sessionStorage.setItem(menuName, "collapse"); + toggleList(this); + }); + + function toggleList(menuCommand) { + $(menuCommand).toggle(); + } + }); + + // Build an array from each tag that's present + + var tagList = $(".tutorials-card-container").map(function () { + return $(this).data("tags").split(",").map(function (item) { + return item.trim(); + }); + }).get(); + + function unique(value, index, self) { + return self.indexOf(value) == index && value != "" + } + + // Only return unique tags + + var tags = tagList.sort().filter(unique); + + // Add filter buttons to the top of the page for each tag + + function createTagMenu() { + tags.forEach(function (item) { + $(".tutorial-filter-menu").append("
" + item + "
") + }) + }; + + createTagMenu(); + + // Remove hyphens if they are present in the filter buttons + + $(".tags").each(function () { + var tags = $(this).text().split(","); + tags.forEach(function (tag, i) { + tags[i] = tags[i].replace(/-/, ' ') + }) + $(this).html(tags.join(", ")); + }); + + // Remove hyphens if they are present in the card body + + $(".tutorial-filter").each(function () { + var tag = $(this).text(); + $(this).html(tag.replace(/-/, ' ')) + }) + + // Remove any empty p tags that Sphinx adds + + $("#tutorial-cards p").each(function (index, item) { + if (!$(item).text().trim()) { + $(item).remove(); + } + }); + + // Jump back to top on pagination click + + $(document).on("click", ".page", function () { + $('html, body').animate( + { scrollTop: $("#dropdown-filter-tags").position().top }, + 'slow' + ); + }); + + var link = $("a[href='intermediate/speech_command_recognition_with_torchaudio.html']"); + + if (link.text() == "SyntaxError") { + console.log("There is an issue with the intermediate/speech_command_recognition_with_torchaudio.html menu item."); + link.text("Speech Command Recognition with torchaudio"); + } + + $(".stars-outer > i").hover(function () { + $(this).prevAll().addBack().toggleClass("fas star-fill"); + }); + + $(".stars-outer > i").on("click", function () { + $(this).prevAll().each(function () { + $(this).addBack().addClass("fas star-fill"); + }); + + $(".stars-outer > i").each(function () { + $(this).unbind("mouseenter mouseleave").css({ + "pointer-events": "none" + }); + }); + }) + + $("#pytorch-side-scroll-right li a").on("click", function (e) { + var href = $(this).attr("href"); + $('html, body').stop().animate({ + scrollTop: $(href).offset().top - 100 + }, 850); + e.preventDefault; + }); + + var lastId, + topMenu = $("#pytorch-side-scroll-right"), + topMenuHeight = topMenu.outerHeight() + 1, + // All sidenav items + menuItems = topMenu.find("a"), + // Anchors for menu items + scrollItems = menuItems.map(function () { + var item = $(this).attr("href"); + if (item.length) { + return item; + } + }); + + $(window).scroll(function () { + var fromTop = $(this).scrollTop() + topMenuHeight; + var article = ".section"; + + $(article).each(function (i) { + var offsetScroll = $(this).offset().top - $(window).scrollTop(); + if ( + offsetScroll <= topMenuHeight + 200 && + offsetScroll >= topMenuHeight - 200 && + scrollItems[i] == "#" + $(this).attr("id") && + $(".hidden:visible") + ) { + $(menuItems).removeClass("side-scroll-highlight"); + $(menuItems[i]).addClass("side-scroll-highlight"); + } + }); + }); + + }, { "jquery": "jquery" }] +}, {}, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "pytorch-sphinx-theme"]); diff --git a/html/_static/js/vendor/anchor.min.js b/html/_static/js/vendor/anchor.min.js new file mode 100644 index 0000000..1216eea --- /dev/null +++ b/html/_static/js/vendor/anchor.min.js @@ -0,0 +1,9 @@ +// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat +// +// AnchorJS - v4.3.0 - 2020-10-21 +// https://www.bryanbraun.com/anchorjs/ +// Copyright (c) 2020 Bryan Braun; Licensed MIT +// +// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat +!function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(this,function(){"use strict";return function(A){function d(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function f(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],d(this.options),this.isTouchDevice=function(){return Boolean("ontouchstart"in window||window.TouchEvent||window.DocumentTouch&&document instanceof DocumentTouch)},this.add=function(A){var e,t,o,n,i,s,a,r,c,l,h,u,p=[];if(d(this.options),"touch"===(h=this.options.visible)&&(h=this.isTouchDevice()?"always":"hover"),0===(e=f(A=A||"h2, h3, h4, h5, h6")).length)return this;for(!function(){if(null!==document.head.querySelector("style.anchorjs"))return;var A,e=document.createElement("style");e.className="anchorjs",e.appendChild(document.createTextNode("")),void 0===(A=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(e):document.head.insertBefore(e,A);e.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",e.sheet.cssRules.length),e.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",e.sheet.cssRules.length),e.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",e.sheet.cssRules.length),e.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',e.sheet.cssRules.length)}(),t=document.querySelectorAll("[id]"),o=[].map.call(t,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),t=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||t||!1}}}); +// @license-end \ No newline at end of file diff --git a/html/_static/js/vendor/bootstrap.min.js b/html/_static/js/vendor/bootstrap.min.js new file mode 100644 index 0000000..534d533 --- /dev/null +++ b/html/_static/js/vendor/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); +//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/html/_static/js/vendor/popper.min.js b/html/_static/js/vendor/popper.min.js new file mode 100644 index 0000000..bb1aaae --- /dev/null +++ b/html/_static/js/vendor/popper.min.js @@ -0,0 +1,5 @@ +/* + Copyright (C) Federico Zivolo 2020 + Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge}); +//# sourceMappingURL=popper.min.js.map diff --git a/html/_static/language_data.js b/html/_static/language_data.js new file mode 100644 index 0000000..250f566 --- /dev/null +++ b/html/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * 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/html/_static/minus.png b/html/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/html/_static/minus.png differ diff --git a/html/_static/plus.png b/html/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/html/_static/plus.png differ diff --git a/html/_static/pygments.css b/html/_static/pygments.css new file mode 100644 index 0000000..0d49244 --- /dev/null +++ b/html/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/html/_static/searchtools.js b/html/_static/searchtools.js new file mode 100644 index 0000000..97d56a7 --- /dev/null +++ b/html/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/html/_static/sphinx_highlight.js b/html/_static/sphinx_highlight.js new file mode 100644 index 0000000..aae669d --- /dev/null +++ b/html/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '
" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!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.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/html/advanced_tutorials/how-to-add-controller.html b/html/advanced_tutorials/how-to-add-controller.html new file mode 100644 index 0000000..c31f5be --- /dev/null +++ b/html/advanced_tutorials/how-to-add-controller.html @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + How to add custom controller — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

How to add custom controller

+
+

This tutorial will show you how to add a controller for a robot

+
+

Before this tutorial, you should read:

+ +
+

1. Add grutopia.core.robot.controller

+

Take this “ChatboxController” as an example,

+
from datetime import datetime
+from typing import Any, Dict, List, Union
+
+import numpy as np
+from omni.isaac.core.scenes import Scene
+from omni.isaac.core.utils.types import ArticulationAction
+
+from grutopia.core.datahub.model_data import LogData, ModelData
+from grutopia.core.robot.controller import BaseController
+from grutopia.core.robot.robot import BaseRobot
+from grutopia.core.robot.robot_model import ControllerModel
+
+
+@BaseController.register('ChatboxController')
+class ChatboxController(BaseController):
+
+    def __init__(self, config: ControllerModel, name: str, robot: BaseRobot, scene: Scene) -> None:
+        config = ControllerModel(name=name, type='chat')
+        super().__init__(config, robot, scene)
+        self._user_config = None
+        self.counter = 1
+
+    def action_to_control(self, action: Union[np.ndarray, List]) -> ArticulationAction:
+        # TODO Check input(like [np.array('I am the sentence', agent_avatar_data)])
+        return self.forward(str(action[0]))
+
+    def forward(self, chat: str) -> ArticulationAction:
+        # TODO Set chat action
+        return ArticulationAction()
+
+    def get_obs(self) -> Dict[str, Any]:
+        return {}
+
+
+
+
+

2. Register at robot_models

+

Like this.

+

alt text

+
+
+

3. Write a demo

+
...
+while env.simulation_app.is_running():
+    ...
+    env_actions = [{
+        h1: {
+            "web_chat": np.array(['你好'])
+        }
+    }]
+    obs = env.step(actions=env_actions)
+env.simulation_app.close()
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/advanced_tutorials/how-to-add-robot.html b/html/advanced_tutorials/how-to-add-robot.html new file mode 100644 index 0000000..a73bc55 --- /dev/null +++ b/html/advanced_tutorials/how-to-add-robot.html @@ -0,0 +1,549 @@ + + + + + + + + + + + + + + + + How to add custom robot — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

How to add custom robot

+
+

This tutorial will show you how to add a robot

+
+
+

1. Add isaac sim robot

+
+

Assuming you already have an usd file of a robot, and it has drivable joints.

+
+

Create a file in grutopia_extension/robots, named demo_robot.py. Inherit the robot class from isaac.

+
from omni.isaac.core.robots.robot import Robot as IsaacRobot
+from omni.isaac.core.utils.stage import add_reference_to_stage
+
+
+class DemoRobot(IsaacRobot):
+
+    def __init__(self,
+                 prim_path: str,
+                 usd_path: str,
+                 name: str,
+                 position: np.ndarray = None,
+                 orientation: np.ndarray = None,
+                 scale: np.ndarray = None):
+        add_reference_to_stage(prim_path=prim_path, usd_path=os.path.abspath(usd_path))
+        super().__init__(prim_path=prim_path, name=name, position=position, orientation=orientation, scale=scale)
+        # Set robot-specific parameters/attributes here
+
+
+
+
+

2. Wrap with grutopia.core.robot.robot

+
from omni.isaac.core.scenes import Scene
+
+from grutopia.core.config.robot import RobotUserConfig as Config
+from grutopia.core.robot.robot import BaseRobot
+from grutopia.core.robot.robot_model import RobotModel
+from grutopia.core.util import log
+
+
+# Register this robot to grutopia.core
+@BaseRobot.register('DemoRobot')
+class DemoRobotWrapper(BaseRobot):
+
+    def __init__(self, config: Config, robot_model: RobotModel, scene: Scene):
+        super().__init__(config, robot_model, scene)
+        self._sensor_config = robot_model.sensors
+        self._gains = robot_model.gains
+        self._start_position = np.array(config.position) if config.position is not None else None
+        self._start_orientation = np.array(config.orientation) if config.orientation is not None else None
+
+        log.debug(f'demo_robot {config.name}: position    : ' + str(self._start_position))
+        log.debug(f'demo_robot {config.name}: orientation : ' + str(self._start_orientation))
+
+        usd_path = robot_model.usd_path
+        if usd_path.startswith('/Isaac'):
+            usd_path = get_assets_root_path() + usd_path
+
+        log.debug(f'demo_robot {config.name}: usd_path         : ' + str(usd_path))
+        log.debug(f'demo_robot {config.name}: config.prim_path : ' + str(config.prim_path))
+
+        # Wrap the robot class here.
+        self.isaac_robot = DemoRobot(
+            prim_path=config.prim_path,
+            name=config.name,
+            position=self._start_position,
+            orientation=self._start_orientation,
+            usd_path=usd_path,
+        )
+
+        self._robot_scale = np.array([1.0, 1.0, 1.0])
+        if config.scale is not None:
+            self._robot_scale = np.array(config.scale)
+            self.isaac_robot.set_local_scale(self._robot_scale)
+
+        # Add the attr you want here.
+
+    ...
+
+    def apply_action(self, action: dict):
+        """
+        Args:
+            action (dict): inputs for controllers.
+        """
+        for controller_name, controller_action in action.items():
+            if controller_name not in self.controllers:
+                log.warn(f'unknown controller {controller_name} in action')
+                continue
+            controller = self.controllers[controller_name]
+            control = controller.action_to_control(controller_action)
+            self.isaac_robot.apply_actuator_model(control, controller_name, self.joint_subset)
+
+    def get_obs(self):
+        """
+        Set the observation you need here.
+        """
+
+        # custom
+        position, orientation = self._robot_base.get_world_pose()
+        obs = {
+            'position': position,
+            'orientation': orientation,
+        }
+
+        # common
+        for c_obs_name, controller_obs in self.controllers.items():
+            obs[c_obs_name] = controller_obs.get_obs()
+        for sensor_name, sensor_obs in self.sensors.items():
+            obs[sensor_name] = sensor_obs.get_data()
+        return obs
+
+
+
    +
  • And there are many other functions in grutopia.core.robot.robot, FYI.

  • +
+
+
+

3. Register at robot_models

+

Add you robot model at grutopia_extension/robots/robot_models.yaml

+
- type: "DemoRobotWrapper"
+  usd_path: "..."
+  controllers:
+  - name: "..."
+    type: "..."
+
+
+
+
+

4. Add controllers and sensors

+

See how to add controller and how to add sensor

+
+
+

5. Write a demo

+

See how to add controller and how to add sensor

+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/advanced_tutorials/how-to-add-sensor.html b/html/advanced_tutorials/how-to-add-sensor.html new file mode 100644 index 0000000..1336138 --- /dev/null +++ b/html/advanced_tutorials/how-to-add-sensor.html @@ -0,0 +1,510 @@ + + + + + + + + + + + + + + + + How to add custom sensor — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

How to add custom sensor

+
+

This tutorial will show you how to add a sensor for a robot

+
+

Before this tutorial, you should read:

+ +
+

1. Create with grutopia.core.robot.sensor

+

The sensors in grutopia are not just tensor. They are interfaces for robots to passively receive all kinds of +information.

+

The only thing we should matter is: implement BaseSensor from grutopia.core.robot.sensor

+

Camera sensor FYI

+
from typing import Dict
+
+from omni.isaac.sensor import Camera as i_Camera
+
+from grutopia.core.config.robot import RobotUserConfig
+from grutopia.core.robot.robot import BaseRobot, Scene
+from grutopia.core.robot.robot_model import SensorModel
+from grutopia.core.robot.sensor import BaseSensor
+from grutopia.core.util import log
+
+
+@BaseSensor.register('Camera')
+class Camera(BaseSensor):
+    """
+    wrap of isaac sim's Camera class
+    """
+
+    def __init__(self,
+                 robot_user_config: RobotUserConfig,
+                 sensor_config: SensorModel,
+                 robot: BaseRobot,
+                 name: str = None,
+                 scene: Scene = None):
+        super().__init__(robot_user_config, sensor_config, robot, name)
+        self.param = None
+        if self.robot_user_config.sensor_params is not None:
+            self.param = [p for p in self.robot_user_config.sensor_params if p.name == self.name][0]
+        self._camera = self.create_camera()
+
+    def create_camera(self) -> i_Camera:
+        size = (1280, 720)
+        if self.param is not None:
+            size = self.param.size
+
+        prim_path = self.robot_user_config.prim_path + '/' + self.sensor_config.prim_path
+        log.debug('camera_prim_path: ' + prim_path)
+        log.debug('name            : ' + '_'.join([self.robot_user_config.name, self.sensor_config.name]))
+        return i_Camera(prim_path=prim_path, resolution=size)
+
+    def sensor_init(self) -> None:
+        if self.param is not None:
+            if self.param.switch:
+                self._camera.initialize()
+                self._camera.add_distance_to_image_plane_to_frame()
+                self._camera.add_semantic_segmentation_to_frame()
+                self._camera.add_instance_segmentation_to_frame()
+                self._camera.add_instance_id_segmentation_to_frame()
+                self._camera.add_bounding_box_2d_tight_to_frame()
+
+    def get_data(self) -> Dict:
+        if self.param is not None:
+            if self.param.switch:
+                rgba = self._camera.get_rgba()
+                depth = self._camera.get_depth()
+                frame = self._camera.get_current_frame()
+                return {'rgba': rgba, 'depth': depth, 'frame': frame}
+        return {}
+
+
+
+
+

2. Register at robot_models

+

Add sensor for robots in grutopia_extension/robots/robot_models.yaml.

+
robots:
+  - type: "HumanoidRobot"
+    ...
+    sensors:
+      - name: "camera"
+        prim_path: "relative/prim/path/to/camera"  # relative path
+        type: "Camera"  # map to key in `register`
+
+
+
+
+

3. Write a demo

+

In simulation_app’s step loop:

+
   ...
+   obs = env.step(actions)
+   photo = obs['robot_name_in_config']['camera']['frame']['rgba']  # `camera` is sensor name in model
+   ...
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/advanced_tutorials/how-to-add-task.html b/html/advanced_tutorials/how-to-add-task.html new file mode 100644 index 0000000..f98ef6f --- /dev/null +++ b/html/advanced_tutorials/how-to-add-task.html @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + how to add custom task — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

how to add custom task

+
+

Wrap a task as you wish

+
+
+

When I need a custom task

+
+

WIP

+
+
+
+

How to wrap the Task

+
+

WIP

+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/datahub.html b/html/api/datahub.html new file mode 100644 index 0000000..3606533 --- /dev/null +++ b/html/api/datahub.html @@ -0,0 +1,773 @@ + + + + + + + + + + + + + + + + grutopia.core.datahub — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.datahub

+
+

datahub

+
+
+class grutopia.core.datahub.IsaacData[source]
+

isaac status in grutopia

+

There are two types of isaac status:

+
    +
  • Action

  • +
  • Observation

  • +
+

structure of isaac status like this:

+
{
+    actions: {
+        [
+            {
+                robot_1: {
+                    cap: param,
+                }
+            }
+        ]
+    },
+    observations: {
+        [
+            {
+                robot_1: {
+                    obs_1: data,
+                    obs_2: data
+                }
+            }
+        ]
+    }
+}
+
+
+
+
+classmethod add_actions(actions: List[ActionData])[source]
+

Add actions

+
+
Parameters:
+

actions – action list

+
+
+

Returns:

+
+ +
+
+classmethod get_action_by_id(task_id: int) None | Dict[Any, Any][source]
+

Get action by id

+
+
Returns:
+

{controller_name: param}})

+
+
Return type:
+

action(dict like {robot_name

+
+
+
+ +
+
+classmethod get_actions() None | List[Dict[Any, Any]][source]
+

Get actions

+
+
Returns:
+

{controller_name: param}}) list

+
+
Return type:
+

action(dict like {robot_name

+
+
+
+ +
+
+classmethod get_obs() List[Dict[str, Any]][source]
+

Get isaac observation data

+
+
Returns:
+

isaac observation data list

+
+
+
+ +
+
+classmethod get_obs_by_id(task_id: int) Dict[str, Any][source]
+

Get isaac observation by id

+
+
Parameters:
+

task_id – isaac task id

+
+
Returns:
+

isaac observation data

+
+
+
+ +
+ +
+
+

local api

+
+
+grutopia.core.datahub.api.get_actions() None | Dict[Any, Any][source]
+

Get all actions

+
+
Returns:
+

action data dict

+
+
Return type:
+

Dict[str, Any]

+
+
+
+ +
+
+grutopia.core.datahub.api.get_all_obs() List[Dict[str, Any]][source]
+

Get all observation data.

+
+
Returns:
+

sensor data dict

+
+
Return type:
+

List[Dict[str, Any]]

+
+
+

```

+
+ +
+
+grutopia.core.datahub.api.get_obs_by_id(task_id: int) Dict[str, Any][source]
+

Get observation by task_id

+
+
Returns:
+

obs data dict

+
+
Return type:
+

Dict[str, Any]

+
+
+
+ +
+
+grutopia.core.datahub.api.send_actions(actions: List[ActionData])[source]
+

send actions to datahub +:param actions: list of [dict of {robot_id: ActionData}] +:type actions: List[ActionData]

+
+ +
+
+grutopia.core.datahub.api.set_obs_data(obs: List[Dict[str, Any]]) None[source]
+

Flush observation data.

+
+
Parameters:
+

obs (List[Dict[str, Any]]) – observation data

+
+
+
+ +
+
+

web api

+

Includes web ui interactive

+
+
+grutopia.core.datahub.web_ui_api.clear(uuid: str = 'none')[source]
+

Clear all data in webui.

+
+ +
+
+grutopia.core.datahub.web_ui_api.get_chat_control(uuid: str = 'none') Dict[str, Any] | None[source]
+

Get chat control data.

+
+
Parameters:
+

uuid (str) – chat control uuid. default: none.

+
+
Returns:
+

chat control data.

+
+
Return type:
+

chat_control (List[Dict[str, Any]])

+
+
+
+ +
+
+grutopia.core.datahub.web_ui_api.get_log_data(uuid: str = 'none') Dict[str, Any] | None[source]
+

Get log data.

+
+
Parameters:
+

uuid (str) – log data uuid. default: none.

+
+
Returns:
+

log data.

+
+
Return type:
+

log_data (list[dict])

+
+
+
+ +
+
+grutopia.core.datahub.web_ui_api.send_chain_of_thought(cot: str, uuid: str = 'none') None[source]
+

chain of thought data

+
+
Parameters:
+
    +
  • uuid (str) – uuid of chain of thought data, defaults to “none”.

  • +
  • cot (str) – chain of thought data.

  • +
+
+
+
+ +
+
+grutopia.core.datahub.web_ui_api.send_chat_control(nickname: str, text: str, img: str | None = None, role: str = 'user', uuid: str = 'none') None[source]
+

Send a new message to the chatbox.

+
+
Parameters:
+
    +
  • nickname (str) – nickname displayed in the chatbox.

  • +
  • text (str) – text to send to the chatbox.

  • +
  • img (str, optional) – image to send to the chatbox. Defaults to None.

  • +
  • role (str, optional) – role name, user or agent. Defaults to “user”.

  • +
  • uuid (str, optional) – uuid of the message. Defaults to ‘none’.

  • +
+
+
+
+ +
+
+grutopia.core.datahub.web_ui_api.send_log_data(log_data: str, log_type: str = 'message', user: str = 'Bob', photo_url: str = 'http://127.0.0.1:8080/static/avatar_default.jpg', uuid: str = 'none') None[source]
+

Send log data.

+
+
Parameters:
+
    +
  • uuid (str) – uuid of log, default is none.

  • +
  • log_data (str) – log data.

  • +
  • log_type (str) – type of log. ‘message’ or ‘user’.

  • +
  • user (str) – logger name. default: Bob.

  • +
  • photo_url (str) – log photo url path.

  • +
+
+
+
+ +

Includes web api endpoints

+
+
+grutopia.core.datahub.web_api.get_actions_by_id(task_id: int)[source]
+

Get actions by task id(int)

+
+
Parameters:
+

task_id (int) – id of task

+
+
Returns:
+

msg str(or None) +data: data

+
+
Return type:
+

msg

+
+
+
+ +
+
+grutopia.core.datahub.web_api.get_all_obs() List[Dict[str, Any]] | None[source]
+

Get all observation data +:returns: List of all observation data +:rtype: obs (List[Dict[str, Any]])

+
+ +
+
+grutopia.core.datahub.web_api.get_obs_by_id(task_id: int) Any | None[source]
+

Get observation by id +:param task_id: id of observation data +:type task_id: int

+
+
Returns:
+

Observation data

+
+
Return type:
+

obs (Any)

+
+
+
+ +
+
+grutopia.core.datahub.web_api.send_actions(actions: List[ActionData]) bool[source]
+

send actions +:param actions: action data list +:type actions: List[ActionData]

+
+
Returns:
+

Send message successfully or not

+
+
+
+ +
+
+grutopia.core.datahub.web_api.set_obs_data(obs: List[Dict[str, Any]]) bool[source]
+

Set observation data web API +:param obs: isaac observation data +:type obs: List[Dict[str, Any]]

+
+
Returns:
+

OK if set successfully

+
+
+
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/env.html b/html/api/env.html new file mode 100644 index 0000000..2267efb --- /dev/null +++ b/html/api/env.html @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + grutopia.core.env — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.env

+
+

env

+
+
+class grutopia.core.env.BaseEnv(config: SimulatorConfig, headless: bool = True, webrtc: bool = False)[source]
+
+

Env base class. All tasks should inherit from this class(or subclass).

+
+
+close()[source]
+

close the environment

+
+ +
+
+get_observations() List[Dict[str, Any]][source]
+

Get observations from Isaac environment +:returns: observations +:rtype: List[Dict[str, Any]]

+
+ +
+
+reset(envs: List[int] | None = None)[source]
+

reset the environment(use isaac word reset)

+
+
Parameters:
+

envs (List[int]) – env need to be reset(default for reset all envs)

+
+
+
+ +
+
+property simulation_app
+

simulation app instance

+
+ +
+
+property simulation_config
+

config of simulation environment

+
+ +
+
+step(actions: List[Dict[str, Any]]) List[Dict[str, Any]][source]
+

run step with given action(with isaac step)

+
+
Parameters:
+

actions (List[Dict[str, Any]]) – action(with isaac step)

+
+
Returns:
+

observations(with isaac step)

+
+
Return type:
+

List[Dict[str, Any]]

+
+
+
+ +
+
+ +
+
+

runner

+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/register.html b/html/api/register.html new file mode 100644 index 0000000..ed86305 --- /dev/null +++ b/html/api/register.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + grutopia.core.register — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.register

+
+

register

+
+
+grutopia.core.register.register.import_all_modules_for_register(custom_module_paths=None, extension_path=None)[source]
+

Import all modules for register.

+
+
Parameters:
+
    +
  • custom_module_paths – custom module paths, e.g. [‘xxx.lib1’, ‘xxx.lib2’, ‘xxx.lib3’]

  • +
  • extension_path – Extension path(integrated in grutopia_extension as default)

  • +
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/robot.html b/html/api/robot.html new file mode 100644 index 0000000..6bb3880 --- /dev/null +++ b/html/api/robot.html @@ -0,0 +1,784 @@ + + + + + + + + + + + + + + + + grutopia.core.robot — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.robot

+
+

robot

+
+
+class grutopia.core.robot.robot.BaseRobot(config: RobotUserConfig, robot_model: RobotModel, scene: Scene)[source]
+

Base class of robot.

+
+
+apply_action(action: dict)[source]
+

Apply actions of controllers to robot.

+
+
Parameters:
+

action (dict) – action dict. +key: controller name. +value: corresponding action array.

+
+
+
+ +
+
+get_obs() dict[source]
+

Get observation of robot, including controllers, sensors, and world pose.

+
+
Raises:
+

NotImplementedError – _description_

+
+
+
+ +
+
+get_robot_articulation() Robot[source]
+

Get isaac robots instance (articulation).

+
+
Returns:
+

robot articulation.

+
+
Return type:
+

Robot

+
+
+
+ +
+
+get_robot_base() RigidPrim[source]
+

Get base link of robot.

+
+
Returns:
+

rigid prim of robot base link.

+
+
Return type:
+

RigidPrim

+
+
+
+ +
+
+get_robot_ik_base() RigidPrim[source]
+

Get base link of ik controlled parts.

+
+
Returns:
+

rigid prim of ik base link.

+
+
Return type:
+

RigidPrim

+
+
+
+ +
+
+get_robot_scale() ndarray[source]
+

Get robot scale.

+
+
Returns:
+

robot scale in (x, y, z).

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+post_reset()[source]
+

Set up things that happen after the world resets.

+
+ +
+
+classmethod register(name: str)[source]
+

Register a robot class with its name(decorator).

+
+
Parameters:
+

name (str) – name of the robot class.

+
+
+
+ +
+
+set_up_to_scene(scene: Scene)[source]
+

Set up robot in the scene.

+
+
Parameters:
+

scene (Scene) – scene to setup.

+
+
+
+ +
+ +
+
+grutopia.core.robot.robot.create_robots(config: TaskUserConfig, robot_models: RobotModels, scene: Scene) Dict[str, BaseRobot][source]
+

Create robot instances in config. +:param config: user config. +:type config: TaskUserConfig +:param robot_models: robot models. +:type robot_models: RobotModels +:param scene: isaac scene. +:type scene: Scene

+
+
Returns:
+

robot instances dictionary.

+
+
Return type:
+

Dict[str, BaseRobot]

+
+
+
+ +
+
+

controller

+
+
+class grutopia.core.robot.controller.BaseController(config: ControllerModel, robot: BaseRobot, scene: Scene)[source]
+

Base class of controller.

+
+
+abstract action_to_control(action: ndarray | List) ArticulationAction[source]
+

Convert input action (in 1d array format) to joint signals to apply.

+
+
Parameters:
+

action (Union[np.ndarray, List]) – input control action.

+
+
Returns:
+

joint signals to apply

+
+
Return type:
+

ArticulationAction

+
+
+
+ +
+
+get_joint_subset() ArticulationSubset[source]
+

Get the joint subset controlled by the controller.

+
+
Returns:
+

joint subset.

+
+
Return type:
+

ArticulationSubset

+
+
+
+ +
+
+get_obs() Dict[str, Any][source]
+

Get observation of controller.

+
+
Returns:
+

observation key and value.

+
+
Return type:
+

Dict[str, Any]

+
+
+
+ +
+
+classmethod register(name: str)[source]
+

Register a controller with its name(decorator).

+
+
Parameters:
+

name (str) – name of the controller

+
+
+
+ +
+ +
+
+grutopia.core.robot.controller.config_inject(user_config: ControllerParams, model: ControllerModel) ControllerModel[source]
+

Merge controller config from user config and robot model.

+
+
Parameters:
+
    +
  • user_config (ControllerParams) – user config.

  • +
  • model (ControllerModel) – controller config from robot model.

  • +
+
+
Returns:
+

merged controller config.

+
+
Return type:
+

ControllerModel

+
+
+
+ +
+
+grutopia.core.robot.controller.create_controllers(config: RobotUserConfig, robot_model: RobotModel, robot: BaseRobot, scene: Scene) Dict[str, BaseController][source]
+

Create all controllers of one robot.

+
+
Parameters:
+
    +
  • config (RobotUserConfig) – user config of the robot.

  • +
  • robot_model (RobotModel) – model of the robot.

  • +
  • robot (BaseRobot) – robot instance.

  • +
  • scene (Scene) – scene from isaac sim.

  • +
+
+
Returns:
+

dict of controllers with controller name as key.

+
+
Return type:
+

Dict[str, BaseController]

+
+
+
+ +
+
+grutopia.core.robot.controller.inject_sub_controllers(parent: BaseController, configs: List[ControllerParams], available: Dict[str, ControllerModel], robot: BaseRobot, scene: Scene)[source]
+

Recursively create and inject sub-controlllers into parent controller.

+
+
Parameters:
+
    +
  • parent (BaseController) – parent controller instance.

  • +
  • configs (List[ControllerParams]) – user configs of sub-controllers.

  • +
  • available (Dict[str, ControllerModel]) – available controllers.

  • +
  • robot (BaseRobot) – robot instance.

  • +
  • scene (Scene) – scene from isaac sim.

  • +
+
+
+
+ +
+
+

sensor

+
+
+class grutopia.core.robot.sensor.BaseSensor(config: SensorModel, robot: BaseRobot, scene: Scene)[source]
+

Base class of sensor.

+
+
+abstract get_data() Dict[source]
+

Get data from sensor.

+
+
Returns:
+

data dict of sensor.

+
+
Return type:
+

Dict

+
+
+
+ +
+
+classmethod register(name: str)[source]
+

Register a sensor class with the given name(decorator). +:param name: name of the sensor class. +:type name: str

+
+ +
+ +
+
+grutopia.core.robot.sensor.config_inject(params: SensorParams, model: SensorModel) SensorModel[source]
+

Merge sensor config from user config and robot model.

+
+
Parameters:
+
    +
  • params (SensorParams) – user config.

  • +
  • model (SensorModel) – sensor config from robot model.

  • +
+
+
Returns:
+

merged sensor config.

+
+
Return type:
+

SensorModel

+
+
+
+ +
+
+grutopia.core.robot.sensor.create_sensors(config: RobotUserConfig, robot_model: RobotModel, robot: BaseRobot, scene: Scene) Dict[str, BaseSensor][source]
+

Create all sensors of one robot.

+
+
Parameters:
+
    +
  • config (RobotUserConfig) – user config of the robot.

  • +
  • robot_model (RobotModel) – model of the robot.

  • +
  • robot (BaseRobot) – robot instance.

  • +
  • scene (Scene) – scene from isaac sim.

  • +
+
+
Returns:
+

dict of sensors with sensor name as key.

+
+
Return type:
+

Dict[str, BaseSensor]

+
+
+
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/scene.html b/html/api/scene.html new file mode 100644 index 0000000..56ae28b --- /dev/null +++ b/html/api/scene.html @@ -0,0 +1,626 @@ + + + + + + + + + + + + + + + + grutopia.core.scene — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.scene

+
+

object

+
+
+class grutopia.core.scene.object.ObjectCommon(config: Object)[source]
+

Object common class.

+
+
+classmethod register(name: str)[source]
+

Register an object class with the given name(decorator).

+
+
Parameters:
+

name (str) – name of the object

+
+
+
+ +
+ +
+
+grutopia.core.scene.object.create_object(config: Object)[source]
+

Create an object. +:param config: configuration of the objects +:type config: ObjectConfig

+
+ +
+
+

usd_op

+
+
+grutopia.core.scene.scene.util.usd_op.add_usd_ref(source_stage: Stage, dest_stage: Stage, src_prim_path: str, dest_prim_path: str) None[source]
+

Add an opened usd into another usd as a reference +set name in dest_prim_path

+
+
Parameters:
+
    +
  • source_stage (Usd.Stage) – source stage

  • +
  • dest_stage (Usd.Stage) – dest stage

  • +
  • src_prim_path (str) – source prim path

  • +
  • dest_prim_path (str) – dest prim path

  • +
+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.add_xform_of_prim(prim: Prim, xform_op: str, set_valve: Any) None[source]
+

Add xform data of a prim with new data

+
+
Parameters:
+
    +
  • prim (Usd.Prim) – objects prim

  • +
  • xform_op (str) – which op to be set

  • +
  • set_valve (Any) – new data to be set, could be Gf.Vec3d, Gf.Rotation

  • +
+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.add_xform_of_prim_old(prim: Prim, xform_op: str, set_valve: Any) None[source]
+

Add xform data of a prim with new data

+
+
Parameters:
+
    +
  • prim (Usd.Prim) – objects prim

  • +
  • xform_op (str) – which op to be set

  • +
  • set_valve (Any) – new data to be set, could be Gf.Vec3d, Gf.Rotation

  • +
+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.compute_bbox(prim: Prim) Range3d[source]
+

Compute Bounding Box using ComputeWorldBound at UsdGeom.Imageable

+
+
Parameters:
+

prim – A prim to compute the bounding box.

+
+
Returns:
+

A range (i.e. bounding box)

+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.create_new_usd(new_usd_path: str, default_prim_name: str, default_axis: str | None = None) Stage[source]
+

Create a new usd

+
+
Parameters:
+
    +
  • new_usd_path (str) – where to place this new usd

  • +
  • default_prim_name (str) – default prim name (root prim path)

  • +
  • default_axis (str) – default axis for new usd

  • +
+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.delete_prim_in_stage(stage: Stage, prim: Prim) None[source]
+

Delete a prim in stage

+
+
Parameters:
+
    +
  • stage (Usd.Stage) – objects stage

  • +
  • prim (Usd.Prim) – prim to be deleted

  • +
+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.delete_xform_of_prim(prim: Prim, xform_op: str) None[source]
+

Delete xform data of a prim

+
+
Parameters:
+
    +
  • prim (Usd.Prim) – objects prim

  • +
  • xform_op (str) – which op to be deleted

  • +
+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.get_local_transform_xform(prim: Prim) Tuple[Vec3d, Rotation, Vec3d][source]
+

Get the local transformation of a prim using Xformable.

+
+
Parameters:
+

prim – The prim to calculate the local transformation.

+
+
Returns:
+

    +
  • Translation vector.

  • +
  • Rotation quaternion, i.e. 3d vector plus angle.

  • +
  • Scale vector.

  • +
+

+
+
Return type:
+

A tuple of

+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.get_world_transform_xform(prim: Prim) Tuple[Vec3d, Rotation, Vec3d][source]
+

Get the local transformation of a prim using Xformable.

+
+
Parameters:
+

prim – The prim to calculate the world transformation.

+
+
Returns:
+

    +
  • Translation vector.

  • +
  • Rotation quaternion, i.e. 3d vector plus angle.

  • +
  • Scale vector.

  • +
+

+
+
Return type:
+

A tuple of

+
+
+
+ +
+
+grutopia.core.scene.scene.util.usd_op.set_xform_of_prim(prim: Prim, xform_op: str, set_valve: Any) None[source]
+

Set xform data of a prim with new data

+
+
Parameters:
+
    +
  • prim (Usd.Prim) – objects prim

  • +
  • xform_op (str) – which op to be set

  • +
  • set_valve (Any) – new data to be set, could be np.array

  • +
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/task.html b/html/api/task.html new file mode 100644 index 0000000..3bc360a --- /dev/null +++ b/html/api/task.html @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + + grutopia.core.task — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.task

+
+

task

+
+
+class grutopia.core.task.task.BaseTask(config: TaskUserConfig, scene: Scene)[source]
+

wrap of omniverse isaac sim’s base task

+
    +
  • enable register for auto register task

  • +
  • contains robots

  • +
+
+
+calculate_metrics() dict[source]
+

[summary]

+
+
Raises:
+

NotImplementedError – [description]

+
+
+
+ +
+
+get_observations() Dict[str, Any][source]
+

Returns current observations from the objects needed for the behavioral layer.

+
+
Returns:
+

observation of robots in this task

+
+
Return type:
+

Dict[str, Any]

+
+
+
+ +
+
+individual_reset()[source]
+

reload this task individually without reloading whole world.

+
+ +
+
+abstract is_done() bool[source]
+

Returns True of the task is done.

+
+
Raises:
+

NotImplementedError – this must be overridden.

+
+
+
+ +
+
+post_reset() None[source]
+

Calls while doing a .reset() on the world.

+
+ +
+
+pre_step(time_step_index: int, simulation_time: float) None[source]
+

called before stepping the physics simulation.

+
+
Parameters:
+
    +
  • time_step_index (int) – [description]

  • +
  • simulation_time (float) – [description]

  • +
+
+
+
+ +
+
+classmethod register(name: str)[source]
+

Register a task with its name(decorator). +:param name: name of the task +:type name: str

+
+ +
+
+set_up_scene(scene: Scene) None[source]
+
+
Adding assets to the stage as well as adding the encapsulated objects such as XFormPrim..etc

to the task_objects happens here.

+
+
+
+
Parameters:
+

scene (Scene) – [description]

+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/api/util.html b/html/api/util.html new file mode 100644 index 0000000..9f104f4 --- /dev/null +++ b/html/api/util.html @@ -0,0 +1,1571 @@ + + + + + + + + + + + + + + + + grutopia.core.util — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

grutopia.core.util

+
+

array

+

Sub-module containing utilities for working with different array backends.

+
+
+grutopia.core.util.array.TENSOR_TYPES = {'numpy': <class 'numpy.ndarray'>, 'torch': <class 'torch.Tensor'>, 'warp': <class 'warp.types.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).

+
+ +
+
+grutopia.core.util.array.TENSOR_TYPE_CONVERSIONS = {'numpy': {<class 'warp.types.array'>: <function <lambda>>, <class 'torch.Tensor'>: <function <lambda>>}, 'torch': {<class 'warp.types.array'>: <function <lambda>>, <class 'numpy.ndarray'>: <function <lambda>>}, 'warp': {<built-in function array>: <function <lambda>>, <class 'torch.Tensor'>: <function <lambda>>}}
+

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).

+
+ +
+
+grutopia.core.util.array.TensorData
+

Type definition for a tensor data.

+

Union of numpy, torch, and warp arrays.

+

alias of Union[ndarray, Tensor, array]

+
+ +
+
+grutopia.core.util.array.convert_to_torch(array: ndarray | Tensor | array, dtype: dtype | None = None, device: device | str | None = None) Tensor[source]
+

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.

+
+
+
Parameters:
+
    +
  • 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.

+
+
+
+ +
+
+

assets

+
+
+

configclass

+

Sub-module that provides a wrapper around the Python 3.7 onwards dataclasses module.

+
+
+grutopia.core.util.configclass.configclass(cls, **kwargs)[source]
+

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. +
  3. Requiring explicit usage of field(default_factory=...)() to reinitialize mutable variables.

  4. +
+

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:

+
from dataclasses import MISSING
+
+from omni.isaac.orbit.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)
+
+
+
+
Parameters:
+
    +
  • cls – The class to wrap around.

  • +
  • **kwargs – Additional arguments to pass to dataclass().

  • +
+
+
Returns:
+

The wrapped class.

+
+
+
+ +
+
+

dict

+

Sub-module for utilities for working with dictionaries.

+
+
+grutopia.core.util.dict.class_to_dict(obj: object) dict[str, Any][source]
+

Convert an object into dictionary recursively.

+
+

Note

+

Ignores all names starting with “__” (i.e. built-in methods).

+
+
+
Parameters:
+

obj – An instance of a class to convert.

+
+
Raises:
+

ValueError – When input argument is not an object.

+
+
Returns:
+

Converted dictionary mapping.

+
+
+
+ +
+
+grutopia.core.util.dict.convert_dict_to_backend(data: dict, backend: str = 'numpy', array_types: Iterable[str] = ('numpy', 'torch', 'warp')) dict[source]
+

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.

+
+
+
Parameters:
+
    +
  • 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.

+
+
+
+ +
+
+grutopia.core.util.dict.dict_to_md5_hash(data: object) str[source]
+

Convert a dictionary into a hashable key using MD5 hash.

+
+
Parameters:
+

data – Input dictionary or configuration object to convert.

+
+
Returns:
+

A string object of double length containing only hexadecimal digits.

+
+
+
+ +
+
+grutopia.core.util.dict.print_dict(val, nesting: int = -4, start: bool = True)[source]
+

Outputs a nested dictionary.

+
+ +
+
+grutopia.core.util.dict.update_class_from_dict(obj, data: dict[str, Any], _ns: str = '') None[source]
+

Reads a dictionary and sets object variables recursively.

+

This function performs in-place update of the class member attributes.

+
+
Parameters:
+
    +
  • 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.

  • +
+
+
+
+ +
+
+grutopia.core.util.dict.update_dict(orig_dict: dict, new_dict: Mapping) dict[source]
+

Updates existing dictionary with values from a new dictionary.

+

This function mimics the dict.update() function. However, it works for +nested dictionaries as well.

+
+
Reference:

https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth

+
+
+
+
Parameters:
+
    +
  • orig_dict – The original dictionary to insert items to.

  • +
  • new_dict – The new dictionary to insert items from.

  • +
+
+
Returns:
+

The updated dictionary.

+
+
+
+ +
+
+

math

+
+
+

omni_usd_util

+
+
+grutopia.core.util.omni_usd_util.compute_path_bbox(prim_path: str) Tuple[Double3, Double3][source]
+

Compute Bounding Box using omni.usd.UsdContext.compute_path_world_bounding_box +See https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd/omni.usd.UsdContext.html# omni.usd.UsdContext.compute_path_world_bounding_box

+
+
Parameters:
+

prim_path – A prim path to compute the bounding box.

+
+
Returns:
+

A range (i.e. bounding box) as a minimum point and maximum point.

+
+
+
+ +
+
+grutopia.core.util.omni_usd_util.get_grabbed_able_xform_paths(root_path: str, prim: Prim, depth: int = 3) List[str][source]
+

get all prim paths of Xform objects under specified prim.

+
+
Parameters:
+
    +
  • root_path (str) – root path of scenes.

  • +
  • prim (Usd.Prim) – target prim.

  • +
  • depth (int, optional) – expected depth of Xform objects relative to root_path. Defaults to 3.

  • +
+
+
Returns:
+

prim paths.

+
+
Return type:
+

List[str]

+
+
+
+ +
+
+grutopia.core.util.omni_usd_util.get_pick_position(robot_base_position: ndarray, prim_path: str) ndarray[source]
+

Get the pick position for a manipulator robots to pick an objects at prim_path. +The pick position is simply the nearest top vertex of the objects’s bounding box.

+
+
Parameters:
+
    +
  • robot_base_position (np.ndarray) – robots base position.

  • +
  • prim_path (str) – prim path of objects to pick.

  • +
+
+
Returns:
+

pick position.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+grutopia.core.util.omni_usd_util.get_world_transform_xform(prim: Prim) Tuple[Vec3d, Rotation, Vec3d][source]
+

Get the local transformation of a prim using omni.usd.get_world_transform_matrix(). +See https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd/omni.usd.get_world_transform_matrix.html +:param prim: The prim to calculate the world transformation.

+
+
Returns:
+

    +
  • Translation vector.

  • +
  • Rotation quaternion, i.e. 3d vector plus angle.

  • +
  • Scale vector.

  • +
+

+
+
Return type:
+

A tuple of

+
+
+
+ +
+
+grutopia.core.util.omni_usd_util.nearest_xform_from_position(stage: Stage, xform_paths: List[str], position: ndarray, threshold: float = 0) str[source]
+

get prim path of nearest Xform objects from the target position.

+
+
Parameters:
+
    +
  • stage (Usd.Stage) – usd stage.

  • +
  • xform_paths (List[str]) – full list of xforms paths.

  • +
  • position (np.ndarray) – target position.

  • +
  • threshold (float, optional) – max distance. Defaults to 0 (unlimited).

  • +
+
+
Returns:
+

prim path of the Xform objects, None if not found.

+
+
Return type:
+

str

+
+
+
+ +
+
+

python

+

A set of utility functions for general python usage

+
+
+class grutopia.core.util.python.Recreatable[source]
+

Simple class that provides an abstract interface automatically saving __init__ args of +the classes inheriting it.

+
+
+get_init_info()[source]
+

Grabs relevant initialization information for this class instance. Useful for directly +reloading an objects from this information, using @create_object_from_init_info.

+
+
Returns:
+

Nested dictionary that contains this objects’ initialization information

+
+
Return type:
+

dict

+
+
+
+ +
+ +
+
+class grutopia.core.util.python.RecreatableAbcMeta(clsname, bases, clsdict)[source]
+

A composite metaclass of both RecreatableMeta and ABCMeta.

+

Adding in ABCMeta to resolve metadata conflicts.

+
+ +
+
+class grutopia.core.util.python.RecreatableMeta(clsname, bases, clsdict)[source]
+

Simple metaclass that automatically saves __init__ args of the instances it creates.

+
+ +
+
+class grutopia.core.util.python.Registerable[source]
+

Simple class template that provides an abstract interface for registering classes.

+
+ +
+
+class grutopia.core.util.python.Serializable[source]
+

Simple class that provides an abstract interface to dump / load states, optionally with serialized functionality +as well.

+
+
+deserialize(state)[source]
+

De-serializes flattened 1D numpy array @state into nested dictionary state. +Should be implemented by subclass.

+
+
Parameters:
+

state (n-array) – encoded + serialized, 1D numerical np.array capturing this objects’s state

+
+
Returns:
+

+
Keyword-mapped states of these objects. Should match structure of output from

self._dump_state()

+
+
+

+
+
Return type:
+

dict

+
+
+
+ +
+
+dump_state(serialized=False)[source]
+

Dumps the state of this objects in either dictionary of flattened numerical form.

+
+
Parameters:
+
    +
  • serialized (bool) – If True, will return the state of this objects as a 1D numpy array. Otherwise,

  • +
  • a (will return) –

  • +
+
+
Returns:
+

+
Either:
    +
  • Keyword-mapped states of these objects, or

  • +
  • encoded + serialized, 1D numerical np.array capturing this objects’ state, where n is @self.state_size

  • +
+
+
+

+
+
Return type:
+

dict or n-array

+
+
+
+ +
+
+load_state(state, serialized=False)[source]
+

Deserializes and loads this objects’ state based on @state

+
+
Parameters:
+
    +
  • state (dict or n-array) –

    Either: +- Keyword-mapped states of these objects, or +- encoded + serialized, 1D numerical np.array capturing this objects’ state,

    +
    +

    where n is @self.state_size

    +
    +

  • +
  • serialized (bool) – If True, will interpret @state as a 1D numpy array. Otherwise, +will assume the input is a (potentially nested) dictionary of states for this objects

  • +
+
+
+
+ +
+
+serialize(state)[source]
+

Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. +Should be implemented by subclass.

+
+
Parameters:
+

state (dict) – Keyword-mapped states of this objects to encode. Should match structure of output from +self._dump_state()

+
+
Returns:
+

encoded + serialized, 1D numerical np.array capturing this objects’s state

+
+
Return type:
+

n-array

+
+
+
+ +
+
+property state_size
+

Returns: +int: Size of this objects’s serialized state

+
+ +
+ +
+
+class grutopia.core.util.python.SerializableNonInstance[source]
+

Identical to Serializable, but intended for non-instance classes

+
+
+classmethod deserialize(state)[source]
+

De-serializes flattened 1D numpy array @state into nested dictionary state. +Should be implemented by subclass.

+
+
Parameters:
+

state (n-array) – encoded + serialized, 1D numerical np.array capturing this objects’s state

+
+
Returns:
+

+
Keyword-mapped states of this objects. Should match structure of output from

self._dump_state()

+
+
+

+
+
Return type:
+

dict

+
+
+
+ +
+
+classmethod dump_state(serialized=False)[source]
+

Dumps the state of this objects in either dictionary of flattened numerical form.

+
+
Parameters:
+
    +
  • serialized (bool) – If True, will return the state of this objects as a 1D numpy array. Otherwise,

  • +
  • a (will return) –

  • +
+
+
Returns:
+

+
Either:
    +
  • Keyword-mapped states of these objects, or

  • +
  • encoded + serialized, 1D numerical np.array capturing this objects’ state, where n is @self.state_size

  • +
+
+
+

+
+
Return type:
+

dict or n-array

+
+
+
+ +
+
+classmethod load_state(state, serialized=False)[source]
+

Deserializes and loads this objects’ state based on @state

+
+
Parameters:
+
    +
  • state (dict or n-array) –

    Either: +- Keyword-mapped states of these objects, or +- encoded + serialized, 1D numerical np.array capturing this objects’ state,

    +
    +

    where n is @self.state_size

    +
    +

  • +
  • serialized (bool) – If True, will interpret @state as a 1D numpy array. Otherwise, will assume the input is +a (potentially nested) dictionary of states for this objects

  • +
+
+
+
+ +
+
+classmethod serialize(state)[source]
+

Serializes nested dictionary state @state into a flattened 1D numpy array for encoding efficiency. +Should be implemented by subclass.

+
+
Parameters:
+

state (dict) – Keyword-mapped states of these objects to encode. Should match structure of output from +self._dump_state()

+
+
Returns:
+

encoded + serialized, 1D numerical np.array capturing this objects’s state

+
+
Return type:
+

n-array

+
+
+
+ +
+ +
+
+class grutopia.core.util.python.UniquelyNamed[source]
+

Simple class that implements a name property, that must be implemented by a subclass. Note that any @Named +entity must be UNIQUE!

+
+
+property name
+

Returns: +str: Name of this instance. Must be unique!

+
+ +
+
+remove_names(include_all_owned=True, skip_ids=None)[source]
+

Checks if self.name exists in the global NAMES registry, and deletes it if so. Possibly also iterates through +all owned member variables and checks for their corresponding names if @include_all_owned is True.

+
+
Parameters:
+
    +
  • include_all_owned (bool) – If True, will iterate through all owned members of this instance and remove their +names as well, if they are UniquelyNamed

  • +
  • skip_ids (None or set of int) – If specified, will skip over any ids in the specified set that are matched +to any attributes found (this compares id(attr) to @skip_ids).

  • +
+
+
+
+ +
+ +
+
+class grutopia.core.util.python.UniquelyNamedNonInstance[source]
+

Identical to UniquelyNamed, but intended for non-instanceable classes

+
+ +
+
+class grutopia.core.util.python.Wrapper(obj)[source]
+

Base class for all wrapper in OmniGibson

+
+
Parameters:
+

obj (any) – Arbitrary python objects instance to wrap

+
+
+
+
+property unwrapped
+

Grabs unwrapped objects

+
+
Returns:
+

The unwrapped objects instance

+
+
Return type:
+

any

+
+
+
+ +
+ +
+
+grutopia.core.util.python.assert_valid_key(key, valid_keys, name=None)[source]
+

Helper function that asserts that @key is in dictionary @valid_keys keys. If not, it will raise an error.

+
+
Parameters:
+
    +
  • key (any) – key to check for in dictionary @dic’s keys

  • +
  • valid_keys (Iterable) – contains keys should be checked with @key

  • +
  • name (str or None) – if specified, is the name associated with the key that will be printed out if the +key is not found. If None, default is “value”

  • +
+
+
+
+ +
+
+grutopia.core.util.python.camel_case_to_snake_case(camel_case_text)[source]
+

Helper function to convert a camel case text to snake case, e.g. “StrawberrySmoothie” -> “strawberry_smoothie”

+
+
Parameters:
+

camel_case_text (str) – Text in camel case

+
+
Returns:
+

snake case text

+
+
Return type:
+

str

+
+
+
+ +
+
+grutopia.core.util.python.clear()[source]
+

Clear state tied to singleton classes

+
+ +
+
+grutopia.core.util.python.create_class_from_registry_and_config(cls_name, cls_registry, cfg, cls_type_descriptor)[source]
+

Helper function to create a class with str type @cls_name, which should be a valid entry in @cls_registry, using +kwargs in dictionary form @cfg to pass to the constructor, with @cls_type_name specified for debugging

+
+
Parameters:
+
    +
  • cls_name (str) – Name of the class to create. This should correspond to the actual class type, in string form

  • +
  • cls_registry (dict) – Class registry. This should map string names of valid classes to create to the +actual class type itself

  • +
  • cfg (dict) – Any keyword arguments to pass to the class constructor

  • +
  • cls_type_descriptor (str) – Description of the class type being created. This can be any string and is used +solely for debugging purposes

  • +
+
+
Returns:
+

Created class instance

+
+
Return type:
+

any

+
+
+
+ +
+
+grutopia.core.util.python.create_object_from_init_info(init_info)[source]
+

Create a new objects based on given init info.

+
+
Parameters:
+

init_info (dict) – Nested dictionary that contains an objects’s init information.

+
+
Returns:
+

Newly created objects.

+
+
Return type:
+

any

+
+
+
+ +
+
+grutopia.core.util.python.extract_class_init_kwargs_from_dict(cls, dic, copy=False)[source]
+

Helper function to return a dictionary of key-values that specifically correspond to @cls class’s __init__ +constructor method, from @dic which may or may not contain additional, irrelevant kwargs. +Note that @dic may possibly be missing certain kwargs as specified by cls.__init__. No error will be raised.

+
+
Parameters:
+
    +
  • cls (object) – Class from which to grab __init__ kwargs that will be be used as filtering keys for @dic

  • +
  • dic (dict) – Dictionary containing multiple key-values

  • +
  • copy (bool) – If True, will deepcopy all values corresponding to the specified @keys

  • +
+
+
Returns:
+

+
Extracted subset dictionary possibly containing only the specified keys from cls.__init__ and their

corresponding values

+
+
+

+
+
Return type:
+

dict

+
+
+
+ +
+
+grutopia.core.util.python.extract_subset_dict(dic, keys, copy=False)[source]
+

Helper function to extract a subset of dictionary key-values from a current dictionary. Optionally (deep)copies +the values extracted from the original @dic if @copy is True.

+
+
Parameters:
+
    +
  • dic (dict) – Dictionary containing multiple key-values

  • +
  • keys (Iterable) – Specific keys to extract from @dic. If the key doesn’t exist in @dic, then the key is skipped

  • +
  • copy (bool) – If True, will deepcopy all values corresponding to the specified @keys

  • +
+
+
Returns:
+

Extracted subset dictionary containing only the specified @keys and their corresponding values

+
+
Return type:
+

dict

+
+
+
+ +
+
+grutopia.core.util.python.get_class_init_kwargs(cls)[source]
+

Helper function to return a list of all valid keyword arguments (excluding “self”) for the given @cls class.

+
+
Parameters:
+

cls (object) – Class from which to grab __init__ kwargs

+
+
Returns:
+

All keyword arguments (excluding “self”) specified by @cls __init__ constructor method

+
+
Return type:
+

list

+
+
+
+ +
+
+grutopia.core.util.python.get_uuid(name, n_digits=8)[source]
+

Helper function to create a unique @n_digits uuid given a unique @name

+
+
Parameters:
+
    +
  • name (str) – Name of the objects or class

  • +
  • n_digits (int) – Number of digits of the uuid, default is 8

  • +
+
+
Returns:
+

uuid

+
+
Return type:
+

int

+
+
+
+ +
+
+grutopia.core.util.python.meets_minimum_version(test_version, minimum_version)[source]
+

Verify that @test_version meets the @minimum_version

+
+
Parameters:
+
    +
  • test_version (str) – Python package version. Should be, e.g., 0.26.1

  • +
  • minimum_version (str) – Python package version to test against. Should be, e.g., 0.27.2

  • +
+
+
Returns:
+

Whether @test_version meets @minimum_version

+
+
Return type:
+

bool

+
+
+
+ +
+
+grutopia.core.util.python.merge_nested_dicts(base_dict, extra_dict, inplace=False, verbose=False)[source]
+

Iteratively updates @base_dict with values from @extra_dict. Note: This generates a new dictionary!

+
+
Parameters:
+
    +
  • base_dict (dict) – Nested base dictionary, which should be updated with all values from @extra_dict

  • +
  • extra_dict (dict) – Nested extra dictionary, whose values will overwrite corresponding ones in @base_dict

  • +
  • inplace (bool) – Whether to modify @base_dict in place or not

  • +
  • verbose (bool) – If True, will print when keys are mismatched

  • +
+
+
Returns:
+

Updated dictionary

+
+
Return type:
+

dict

+
+
+
+ +
+
+grutopia.core.util.python.save_init_info(func)[source]
+

Decorator to save the init info of an objects to objects._init_info.

+

_init_info contains class name and class constructor’s input args.

+
+ +
+
+grutopia.core.util.python.snake_case_to_camel_case(snake_case_text)[source]
+

Helper function to convert a snake case text to camel case, e.g. “strawberry_smoothie” -> “StrawberrySmoothie”

+
+
Parameters:
+

snake_case_text (str) – Text in snake case

+
+
Returns:
+

camel case text

+
+
Return type:
+

str

+
+
+
+ +
+
+grutopia.core.util.python.subclass_factory(name, base_classes, __init__=None, **kwargs)[source]
+

Programmatically generates a new class type with name @name, subclassing from base classes @base_classes, with +corresponding __init__ call @__init__.

+

NOTE: If __init__ is None (default), the __init__ call from @base_classes will be used instead.

+

cf. https://stackoverflow.com/questions/15247075/how-can-i-dynamically-create-derived-classes-from-a-base-class

+
+
Parameters:
+
    +
  • name (str) – Generated class name

  • +
  • base_classes (type, or list of type) – Base class(es) to use for generating the subclass

  • +
  • __init__ (None or function) – Init call to use for the base class when it is instantiated. If None if specified, +the newly generated class will automatically inherit the __init__ call from @base_classes

  • +
  • **kwargs (any) – keyword-mapped parameters to override / set in the child class, where the keys represent +the class / instance attribute to modify and the values represent the functions / value to set

  • +
+
+
+
+ +
+
+

string

+

Submodule containing utilities for transforming strings and regular expressions.

+
+
+grutopia.core.util.string.callable_to_string(value: Callable) str[source]
+

Converts a callable object to a string.

+
+
Parameters:
+

value – A callable object.

+
+
Raises:
+

ValueError – When the input argument is not a callable object.

+
+
Returns:
+

A string representation of the callable object.

+
+
+
+ +
+
+grutopia.core.util.string.is_lambda_expression(name: str) bool[source]
+

Checks if the input string is a lambda expression.

+
+
Parameters:
+

name – The input string.

+
+
Returns:
+

Whether the input string is a lambda expression.

+
+
+
+ +
+
+grutopia.core.util.string.resolve_matching_names(keys: str | Sequence[str], list_of_strings: Sequence[str], preserve_order: bool = False) tuple[list[int], list[str]][source]
+

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 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 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 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 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.

+
+
+
Parameters:
+
    +
  • 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.

  • +
+
+
+
+ +
+
+grutopia.core.util.string.resolve_matching_names_values(data: dict[str, Any], list_of_strings: Sequence[str], preserve_order: bool = False) tuple[list[int], list[str], list[Any]][source]
+

Match a list of regular expressions in a dictionary against a list of strings and return +the matched indices, names, and values.

+

If the 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 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 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 +preserve_order is True, it will return them as: ([0, 3, 4, 1, 2], [‘a’, ‘d’, ‘e’, ‘b’, ‘c’], [1, 1, 1, 2, 2]).

+
+
Parameters:
+
    +
  • 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 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.

  • +
+
+
+
+ +
+
+grutopia.core.util.string.string_to_callable(name: str) Callable[source]
+

Resolves the module and function names to return the function.

+
+
Parameters:
+

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

The function loaded from the module.

+
+
Return type:
+

Callable

+
+
+
+ +
+
+grutopia.core.util.string.to_camel_case(snake_str: str, to: str = 'cC') str[source]
+

Converts a string from snake case to camel case.

+
+
Parameters:
+
    +
  • 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.

+
+
+
+ +
+
+grutopia.core.util.string.to_snake_case(camel_str: str) str[source]
+

Converts a string from camel case to snake case.

+
+
Parameters:
+

camel_str – A string in camel case.

+
+
Returns:
+

A string in snake case (i.e. with ‘_’)

+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/genindex.html b/html/genindex.html new file mode 100644 index 0000000..ebfe745 --- /dev/null +++ b/html/genindex.html @@ -0,0 +1,1017 @@ + + + + + + + + + + + + + + + Index — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + + +

Index

+ +
+ A + | B + | C + | D + | E + | G + | I + | L + | M + | N + | O + | P + | R + | S + | T + | U + | W + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

G

+ + + +
    +
  • + grutopia.core.datahub.web_api + +
  • +
  • + grutopia.core.datahub.web_ui_api + +
  • +
  • + grutopia.core.register.register + +
  • +
  • + grutopia.core.robot.controller + +
  • +
  • + grutopia.core.robot.robot + +
  • +
  • + grutopia.core.robot.sensor + +
  • +
  • + grutopia.core.scene.object + +
  • +
  • + grutopia.core.scene.scene.util.usd_op + +
  • +
  • + grutopia.core.task.task + +
  • +
  • + grutopia.core.util.array + +
  • +
  • + grutopia.core.util.assets + +
  • +
  • + grutopia.core.util.configclass + +
  • +
  • + grutopia.core.util.dict + +
  • +
  • + grutopia.core.util.math + +
  • +
  • + grutopia.core.util.omni_usd_util + +
  • +
  • + grutopia.core.util.python + +
  • +
  • + grutopia.core.util.string + +
  • +
+ +

I

+ + + +
+ +

L

+ + +
+ +

M

+ + +
+ +

N

+ + + +
+ +

O

+ + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

W

+ + +
+ + + +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/get_started/30-min-to-get-started.html b/html/get_started/30-min-to-get-started.html new file mode 100644 index 0000000..c805498 --- /dev/null +++ b/html/get_started/30-min-to-get-started.html @@ -0,0 +1,985 @@ + + + + + + + + + + + + + + + + 30 minutes to get started — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

30 minutes to get started

+

In this tutorial, we’ll add a MyCobot280Pi to

+
+

Get robot resources

+
+

1. URDF and usd

+

we can get full resources here

+

if you are not familiar to ROS/ROS2, you need to rename the source path in URDF file

+

img.png

+

my resource path is /home/apx103/Desktop/mycobot_280_pi, so change meshes’ filename attr like this:

+
<visual>
+  <geometry>
+   <mesh filename="file:///home/apx103/Desktop/mycobot_280_pi/joint4.dae"/>
+  </geometry>
+  <origin xyz = "0.0 0 0.03056 " rpy = " 0 -1.5708 0"/>
+</visual>
+
+
+
+
+

2. import URDF and gen usd file

+

follow this instruction to get usd like this:

+
.
+├── materials
+│   ├── joint2.png
+│   ├── joint3.png
+│   ├── joint4.png
+│   ├── joint5.png
+│   ├── joint6.png
+│   └── joint7.png
+└──  mycobot_280pi_with_camera_flange.usd
+
+
+

then follow this instruction to get robot description

+

my description is like this:

+
# The robot description defines the generalized coordinates and how to map those
+# to the underlying URDF dofs.
+
+api_version: 1.0
+
+# Defines the generalized coordinates. Each generalized coordinate is assumed
+# to have an entry in the URDF.
+# Lula will only use these joints to control the robot position.
+cspace:
+    - joint2_to_joint1
+    - joint3_to_joint2
+    - joint4_to_joint3
+    - joint5_to_joint4
+    - joint6_to_joint5
+    - joint6output_to_joint6
+
+root_link: g_base
+
+default_q: [
+    0.0,-0.0,-0.0,-0.0,0.0,-0.0
+]
+
+# Most dimensions of the cspace have a direct corresponding element
+# in the URDF. This list of rules defines how unspecified coordinates
+# should be extracted or how values in the URDF should be overwritten.
+
+cspace_to_urdf_rules:
+
+# Lula uses collision spheres to define the robot geometry in order to avoid
+# collisions with external obstacles.  If no spheres are specified, Lula will
+# not be able to avoid obstacles.
+
+collision_spheres:
+  - joint1:
+    - "center": [0.0, 0.0, 0.039]
+      "radius": 0.035
+  - joint2:
+    - "center": [0.0, 0.0, 0.0]
+      "radius": 0.02
+    - "center": [0.0, 0.0, -0.045]
+      "radius": 0.02
+    - "center": [0.0, 0.0, -0.011]
+      "radius": 0.02
+    - "center": [0.0, 0.0, -0.023]
+      "radius": 0.02
+    - "center": [0.0, 0.0, -0.034]
+      "radius": 0.02
+  - joint4:
+    - "center": [0.0, 0.0, 0.0]
+      "radius": 0.02
+    - "center": [-0.094, -0.0, -0.0]
+      "radius": 0.02
+    - "center": [-0.016, -0.0, -0.0]
+      "radius": 0.02
+    - "center": [-0.031, -0.0, -0.0]
+      "radius": 0.02
+    - "center": [-0.047, -0.0, -0.0]
+      "radius": 0.02
+    - "center": [-0.063, -0.0, -0.0]
+      "radius": 0.02
+    - "center": [-0.078, -0.0, -0.0]
+      "radius": 0.02
+  - joint3:
+    - "center": [-0.0, -0.0, 0.064]
+      "radius": 0.02
+    - "center": [-0.107, -0.0, 0.064]
+      "radius": 0.02
+    - "center": [-0.018, -0.0, 0.064]
+      "radius": 0.02
+    - "center": [-0.036, -0.0, 0.064]
+      "radius": 0.02
+    - "center": [-0.053, -0.0, 0.064]
+      "radius": 0.02
+    - "center": [-0.071, -0.0, 0.064]
+      "radius": 0.02
+    - "center": [-0.089, -0.0, 0.064]
+      "radius": 0.02
+  - joint5:
+    - "center": [0.0, 0.0, 0.0]
+      "radius": 0.02
+  - joint6:
+    - "center": [0.0, 0.0, 0.0]
+      "radius": 0.02
+
+
+
+
+

3. Test files

+
+

Test whether these files work

+
+

open lula test extension in isaac sim to test files we get before

+

img.png

+

we only need to test lula Kunematics Solver. And then we know:

+
    +
  1. robot(manipulator) can get solve using lula Kunematics Solver.

  2. +
  3. description file of robot(manipulator) is work.

  4. +
+
+
+
+

Add Robot

+
+

What we need to do

+

In this example, we just add a IK controller to set the position(angle included) of manipulator’s end effector.

+
+
+

move resources to assets

+

we need to move resources to assets/robots/mycobot_280_pi_with_camera_flange like:

+
.
+├── materials
+│   ├── joint2.png
+│   ├── joint3.png
+│   ├── joint4.png
+│   ├── joint5.png
+│   ├── joint6.png
+│   └── joint7.png
+├── mycobot_280pi_with_camera_flange.urdf
+├── mycobot_280pi_with_camera_flange.usd
+└── robot_descriptor.yaml
+
+
+
+
+

Add Robot Class

+
    +
  1. At first, we need to define a Robot class based on omni.isaac.core.robots.robot.Robot. It wraps end effector and articulation.

    +
    import os
    +import numpy as np
    +from omni.isaac.core.prims import RigidPrim
    +from omni.isaac.core.robots.robot import Robot as IsaacRobot
    +from omni.isaac.core.scenes import Scene
    +from omni.isaac.core.utils.nucleus import get_assets_root_path
    +from omni.isaac.core.utils.stage import add_reference_to_stage
    +
    +from tao_yuan.core.config.robot import RobotUserConfig as Config
    +from tao_yuan.core.robot.robot import BaseRobot
    +from tao_yuan.core.robot.robot_model import RobotModel
    +from tao_yuan.core.util import log
    +
    +
    +
    +class MyCobot280(IsaacRobot):
    +    def __init__(self,
    +                 prim_path: str,
    +                 usd_path: str,
    +                 name: str,
    +                 position: np.ndarray = None,
    +                 orientation: np.ndarray = None,
    +                 scale: np.ndarray = None):
    +        add_reference_to_stage(
    +            prim_path=prim_path, usd_path=os.path.abspath(usd_path))
    +        super().__init__(
    +            prim_path=prim_path,
    +            name=name,
    +            position=position,
    +            orientation=orientation,
    +            scale=scale)
    +        self._end_effector_prim_path = prim_path + '/joint6_flange'
    +        self._end_effector = RigidPrim(
    +            prim_path=self._end_effector_prim_path,
    +            name=name + '_end_effector',
    +        )
    +
    +@property
    +def end_effector_prim_path(self):
    +    return self._end_effector_prim_path
    +
    +@property
    +def end_effector(self):
    +    return self._end_effector
    +
    +
    +
  2. +
  3. Then we need to wrap this Robot with tao_yuan.core.robot.BaseRobot

    +
    @BaseRobot.regester('MyCobot280PiRobot')
    +class MyCobot280PiRobot(BaseRobot):
    +def __init__(self, config: Config, robot_model: RobotModel, scene: Scene):
    +    super().__init__(config, robot_model, scene)
    +    self._sensor_config = robot_model.sensors
    +    self._start_position = np.array(
    +        config.position) if config.position is not None else None
    +    self._start_orientation = np.array(
    +        config.orientation) if config.orientation is not None else None
    +
    +    log.debug(f'mycobot_280_pi {config.name}: position    : ' +
    +              str(self._start_position))
    +    log.debug(f'mycobot_280_pi {config.name}: orientation : ' +
    +              str(self._start_orientation))
    +
    +    usd_path = robot_model.usd_path
    +    if usd_path.startswith('/Isaac'):
    +        usd_path = get_assets_root_path() + usd_path
    +    print(usd_path)
    +
    +    log.debug(f'mycobot_280_pi {config.name}: usd_path         : ' +
    +              str(usd_path))
    +    log.debug(f'mycobot_280_pi {config.name}: config.prim_path : ' +
    +              str(config.prim_path))
    +    self.isaac_robot = MyCobot280(
    +        prim_path=config.prim_path,
    +        name=config.name,
    +        position=self._start_position,
    +        orientation=self._start_orientation,
    +        usd_path=usd_path,
    +    )
    +
    +    self._robot_scale = np.array([1.0, 1.0, 1.0])
    +    if config.scale is not None:
    +        self._robot_scale = np.array(config.scale)
    +        self.isaac_robot.set_local_scale(self._robot_scale)
    +
    +    self._robot_ik_base = RigidPrim(
    +        prim_path=config.prim_path + '/joint1',
    +        name=config.name + '_ik_base_link',
    +    )
    +
    +    self._robot_base = RigidPrim(
    +        prim_path=config.prim_path + '/g_base',
    +        name=config.name + '_base_link')
    +
    +def get_robot_scale(self):
    +    return self._robot_scale
    +
    +def get_robot_ik_base(self):
    +    return self._robot_ik_base
    +
    +def get_world_pose(self):
    +    return self._robot_base.get_world_pose()
    +
    +def apply_action(self, action: dict):
    +    """
    +    Args:
    +        action (dict): inputs for controllers.
    +    """
    +    for controller_name, controller_action in action.items():
    +        if controller_name not in self.controllers:
    +            log.warn(f'unknown controller {controller_name} in action')
    +            continue
    +        controller = self.controllers[controller_name]
    +        control = controller.action_to_control(controller_action)
    +        self.isaac_robot.apply_action(control)
    +
    +def get_obs(self):
    +    """Add obs you need here."""
    +    position, orientation = self._robot_base.get_world_pose()
    +
    +    # custom
    +    obs = {
    +        'position': position,
    +        'orientation': orientation,
    +    }
    +
    +    eef_world_pose = self.isaac_robot.end_effector.get_world_pose()
    +    obs['eef_position'] = eef_world_pose[0]
    +    obs['eef_orientation'] = eef_world_pose[1]
    +
    +    # common
    +    obs.update(super().get_obs())
    +    return obs
    +
    +
    +
  4. +
+
+
+

Add Controller

+

In this example, we use a framework integrated controller ty_extension.controllers.ik_controller to solve IK for our robot.

+
# yapf: disable
+from typing import Dict, List, Tuple
+
+import numpy as np
+from omni.isaac.core.articulations import Articulation
+from omni.isaac.core.scenes import Scene
+from omni.isaac.core.utils.numpy.rotations import rot_matrices_to_quats
+from omni.isaac.core.utils.types import ArticulationAction
+from omni.isaac.motion_generation import ArticulationKinematicsSolver, LulaKinematicsSolver
+
+from tao_yuan.core.robot.controller import BaseController
+from tao_yuan.core.robot.robot import BaseRobot
+from tao_yuan.core.robot.robot_model import ControllerModel
+
+# yapf: enable
+
+
+@BaseController.register('InverseKinematicsController')
+class InverseKinematicsController(BaseController):
+
+    def __init__(self, config: ControllerModel, robot: BaseRobot, scene: Scene):
+        super().__init__(config=config, robot=robot, scene=scene)
+        self._kinematics_solver = KinematicsSolver(
+            robot_articulation=robot.isaac_robot,
+            robot_description_path=config.robot_description_path,
+            robot_urdf_path=config.robot_urdf_path,
+            end_effector_frame_name=config.end_effector_frame_name,
+        )
+        self.joint_subset = self._kinematics_solver.get_joints_subset()
+        if config.reference:
+            assert config.reference in ['world', 'robot', 'arm_base'], \
+                f'unknown ik controller reference {config.reference}'
+            self._reference = config.reference
+        else:
+            self._reference = 'world'
+
+        self.success = False
+        self.last_action = None
+        self.threshold = 0.01 if config.threshold is None else config.threshold
+
+        self._ik_base = robot.get_robot_ik_base()
+        self._robot_scale = robot.get_robot_scale()
+        if self._reference == 'robot':
+            # The local pose of ik base is assumed not to change during simulation for ik controlled parts.
+            # However, the world pose won't change even its base link has moved for some robots like ridgeback franka,
+            # so the ik base pose returned by get_local_pose may change during simulation, which is unexpected.
+            # So the initial local pose of ik base is saved at first and used during the whole simulation.
+            self._ik_base_local_pose = self._ik_base.get_local_pose()
+
+    def get_ik_base_world_pose(self) -> Tuple[np.ndarray, np.ndarray]:
+        if self._reference == 'robot':
+            ik_base_pose = self._ik_base_local_pose
+        elif self._reference == 'arm_base':
+            # Robot base is always at the origin.
+            ik_base_pose = (np.array([0, 0, 0]), np.array([1, 0, 0, 0]))
+        else:
+            ik_base_pose = self._ik_base.get_world_pose()
+        return ik_base_pose
+
+    def forward(self, eef_target_position: np.ndarray,
+                eef_target_orientation: np.ndarray) -> Tuple[ArticulationAction, bool]:
+        self.last_action = [eef_target_position, eef_target_orientation]
+
+        if eef_target_position is None:
+            # Keep joint positions to lock pose.
+            subset = self._kinematics_solver.get_joints_subset()
+            return subset.make_articulation_action(joint_positions=subset.get_joint_positions(),
+                                                   joint_velocities=subset.get_joint_velocities()), True
+
+        ik_base_pose = self.get_ik_base_world_pose()
+        self._kinematics_solver.set_robot_base_pose(robot_position=ik_base_pose[0] / self._robot_scale,
+                                                    robot_orientation=ik_base_pose[1])
+        return self._kinematics_solver.compute_inverse_kinematics(
+            target_position=eef_target_position / self._robot_scale,
+            target_orientation=eef_target_orientation,
+        )
+
+    def action_to_control(self, action: List | np.ndarray):
+        """
+        Args:
+            action (np.ndarray): n-element 1d array containing:
+              0. eef_target_position
+              1. eef_target_orientation
+        """
+        assert len(action) == 2, 'action must contain 2 elements'
+        assert self._kinematics_solver is not None, 'kinematics solver is not initialized'
+
+        eef_target_position = None if action[0] is None else np.array(action[0])
+        eef_target_orientation = None if action[1] is None else np.array(action[1])
+
+        result, self.success = self.forward(
+            eef_target_position=eef_target_position,
+            eef_target_orientation=eef_target_orientation,
+        )
+        return result
+
+    def get_obs(self) -> Dict[str, np.ndarray]:
+        """Compute the pose of the robot end effector using the simulated robot's current joint positions
+
+        Returns:
+            Dict[str, np.ndarray]:
+            - eef_position: eef position
+            - eef_orientation: eef orientation quats
+            - success: if solver converged successfully
+            - finished: applied action has been finished
+        """
+        ik_base_pose = self.get_ik_base_world_pose()
+        self._kinematics_solver.set_robot_base_pose(robot_position=ik_base_pose[0] / self._robot_scale,
+                                                    robot_orientation=ik_base_pose[1])
+        pos, ori = self._kinematics_solver.compute_end_effector_pose()
+
+        finished = False
+        if self.last_action is not None:
+            if self.last_action[0] is not None:
+                dist_from_goal = np.linalg.norm(pos - self.last_action[0])
+                if dist_from_goal < self.threshold * self.robot.get_robot_scale()[0]:
+                    finished = True
+
+        return {
+            'eef_position': pos * self._robot_scale,
+            'eef_orientation': rot_matrices_to_quats(ori),
+            'success': self.success,
+            'finished': finished,
+        }
+
+
+class KinematicsSolver(ArticulationKinematicsSolver):
+    """Kinematics Solver for robot.  This class loads a LulaKinematicsSovler object
+
+    Args:
+        robot_description_path (str): path to a robot description yaml file \
+            describing the cspace of the robot and other relevant parameters
+        robot_urdf_path (str): path to a URDF file describing the robot
+        end_effector_frame_name (str): The name of the end effector.
+    """
+
+    def __init__(self, robot_articulation: Articulation, robot_description_path: str, robot_urdf_path: str,
+                 end_effector_frame_name: str):
+        self._kinematics = LulaKinematicsSolver(robot_description_path, robot_urdf_path)
+
+        ArticulationKinematicsSolver.__init__(self, robot_articulation, self._kinematics, end_effector_frame_name)
+
+        if hasattr(self._kinematics, 'set_max_iterations'):
+            self._kinematics.set_max_iterations(150)
+        else:
+            self._kinematics.ccd_max_iterations = 150
+
+        return
+
+    def set_robot_base_pose(self, robot_position: np.array, robot_orientation: np.array):
+        return self._kinematics.set_robot_base_pose(robot_position=robot_position, robot_orientation=robot_orientation)
+
+
+
+
+

Add config

+

After add robot and add controller, we need register our robot to ty_extension/robots/robot_models.yaml as follows

+
- type: "MyCobot280PiRobot"
+  usd_path: "TY-1/assets/robots/mycobot_280pi_with_camera_flange/mycobot_280pi_with_camera_flange.usd"
+  controllers:
+  - name: "ik_controller"
+    type: "InverseKinematicsController"
+    robot_description_path: "TY-1/assets/robots/mycobot_280pi_with_camera_flange/robot_descriptor.yaml"
+    robot_urdf_path: "TY-1/assets/robots/mycobot_280pi_with_camera_flange/mycobot_280pi_with_camera_flange.urdf"
+    end_effector_frame_name: "joint6_flange"
+    threshold: 0.01
+
+
+

this file combine robot with controllers and sensors, and setup some param that app users have not deed to know.

+
+
+
+

Test

+

to test a new robot in our framework. we need to create two files:

+
    +
  1. run file: define running process.

  2. +
  3. config file: define scene\robots\objects and any well load to isaac sim dynamically.

  4. +
+
+

run file

+
from tao_yuan.core.config import SimulatorConfig
+from tao_yuan.core.env import BaseEnv
+
+file_path = './TY-1/demo/configs/follow_target_mycobot.yaml'
+sim_config = SimulatorConfig(file_path)
+
+# env = BaseEnv(sim_config)
+env = BaseEnv(sim_config, headless=False)
+
+while env.simulation_app.is_running():
+    actions = []
+    for task in env.config.tasks:
+        target_cube_pose = env.runner.get_obj(task.objects[0].name).get_world_pose()
+        action = {
+            'mycobot': {
+                'ik_controller': target_cube_pose,
+            },
+        }
+        actions.append(action)
+    observations = env.step(actions)
+
+env.simulation_app.close()
+
+
+
+
+

config

+
simulator:
+  physics_dt: 0.01666  # 1 / 60
+  rendering_dt: 0.01666  # 1 / 60
+
+env:
+  bg_type: null
+
+render:
+  render: true
+
+tasks:
+- type: "SingleInferenceTask"
+  name: "mycobot280pi_follow_cube"
+  env_num: 1
+  offset_size: 1.0
+  robots:
+  - name: mycobot
+    prim_path: "/mycobot"
+    type: "MyCobot280PiRobot"
+    position: [.0, .0, .0]
+    scale: [1, 1, 1]
+    controller_params:
+    - name: "ik_controller"
+    - name: "rmp_controller"
+
+  objects:
+  - name: target_cube
+    type: VisualCube
+    prim_path: /target_cube
+    position: [0.08, 0.1, 0.3]
+    scale: [0.05015, 0.05015, 0.05015]
+    color: [0, 0, 1.0]
+
+
+
+
+

Run Test

+

Run test file at root path of isaac sim like:

+
python ./TY-1/demo/follow_target_mycobot280.py
+
+
+

we get a follow target mycobot demo

+

img.png

+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/get_started/installation.html b/html/get_started/installation.html new file mode 100644 index 0000000..1706806 --- /dev/null +++ b/html/get_started/installation.html @@ -0,0 +1,540 @@ + + + + + + + + + + + + + + + + Installation — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

Installation

+
+

Prerequisites

+
    +
  • OS: Ubuntu 20.04+

  • +
  • RAM: 32GB+

  • +
  • GPU: NVIDIA RTX 2070+

  • +
  • NVIDIA Driver: 525.85+

  • +
+
+
+

Install from source (Linux)

+

Make sure you have Isaac Sim 2023.1.1 installed.

+

Conda is required to install from source.

+
    +
  1. Navigate to Isaac Sim root path (default path in Ubuntu is $HOME/.local/share/ov/pkg/isaac_sim-2023.1.1) and clone the repository.

    +
    $ cd PATH/TO/ISAAC_SIM/ROOT
    +$ git clone git@github.com:OpenRobotLab/GRUtopia.git
    +
    +
    +
  2. +
  3. Download dataset and save it to the assets directory under GRUtopia root path.

    +

    The file structure should be like:

    +
    GRUtopia
    +├── assets
    +│   ├── objects
    +│   ├── policy
    +│   ├── robots
    +│   └── scenes
    +├── demo
    +│   ├── configs
    +│   ├── h1_city.py
    +│   ├── h1_locomotion.py
    +│   └── h1_npc.py
    +...
    +
    +
    +
  4. +
  5. Navigate to GRUtopia root path and configure the conda environment.

    +
    $ cd PATH/TO/GRUTOPIA/ROOT
    +
    +# Conda environment will be created and configured automatically with prompt.
    +$ ./setup_conda.sh
    +
    +$ cd .. && conda activate grutopia  # or your conda env name
    +
    +
    +
  6. +
  7. Verify the Installation.

    +

    Run at the root path of Isaac Sim:

    +
    $ cd PATH/TO/ISAAC_SIM/ROOT
    +$ python ./GRUtopia/demo/h1_locomotion.py  # start simulation
    +
    +
    +
  8. +
+
+
+

Install with Docker (Linux)

+

Make sure you have Docker installed.

+
    +
  1. Clone the GRUtopia repository to any desired location.

    +
    $ git clone git@github.com:OpenRobotLab/GRUtopia.git
    +
    +
    +
  2. +
  3. Download dataset and save it to the assets directory under GRUtopia root path.

    +

    The file structure should be like:

    +
    GRUtopia
    +├── assets
    +│   ├── objects
    +│   ├── policy
    +│   ├── robots
    +│   └── scenes
    +├── demo
    +│   ├── configs
    +│   ├── h1_city.py
    +│   ├── h1_locomotion.py
    +│   └── h1_npc.py
    +...
    +
    +
    +
  4. +
  5. Pull the Isaac Sim image (docker login is required, please refer to NGC Documents).

    +
    $ docker pull nvcr.io/nvidia/isaac-sim:2023.1.1
    +
    +
    +
  6. +
  7. Build docker image.

    +
    $ cd PATH/TO/GRUTOPIA/ROOT
    +
    +$ docker build -t grutopia:0.0.1 .
    +
    +
    +
  8. +
  9. Start docker container.

    +
    $ cd PATH/TO/GRUTOPIA/ROOT
    +
    +$ export CACHE_ROOT=$HOME/docker  # set cache root path
    +$ export WEBUI_HOST=127.0.0.1  # set webui listen address, default to 127.0.0.1
    +
    +$ docker run --name grutopia -it --rm --gpus all --network host \
    +  -e "ACCEPT_EULA=Y" \
    +  -e "PRIVACY_CONSENT=Y" \
    +  -e "WEBUI_HOST=${WEBUI_HOST}" \
    +  -v ${PWD}:/isaac-sim/GRUtopia \
    +  -v ${CACHE_ROOT}/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/cache/ov:/root/.cache/ov:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/cache/pip:/root/.cache/pip:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/data:/root/.local/share/ov/data:rw \
    +  -v ${CACHE_ROOT}/isaac-sim/documents:/root/Documents:rw \
    +  grutopia:0.0.1
    +
    +
    +
  10. +
  11. Verify the Installation.

    +

    Run inside container:

    +
    # run inside container
    +$ python ./GRUtopia/demo/h1_locomotion.py  # start simulation
    +
    +
    +
  12. +
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/get_started/wander-with-keyboard.html b/html/get_started/wander-with-keyboard.html new file mode 100644 index 0000000..2ff99be --- /dev/null +++ b/html/get_started/wander-with-keyboard.html @@ -0,0 +1,460 @@ + + + + + + + + + + + + + + + + Wander with keyboard — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

Wander with keyboard

+
+

This tutorial guides you to wander with keyboard as h1 robot.

+
+
+

Wander in house

+
# decompress the house scene
+$ cd PATH/TO/GRUTOPIA/ROOT
+$ cd assets/scenes/
+$ unzip demo_house.zip
+# start simulation
+$ cd ../../..
+$ python ./GRUtopia/demo/h1_house.py
+
+
+

You can control the h1 robot with keyboard command:

+
    +
  • W: Move Forward

  • +
  • S: Move Backward

  • +
  • A: Move Left

  • +
  • D: Move Right

  • +
  • Q: Turn Left

  • +
  • E: Turn Right

  • +
+

You can change camera view to perspective/first-person/third-person camera.

+
+
+

Wander in city

+
# decompress the city scene
+$ cd PATH/TO/GRUTOPIA/ROOT
+$ cd assets/scenes/
+$ unzip demo_city.zip
+# start simulation
+$ cd ../../..
+$ python ./GRUtopia/demo/h1_city.py
+
+
+

You can control the h1 robot with keyboard command:

+
    +
  • W: Move Forward

  • +
  • S: Move Backward

  • +
  • A: Move Left

  • +
  • D: Move Right

  • +
  • Q: Turn Left

  • +
  • E: Turn Right

  • +
+

You can change camera view to perspective/first-person/third-person camera.

+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/get_started/webui.html b/html/get_started/webui.html new file mode 100644 index 0000000..bfc8f7b --- /dev/null +++ b/html/get_started/webui.html @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + Interact with NPC through WebUI — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

Interact with NPC through WebUI

+
+

This tutorial guides you to interact with NPC through WebUI.

+
+
+

Currently WebUI is only supported with docker. Make sure you have built the docker image following the instruction of installation with docker.

+
+
+

Start WebUI process

+

Start docker container and start WebUI process within the container.

+
cd PATH/TO/GRUTOPIA/ROOT
+
+$ export CACHE_ROOT=$HOME/docker  # set cache root path
+$ export WEBUI_HOST=127.0.0.1  # set WebUI listen address, default to 127.0.0.1
+
+$ docker run --name grutopia -it --rm --gpus all --network host \
+  -e "ACCEPT_EULA=Y" \
+  -e "PRIVACY_CONSENT=Y" \
+  -e "WEBUI_HOST=${WEBUI_HOST}" \
+  -v ${PWD}:/isaac-sim/GRUtopia \
+  -v ${CACHE_ROOT}/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \
+  -v ${CACHE_ROOT}/isaac-sim/cache/ov:/root/.cache/ov:rw \
+  -v ${CACHE_ROOT}/isaac-sim/cache/pip:/root/.cache/pip:rw \
+  -v ${CACHE_ROOT}/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \
+  -v ${CACHE_ROOT}/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \
+  -v ${CACHE_ROOT}/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \
+  -v ${CACHE_ROOT}/isaac-sim/data:/root/.local/share/ov/data:rw \
+  -v ${CACHE_ROOT}/isaac-sim/documents:/root/Documents:rw \
+  grutopia:0.0.1
+
+# run inside container
+$ ./webui_start.sh  # start WebUI process
+
+
+

Now you can access WebUI from http://${WEBUI_HOST}:8080.

+
+
+

Start simulation

+

GPT-4o is used as npc by default so an openai api key is required.

+

Run inside container:

+
# run inside container
+$ sed -i 's/YOUR_OPENAI_API_KEY/{YOUR_OPENAI_API_KEY}/g' GRUtopia/demo/config/h1_npc.yaml  # set openai api key
+$ python GRUtopia/demo/h1_npc.py  # start simulation
+
+
+

Now the simulation is available through WebRTC in the WebUI page.

+

You can control the h1 robot with keyboard command:

+
    +
  • W: Move Forward

  • +
  • S: Move Backward

  • +
  • A: Move Left

  • +
  • D: Move Right

  • +
  • Q: Turn Left

  • +
  • E: Turn Right

  • +
+

And you can talk to npc as agent in the chatbox. The left side of the screen will display Isaac Sim’s window, where you can switch to the robot’s camera view. The right side features the chat window, where you can interact with the NPC. Ensure your questions are related to the scene, the robot’s view, or its position, as unrelated queries might not yield useful responses. Replies will appear in the chat window within a few seconds. During this time, you can continue moving the robot or ask additional questions, which will be answered sequentially.

+

Note that the NPC might not always provide accurate answers due to design limitations.

+

Occasionally, unexpected responses from the LLM or code errors may cause issues. Check the error logs or contact us for support in resolving these problems.

+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..cf92d88 --- /dev/null +++ b/html/index.html @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + Welcome to GRUtopia’s documentation! — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

Welcome to GRUtopia’s documentation!

+
+

Introduction

+ +
+ + + + +
+
+

Indices and tables

+ +
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/introduction/introduction.html b/html/introduction/introduction.html new file mode 100644 index 0000000..2d28374 --- /dev/null +++ b/html/introduction/introduction.html @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + Introduction — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

Introduction

+

Current embodied intelligence research urgently needs to overcome the problem of disconnection between high-level perceptual planning and low-level motion control. By constructing a highly realistic simulation environment, not only can it enhance the robot’s perception and behavior planning capabilities but also promote the development of multi-module collaborative control strategies, ultimately steadily advancing towards the goal of general-purpose embodied robots. (Research Needs)

+

High-level studies are typically conducted on static datasets or simulation platforms, which often cannot provide environments with both visual realism and physical realism at the same time, limiting the transferability of research results to real-world application scenarios. At the same time, the development of large-model technology has opened up new paths for improving the perception and behavior planning abilities of robots, making the realization of the goal of universal robots no longer distant. (Industry Status)

+

To address these challenges, the OpenRobotLab team of Shanghai AI Lab proposes the GRUtopia Embodied Intelligence Simulation Platform. The platform features:

+
    +
  1. A large-scale scene dataset covering various application scenarios, capable of providing rich and realistic visual and physical environments for embodied research;

  2. +
  3. An API library and extensive toolkit containing mainstream robotic control algorithms, enabling plug-and-play functionality with just one line of code to achieve a realistic control process, reproducing all kinds of actual situations likely encountered during planning processes;

  4. +
  5. The toolkit also provides functions like algorithm migration and strategy training.

  6. +
+

Additionally, there is a task generation system for embodied tasks driven by large models and an NPC interaction system, marking the first time automated embodied task generation and multimodal interactive NPCs have been achieved. This offers infinite training tasks for developing generalized agents and also serves as a foundation for studying embodied behavior interpretability and human-machine interactions. (Achievement Definition)

+
+ + +
+ +
+ +
+
+ +
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/objects.inv b/html/objects.inv new file mode 100644 index 0000000..a9f9a16 Binary files /dev/null and b/html/objects.inv differ diff --git a/html/py-modindex.html b/html/py-modindex.html new file mode 100644 index 0000000..4ffd714 --- /dev/null +++ b/html/py-modindex.html @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + Python Module Index — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ +
    + +
  • + + Docs + > +
  • + + +
  • Python Module Index
  • + + +
  • + +
  • + + +
+ + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + + +

Python Module Index

+ +
+ g +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ g
+ grutopia +
    + grutopia.core.datahub.api +
    + grutopia.core.datahub.model_data +
    + grutopia.core.datahub.web_api +
    + grutopia.core.datahub.web_ui_api +
    + grutopia.core.register.register +
    + grutopia.core.robot.controller +
    + grutopia.core.robot.robot +
    + grutopia.core.robot.sensor +
    + grutopia.core.scene.object +
    + grutopia.core.scene.scene.util.usd_op +
    + grutopia.core.task.task +
    + grutopia.core.util.array +
    + grutopia.core.util.assets +
    + grutopia.core.util.configclass +
    + grutopia.core.util.dict +
    + grutopia.core.util.math +
    + grutopia.core.util.omni_usd_util +
    + grutopia.core.util.python +
    + grutopia.core.util.string +
+ + +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/search.html b/html/search.html new file mode 100644 index 0000000..ba39b8e --- /dev/null +++ b/html/search.html @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + Search — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + + + + +
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/searchindex.js b/html/searchindex.js new file mode 100644 index 0000000..36bf756 --- /dev/null +++ b/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["advanced_tutorials/how-to-add-controller", "advanced_tutorials/how-to-add-robot", "advanced_tutorials/how-to-add-sensor", "api/datahub", "api/env", "api/register", "api/robot", "api/scene", "api/task", "api/util", "get_started/installation", "get_started/wander-with-keyboard", "get_started/webui", "index", "introduction/introduction", "tutorials/how-to-use-controller", "tutorials/how-to-use-npc", "tutorials/how-to-use-robot", "tutorials/how-to-use-sensor"], "filenames": ["advanced_tutorials/how-to-add-controller.md", "advanced_tutorials/how-to-add-robot.md", "advanced_tutorials/how-to-add-sensor.md", "api/datahub.rst", "api/env.rst", "api/register.rst", "api/robot.rst", "api/scene.rst", "api/task.rst", "api/util.rst", "get_started/installation.md", "get_started/wander-with-keyboard.md", "get_started/webui.md", "index.rst", "introduction/introduction.md", "tutorials/how-to-use-controller.md", "tutorials/how-to-use-npc.md", "tutorials/how-to-use-robot.md", "tutorials/how-to-use-sensor.md"], "titles": ["How to add custom controller", "How to add custom robot", "How to add custom sensor", "grutopia.core.datahub", "grutopia.core.env", "grutopia.core.register", "grutopia.core.robot", "grutopia.core.scene", "grutopia.core.task", "grutopia.core.util", "Installation", "Wander with keyboard", "Interact with NPC through WebUI", "Welcome to GRUtopia\u2019s documentation!", "Introduction", "How to use controller", "Customize NPC with your algorithm", "How to use robot", "How to use sensor"], "terms": {"thi": [0, 1, 2, 3, 7, 8, 9, 11, 12, 14, 15, 17, 18], "tutori": [0, 1, 2, 11, 12, 15, 17, 18], "show": [0, 1, 2, 15, 17, 18], "you": [0, 1, 2, 10, 11, 12, 15, 16, 17, 18], "befor": [0, 2, 8], "should": [0, 2, 9, 10], "read": [0, 2, 9, 15], "take": 0, "chatboxcontrol": 0, "an": [0, 1, 7, 9, 12, 14, 15, 17, 18], "exampl": [0, 9, 15, 16], "from": [0, 1, 2, 6, 8, 9, 12, 16, 17, 18], "datetim": 0, "import": [0, 1, 2, 5, 9, 17], "type": [0, 1, 2, 3, 4, 6, 7, 8, 9, 16, 17, 18], "ani": [0, 3, 4, 6, 7, 8, 9, 10], "dict": [0, 1, 2, 3, 4, 6, 8, 16], "list": [0, 3, 4, 6, 9], "union": [0, 6, 9], "numpi": [0, 9, 17], "np": [0, 1, 6, 7, 9, 15, 17], "omni": [0, 1, 2, 9], "isaac": [0, 2, 3, 4, 6, 8, 9, 10, 12, 15, 18], "scene": [0, 1, 2, 6, 8, 9, 10, 11, 12, 13, 14], "util": [0, 1, 2, 7, 13], "articulationact": [0, 6], "datahub": [0, 13], "model_data": 0, "logdata": 0, "modeldata": 0, "basecontrol": [0, 6], "baserobot": [0, 1, 2, 6], "controllermodel": [0, 6], "class": [0, 1, 2, 3, 6, 7, 8, 9, 16], "def": [0, 1, 2, 16], "__init__": [0, 1, 2, 9], "self": [0, 1, 2, 9, 16], "config": [0, 1, 2, 4, 6, 7, 8, 9, 10, 12, 16], "name": [0, 1, 2, 3, 6, 7, 8, 9, 10, 12, 16, 17, 18], "str": [0, 1, 2, 3, 4, 6, 7, 8, 9], "none": [0, 1, 2, 3, 4, 5, 7, 8, 9, 16], "chat": [0, 3, 12, 15, 16], "super": [0, 1, 2], "_user_config": 0, "counter": 0, "action_to_control": [0, 1, 6], "action": [0, 1, 2, 3, 4, 6, 15, 17, 18], "ndarrai": [0, 1, 6, 9], "todo": 0, "check": [0, 9, 12, 15, 18], "input": [0, 1, 6, 9], "like": [0, 3, 10, 14], "arrai": [0, 1, 6, 7, 15], "i": [0, 1, 2, 3, 7, 8, 9, 10, 12, 14, 16, 18], "am": 0, "sentenc": 0, "agent_avatar_data": 0, "return": [0, 1, 2, 3, 4, 6, 7, 8, 9], "forward": [0, 11, 12], "0": [0, 1, 2, 3, 9, 10, 12, 15, 17], "set": [0, 1, 3, 6, 7, 9, 10, 12], "get_ob": [0, 1, 3, 6], "while": [0, 8, 15, 17, 18], "env": [0, 2, 10, 13, 15, 17, 18], "simulation_app": [0, 2, 4, 15, 17, 18], "is_run": [0, 15, 17, 18], "env_act": [0, 15, 17, 18], "h1": [0, 11, 12, 15, 17], "web_chat": [0, 16], "\u4f60\u597d": 0, "ob": [0, 1, 2, 3, 15, 16, 17, 18], "step": [0, 2, 4, 8, 15, 17, 18], "close": [0, 4, 15, 17, 18], "assum": [1, 9], "alreadi": [1, 9], "have": [1, 9, 10, 12, 14], "usd": [1, 7, 9], "file": [1, 10], "ha": [1, 9, 14], "drivabl": 1, "joint": [1, 6, 15], "creat": [1, 6, 7, 9, 10], "ty_extens": [], "demo_robot": 1, "py": [1, 10, 11, 12, 18], "inherit": [1, 9], "isaacrobot": 1, "stage": [1, 7, 8, 9], "add_reference_to_stag": 1, "demorobot": 1, "prim_path": [1, 2, 9, 17, 18], "usd_path": 1, "posit": [1, 9, 12, 16, 17], "orient": [1, 16], "scale": [1, 6, 7, 9, 14, 17], "o": [1, 10], "path": [1, 2, 3, 5, 7, 9, 10, 11, 12, 14, 17, 18], "abspath": 1, "specif": [1, 9], "paramet": [1, 3, 4, 5, 6, 7, 8, 9], "attribut": [1, 9], "here": [1, 8, 17, 18], "robotuserconfig": [1, 2, 6], "robotmodel": [1, 6, 18], "log": [1, 2, 3, 10, 12], "demorobotwrapp": 1, "_sensor_config": 1, "_gain": 1, "gain": 1, "_start_posit": 1, "els": 1, "_start_orient": 1, "debug": [1, 2, 9], "f": [1, 17], "startswith": 1, "get_assets_root_path": 1, "isaac_robot": 1, "_robot_scal": 1, "set_local_scal": 1, "attr": [1, 9], "want": 1, "apply_act": [1, 6], "arg": [1, 9, 16], "controller_nam": [1, 3], "controller_act": 1, "item": [1, 9], "warn": 1, "unknown": [1, 9], "continu": [1, 12], "apply_actuator_model": 1, "joint_subset": 1, "observ": [1, 3, 4, 6, 8, 16, 18], "need": [1, 4, 8, 14, 16, 17], "_robot_bas": 1, "get_world_pos": 1, "common": [1, 7], "c_obs_nam": 1, "controller_ob": 1, "sensor_nam": 1, "sensor_ob": 1, "get_data": [1, 2, 6], "And": [1, 12, 16], "ar": [1, 2, 3, 9, 12, 14, 16], "mani": 1, "other": [1, 9, 16], "function": [1, 9, 14, 16], "fyi": [1, 2, 16], "model": [1, 2, 6, 14], "yaml": [1, 2, 12, 15, 17, 18], "see": [1, 9, 17], "The": [2, 7, 9, 10, 12, 14], "just": [2, 14], "tensor": [2, 9], "thei": [2, 9, 15], "interfac": [2, 9], "passiv": 2, "receiv": 2, "all": [2, 3, 5, 6, 9, 10, 12, 14, 15], "kind": [2, 14], "inform": [2, 9], "onli": [2, 9, 12, 14], "thing": [2, 6], "we": [2, 15, 17, 18], "matter": 2, "implement": [2, 9], "basesensor": [2, 6], "camera": [2, 11, 12, 16, 18], "i_camera": 2, "sensormodel": [2, 6], "wrap": [2, 8, 9], "sim": [2, 6, 8, 10, 12], "": [2, 8, 9, 11, 12, 14, 15, 18], "robot_user_config": 2, "sensor_config": 2, "param": [2, 3, 6, 7, 8, 9], "sensor_param": 2, "p": 2, "_camera": 2, "create_camera": 2, "size": [2, 9], "1280": 2, "720": 2, "camera_prim_path": 2, "_": [2, 9], "join": 2, "resolut": 2, "sensor_init": 2, "switch": [2, 12], "initi": [2, 9], "add_distance_to_image_plane_to_fram": 2, "add_semantic_segmentation_to_fram": 2, "add_instance_segmentation_to_fram": 2, "add_instance_id_segmentation_to_fram": 2, "add_bounding_box_2d_tight_to_fram": 2, "rgba": [2, 18], "get_rgba": 2, "depth": [2, 9], "get_depth": 2, "frame": [2, 16], "get_current_fram": 2, "humanoidrobot": [2, 15, 17, 18], "rel": [2, 9, 18], "prim": [2, 6, 7, 9, 18], "map": [2, 9], "kei": [2, 6, 9, 12], "In": [2, 16, 18], "loop": [2, 15, 18], "photo": [2, 3, 18], "robot_name_in_config": [2, 18], "wish": [], "wip": [], "isaacdata": 3, "sourc": [3, 4, 5, 6, 7, 8, 9, 15], "statu": [3, 14], "There": 3, "two": [3, 9], "structur": [3, 9, 10], "robot_1": 3, "cap": 3, "obs_1": 3, "data": [3, 6, 7, 9, 10, 12, 16, 18], "obs_2": 3, "classmethod": [3, 6, 7, 8, 9], "add_act": 3, "actiondata": 3, "add": [3, 7, 9, 13], "get_action_by_id": 3, "task_id": 3, "int": [3, 4, 8, 9], "get": [3, 4, 6, 7, 9, 16, 18], "id": [3, 9], "robot_nam": [3, 16], "get_act": 3, "get_obs_by_id": 3, "task": [3, 13, 14, 17], "get_all_ob": 3, "sensor": [3, 13], "send_act": 3, "send": [3, 16], "robot_id": 3, "set_obs_data": 3, "flush": 3, "includ": [3, 6, 9], "ui": 3, "interact": [3, 13, 14], "web_ui_api": 3, "get_chat_control": 3, "uuid": [3, 9], "control": [3, 11, 12, 13, 14, 17], "default": [3, 4, 5, 7, 9, 10, 12], "chat_control": [3, 16], "get_log_data": 3, "log_data": 3, "send_chain_of_thought": 3, "cot": 3, "chain": 3, "thought": 3, "send_chat_control": 3, "nicknam": 3, "text": [3, 9], "img": 3, "role": 3, "user": [3, 6], "new": [3, 7, 9, 14, 16], "messag": [3, 16], "chatbox": [3, 12], "displai": [3, 12], "option": [3, 9], "imag": [3, 7, 10, 12], "agent": [3, 12, 14], "send_log_data": 3, "log_typ": 3, "bob": 3, "photo_url": 3, "http": [3, 9, 12], "127": [3, 10, 12], "1": [3, 9, 10, 12, 17], "8080": [3, 12], "static": [3, 14], "avatar_default": 3, "jpg": 3, "logger": 3, "url": 3, "endpoint": [3, 16], "web_api": 3, "get_actions_by_id": 3, "msg": 3, "rtype": [3, 4], "bool": [3, 4, 8, 9], "ok": 3, "successfulli": 3, "baseenv": [4, 17], "simulatorconfig": [4, 17], "headless": [4, 17], "true": [4, 8, 9, 17], "webrtc": [4, 12], "fals": [4, 9, 17], "environ": [4, 10, 14], "get_observ": [4, 8], "reset": [4, 6, 8], "us": [4, 7, 9, 12, 13, 16], "word": 4, "properti": [4, 9], "simul": [4, 8, 10, 11, 14, 17], "app": 4, "instanc": [4, 6, 9], "simulation_config": 4, "run": [4, 10, 12, 18], "given": [4, 6, 7, 9], "simulatorrunn": [], "import_all_modules_for_regist": 5, "custom_module_path": 5, "extension_path": 5, "modul": [5, 9, 13, 14], "custom": [5, 13], "e": [5, 7, 9, 10, 11, 12], "g": [5, 9, 12], "xxx": 5, "lib1": 5, "lib2": 5, "lib3": 5, "extens": [5, 14], "integr": 5, "robot_model": [6, 15, 17, 18], "base": [6, 8, 9], "appli": 6, "valu": [6, 9, 16], "correspond": [6, 9], "world": [6, 7, 8, 9, 14, 16, 17], "pose": 6, "rais": [6, 8, 9], "notimplementederror": [6, 8], "_description_": 6, "get_robot_articul": 6, "articul": 6, "get_robot_bas": 6, "rigidprim": 6, "link": 6, "rigid": 6, "get_robot_ik_bas": 6, "ik": 6, "part": 6, "get_robot_scal": 6, "x": [6, 9], "y": [6, 10, 12], "z": 6, "post_reset": [6, 8], "up": [6, 14], "happen": [6, 8], "after": 6, "regist": [6, 7, 8, 9, 13], "its": [6, 8, 9, 12], "decor": [6, 7, 8, 9], "set_up_to_scen": 6, "setup": 6, "create_robot": 6, "taskuserconfig": [6, 8], "dictionari": [6, 9], "abstract": [6, 8, 9], "convert": [6, 9], "1d": [6, 9], "format": [6, 9], "signal": 6, "get_joint_subset": 6, "articulationsubset": 6, "subset": [6, 9], "config_inject": 6, "user_config": 6, "controllerparam": 6, "merg": 6, "create_control": 6, "one": [6, 14], "inject_sub_control": 6, "parent": 6, "avail": [6, 12], "recurs": [6, 9], "inject": 6, "sub": [6, 9], "controll": 6, "sensorparam": 6, "create_sensor": 6, "objectcommon": 7, "create_object": 7, "configur": [7, 9, 10], "objectconfig": 7, "add_usd_ref": 7, "source_stag": 7, "dest_stag": 7, "src_prim_path": 7, "dest_prim_path": 7, "open": [7, 14], "anoth": 7, "refer": [7, 9, 10, 16], "dest": 7, "add_xform_of_prim": 7, "xform_op": 7, "set_valv": 7, "xform": [7, 9], "which": [7, 9, 12, 14], "op": 7, "could": 7, "gf": 7, "vec3d": [7, 9], "rotat": [7, 9], "add_xform_of_prim_old": 7, "compute_bbox": 7, "range3d": 7, "comput": [7, 9], "bound": [7, 9], "box": [7, 9], "computeworldbound": 7, "usdgeom": 7, "A": [7, 9, 11, 12, 14], "rang": [7, 9], "create_new_usd": 7, "new_usd_path": 7, "default_prim_nam": 7, "default_axi": 7, "where": [7, 9, 12], "place": [7, 9], "root": [7, 9, 10, 11, 12], "axi": 7, "delete_prim_in_stag": 7, "delet": [7, 9], "delete_xform_of_prim": 7, "get_local_transform_xform": 7, "tupl": [7, 9], "local": [7, 9, 10, 12], "transform": [7, 9], "xformabl": 7, "calcul": [7, 9], "translat": [7, 9], "vector": [7, 9], "quaternion": [7, 9], "3d": [7, 9], "plu": [7, 9], "angl": [7, 9], "get_world_transform_xform": [7, 9], "set_xform_of_prim": 7, "basetask": 8, "omnivers": [8, 9, 10, 12], "enabl": [8, 14], "auto": 8, "contain": [8, 9, 10, 12, 14], "robot": [8, 9, 10, 11, 12, 13, 14, 16], "calculate_metr": 8, "summari": 8, "descript": [8, 9], "current": [8, 9, 12, 14], "object": [8, 9, 10], "behavior": [8, 14], "layer": 8, "individual_reset": 8, "reload": [8, 9], "individu": 8, "without": 8, "whole": 8, "is_don": 8, "done": [8, 9, 17], "must": [8, 9], "overridden": 8, "call": [8, 9], "do": 8, "pre_step": 8, "time_step_index": 8, "simulation_tim": 8, "float": [8, 9], "physic": [8, 14], "set_up_scen": 8, "ad": [8, 9], "asset": [8, 10, 11], "well": [8, 9], "encapsul": 8, "xformprim": 8, "etc": 8, "task_object": 8, "work": 9, "differ": 9, "backend": 9, "tensor_typ": 9, "torch": 9, "warp": 9, "each": 9, "wp": 9, "tensor_type_convers": 9, "lambda": 9, "built": [9, 12], "nest": 9, "convers": 9, "outer": 9, "target": 9, "inner": 9, "tensordata": 9, "definit": [9, 14], "alia": 9, "convert_to_torch": 9, "dtype": 9, "devic": 9, "tri": 9, "If": [9, 16], "directli": 9, "deduc": 9, "For": 9, "cpu": 9, "cuda": 9, "sinc": 9, "pytorch": 9, "doe": 9, "support": [9, 12], "unsign": 9, "integ": 9, "sign": 9, "cast": 9, "It": [9, 17], "can": [9, 11, 12, 14, 15, 16, 18], "defin": 16, "host": [10, 12], "server": [], "resourc": [], "store": 9, "By": 14, "nucleu": [], "make": [9, 10, 12, 14, 15, 17], "distribut": [], "easier": [], "repositori": 10, "smaller": [], "code": [12, 14, 15], "wise": [], "more": 15, "pleas": [10, 15], "isaac_nucleus_dir": [], "content": [], "product": [], "s3": [], "u": 12, "west": [], "2": 9, "amazonaw": [], "com": [9, 10], "2023": 10, "directori": 10, "nvidia": [9, 10, 12], "isaac_orbit_nucleus_dir": [], "sampl": [], "orbit": 9, "nucleus_asset_root_dir": [], "resolv": [9, 12], "api": [12, 14, 16], "follow": 12, "persist": [], "asset_root": [], "iter": 9, "over": 9, "connect": [], "first": [11, 14], "cloud": [], "nvidia_nucleus_dir": [], "check_file_path": [], "liter": [], "exist": [9, 15, 17, 18], "possibl": [], "below": [], "read_fil": [], "bytesio": [], "filenotfounderror": [], "when": [9, 18], "found": 9, "retrieve_file_path": [], "download_dir": [], "force_download": [], "retriev": [], "absolut": [], "download": 10, "machin": 14, "case": 9, "system": [14, 16], "temporari": [], "whether": 9, "forc": [], "overwrit": 9, "runtimeerror": [], "cannot": [9, 14], "copi": 9, "provid": [9, 12, 14], "wrapper": 9, "around": 9, "3": 9, "7": 9, "onward": 9, "dataclass": 9, "cl": 9, "kwarg": 9, "extra": 9, "As": 9, "standard": 9, "main": 9, "issu": [9, 12], "them": [9, 15, 16, 18], "non": 9, "gener": [9, 14], "These": 9, "requir": [9, 10, 12], "annot": 9, "member": 9, "explicit": 9, "usag": [9, 15], "field": 9, "default_factori": 9, "reiniti": 9, "mutabl": 9, "variabl": 9, "deal": 9, "abov": 9, "also": [9, 14, 15], "addit": [9, 12], "helper": 9, "easili": 9, "miss": 9, "viewercfg": 9, "ey": 9, "5": 9, "purpos": [9, 14], "lookat": 9, "envcfg": 9, "num_env": 9, "episode_length": 9, "2000": 9, "viewer": 9, "env_cfg": 9, "24": 9, "print": 9, "to_dict": 9, "env_cfg_copi": 9, "replac": 9, "arbitrari": 9, "keyword": 9, "argument": 9, "32": 9, "pass": 9, "class_to_dict": 9, "obj": 9, "ignor": 9, "start": [9, 10, 11], "__": 9, "method": 9, "valueerror": 9, "convert_dict_to_backend": 9, "array_typ": 9, "desir": [9, 10], "left": [9, 11, 12], "unchang": 9, "referenc": 9, "so": [9, 12], "specifi": 9, "updat": 9, "dict_to_md5_hash": 9, "hashabl": 9, "md5": 9, "hash": 9, "doubl": 9, "length": 9, "hexadecim": 9, "digit": 9, "print_dict": 9, "val": 9, "4": 9, "output": 9, "update_class_from_dict": 9, "_n": 9, "perform": 9, "namespac": 9, "typeerror": 9, "match": 9, "keyerror": 9, "update_dict": 9, "orig_dict": 9, "new_dict": 9, "mimic": 9, "howev": 9, "stackoverflow": 9, "question": [9, 12], "3232943": 9, "vari": 9, "origin": 9, "insert": 9, "compute_path_bbox": 9, "double3": 9, "usdcontext": 9, "compute_path_world_bounding_box": 9, "doc": 9, "kit": [9, 10, 12], "latest": 9, "html": 9, "minimum": 9, "point": 9, "maximum": 9, "get_grabbed_able_xform_path": 9, "root_path": 9, "under": [9, 10, 18], "expect": 9, "get_pick_posit": 9, "robot_base_posit": 9, "pick": [9, 16], "manipul": 9, "simpli": 9, "nearest": 9, "top": 9, "vertex": 9, "get_world_transform_matrix": 9, "nearest_xform_from_posit": 9, "xform_path": 9, "threshold": 9, "full": [9, 16], "max": 9, "distanc": 9, "unlimit": 9, "recreat": 9, "simpl": 9, "automat": [9, 10], "save": [9, 10], "get_init_info": 9, "grab": 9, "relev": 9, "create_object_from_init_info": 9, "recreatableabcmeta": 9, "clsname": 9, "clsdict": 9, "composit": 9, "metaclass": 9, "both": [9, 14], "recreatablemeta": 9, "abcmeta": 9, "metadata": 9, "conflict": 9, "register": 9, "templat": 9, "serializ": 9, "dump": 9, "load": 9, "state": 9, "serial": 9, "deseri": 9, "de": 9, "flatten": 9, "subclass": 9, "n": 9, "encod": 9, "numer": 9, "captur": 9, "_dump_stat": 9, "dump_stat": 9, "either": 9, "form": 9, "otherwis": 9, "state_s": 9, "load_stat": 9, "interpret": [9, 14], "potenti": 9, "effici": 9, "serializablenoninst": 9, "ident": 9, "intend": 9, "uniquelynam": 9, "note": [9, 12], "entiti": 9, "uniqu": 9, "remove_nam": 9, "include_all_own": 9, "skip_id": 9, "global": 9, "registri": 9, "possibli": 9, "through": [9, 13], "own": [9, 16], "remov": 9, "skip": 9, "compar": 9, "uniquelynamednoninst": 9, "omnigibson": 9, "unwrap": 9, "assert_valid_kei": 9, "valid_kei": 9, "assert": 9, "error": [9, 12], "dic": 9, "associ": 9, "out": 9, "camel_case_to_snake_cas": 9, "camel_case_text": 9, "camel": 9, "snake": 9, "strawberrysmoothi": 9, "strawberry_smoothi": 9, "clear": [3, 9], "ti": 9, "singleton": 9, "create_class_from_registry_and_config": 9, "cls_name": 9, "cls_registri": 9, "cfg": 9, "cls_type_descriptor": 9, "valid": 9, "entri": [9, 15], "constructor": 9, "cls_type_nam": 9, "actual": [9, 14], "itself": 9, "being": 9, "sole": 9, "init_info": 9, "init": 9, "info": [9, 16], "newli": 9, "extract_class_init_kwargs_from_dict": 9, "mai": [9, 12], "irrelev": 9, "certain": 9, "No": 9, "filter": 9, "multipl": 9, "deepcopi": 9, "extract": 9, "extract_subset_dict": 9, "deep": 9, "doesn": [9, 17], "t": [9, 10, 17], "get_class_init_kwarg": 9, "exclud": 9, "get_uuid": 9, "n_digit": 9, "8": 9, "number": 9, "meets_minimum_vers": 9, "test_vers": 9, "minimum_vers": 9, "verifi": [9, 10], "meet": 9, "packag": 9, "version": 9, "26": 9, "test": 9, "against": 9, "27": 9, "merge_nested_dict": 9, "base_dict": 9, "extra_dict": 9, "inplac": 9, "verbos": 9, "whose": 9, "ones": 9, "modifi": 9, "mismatch": 9, "save_init_info": 9, "func": 9, "_init_info": 9, "snake_case_to_camel_cas": 9, "snake_case_text": 9, "subclass_factori": 9, "base_class": 9, "programmat": 9, "instead": 9, "cf": 9, "15247075": 9, "how": [9, 13], "dynam": 9, "deriv": 9, "instanti": 9, "overrid": 9, "child": 9, "repres": 9, "submodul": 9, "regular": 9, "express": 9, "callable_to_str": 9, "callabl": 9, "represent": 9, "is_lambda_express": 9, "resolve_matching_nam": 9, "sequenc": 9, "list_of_str": 9, "preserve_ord": 9, "queri": [9, 12], "indic": 9, "order": 9, "same": [9, 14], "mean": 9, "dictat": 9, "consid": 9, "b": 9, "c": 9, "d": [9, 11, 12], "sort": 9, "preserv": 9, "resolve_matching_names_valu": 9, "string_to_cal": 9, "attribute_nam": 9, "to_camel_cas": 9, "snake_str": 9, "cc": 9, "convent": 9, "invalid": 9, "to_snake_cas": 9, "camel_str": 9, "ll": [], "mycobot280pi": [], "familiar": [], "ro": [], "ros2": [], "renam": [], "my": [], "home": [10, 12], "apx103": [], "desktop": [], "mycobot_280_pi": [], "chang": [11, 16], "mesh": [], "filenam": [], "visual": 14, "geometri": [], "joint4": [], "dae": [], "xyz": [], "03056": [], "rpy": [], "5708": [], "instruct": 12, "materi": [], "joint2": [], "png": [], "joint3": [], "joint5": [], "joint6": [], "joint7": [], "mycobot_280pi_with_camera_flang": [], "coordin": [], "those": [], "underli": [], "dof": [], "api_vers": [], "lula": [], "cspace": [], "joint2_to_joint1": [], "joint3_to_joint2": [], "joint4_to_joint3": [], "joint5_to_joint4": [], "joint6_to_joint5": [], "joint6output_to_joint6": [], "root_link": [], "g_base": [], "default_q": [], "most": [], "dimens": [], "direct": [], "element": [], "rule": [], "unspecifi": [], "overwritten": [], "cspace_to_urdf_rul": [], "collis": [], "sphere": [], "avoid": [], "extern": [], "obstacl": [], "abl": [], "collision_spher": [], "joint1": [], "center": [], "039": [], "radiu": [], "035": [], "02": [], "045": [], "011": [], "023": [], "034": [], "094": [], "016": [], "031": [], "047": [], "063": [], "078": [], "064": [], "107": [], "018": [], "036": [], "053": [], "071": [], "089": [], "kunemat": [], "solver": [], "know": 18, "solv": [], "end": [], "effector": [], "mycobot_280_pi_with_camera_flang": [], "robot_descriptor": [], "At": 14, "core": [13, 16, 17, 18], "tao_yuan": [], "mycobot280": [], "_end_effector_prim_path": [], "joint6_flang": [], "_end_effector": [], "end_effector_prim_path": [], "end_effector": [], "Then": 16, "regest": [], "mycobot280pirobot": [], "_robot_ik_bas": [], "_ik_base_link": [], "_base_link": [], "eef_world_pos": [], "eef_posit": [], "eef_orient": [], "framework": [], "ik_control": [], "our": 16, "yapf": [], "disabl": [], "rot_matrices_to_quat": [], "motion_gener": [], "articulationkinematicssolv": [], "lulakinematicssolv": [], "inversekinematicscontrol": [], "_kinematics_solv": [], "kinematicssolv": [], "robot_articul": [], "robot_description_path": [], "robot_urdf_path": [], "end_effector_frame_nam": [], "get_joints_subset": [], "arm_bas": [], "_refer": [], "success": [], "last_act": [], "01": [], "_ik_bas": [], "dure": [12, 14], "won": [], "even": 15, "some": [], "ridgeback": [], "franka": [], "get_local_pos": [], "unexpect": 12, "_ik_base_local_pos": [], "get_ik_base_world_pos": [], "ik_base_pos": [], "elif": [], "alwai": 12, "eef_target_posit": [], "eef_target_orient": [], "keep": [], "lock": [], "make_articulation_act": [], "joint_posit": [], "get_joint_posit": [], "joint_veloc": [], "get_joint_veloc": [], "set_robot_base_pos": [], "robot_posit": [], "robot_orient": [], "compute_inverse_kinemat": [], "target_posit": [], "target_orient": [], "len": [], "kinemat": [], "result": 14, "eef": [], "quat": [], "converg": [], "finish": [], "been": 14, "po": [], "ori": [], "compute_end_effector_pos": [], "dist_from_go": [], "linalg": [], "norm": [], "lulakinematicssovl": [], "describ": [], "_kinemat": [], "hasattr": [], "set_max_iter": [], "150": [], "ccd_max_iter": [], "ty": [], "combin": [], "deed": [], "process": [14, 16], "file_path": 17, "demo": [10, 11, 12, 16, 18], "follow_target_mycobot": [], "sim_config": 17, "target_cube_pos": [], "runner": [], "get_obj": [], "mycobot": [], "append": [], "physics_dt": 17, "01666": [], "60": [], "rendering_dt": 17, "bg_type": 17, "null": 17, "render": 17, "singleinferencetask": 17, "mycobot280pi_follow_cub": [], "env_num": 17, "offset_s": 17, "controller_param": [], "rmp_control": [], "target_cub": [], "visualcub": [], "08": [], "05015": [], "color": [], "python": [10, 11, 12], "follow_target_mycobot280": [], "ubuntu": 10, "20": 10, "04": 10, "22": [], "oper": [], "gpu": [10, 12], "rtx": 10, "2070": 10, "higher": [], "driver": 10, "recommend": [], "525": 10, "85": 10, "toolkit": 14, "conda": 10, "10": [], "13": [], "clone": 10, "repo": [], "oif": [], "share": [10, 12], "ov": [10, 12], "pkg": 10, "isaac_sim": 10, "cd": [10, 11, 12], "activ": 10, "path_to_source_cod": [], "bash": [], "setup_conda": 10, "sh": [10, 12], "necessari": [], "your": [10, 12, 13, 17], "move_jetbot_headless": [], "anywher": [], "pull": 10, "ngc": 10, "login": 10, "nvcr": 10, "io": 10, "build": 10, "TO": [10, 11, 12], "grutopia": [10, 11, 12, 14, 17, 18], "export": [10, 12], "cache_root": [10, 12], "cach": [10, 12], "webui_host": [10, 12], "webui": [3, 10, 13], "listen": [10, 12], "address": [10, 12, 14], "rm": [10, 12], "network": [10, 12], "accept_eula": [10, 12], "privacy_cons": [10, 12], "v": [10, 12], "pwd": [10, 12], "test_script": [], "run_script": [], "rw": [10, 12], "pip": [10, 12], "glcach": [10, 12], "computecach": [10, 12], "nv": [10, 12], "document": [10, 12], "tty": [], "webui_start": 12, "access": 12, "browser": [], "h1_keyboard_loco": [], "try": 17, "instal": [12, 13], "30": [], "minut": [], "npc": [13, 14], "web": 16, "index": 13, "search": 13, "page": [12, 13], "embodi": 14, "intellig": 14, "research": 14, "urgent": 14, "overcom": 14, "problem": [12, 14], "disconnect": 14, "between": 14, "high": 14, "level": 14, "perceptu": 14, "plan": 14, "low": 14, "motion": 14, "construct": 14, "highli": 14, "realist": 14, "enhanc": 14, "percept": 14, "capabl": 14, "promot": 14, "develop": 14, "multi": 14, "collabor": 14, "strategi": 14, "ultim": 14, "steadili": 14, "advanc": 14, "toward": 14, "goal": 14, "studi": 14, "typic": 14, "conduct": 14, "dataset": [10, 14], "platform": 14, "often": 14, "realism": 14, "time": [12, 14], "limit": [12, 14], "transfer": 14, "real": 14, "applic": 14, "scenario": 14, "larg": 14, "technologi": 14, "improv": 14, "abil": 14, "realiz": 14, "univers": 14, "longer": 14, "distant": 14, "industri": 14, "To": [14, 15], "challeng": 14, "openrobotlab": [10, 14], "team": 14, "shanghai": 14, "ai": 14, "lab": 14, "propos": 14, "taoyuan": [], "featur": [12, 14], "cover": 14, "variou": 14, "rich": 14, "librari": 14, "mainstream": 14, "algorithm": [13, 14], "plug": 14, "plai": 14, "line": 14, "achiev": 14, "reproduc": 14, "situat": 14, "encount": 14, "migrat": 14, "train": 14, "addition": 14, "driven": 14, "mark": 14, "autom": 14, "multimod": 14, "offer": 14, "infinit": 14, "serv": 14, "foundat": 14, "human": 14, "usual": 15, "re": [15, 16], "move": [11, 12, 15, 17], "rab": 15, "speak": 15, "onlin": 15, "move_to_point": 15, "llm": 12, "caller": [], "context": 16, "prompt": 10, "infer": 16, "llm_caller": 16, "accord": 16, "reimplement": [], "feed": [], "fed": 16, "respons": [12, 16], "sent": 16, "back": 16, "hierarchi": 16, "task_nam": 16, "controller_0": 16, "controller_1": 16, "sensor_0": 16, "sensor_1": 16, "task_ob": 16, "robot_ob": 16, "user_messag": [], "processed_user_messag": [], "bbox_label_data": 16, "bounding_box_2d_tight": 16, "bbox": 16, "idtolabel": 16, "update_robot_view": [], "update_robot_pos": [], "response_queu": [], "empti": [], "openai": [12, 16], "npc_name": 16, "model_nam": [], "openai_api_kei": [], "sk": [], "xxxxxx": [], "servic": 16, "api_base_url": 16, "api_endpoint": 16, "240": 17, "h1_locomot": [10, 17, 18], "05": 17, "find": 18, "got": 18, "suggest": [], "entrypoint": [], "docker_start": [], "yml": [], "noqa": [], "introduc": [], "project": [], "schema": 16, "bbox_label_data_from_camera": 16, "detail": 16, "omit": 16, "grutopia_extens": [1, 2, 5, 15, 17, 18], "ram": 10, "32gb": 10, "sure": [10, 12], "navig": 10, "git": 10, "github": 10, "polici": 10, "h1_citi": [10, 11], "h1_npc": [10, 12], "locat": 10, "insid": [10, 12], "guid": [11, 12], "decompress": 11, "unzip": 11, "demo_hous": 11, "zip": 11, "h1_hous": 11, "command": [11, 12], "w": [11, 12], "backward": [11, 12], "right": [11, 12], "q": [11, 12], "turn": [11, 12], "view": [11, 12], "perspect": 11, "person": 11, "third": 11, "demo_c": 11, "docker": 12, "within": 12, "now": 12, "gpt": 12, "4o": 12, "sed": 12, "your_openai_api_kei": 12, "keyboard": [12, 13], "talk": 12, "side": 12, "screen": 12, "window": 12, "ensur": 12, "relat": 12, "unrel": 12, "might": 12, "yield": 12, "repli": 12, "appear": 12, "few": 12, "second": 12, "ask": 12, "answer": 12, "sequenti": 12, "accur": 12, "due": 12, "design": 12, "occasion": 12, "caus": 12, "contact": 12, "wander": 13, "launch": 16}, "objects": {"grutopia.core.datahub": [[3, 0, 1, "", "IsaacData"], [3, 2, 0, "-", "api"], [3, 2, 0, "-", "model_data"], [3, 2, 0, "-", "web_api"], [3, 2, 0, "-", "web_ui_api"]], "grutopia.core.datahub.IsaacData": [[3, 1, 1, "", "add_actions"], [3, 1, 1, "", "get_action_by_id"], [3, 1, 1, "", "get_actions"], [3, 1, 1, "", "get_obs"], [3, 1, 1, "", "get_obs_by_id"]], "grutopia.core.datahub.api": [[3, 3, 1, "", "get_actions"], [3, 3, 1, "", "get_all_obs"], [3, 3, 1, "", "get_obs_by_id"], [3, 3, 1, "", "send_actions"], [3, 3, 1, "", "set_obs_data"]], "grutopia.core.datahub.web_api": [[3, 3, 1, "", "get_actions_by_id"], [3, 3, 1, "", "get_all_obs"], [3, 3, 1, "", "get_obs_by_id"], [3, 3, 1, "", "send_actions"], [3, 3, 1, "", "set_obs_data"]], "grutopia.core.datahub.web_ui_api": [[3, 3, 1, "", "clear"], [3, 3, 1, "", "get_chat_control"], [3, 3, 1, "", "get_log_data"], [3, 3, 1, "", "send_chain_of_thought"], [3, 3, 1, "", "send_chat_control"], [3, 3, 1, "", "send_log_data"]], "grutopia.core.env": [[4, 0, 1, "", "BaseEnv"]], "grutopia.core.env.BaseEnv": [[4, 1, 1, "", "close"], [4, 1, 1, "", "get_observations"], [4, 1, 1, "", "reset"], [4, 4, 1, "", "simulation_app"], [4, 4, 1, "", "simulation_config"], [4, 1, 1, "", "step"]], "grutopia.core.register": [[5, 2, 0, "-", "register"]], "grutopia.core.register.register": [[5, 3, 1, "", "import_all_modules_for_register"]], "grutopia.core.robot": [[6, 2, 0, "-", "controller"], [6, 2, 0, "-", "robot"], [6, 2, 0, "-", "sensor"]], "grutopia.core.robot.controller": [[6, 0, 1, "", "BaseController"], [6, 3, 1, "", "config_inject"], [6, 3, 1, "", "create_controllers"], [6, 3, 1, "", "inject_sub_controllers"]], "grutopia.core.robot.controller.BaseController": [[6, 1, 1, "", "action_to_control"], [6, 1, 1, "", "get_joint_subset"], [6, 1, 1, "", "get_obs"], [6, 1, 1, "", "register"]], "grutopia.core.robot.robot": [[6, 0, 1, "", "BaseRobot"], [6, 3, 1, "", "create_robots"]], "grutopia.core.robot.robot.BaseRobot": [[6, 1, 1, "", "apply_action"], [6, 1, 1, "", "get_obs"], [6, 1, 1, "", "get_robot_articulation"], [6, 1, 1, "", "get_robot_base"], [6, 1, 1, "", "get_robot_ik_base"], [6, 1, 1, "", "get_robot_scale"], [6, 1, 1, "", "post_reset"], [6, 1, 1, "", "register"], [6, 1, 1, "", "set_up_to_scene"]], "grutopia.core.robot.sensor": [[6, 0, 1, "", "BaseSensor"], [6, 3, 1, "", "config_inject"], [6, 3, 1, "", "create_sensors"]], "grutopia.core.robot.sensor.BaseSensor": [[6, 1, 1, "", "get_data"], [6, 1, 1, "", "register"]], "grutopia.core.scene": [[7, 2, 0, "-", "object"]], "grutopia.core.scene.object": [[7, 0, 1, "", "ObjectCommon"], [7, 3, 1, "", "create_object"]], "grutopia.core.scene.object.ObjectCommon": [[7, 1, 1, "", "register"]], "grutopia.core.scene.scene.util": [[7, 2, 0, "-", "usd_op"]], "grutopia.core.scene.scene.util.usd_op": [[7, 3, 1, "", "add_usd_ref"], [7, 3, 1, "", "add_xform_of_prim"], [7, 3, 1, "", "add_xform_of_prim_old"], [7, 3, 1, "", "compute_bbox"], [7, 3, 1, "", "create_new_usd"], [7, 3, 1, "", "delete_prim_in_stage"], [7, 3, 1, "", "delete_xform_of_prim"], [7, 3, 1, "", "get_local_transform_xform"], [7, 3, 1, "", "get_world_transform_xform"], [7, 3, 1, "", "set_xform_of_prim"]], "grutopia.core.task": [[8, 2, 0, "-", "task"]], "grutopia.core.task.task": [[8, 0, 1, "", "BaseTask"]], "grutopia.core.task.task.BaseTask": [[8, 1, 1, "", "calculate_metrics"], [8, 1, 1, "", "get_observations"], [8, 1, 1, "", "individual_reset"], [8, 1, 1, "", "is_done"], [8, 1, 1, "", "post_reset"], [8, 1, 1, "", "pre_step"], [8, 1, 1, "", "register"], [8, 1, 1, "", "set_up_scene"]], "grutopia.core.util": [[9, 2, 0, "-", "array"], [9, 2, 0, "-", "assets"], [9, 2, 0, "-", "configclass"], [9, 2, 0, "-", "dict"], [9, 2, 0, "-", "math"], [9, 2, 0, "-", "omni_usd_util"], [9, 2, 0, "-", "python"], [9, 2, 0, "-", "string"]], "grutopia.core.util.array": [[9, 5, 1, "", "TENSOR_TYPES"], [9, 5, 1, "", "TENSOR_TYPE_CONVERSIONS"], [9, 5, 1, "", "TensorData"], [9, 3, 1, "", "convert_to_torch"]], "grutopia.core.util.configclass": [[9, 3, 1, "", "configclass"]], "grutopia.core.util.dict": [[9, 3, 1, "", "class_to_dict"], [9, 3, 1, "", "convert_dict_to_backend"], [9, 3, 1, "", "dict_to_md5_hash"], [9, 3, 1, "", "print_dict"], [9, 3, 1, "", "update_class_from_dict"], [9, 3, 1, "", "update_dict"]], "grutopia.core.util.omni_usd_util": [[9, 3, 1, "", "compute_path_bbox"], [9, 3, 1, "", "get_grabbed_able_xform_paths"], [9, 3, 1, "", "get_pick_position"], [9, 3, 1, "", "get_world_transform_xform"], [9, 3, 1, "", "nearest_xform_from_position"]], "grutopia.core.util.python": [[9, 0, 1, "", "Recreatable"], [9, 0, 1, "", "RecreatableAbcMeta"], [9, 0, 1, "", "RecreatableMeta"], [9, 0, 1, "", "Registerable"], [9, 0, 1, "", "Serializable"], [9, 0, 1, "", "SerializableNonInstance"], [9, 0, 1, "", "UniquelyNamed"], [9, 0, 1, "", "UniquelyNamedNonInstance"], [9, 0, 1, "", "Wrapper"], [9, 3, 1, "", "assert_valid_key"], [9, 3, 1, "", "camel_case_to_snake_case"], [9, 3, 1, "", "clear"], [9, 3, 1, "", "create_class_from_registry_and_config"], [9, 3, 1, "", "create_object_from_init_info"], [9, 3, 1, "", "extract_class_init_kwargs_from_dict"], [9, 3, 1, "", "extract_subset_dict"], [9, 3, 1, "", "get_class_init_kwargs"], [9, 3, 1, "", "get_uuid"], [9, 3, 1, "", "meets_minimum_version"], [9, 3, 1, "", "merge_nested_dicts"], [9, 3, 1, "", "save_init_info"], [9, 3, 1, "", "snake_case_to_camel_case"], [9, 3, 1, "", "subclass_factory"]], "grutopia.core.util.python.Recreatable": [[9, 1, 1, "", "get_init_info"]], "grutopia.core.util.python.Serializable": [[9, 1, 1, "", "deserialize"], [9, 1, 1, "", "dump_state"], [9, 1, 1, "", "load_state"], [9, 1, 1, "", "serialize"], [9, 4, 1, "", "state_size"]], "grutopia.core.util.python.SerializableNonInstance": [[9, 1, 1, "", "deserialize"], [9, 1, 1, "", "dump_state"], [9, 1, 1, "", "load_state"], [9, 1, 1, "", "serialize"]], "grutopia.core.util.python.UniquelyNamed": [[9, 4, 1, "", "name"], [9, 1, 1, "", "remove_names"]], "grutopia.core.util.python.Wrapper": [[9, 4, 1, "", "unwrapped"]], "grutopia.core.util.string": [[9, 3, 1, "", "callable_to_string"], [9, 3, 1, "", "is_lambda_expression"], [9, 3, 1, "", "resolve_matching_names"], [9, 3, 1, "", "resolve_matching_names_values"], [9, 3, 1, "", "string_to_callable"], [9, 3, 1, "", "to_camel_case"], [9, 3, 1, "", "to_snake_case"]]}, "objtypes": {"0": "py:class", "1": "py:method", "2": "py:module", "3": "py:function", "4": "py:property", "5": "py:data"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"], "2": ["py", "module", "Python module"], "3": ["py", "function", "Python function"], "4": ["py", "property", "Python property"], "5": ["py", "data", "Python data"]}, "titleterms": {"how": [0, 1, 2, 15, 17, 18], "add": [0, 1, 2, 17], "custom": [0, 1, 2, 16], "control": [0, 1, 6, 15], "1": [0, 1, 2], "tao_yuan": [], "core": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "robot": [0, 1, 2, 6, 15, 17, 18], "2": [0, 1, 2], "regist": [0, 1, 2, 5], "robot_model": [0, 1, 2], "3": [0, 1, 2], "write": [0, 1, 2], "demo": [0, 1, 2, 17], "isaac": 1, "sim": 1, "wrap": 1, "4": 1, "sensor": [1, 2, 6, 18], "5": 1, "creat": 2, "task": [4, 8, 16], "when": [], "i": 15, "need": [], "datahub": 3, "local": 3, "api": [3, 13], "web": 3, "env": 4, "base": [4, 16], "class": 4, "all": [4, 17], "should": 4, "inherit": 4, "from": [4, 10], "thi": 4, "subclass": 4, "runner": 4, "scene": 7, "object": 7, "usd_op": 7, "util": 9, "arrai": 9, "asset": 9, "configclass": 9, "dict": 9, "math": 9, "omni_usd_util": 9, "python": 9, "string": 9, "30": [], "minut": [], "get": 13, "start": [12, 13], "resourc": [], "urdf": [], "usd": [], "import": [], "gen": [], "file": 16, "test": [], "what": 15, "we": [], "do": [], "move": [], "config": 17, "run": 17, "instal": 10, "prerequisit": 10, "us": [15, 17, 18], "docker": 10, "verifi": [], "welcom": 13, "ty": [], "": 13, "document": 13, "introduct": [13, 14], "tutori": 13, "advanc": 13, "refer": 13, "indic": 13, "tabl": 13, "rl": [], "which": [15, 18], "ar": [15, 18], "our": [15, 18], "support": [15, 18], "npc": [12, 16], "check": [], "exist": [], "usag": 17, "ui": [], "build": [], "imag": [], "webui": 12, "case": [], "enabl": [], "webrtc": [], "loop": [], "infrastructur": [], "implement": 16, "your": 16, "own": [], "llm": 16, "caller": 16, "taoyuan": [], "set": 16, "configur": 16, "yaml": 16, "algorithm": 16, "prompt": 16, "reimplement": 16, "feed": 16, "method": 16, "grutopia": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 16], "sourc": 10, "linux": 10, "wander": 11, "keyboard": 11, "hous": 11, "citi": 11, "interact": 12, "through": 12, "process": 12, "simul": 12, "avail": 17}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"How to add custom controller": [[0, "how-to-add-custom-controller"]], "1. Add grutopia.core.robot.controller": [[0, "add-grutopia-core-robot-controller"]], "2. Register at robot_models": [[0, "register-at-robot-models"], [2, "register-at-robot-models"]], "3. Write a demo": [[0, "write-a-demo"], [2, "write-a-demo"]], "How to add custom robot": [[1, "how-to-add-custom-robot"]], "1. Add isaac sim robot": [[1, "add-isaac-sim-robot"]], "2. Wrap with grutopia.core.robot.robot": [[1, "wrap-with-grutopia-core-robot-robot"]], "3. Register at robot_models": [[1, "register-at-robot-models"]], "4. Add controllers and sensors": [[1, "add-controllers-and-sensors"]], "5. Write a demo": [[1, "write-a-demo"]], "How to add custom sensor": [[2, "how-to-add-custom-sensor"]], "1. Create with grutopia.core.robot.sensor": [[2, "create-with-grutopia-core-robot-sensor"]], "grutopia.core.datahub": [[3, "grutopia-core-datahub"]], "datahub": [[3, "datahub"]], "local api": [[3, "module-grutopia.core.datahub.api"]], "web api": [[3, "module-grutopia.core.datahub.web_ui_api"]], "grutopia.core.env": [[4, "grutopia-core-env"]], "env": [[4, "env"]], "Env base class. All tasks should inherit from this class(or subclass).": [[4, "env-base-class-all-tasks-should-inherit-from-this-class-or-subclass"]], "runner": [[4, "runner"]], "grutopia.core.register": [[5, "grutopia-core-register"]], "register": [[5, "module-grutopia.core.register.register"]], "grutopia.core.robot": [[6, "grutopia-core-robot"]], "robot": [[6, "module-grutopia.core.robot.robot"]], "controller": [[6, "module-grutopia.core.robot.controller"]], "sensor": [[6, "module-grutopia.core.robot.sensor"]], "grutopia.core.scene": [[7, "grutopia-core-scene"]], "object": [[7, "module-grutopia.core.scene.object"]], "usd_op": [[7, "module-grutopia.core.scene.scene.util.usd_op"]], "grutopia.core.task": [[8, "grutopia-core-task"]], "task": [[8, "module-grutopia.core.task.task"]], "grutopia.core.util": [[9, "grutopia-core-util"]], "array": [[9, "module-grutopia.core.util.array"]], "assets": [[9, "module-grutopia.core.util.assets"]], "configclass": [[9, "module-grutopia.core.util.configclass"]], "dict": [[9, "module-grutopia.core.util.dict"]], "math": [[9, "module-grutopia.core.util.math"]], "omni_usd_util": [[9, "module-grutopia.core.util.omni_usd_util"]], "python": [[9, "module-grutopia.core.util.python"]], "string": [[9, "module-grutopia.core.util.string"]], "Installation": [[10, "installation"]], "Prerequisites": [[10, "prerequisites"]], "Install from source (Linux)": [[10, "install-from-source-linux"]], "Install with Docker (Linux)": [[10, "install-with-docker-linux"]], "Wander with keyboard": [[11, "wander-with-keyboard"]], "Wander in house": [[11, "wander-in-house"]], "Wander in city": [[11, "wander-in-city"]], "Interact with NPC through WebUI": [[12, "interact-with-npc-through-webui"]], "Start WebUI process": [[12, "start-webui-process"]], "Start simulation": [[12, "start-simulation"]], "Welcome to GRUtopia\u2019s documentation!": [[13, "welcome-to-grutopia-s-documentation"]], "Introduction": [[13, null], [14, "introduction"]], "Get Started": [[13, null]], "Tutorials": [[13, null]], "Advanced Tutorials": [[13, null]], "API Reference": [[13, null]], "Indices and tables": [[13, "indices-and-tables"]], "How to use controller": [[15, "how-to-use-controller"]], "What is Controller": [[15, "what-is-controller"]], "Which controllers are our robots supported": [[15, "which-controllers-are-our-robots-supported"]], "How to use a controller": [[15, "how-to-use-a-controller"]], "Customize NPC with your algorithm": [[16, "customize-npc-with-your-algorithm"]], "Set configuration in task yaml file.": [[16, "set-configuration-in-task-yaml-file"]], "Customize your prompt and implement LLM caller": [[16, "customize-your-prompt-and-implement-llm-caller"]], "Reimplement feed method in grutopia.npc.base": [[16, "reimplement-feed-method-in-grutopia-npc-base"]], "How to use robot": [[17, "how-to-use-robot"]], "All available robots": [[17, "all-available-robots"]], "Usage": [[17, "usage"]], "Add robot to config": [[17, "add-robot-to-config"]], "Run demo": [[17, "run-demo"]], "How to use sensor": [[18, "how-to-use-sensor"]], "Which sensors are our robots supported": [[18, "which-sensors-are-our-robots-supported"]], "How to use a sensor": [[18, "how-to-use-a-sensor"]]}, "indexentries": {"isaacdata (class in grutopia.core.datahub)": [[3, "grutopia.core.datahub.IsaacData"]], "add_actions() (grutopia.core.datahub.isaacdata class method)": [[3, "grutopia.core.datahub.IsaacData.add_actions"]], "clear() (in module grutopia.core.datahub.web_ui_api)": [[3, "grutopia.core.datahub.web_ui_api.clear"]], "get_action_by_id() (grutopia.core.datahub.isaacdata class method)": [[3, "grutopia.core.datahub.IsaacData.get_action_by_id"]], "get_actions() (grutopia.core.datahub.isaacdata class method)": [[3, "grutopia.core.datahub.IsaacData.get_actions"]], "get_actions() (in module grutopia.core.datahub.api)": [[3, "grutopia.core.datahub.api.get_actions"]], "get_actions_by_id() (in module grutopia.core.datahub.web_api)": [[3, "grutopia.core.datahub.web_api.get_actions_by_id"]], "get_all_obs() (in module grutopia.core.datahub.api)": [[3, "grutopia.core.datahub.api.get_all_obs"]], "get_all_obs() (in module grutopia.core.datahub.web_api)": [[3, "grutopia.core.datahub.web_api.get_all_obs"]], "get_chat_control() (in module grutopia.core.datahub.web_ui_api)": [[3, "grutopia.core.datahub.web_ui_api.get_chat_control"]], "get_log_data() (in module grutopia.core.datahub.web_ui_api)": [[3, "grutopia.core.datahub.web_ui_api.get_log_data"]], "get_obs() (grutopia.core.datahub.isaacdata class method)": [[3, "grutopia.core.datahub.IsaacData.get_obs"]], "get_obs_by_id() (grutopia.core.datahub.isaacdata class method)": [[3, "grutopia.core.datahub.IsaacData.get_obs_by_id"]], "get_obs_by_id() (in module grutopia.core.datahub.api)": [[3, "grutopia.core.datahub.api.get_obs_by_id"]], "get_obs_by_id() (in module grutopia.core.datahub.web_api)": [[3, "grutopia.core.datahub.web_api.get_obs_by_id"]], "grutopia.core.datahub.api": [[3, "module-grutopia.core.datahub.api"]], "grutopia.core.datahub.model_data": [[3, "module-grutopia.core.datahub.model_data"]], "grutopia.core.datahub.web_api": [[3, "module-grutopia.core.datahub.web_api"]], "grutopia.core.datahub.web_ui_api": [[3, "module-grutopia.core.datahub.web_ui_api"]], "module": [[3, "module-grutopia.core.datahub.api"], [3, "module-grutopia.core.datahub.model_data"], [3, "module-grutopia.core.datahub.web_api"], [3, "module-grutopia.core.datahub.web_ui_api"], [5, "module-grutopia.core.register.register"], [6, "module-grutopia.core.robot.controller"], [6, "module-grutopia.core.robot.robot"], [6, "module-grutopia.core.robot.sensor"], [7, "module-grutopia.core.scene.object"], [7, "module-grutopia.core.scene.scene.util.usd_op"], [8, "module-grutopia.core.task.task"], [9, "module-grutopia.core.util.array"], [9, "module-grutopia.core.util.assets"], [9, "module-grutopia.core.util.configclass"], [9, "module-grutopia.core.util.dict"], [9, "module-grutopia.core.util.math"], [9, "module-grutopia.core.util.omni_usd_util"], [9, "module-grutopia.core.util.python"], [9, "module-grutopia.core.util.string"]], "send_actions() (in module grutopia.core.datahub.api)": [[3, "grutopia.core.datahub.api.send_actions"]], "send_actions() (in module grutopia.core.datahub.web_api)": [[3, "grutopia.core.datahub.web_api.send_actions"]], "send_chain_of_thought() (in module grutopia.core.datahub.web_ui_api)": [[3, "grutopia.core.datahub.web_ui_api.send_chain_of_thought"]], "send_chat_control() (in module grutopia.core.datahub.web_ui_api)": [[3, "grutopia.core.datahub.web_ui_api.send_chat_control"]], "send_log_data() (in module grutopia.core.datahub.web_ui_api)": [[3, "grutopia.core.datahub.web_ui_api.send_log_data"]], "set_obs_data() (in module grutopia.core.datahub.api)": [[3, "grutopia.core.datahub.api.set_obs_data"]], "set_obs_data() (in module grutopia.core.datahub.web_api)": [[3, "grutopia.core.datahub.web_api.set_obs_data"]], "baseenv (class in grutopia.core.env)": [[4, "grutopia.core.env.BaseEnv"]], "close() (grutopia.core.env.baseenv method)": [[4, "grutopia.core.env.BaseEnv.close"]], "get_observations() (grutopia.core.env.baseenv method)": [[4, "grutopia.core.env.BaseEnv.get_observations"]], "reset() (grutopia.core.env.baseenv method)": [[4, "grutopia.core.env.BaseEnv.reset"]], "simulation_app (grutopia.core.env.baseenv property)": [[4, "grutopia.core.env.BaseEnv.simulation_app"]], "simulation_config (grutopia.core.env.baseenv property)": [[4, "grutopia.core.env.BaseEnv.simulation_config"]], "step() (grutopia.core.env.baseenv method)": [[4, "grutopia.core.env.BaseEnv.step"]], "grutopia.core.register.register": [[5, "module-grutopia.core.register.register"]], "import_all_modules_for_register() (in module grutopia.core.register.register)": [[5, "grutopia.core.register.register.import_all_modules_for_register"]], "basecontroller (class in grutopia.core.robot.controller)": [[6, "grutopia.core.robot.controller.BaseController"]], "baserobot (class in grutopia.core.robot.robot)": [[6, "grutopia.core.robot.robot.BaseRobot"]], "basesensor (class in grutopia.core.robot.sensor)": [[6, "grutopia.core.robot.sensor.BaseSensor"]], "action_to_control() (grutopia.core.robot.controller.basecontroller method)": [[6, "grutopia.core.robot.controller.BaseController.action_to_control"]], "apply_action() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.apply_action"]], "config_inject() (in module grutopia.core.robot.controller)": [[6, "grutopia.core.robot.controller.config_inject"]], "config_inject() (in module grutopia.core.robot.sensor)": [[6, "grutopia.core.robot.sensor.config_inject"]], "create_controllers() (in module grutopia.core.robot.controller)": [[6, "grutopia.core.robot.controller.create_controllers"]], "create_robots() (in module grutopia.core.robot.robot)": [[6, "grutopia.core.robot.robot.create_robots"]], "create_sensors() (in module grutopia.core.robot.sensor)": [[6, "grutopia.core.robot.sensor.create_sensors"]], "get_data() (grutopia.core.robot.sensor.basesensor method)": [[6, "grutopia.core.robot.sensor.BaseSensor.get_data"]], "get_joint_subset() (grutopia.core.robot.controller.basecontroller method)": [[6, "grutopia.core.robot.controller.BaseController.get_joint_subset"]], "get_obs() (grutopia.core.robot.controller.basecontroller method)": [[6, "grutopia.core.robot.controller.BaseController.get_obs"]], "get_obs() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.get_obs"]], "get_robot_articulation() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.get_robot_articulation"]], "get_robot_base() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.get_robot_base"]], "get_robot_ik_base() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.get_robot_ik_base"]], "get_robot_scale() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.get_robot_scale"]], "grutopia.core.robot.controller": [[6, "module-grutopia.core.robot.controller"]], "grutopia.core.robot.robot": [[6, "module-grutopia.core.robot.robot"]], "grutopia.core.robot.sensor": [[6, "module-grutopia.core.robot.sensor"]], "inject_sub_controllers() (in module grutopia.core.robot.controller)": [[6, "grutopia.core.robot.controller.inject_sub_controllers"]], "post_reset() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.post_reset"]], "register() (grutopia.core.robot.controller.basecontroller class method)": [[6, "grutopia.core.robot.controller.BaseController.register"]], "register() (grutopia.core.robot.robot.baserobot class method)": [[6, "grutopia.core.robot.robot.BaseRobot.register"]], "register() (grutopia.core.robot.sensor.basesensor class method)": [[6, "grutopia.core.robot.sensor.BaseSensor.register"]], "set_up_to_scene() (grutopia.core.robot.robot.baserobot method)": [[6, "grutopia.core.robot.robot.BaseRobot.set_up_to_scene"]], "objectcommon (class in grutopia.core.scene.object)": [[7, "grutopia.core.scene.object.ObjectCommon"]], "add_usd_ref() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.add_usd_ref"]], "add_xform_of_prim() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.add_xform_of_prim"]], "add_xform_of_prim_old() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.add_xform_of_prim_old"]], "compute_bbox() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.compute_bbox"]], "create_new_usd() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.create_new_usd"]], "create_object() (in module grutopia.core.scene.object)": [[7, "grutopia.core.scene.object.create_object"]], "delete_prim_in_stage() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.delete_prim_in_stage"]], "delete_xform_of_prim() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.delete_xform_of_prim"]], "get_local_transform_xform() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.get_local_transform_xform"]], "get_world_transform_xform() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.get_world_transform_xform"]], "grutopia.core.scene.object": [[7, "module-grutopia.core.scene.object"]], "grutopia.core.scene.scene.util.usd_op": [[7, "module-grutopia.core.scene.scene.util.usd_op"]], "register() (grutopia.core.scene.object.objectcommon class method)": [[7, "grutopia.core.scene.object.ObjectCommon.register"]], "set_xform_of_prim() (in module grutopia.core.scene.scene.util.usd_op)": [[7, "grutopia.core.scene.scene.util.usd_op.set_xform_of_prim"]], "basetask (class in grutopia.core.task.task)": [[8, "grutopia.core.task.task.BaseTask"]], "calculate_metrics() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.calculate_metrics"]], "get_observations() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.get_observations"]], "grutopia.core.task.task": [[8, "module-grutopia.core.task.task"]], "individual_reset() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.individual_reset"]], "is_done() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.is_done"]], "post_reset() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.post_reset"]], "pre_step() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.pre_step"]], "register() (grutopia.core.task.task.basetask class method)": [[8, "grutopia.core.task.task.BaseTask.register"]], "set_up_scene() (grutopia.core.task.task.basetask method)": [[8, "grutopia.core.task.task.BaseTask.set_up_scene"]], "recreatable (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.Recreatable"]], "recreatableabcmeta (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.RecreatableAbcMeta"]], "recreatablemeta (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.RecreatableMeta"]], "registerable (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.Registerable"]], "serializable (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.Serializable"]], "serializablenoninstance (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.SerializableNonInstance"]], "tensor_types (in module grutopia.core.util.array)": [[9, "grutopia.core.util.array.TENSOR_TYPES"]], "tensor_type_conversions (in module grutopia.core.util.array)": [[9, "grutopia.core.util.array.TENSOR_TYPE_CONVERSIONS"]], "tensordata (in module grutopia.core.util.array)": [[9, "grutopia.core.util.array.TensorData"]], "uniquelynamed (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.UniquelyNamed"]], "uniquelynamednoninstance (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.UniquelyNamedNonInstance"]], "wrapper (class in grutopia.core.util.python)": [[9, "grutopia.core.util.python.Wrapper"]], "assert_valid_key() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.assert_valid_key"]], "callable_to_string() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.callable_to_string"]], "camel_case_to_snake_case() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.camel_case_to_snake_case"]], "class_to_dict() (in module grutopia.core.util.dict)": [[9, "grutopia.core.util.dict.class_to_dict"]], "clear() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.clear"]], "compute_path_bbox() (in module grutopia.core.util.omni_usd_util)": [[9, "grutopia.core.util.omni_usd_util.compute_path_bbox"]], "configclass() (in module grutopia.core.util.configclass)": [[9, "grutopia.core.util.configclass.configclass"]], "convert_dict_to_backend() (in module grutopia.core.util.dict)": [[9, "grutopia.core.util.dict.convert_dict_to_backend"]], "convert_to_torch() (in module grutopia.core.util.array)": [[9, "grutopia.core.util.array.convert_to_torch"]], "create_class_from_registry_and_config() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.create_class_from_registry_and_config"]], "create_object_from_init_info() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.create_object_from_init_info"]], "deserialize() (grutopia.core.util.python.serializable method)": [[9, "grutopia.core.util.python.Serializable.deserialize"]], "deserialize() (grutopia.core.util.python.serializablenoninstance class method)": [[9, "grutopia.core.util.python.SerializableNonInstance.deserialize"]], "dict_to_md5_hash() (in module grutopia.core.util.dict)": [[9, "grutopia.core.util.dict.dict_to_md5_hash"]], "dump_state() (grutopia.core.util.python.serializable method)": [[9, "grutopia.core.util.python.Serializable.dump_state"]], "dump_state() (grutopia.core.util.python.serializablenoninstance class method)": [[9, "grutopia.core.util.python.SerializableNonInstance.dump_state"]], "extract_class_init_kwargs_from_dict() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.extract_class_init_kwargs_from_dict"]], "extract_subset_dict() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.extract_subset_dict"]], "get_class_init_kwargs() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.get_class_init_kwargs"]], "get_grabbed_able_xform_paths() (in module grutopia.core.util.omni_usd_util)": [[9, "grutopia.core.util.omni_usd_util.get_grabbed_able_xform_paths"]], "get_init_info() (grutopia.core.util.python.recreatable method)": [[9, "grutopia.core.util.python.Recreatable.get_init_info"]], "get_pick_position() (in module grutopia.core.util.omni_usd_util)": [[9, "grutopia.core.util.omni_usd_util.get_pick_position"]], "get_uuid() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.get_uuid"]], "get_world_transform_xform() (in module grutopia.core.util.omni_usd_util)": [[9, "grutopia.core.util.omni_usd_util.get_world_transform_xform"]], "grutopia.core.util.array": [[9, "module-grutopia.core.util.array"]], "grutopia.core.util.assets": [[9, "module-grutopia.core.util.assets"]], "grutopia.core.util.configclass": [[9, "module-grutopia.core.util.configclass"]], "grutopia.core.util.dict": [[9, "module-grutopia.core.util.dict"]], "grutopia.core.util.math": [[9, "module-grutopia.core.util.math"]], "grutopia.core.util.omni_usd_util": [[9, "module-grutopia.core.util.omni_usd_util"]], "grutopia.core.util.python": [[9, "module-grutopia.core.util.python"]], "grutopia.core.util.string": [[9, "module-grutopia.core.util.string"]], "is_lambda_expression() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.is_lambda_expression"]], "load_state() (grutopia.core.util.python.serializable method)": [[9, "grutopia.core.util.python.Serializable.load_state"]], "load_state() (grutopia.core.util.python.serializablenoninstance class method)": [[9, "grutopia.core.util.python.SerializableNonInstance.load_state"]], "meets_minimum_version() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.meets_minimum_version"]], "merge_nested_dicts() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.merge_nested_dicts"]], "name (grutopia.core.util.python.uniquelynamed property)": [[9, "grutopia.core.util.python.UniquelyNamed.name"]], "nearest_xform_from_position() (in module grutopia.core.util.omni_usd_util)": [[9, "grutopia.core.util.omni_usd_util.nearest_xform_from_position"]], "print_dict() (in module grutopia.core.util.dict)": [[9, "grutopia.core.util.dict.print_dict"]], "remove_names() (grutopia.core.util.python.uniquelynamed method)": [[9, "grutopia.core.util.python.UniquelyNamed.remove_names"]], "resolve_matching_names() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.resolve_matching_names"]], "resolve_matching_names_values() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.resolve_matching_names_values"]], "save_init_info() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.save_init_info"]], "serialize() (grutopia.core.util.python.serializable method)": [[9, "grutopia.core.util.python.Serializable.serialize"]], "serialize() (grutopia.core.util.python.serializablenoninstance class method)": [[9, "grutopia.core.util.python.SerializableNonInstance.serialize"]], "snake_case_to_camel_case() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.snake_case_to_camel_case"]], "state_size (grutopia.core.util.python.serializable property)": [[9, "grutopia.core.util.python.Serializable.state_size"]], "string_to_callable() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.string_to_callable"]], "subclass_factory() (in module grutopia.core.util.python)": [[9, "grutopia.core.util.python.subclass_factory"]], "to_camel_case() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.to_camel_case"]], "to_snake_case() (in module grutopia.core.util.string)": [[9, "grutopia.core.util.string.to_snake_case"]], "unwrapped (grutopia.core.util.python.wrapper property)": [[9, "grutopia.core.util.python.Wrapper.unwrapped"]], "update_class_from_dict() (in module grutopia.core.util.dict)": [[9, "grutopia.core.util.dict.update_class_from_dict"]], "update_dict() (in module grutopia.core.util.dict)": [[9, "grutopia.core.util.dict.update_dict"]]}}) \ No newline at end of file diff --git a/html/tutorials/how-to-run-rl.html b/html/tutorials/how-to-run-rl.html new file mode 100644 index 0000000..9640883 --- /dev/null +++ b/html/tutorials/how-to-run-rl.html @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + how to run rl on TY-1 — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

how to run rl on TY-1

+
+

WIP

+
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/how-to-use-controller.html b/html/tutorials/how-to-use-controller.html new file mode 100644 index 0000000..1b60331 --- /dev/null +++ b/html/tutorials/how-to-use-controller.html @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + How to use controller — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

How to use controller

+
+

This tutorial will show you how to use an existed controller for a robot

+
+
+

What is Controller

+

Controllers usually control joints of robot. Also, They’re the entries of robot actions. To make robot move, rab, or +even speak, chat online, we use controllers.

+
+
+

Which controllers are our robots supported

+

Check grutopia_extension/robots/robot_models.yaml,

+

img.png

+

This is all controller robot HumanoidRobot can use.

+
+
+

How to use a controller

+

Use them in isaac simulation_app’s step loops.

+

for example:

+
while env.simulation_app.is_running():
+    actions = [{
+        h1: {
+            "move_to_point": np.array([.0, .0, .0]),
+        },
+    }]
+    obs = env.step(actions=env_actions)
+    ...
+env.simulation_app.close()
+
+
+

for more usage, please read source code~

+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/how-to-use-npc.html b/html/tutorials/how-to-use-npc.html new file mode 100644 index 0000000..465589d --- /dev/null +++ b/html/tutorials/how-to-use-npc.html @@ -0,0 +1,470 @@ + + + + + + + + + + + + + + + + Customize NPC with your algorithm — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

Customize NPC with your algorithm

+
+

Set configuration in task yaml file.

+

If you’re using openai api, you can refer to Web Demo.

+

If you are using other llm api service, you can set your api endpoint.

+
npc:
+- name: "npc_name"
+  api_base_url: "api_endpoint"
+    # other configurations
+
+
+

You can change the schema of NPC configuration in grutopia.core.config.npc.

+
+
+

Customize your prompt and implement LLM caller

+

Our system message and in-context example are defined in grutopia.npc.prompt. And the LLM inference process are in grutopia.npc.llm_caller. You can customize them according to your own needs and algorithms.

+
+
+

Reimplement feed method in grutopia.npc.base

+

Reimplement the feed method of NPC class in grutopia.npc.base.

+

In feed function, observation in dict type is processed and fed into the llm caller, new responses from llm caller are sent back to the robot.

+

Base feed method FYI.

+
def feed(self, obs: dict):
+    """feed npc with observation.
+
+    Args:
+        obs (dict): full observation of the world, with hierarchy of
+            obs
+            task_name:
+                robot_name:
+                position
+                orientation
+                controller_0
+                controller_1
+                ...
+                sensor_0
+                sensor_1
+                ...
+    """
+    for task_obs in obs.values():
+        for robot_obs in task_obs.values():
+            chat = robot_obs.get('web_chat', None)
+            if chat is not None and chat['chat_control']:
+                # process observation
+                position = robot_obs.get('position', None)
+                orientation = robot_obs.get('orientation', None)
+                bbox_label_data_from_camera = robot_obs['camera']['frame']['bounding_box_2d_tight']
+                bbox = bbox_label_data['data']
+                idToLabels = bbox_label_data['info']['idToLabels']
+                # feed processed observation into llm caller
+                # pick response from llm caller and send back to robot
+                # details omitted
+
+
+
+

Then you can launch the web demo and chat with your NPC.

+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/how-to-use-robot.html b/html/tutorials/how-to-use-robot.html new file mode 100644 index 0000000..ea53b6e --- /dev/null +++ b/html/tutorials/how-to-use-robot.html @@ -0,0 +1,478 @@ + + + + + + + + + + + + + + + + How to use robot — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

How to use robot

+
+

This tutorial will show you how to use an existing robot.

+
+
+

All available robots

+

See grutopia_extension/robots/robot_models.yaml.

+

img.png

+
+
+

Usage

+
+

Add robot to config

+

Add a robot to config:

+
simulator:
+  physics_dt: 1/240
+  rendering_dt: 1/240
+
+env:
+  bg_type: null
+
+render:
+  render: true
+
+tasks:
+- type: "SingleInferenceTask"
+  name: "h1_locomotion"
+  env_num: 1
+  offset_size: 1.0
+  robots:  # Add robots here
+  - name: h1
+    prim_path: "/World/h1"
+    type: "HumanoidRobot"
+    position: [.0, .0, 1.05]
+    scale: [1, 1, 1]
+
+
+

Done.

+
+
+

Run demo

+

try this demo:

+
from grutopia.core.config import SimulatorConfig
+from grutopia.core.env import BaseEnv
+
+file_path = f'{path/to/your/config}'
+sim_config = SimulatorConfig(file_path)
+
+env = BaseEnv(sim_config, headless=False)
+import numpy as np
+
+while env.simulation_app.is_running():
+
+    obs = env.step(actions=env_actions)
+env.simulation_app.close()
+
+
+

It runs, but the robot doesn’t move.

+

We need to add controller for this robot to make it move.

+

See how to use controller

+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/how-to-use-sensor.html b/html/tutorials/how-to-use-sensor.html new file mode 100644 index 0000000..127d781 --- /dev/null +++ b/html/tutorials/how-to-use-sensor.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + How to use sensor — GRUtopia v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

How to use sensor

+
+

This tutorial will show you how to use an existed sensors of robot

+
+
+

Which sensors are our robots supported

+

In grutopia/core/robot/robot_model.py, We know Sensors is under RobotModel.

+

img.png

+

Check grutopia_extension/robots/robot_models.yaml. We find

+
robots:
+  - type: "HumanoidRobot"
+    ...
+    sensors:
+      - name: "camera"  # <- this is sensor name
+        prim_path: "relative/prim/path/to/camera"
+        type: "Camera"
+
+
+
+
+

How to use a sensor

+

When we run demo/h1_locomotion.py, observation from sensors can be got from obs (obs = env.step(actions=env_actions))

+

Use them in isaac simulation_app’s step loops.

+
while env.simulation_app.is_running():
+   ...
+   obs = env.step(actions)
+   photo = obs['robot_name_in_config']['camera']['rgba']  # here get `camera` data
+   ...
+env.simulation_app.close()
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/how-to-use-task.html b/html/tutorials/how-to-use-task.html new file mode 100644 index 0000000..e691d0d --- /dev/null +++ b/html/tutorials/how-to-use-task.html @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + how to use task — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

how to use task

+
+

This tutorial will show you how to use task

+
+
+

What is Task

+

WIP

+
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/how-to-use-web-ui.html b/html/tutorials/how-to-use-web-ui.html new file mode 100644 index 0000000..97e797e --- /dev/null +++ b/html/tutorials/how-to-use-web-ui.html @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + how to use web ui — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

how to use web ui

+
+

This tutorial will show you how to use webui

+
+
+

We only suggest use webui with docker

+
+
+

Build docker image

+
cd ${path_to_source_code}
+
+docker build -t tao_yuan:0.0.1 .
+
+
+
+
+

Start a WebUI demo

+

Start docker container.

+
cd ${path_to_source_code}
+
+docker run --name isaac-sim --entrypoint bash -it --rm --gpus all \
+  -e "ACCEPT_EULA=Y" \
+  -e "PRIVACY_CONSENT=Y" \
+  -v ./TY-1:/isaac-sim/TY-1 \
+  -v ./TY-1/test/.test_scripts:/isaac-sim/run_scripts \
+  -v ~/docker/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \
+  -v ~/docker/isaac-sim/cache/ov:/root/.cache/ov:rw \
+  -v ~/docker/isaac-sim/cache/pip:/root/.cache/pip:rw \
+  -v ~/docker/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \
+  -v ~/docker/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \
+  -v ~/docker/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \
+  -v ~/docker/isaac-sim/data:/root/.local/share/ov/data:rw \
+  -v ~/docker/isaac-sim/documents:/root/Documents:rw \
+  tao_yuan:0.0.1
+
+
+

Then start demo

+
# in container
+./docker_start.sh ./TY-1/
+
+
+
+
+

How to write a WebUI Case

+
+

1. Enable WebRTC

+
import numpy as np
+
+from tao_yuan.core.config import SimulatorConfig
+from tao_yuan.core.env import BaseEnv
+
+sim_config = SimulatorConfig('/path/to/your/config.yml')  # noqa
+env = BaseEnv(sim_config, headless=True, webrtc=True)  # Set `webrtc`
+
+
+
+
+

2. Run loop

+
while env.simulation_app.is_running():
+    # Before step <>
+    obs = env.step(actions=[])
+    # After step <>
+env.simulation_app.close()
+
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/html/tutorials/infra.html b/html/tutorials/infra.html new file mode 100644 index 0000000..03a1273 --- /dev/null +++ b/html/tutorials/infra.html @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + infrastructure — TY-1 v0.1.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ Shortcuts +
+
+ +
+
+ +
+ +
+
+ + +
+

infrastructure

+
+

In this tutorial we will introduce you the infrastructure of this Project

+
+
+ + +
+ +
+ +
+
+ +
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file