Unverified Commit d2a41266 authored by Michael Gussert's avatar Michael Gussert Committed by GitHub

Adds walkthrough section in documentation with jetbot tutorial (#2368)

# Description

The intent is to create an in depth walkthrough for setting up a
project, adding a robot, and training it in the direct workflow. the
goal is to reference our tutorials and other documentation
appropriately, and build off of the walkthrough for other workflows in
the future

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------
Signed-off-by: 's avatarMichael Gussert <michael@gussert.com>
Co-authored-by: 's avatarKelly Guo <kellyg@nvidia.com>
parent 9f1aa4cd
...@@ -45,7 +45,7 @@ repos: ...@@ -45,7 +45,7 @@ repos:
- id: codespell - id: codespell
additional_dependencies: additional_dependencies:
- tomli - tomli
exclude: "CONTRIBUTORS.md" exclude: "CONTRIBUTORS.md|docs/source/setup/walkthrough/concepts_env_design.rst"
# FIXME: Figure out why this is getting stuck under VPN. # FIXME: Figure out why this is getting stuck under VPN.
# - repo: https://github.com/RobertCraigie/pyright-python # - repo: https://github.com/RobertCraigie/pyright-python
# rev: v1.1.315 # rev: v1.1.315
......
...@@ -74,19 +74,32 @@ Table of Contents ...@@ -74,19 +74,32 @@ Table of Contents
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Getting Started :caption: Isaac Lab
source/setup/ecosystem source/setup/ecosystem
source/setup/quickstart
source/setup/installation/index source/setup/installation/index
source/setup/installation/cloud_installation source/setup/installation/cloud_installation
source/refs/reference_architecture/index
.. toctree::
:maxdepth: 2
:caption: Getting Started
:titlesonly:
source/setup/quickstart
source/setup/walkthrough/index
source/tutorials/index
source/how-to/index
source/overview/developer-guide/index
.. toctree:: .. toctree::
:maxdepth: 3 :maxdepth: 3
:caption: Overview :caption: Overview
:titlesonly: :titlesonly:
source/overview/developer-guide/index
source/overview/core-concepts/index source/overview/core-concepts/index
source/overview/environments source/overview/environments
source/overview/reinforcement-learning/index source/overview/reinforcement-learning/index
...@@ -109,8 +122,6 @@ Table of Contents ...@@ -109,8 +122,6 @@ Table of Contents
:caption: Resources :caption: Resources
:titlesonly: :titlesonly:
source/tutorials/index
source/how-to/index
source/deployment/index source/deployment/index
source/policy_deployment/index source/policy_deployment/index
...@@ -133,7 +144,7 @@ Table of Contents ...@@ -133,7 +144,7 @@ Table of Contents
:maxdepth: 1 :maxdepth: 1
:caption: References :caption: References
source/refs/reference_architecture/index
source/refs/additional_resources source/refs/additional_resources
source/refs/contributing source/refs/contributing
source/refs/troubleshooting source/refs/troubleshooting
......
<svg width="576" height="720" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="hidden"><defs><clipPath id="clip0"><rect x="0" y="0" width="238125" height="238125"/></clipPath><image width="32" height="32" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAKpUExURQAAACZeiShbhidbhSZchidbiCdchidchidbhidchiddhitVjiRtkidaiCdchypjkSxpmi5toC9voy9vpC9uoi5snyxomSlgjCZbhyddhydchyhfi02Htsvd7Iqy1DR6tDR5sy1pmidchidYiSVbhi1qnIyz1P///+Ls9ClhjiZbhiZch0GCuaLC3WqeyCtmlidchidbhyheiilgjSpikDR6syxnlyZchSlbiSdbhydbhjR5siJViCZchyhbhydchidbhylahCdbhi5rni9uozBxpr+YFb6WFb6XFr2YFb+WFydchipkkr2YFNCmGOe3Gua2GtWpGL+YFb2VFiddhDJ2rixml9uuGf/KHderGb6XFyddhilhjyhch7+YFPbDHL+YFr6UGCZchyxomCZchyVdg76YFuCyGsifF7+XFSZbhy5snjBxpy1pmypjkidchiddh76WFPPBHMCZFzN4sCdbhydbhyVchydchydchyZciMGZFr6XFdmsGd+xGb6YFShchShchr+YFb+WFb6YFsCYFr6XFMCYFtqtGf7JHeO0Gr2YFiZchyteiL+YFcGaFuGzGuW2GuGyGr+XFSddhilgjr6aFMCZFvC+G8CXFiRbhjJ1rL6WF9GmGMaeF76YFiZdiCpkk7+YFtisGe+9G7+XFsKZFCNchyZchylij86kGL+YFyNdiyZchydchydcht2wGdyvGcyiF7+XFcSZGvrGHb6XFr+YFsCYF8CWFsGVEr+WFL6YFsCaFdOnGM+lGMeeF7+XFL6XFv/PMv/po//ZXb+YFb6XFvzIHf/jhv/22L+YFb+PEMCYFc2jGPPAHP/RO//vvPbWb9KnGL+YFb2XE7+YF8uhF+S1Gui4GtyuGcyjF7uVGcCZF7+ZFb6XFr+YFr6WFr+YGcx9OeAAAADjdFJOUwA2gbDO3uPcyalwEgev/////////////+xCdf/////////xGrj//////3jH/////5jG//////+ZOGKD/w9/yN3kH97///+D3tyzOML/r//////4OjT//////8t2/4Cw//8rpv/7Kc///2zF//////xoP/+d//e3pKOKL0bq//+/wc5UssnJ4P/////OoB6P/////8Zu/z///6Uq/5L//2q+/6T///wZJOb//6oWtLlh////6B7/xbi1jR2XgUn///+8ov///7p2////kBDd////////7Bu0//////8petbj2Lc0YoVqTwAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAfRJREFUOE9jIAEwMjGzsLKxc3BycUNFUAAPLx+/gKCQsIiomLiEJFQQGUhJy8jKyYOBgqKSMlQUCaioqqlrQBTIy2tqQUWRgLaojq4eVF5e3wAqigQMjYxBwMQUpMDMHCqKBCwsQcCKzxqLAhtbO3Z7CHDApsDRydjZRQgKXEHyQAVu7u4enl4QBd4+YF3IwMzc188/IDAoGCQfwhcKFUaAMAPf8IiIiMgokILoGKgoEjCJjQMpiE9IBCpISoaKIoBQSmpaOlBBREYmUEFWNlQ4J1cMAvLyCwoTikAKAouBClgEwdIleaVl5WBQUVlVXVMLko+oqwcqaHAGK8hrdGxqbgGD1rb2jk6wgq5uoIIeM5B8TmlvX3/tBAiYGA+WjoiYNBmoYMpUkILcsmnTZ0CFESByJlDBLL7ZQAVi5XPmQkWRwLz5QAUMCxYqgBQsWgwVRYAlS5eBFCxfoZmruhKLgs5Vq0HyDAxrrNauW7ceqmDSBhjYuGnzFogCCIAo2JqwbTsU7Ni5CyoFATAFu/eAwF6oKBKAKOjctx8EDhyEiiKBQ5EgBUBw+MjRiA3HoKJI4PiBExAFJ9VPdW46DRVFAmfOnjsPVnDh4qXLV1DcDwVXryVcD79xc+Kt2wnb7kDF0EDiXc979x883PMIyicCMDAAAEQO3ZTAzLYvAAAAAElFTkSuQmCC" preserveAspectRatio="none" id="img1"></image><clipPath id="clip2"><rect x="-0.0625" y="0" width="238125" height="238125"/></clipPath><clipPath id="clip3"><rect x="0" y="0" width="247650" height="238125"/></clipPath><clipPath id="clip4"><rect x="0" y="0" width="238125" height="238125"/></clipPath><clipPath id="clip5"><rect x="0" y="0" width="247650" height="247650"/></clipPath><clipPath id="clip6"><rect x="-0.125" y="0" width="247650" height="247650"/></clipPath><clipPath id="clip7"><rect x="0" y="0" width="247650" height="238125"/></clipPath><clipPath id="clip8"><rect x="0" y="0" width="238125" height="238125"/></clipPath></defs><g><rect x="0" y="0" width="576" height="720" fill="#FFFFFF"/><path d="M38 68.6063C38 61.0917 44.0917 55 51.6063 55L539.394 55C546.908 55 553 61.0917 553 68.6063L553 651.394C553 658.908 546.908 665 539.394 665L51.6063 665C44.0917 665 38 658.908 38 651.394Z" fill="#D9F2D0" fill-rule="evenodd"/><path d="M100 162.545C100 156.169 105.169 151 111.545 151L525.454 151C531.831 151 537 156.169 537 162.545L537 581.455C537 587.831 531.831 593 525.454 593L111.545 593C105.169 593 100 587.831 100 581.455Z" fill="#FFEACD" fill-rule="evenodd"/><path d="M129 297.712C129 290.139 135.139 284 142.712 284L504.288 284C511.861 284 518 290.139 518 297.712L518 509.288C518 516.861 511.861 523 504.288 523L142.712 523C135.139 523 129 516.861 129 509.288Z" fill="#DCEAF7" fill-rule="evenodd"/><path d="M19 34C19 32.8954 19.8954 32 21 32L29 32C30.1046 32 31 32.8954 31 34L31 42C31 43.1046 30.1046 44 29 44L21 44C19.8954 44 19 43.1046 19 42Z" fill="#156082" fill-rule="evenodd"/><path d="M19 37.8334C19 36.2686 20.2686 35 21.8334 35L41.1666 35C42.7314 35 44 36.2686 44 37.8334L44 49.1666C44 50.7314 42.7314 52 41.1666 52L21.8334 52C20.2686 52 19 50.7314 19 49.1666Z" fill="#156082" fill-rule="evenodd"/><path d="M20 38.8334C20 37.2686 21.2686 36 22.8334 36L42.1666 36C43.7314 36 45 37.2686 45 38.8334L45 50.1666C45 51.7314 43.7314 53 42.1666 53L22.8334 53C21.2686 53 20 51.7314 20 50.1666Z" fill="#1D85B3" fill-rule="evenodd"/><g clip-path="url(#clip0)" transform="matrix(0.000104987 0 0 0.000104987 109 562)"><g clip-path="url(#clip2)"><use width="100%" height="100%" xlink:href="#img1" transform="matrix(7441.41 0 0 7441.41 -0.0625 0)"></use></g></g><path d="M143.958 198.375C141.479 198.375 139.468 200.446 139.468 203 139.468 205.554 141.479 207.625 143.958 207.625 146.438 207.625 148.448 205.554 148.448 203 148.448 200.446 146.438 198.375 143.958 198.375ZM141.879 190 146.079 190 147.137 194.357 147.453 194.477C148.259 194.828 149.005 195.297 149.67 195.862L149.679 195.871 153.9 194.627 156 198.373 152.824 201.537 152.834 201.591C152.902 202.051 152.938 202.521 152.938 203 152.938 203.479 152.902 203.949 152.834 204.409L152.818 204.498 155.958 207.627 153.858 211.373 149.669 210.139 148.614 210.911C148.244 211.143 147.856 211.348 147.453 211.523L147.137 211.643 146.079 216 141.879 216 140.826 211.66 140.463 211.523C140.06 211.348 139.672 211.143 139.302 210.911L138.26 210.148 134.1 211.373 132 207.627 135.105 204.534 135.082 204.409C135.014 203.949 134.979 203.479 134.979 203 134.979 202.521 135.014 202.051 135.082 201.591L135.105 201.466 132 198.373 134.1 194.627 138.26 195.852 139.302 195.089C139.672 194.857 140.06 194.652 140.463 194.477L140.826 194.34Z" fill="#FFA225" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(50.6245 51)">isaac_lab_tutorial</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(84.2706 78)">scripts</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(84.2706 112)">source</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(80.8823 617)">LICENSE</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(80.8823 651)">README.md</text><path d="M53 643.5C53 638.253 57.0294 634 62 634 66.9706 634 71 638.253 71 643.5 71 648.747 66.9706 653 62 653 57.0294 653 53 648.747 53 643.5Z" stroke="#D86ECC" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><rect x="61" y="640" width="2" height="11" fill="#D86ECC"/><path d="M60 637.5C60 636.672 60.8954 636 62 636 63.1046 636 64 636.672 64 637.5 64 638.328 63.1046 639 62 639 60.8954 639 60 638.328 60 637.5Z" fill="#D86ECC" fill-rule="evenodd"/><path d="M68.6743 607.176C67.3835 605.885 65.2907 605.885 63.9999 607.176 62.7091 608.467 62.7091 610.56 63.9999 611.851 65.2907 613.141 67.3835 613.141 68.6743 611.851 69.9651 610.56 69.9651 608.467 68.6743 607.176ZM69.8289 606.022C71.7522 607.945 71.7522 611.063 69.8289 612.987 67.9056 614.91 64.7872 614.91 62.8639 612.987 60.9406 611.063 60.9406 607.945 62.8639 606.022 64.7872 604.098 67.9056 604.098 69.8289 606.022Z" fill="#00B050" fill-rule="evenodd"/><path d="M64.429 612.216C64.5388 612.326 64.5388 612.503 64.429 612.613L55.4004 621.642C55.2907 621.752 55.1128 621.752 55.0031 621.642L54.2086 620.847C54.0988 620.738 54.0988 620.56 54.2085 620.45L63.2372 611.421C63.3469 611.312 63.5248 611.312 63.6345 611.421Z" fill="#00B050" fill-rule="evenodd"/><path d="M55.1602 616.082C55.2699 615.973 55.4478 615.973 55.5575 616.082L57.222 617.747C57.3317 617.857 57.3317 618.035 57.222 618.144L56.4275 618.939C56.3178 619.049 56.1399 619.049 56.0302 618.939L54.3656 617.274C54.2559 617.164 54.2559 616.987 54.3656 616.877Z" fill="#00B050" fill-rule="evenodd"/><path d="M53.2951 617.947C53.4048 617.838 53.5827 617.838 53.6924 617.947L55.3569 619.612C55.4666 619.722 55.4666 619.9 55.3569 620.009L54.5624 620.804C54.4527 620.914 54.2748 620.914 54.1651 620.804L52.5005 619.139C52.3908 619.029 52.3908 618.852 52.5005 618.742Z" fill="#00B050" fill-rule="evenodd"/><path d="M52 64C52 62.8955 52.8955 62 54 62L63 62C64.1046 62 65 62.8955 65 64L65 72C65 73.1045 64.1046 74 63 74L54 74C52.8955 74 52 73.1045 52 72Z" fill="#156082" fill-rule="evenodd"/><path d="M52 67.6667C52 66.1939 53.1939 65 54.6667 65L75.3333 65C76.8061 65 78 66.1939 78 67.6667L78 78.3333C78 79.8061 76.8061 81 75.3333 81L54.6667 81C53.1939 81 52 79.8061 52 78.3333Z" fill="#156082" fill-rule="evenodd"/><path d="M53 68.8334C53 67.2686 54.2686 66 55.8334 66L76.1666 66C77.7314 66 79 67.2686 79 68.8334L79 80.1666C79 81.7314 77.7314 83 76.1666 83L55.8334 83C54.2686 83 53 81.7314 53 80.1666Z" fill="#1D85B3" fill-rule="evenodd"/><path d="M54 97C54 95.8954 54.8954 95 56 95L64 95C65.1046 95 66 95.8954 66 97L66 105C66 106.105 65.1046 107 64 107L56 107C54.8954 107 54 106.105 54 105Z" fill="#156082" fill-rule="evenodd"/><path d="M54 99.8334C54 98.2686 55.2686 97 56.8334 97L76.1666 97C77.7314 97 79 98.2686 79 99.8334L79 111.167C79 112.731 77.7314 114 76.1666 114L56.8334 114C55.2686 114 54 112.731 54 111.167Z" fill="#156082" fill-rule="evenodd"/><path d="M55 101.667C55 100.194 56.1939 99 57.6667 99L77.3333 99C78.8061 99 80 100.194 80 101.667L80 112.333C80 113.806 78.8061 115 77.3333 115L57.6667 115C56.1939 115 55 113.806 55 112.333Z" fill="#1D85B3" fill-rule="evenodd"/><path d="M80 129.167C80 127.97 80.9701 127 82.1667 127L90.8333 127C92.0299 127 93 127.97 93 129.167L93 137.833C93 139.03 92.0299 140 90.8333 140L82.1667 140C80.9701 140 80 139.03 80 137.833Z" fill="#156082" fill-rule="evenodd"/><path d="M80 132.833C80 131.269 81.2685 130 82.8334 130L103.167 130C104.731 130 106 131.269 106 132.833L106 144.167C106 145.731 104.731 147 103.167 147L82.8334 147C81.2685 147 80 145.731 80 144.167Z" fill="#156082" fill-rule="evenodd"/><path d="M81 133.833C81 132.269 82.2685 131 83.8334 131L104.167 131C105.731 131 107 132.269 107 133.833L107 145.167C107 146.731 105.731 148 104.167 148L83.8334 148C82.2685 148 81 146.731 81 145.167Z" fill="#1D85B3" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(112.231 146)">isaac_lab_tutorial</text><path d="M107 163C107 161.895 107.895 161 109 161L117 161C118.105 161 119 161.895 119 163L119 171C119 172.105 118.105 173 117 173L109 173C107.895 173 107 172.105 107 171Z" fill="#156082" fill-rule="evenodd"/><path d="M107 165.833C107 164.269 108.269 163 109.833 163L129.167 163C130.731 163 132 164.269 132 165.833L132 177.167C132 178.731 130.731 180 129.167 180L109.833 180C108.269 180 107 178.731 107 177.167Z" fill="#156082" fill-rule="evenodd"/><path d="M108 167.833C108 166.269 109.269 165 110.833 165L130.167 165C131.731 165 133 166.269 133 167.833L133 179.167C133 180.731 131.731 182 130.167 182L110.833 182C109.269 182 108 180.731 108 179.167Z" fill="#1D85B3" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(138.605 180)">config</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(138.604 546)">pyproject.toml</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(138.604 580)">setup.py</text><path d="M108 259.167C108 257.97 108.97 257 110.167 257L118.833 257C120.03 257 121 257.97 121 259.167L121 267.833C121 269.03 120.03 270 118.833 270L110.167 270C108.97 270 108 269.03 108 267.833Z" fill="#156082" fill-rule="evenodd"/><path d="M108 262.833C108 261.269 109.269 260 110.833 260L131.167 260C132.731 260 134 261.269 134 262.833L134 274.167C134 275.731 132.731 277 131.167 277L110.833 277C109.269 277 108 275.731 108 274.167Z" fill="#156082" fill-rule="evenodd"/><path d="M109 263.833C109 262.269 110.269 261 111.833 261L132.167 261C133.731 261 135 262.269 135 263.833L135 275.167C135 276.731 133.731 278 132.167 278L111.833 278C110.269 278 109 276.731 109 275.167Z" fill="#1D85B3" fill-rule="evenodd"/><path d="M108 227C108 225.895 108.895 225 110 225L118 225C119.105 225 120 225.895 120 227L120 235C120 236.105 119.105 237 118 237L110 237C108.895 237 108 236.105 108 235Z" fill="#156082" fill-rule="evenodd"/><path d="M108 229.833C108 228.269 109.269 227 110.833 227L130.167 227C131.731 227 133 228.269 133 229.833L133 241.167C133 242.731 131.731 244 130.167 244L110.833 244C109.269 244 108 242.731 108 241.167Z" fill="#156082" fill-rule="evenodd"/><path d="M109 231.833C109 230.269 110.269 229 111.833 229L131.167 229C132.731 229 134 230.269 134 231.833L134 243.167C134 244.731 132.731 246 131.167 246L111.833 246C110.269 246 109 244.731 109 243.167Z" fill="#1D85B3" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(139.846 243)">docs</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(139.846 277)">isaac_lab_tutorial</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(162.086 208)">extension.toml</text><path d="M120.958 537.053C118.479 537.053 116.468 539.044 116.468 541.5 116.468 543.956 118.479 545.947 120.958 545.947 123.438 545.947 125.448 543.956 125.448 541.5 125.448 539.044 123.438 537.053 120.958 537.053ZM118.879 529 123.079 529 124.137 533.19 124.453 533.305C125.259 533.642 126.005 534.093 126.67 534.637L126.679 534.645 130.9 533.449 133 537.051 129.824 540.093 129.834 540.146C129.902 540.587 129.938 541.039 129.938 541.5 129.938 541.961 129.902 542.413 129.834 542.854L129.818 542.941 132.958 545.949 130.858 549.551 126.669 548.364 125.614 549.107C125.244 549.329 124.856 549.526 124.453 549.695L124.137 549.81 123.079 554 118.879 554 117.826 549.827 117.463 549.695C117.06 549.526 116.672 549.329 116.302 549.107L115.26 548.373 111.1 549.551 109 545.949 112.105 542.975 112.082 542.854C112.014 542.413 111.979 541.961 111.979 541.5 111.979 541.039 112.014 540.587 112.082 540.146L112.105 540.025 109 537.051 111.1 533.449 115.26 534.627 116.302 533.893C116.672 533.671 117.06 533.474 117.463 533.305L117.826 533.173Z" fill="#FFA225" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(166.252 310)">tasks</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(192.944 345)">direct</text><path d="M134 293C134 291.895 134.895 291 136 291L144 291C145.105 291 146 291.895 146 293L146 302C146 303.105 145.105 304 144 304L136 304C134.895 304 134 303.105 134 302Z" fill="#156082" fill-rule="evenodd"/><path d="M134 296.833C134 295.269 135.269 294 136.833 294L156.167 294C157.731 294 159 295.269 159 296.833L159 308.167C159 309.731 157.731 311 156.167 311L136.833 311C135.269 311 134 309.731 134 308.167Z" fill="#156082" fill-rule="evenodd"/><path d="M135 297.833C135 296.269 136.269 295 137.833 295L157.167 295C158.731 295 160 296.269 160 297.833L160 309.167C160 310.731 158.731 312 157.167 312L137.833 312C136.269 312 135 310.731 135 309.167Z" fill="#1D85B3" fill-rule="evenodd"/><path d="M160 328C160 326.895 160.895 326 162 326L170 326C171.105 326 172 326.895 172 328L172 336C172 337.105 171.105 338 170 338L162 338C160.895 338 160 337.105 160 336Z" fill="#156082" fill-rule="evenodd"/><path d="M160 331.833C160 330.269 161.269 329 162.833 329L182.167 329C183.731 329 185 330.269 185 331.833L185 343.167C185 344.731 183.731 346 182.167 346L162.833 346C161.269 346 160 344.731 160 343.167Z" fill="#156082" fill-rule="evenodd"/><path d="M161 332.833C161 331.269 162.269 330 163.833 330L183.167 330C184.731 330 186 331.269 186 332.833L186 344.167C186 345.731 184.731 347 183.167 347L163.833 347C162.269 347 161 345.731 161 344.167Z" fill="#1D85B3" fill-rule="evenodd"/><path d="M31 58 31.0001 650.996" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M66 119 66.0001 593.193" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M93 154 93.0001 576.589" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M120 187 120 219.101" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M122 283 122 511.231" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M204 395.214C204 389.573 208.573 385 214.214 385L489.786 385C495.427 385 500 389.573 500 395.214L500 502.786C500 508.427 495.427 513 489.786 513L214.214 513C208.573 513 204 508.427 204 502.786Z" fill="#F2CFEE" fill-rule="evenodd"/><path d="M187 359C187 357.895 187.895 357 189 357L197 357C198.105 357 199 357.895 199 359L199 368C199 369.105 198.105 370 197 370L189 370C187.895 370 187 369.105 187 368Z" fill="#156082" fill-rule="evenodd"/><path d="M187 362.833C187 361.269 188.269 360 189.833 360L209.167 360C210.731 360 212 361.269 212 362.833L212 374.167C212 375.731 210.731 377 209.167 377L189.833 377C188.269 377 187 375.731 187 374.167Z" fill="#156082" fill-rule="evenodd"/><path d="M188 363.833C188 362.269 189.269 361 190.833 361L210.167 361C211.731 361 213 362.269 213 363.833L213 375.167C213 376.731 211.731 378 210.167 378L190.833 378C189.269 378 188 376.731 188 375.167Z" fill="#1D85B3" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(218.751 376)">isaac_lab_tutorial</text><path d="M213 392C213 390.895 213.895 390 215 390L224 390C225.105 390 226 390.895 226 392L226 400C226 401.105 225.105 402 224 402L215 402C213.895 402 213 401.105 213 400Z" fill="#156082" fill-rule="evenodd"/><path d="M213 394.833C213 393.269 214.269 392 215.833 392L236.167 392C237.731 392 239 393.269 239 394.833L239 406.167C239 407.731 237.731 409 236.167 409L215.833 409C214.269 409 213 407.731 213 406.167Z" fill="#156082" fill-rule="evenodd"/><path d="M214 396.667C214 395.194 215.194 394 216.667 394L237.333 394C238.806 394 240 395.194 240 396.667L240 407.333C240 408.806 238.806 410 237.333 410L216.667 410C215.194 410 214 408.806 214 407.333Z" fill="#1D85B3" fill-rule="evenodd"/><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(244.752 408)">agents</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(242.483 439)">__init__.py</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(241.295 471)">isaac_lab_tutorial_env_cfg.py</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="14" transform="translate(242.033 504)">isaac_lab_tutorial_env.py</text><g clip-path="url(#clip3)" transform="matrix(0.000104987 0 0 0.000104987 213 420)"><g clip-path="url(#clip4)" transform="matrix(1.04 0 0 1 -0.125 -0.25)"><use width="100%" height="100%" xlink:href="#img1" transform="scale(7441.41 7441.41)"></use></g></g><g clip-path="url(#clip5)" transform="matrix(0.000104987 0 0 0.000104987 213 451)"><g clip-path="url(#clip6)"><use width="100%" height="100%" xlink:href="#img1" transform="matrix(7739.06 0 0 7739.06 -0.125 0)"></use></g></g><g clip-path="url(#clip7)" transform="matrix(0.000104987 0 0 0.000104987 213 483)"><g clip-path="url(#clip8)" transform="matrix(1.04 0 0 1 -0.125 0)"><use width="100%" height="100%" xlink:href="#img1" transform="scale(7441.41 7441.41)"></use></g></g><path d="M146 317 146 518.337" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M171 352 171 518.37" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><path d="M197 384 197 503.395" stroke="#156082" stroke-width="2" stroke-miterlimit="8" fill="none" fill-rule="evenodd"/><text fill="#00B050" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="28" transform="translate(452.326 99)">Project</text><text fill="#FFA225" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="28" transform="translate(401.739 193)">Extension</text><text fill="#4E95D9" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="28" transform="translate(399.269 328)">Modules</text><text fill="#D86ECC" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="28" transform="translate(435.682 425)">Task</text></g></svg>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg width="1104" height="720" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="hidden"><defs><clipPath id="clip0"><rect x="0" y="0" width="1104" height="720"/></clipPath><linearGradient x1="891.756" y1="362.907" x2="975.715" y2="49.5692" gradientUnits="userSpaceOnUse" spreadMethod="reflect" id="fill1"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.38" stop-color="#91D0ED"/><stop offset="0.52" stop-color="#74C4E9"/><stop offset="0.65" stop-color="#91D0ED"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient><linearGradient x1="817.422" y1="7.67678" x2="733.464" y2="321.014" gradientUnits="userSpaceOnUse" spreadMethod="reflect" id="fill2"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.38" stop-color="#D86060"/><stop offset="0.52" stop-color="#C00000"/><stop offset="0.65" stop-color="#D86060"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient><linearGradient x1="1044.94" y1="500.364" x2="733.836" y2="408.483" gradientUnits="userSpaceOnUse" spreadMethod="reflect" id="fill3"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.38" stop-color="#91D0ED"/><stop offset="0.52" stop-color="#74C4E9"/><stop offset="0.65" stop-color="#91D0ED"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient><linearGradient x1="687.941" y1="565.661" x2="999.047" y2="657.543" gradientUnits="userSpaceOnUse" spreadMethod="reflect" id="fill4"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.38" stop-color="#D86060"/><stop offset="0.52" stop-color="#C00000"/><stop offset="0.65" stop-color="#D86060"/><stop offset="1" stop-color="#FFFFFF"/></linearGradient></defs><g clip-path="url(#clip0)"><rect x="0" y="0" width="1104" height="720" fill="#FFFFFF"/><path d="M282.202 287.327 474.297 338.799 480.891 314.189 516.922 376.597 454.515 412.628 461.109 388.019 269.013 336.547Z" fill="#FF0000" fill-rule="evenodd"/><path d="M261.19 339.938 165.444 165.631 143.114 177.898 163.243 108.704 232.437 128.833 210.106 141.099 305.852 315.405Z" fill="#215F9A" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 234.961 296.41)"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 291.415 264.891)"/><path d="M234.268 295.483C231.673 291.009 233.196 285.278 237.67 282.683L270.075 263.887C274.549 261.292 280.28 262.815 282.875 267.289L308.376 311.252C310.971 315.727 309.448 321.458 304.973 324.053L272.569 342.849C268.095 345.444 262.364 343.921 259.769 339.446Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#008450" fill-rule="evenodd"/><path d="M268.976 355.139 267.412 340.497 272.576 337.519 275.703 366.802Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd"/><path d="M0 13.4645 5.96053 0 11.9211 0 0 26.929Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866253 0.499606 0.499606 0.866253 310.885 315.236)"/><path d="M257.479 265.354 260.078 274.422 251.744 279.24 245.183 272.462Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 234.951 296.157)"/><path d="M235.71 298.141 228.746 302.176" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M236.834 300.08 229.87 304.115" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M237.962 302.017 230.997 306.052" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M239.085 303.956 232.121 307.99" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M240.212 305.893 233.247 309.928" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M241.336 307.832 234.372 311.866" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M242.463 309.769 235.499 313.803" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M243.587 311.708 236.623 315.742" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M244.807 313.591 237.842 317.625" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M245.931 315.529 238.966 319.564" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M247.058 317.466 240.093 321.501" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M248.182 319.405 241.217 323.44" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M249.309 321.342 242.344 325.377" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M250.432 323.281 243.468 327.315" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M251.46 325.276 244.591 329.214" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M252.585 327.214 245.717 331.152" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M251.061 325.508 244.576 329.35" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M252.485 327.272 245.761 331.258" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M253.81 329.094 246.845 333.129" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 291.405 264.638)"/><path d="M292.164 266.623 285.199 270.657" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M293.288 268.561 286.323 272.596" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M294.415 270.498 287.45 274.533" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M295.539 272.437 288.574 276.472" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M296.666 274.374 289.701 278.409" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M297.789 276.313 290.825 280.347" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M298.917 278.25 291.952 282.284" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M300.04 280.189 293.076 284.223" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M301.26 282.072 294.296 286.106" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M302.384 284.011 295.42 288.045" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M303.511 285.947 296.547 289.982" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M304.635 287.886 297.671 291.921" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M305.762 289.823 298.797 293.858" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M306.886 291.762 299.921 295.797" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M307.913 293.757 301.045 297.696" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M309.038 295.695 302.17 299.634" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M307.514 293.989 301.03 297.831" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M308.938 295.753 302.214 299.74" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M310.263 297.575 303.298 301.61" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M234.258 295.23C231.663 290.756 233.186 285.025 237.66 282.43L270.065 263.634C274.539 261.039 280.27 262.562 282.865 267.036L308.366 311C310.961 315.474 309.438 321.205 304.963 323.8L272.559 342.596C268.085 345.191 262.354 343.668 259.758 339.194Z" fill="#008450" fill-rule="evenodd"/><path d="M240.002 293.224 277.946 271.226 303.82 315.856 265.875 337.854Z" fill="#434343" fill-rule="evenodd"/><path d="M247.215 293.795 277.667 276.023 299.79 313.931 269.338 331.702Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M265.738 308.342C262.223 302.347 264.234 294.638 270.229 291.123 276.224 287.608 283.933 289.619 287.448 295.614 290.962 301.609 288.952 309.318 282.957 312.833 276.962 316.348 269.253 314.337 265.738 308.342Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#999999" fill-rule="evenodd"/><path d="M268.966 354.886 267.402 340.244 272.565 337.266 275.693 366.55Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd"/><path d="M0 13.4645 5.96053 0 11.9211 0 0 26.929Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866253 0.499606 0.499606 0.866253 310.875 314.984)"/><path d="M243.36 283.874 268.672 269.272 270.29 272.077 244.978 286.678Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#999999" fill-rule="evenodd"/><path d="M257.469 265.101 260.068 274.169 251.734 278.988 245.173 272.21Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M240.191 298.93 253.687 291.149 258.258 299.078 244.762 306.859Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M245.946 308.843 259.443 301.062 264.014 308.99 250.517 316.771Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M251.472 318.36 264.969 310.579 269.54 318.508 256.043 326.289Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M257.228 328.273 270.724 320.492 275.295 328.42 261.799 336.201Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M2-2.90357e-06 2.00009 62.3157-1.99991 62.3157-2 2.90357e-06ZM6.00009 60.3157 0.000104987 72.3157-5.99991 60.3157Z" fill="#A648D9" transform="matrix(1 0 0 -1 111 650.316)"/><path d="M110 648 162.754 648 162.754 652 110 652ZM160.754 644 172.754 650 160.754 656Z" fill="#FFA225"/><path d="M100 650C100 643.373 105.373 638 112 638 118.627 638 124 643.373 124 650 124 656.627 118.627 662 112 662 105.373 662 100 656.627 100 650Z" stroke="#042433" stroke-width="2" stroke-miterlimit="8" fill="#FFFFFF" fill-rule="evenodd"/><text fill="#404040" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="48" transform="translate(54.1349 655)">W</text><path d="M0.973376-1.74715 39.4447 19.686 37.4979 23.1803-0.973376 1.74715ZM39.6442 15.2183 47.207 26.3001 33.804 25.7013Z" fill="#A648D9" transform="matrix(-1 0 0 1 267.207 297)"/><path d="M265.491 298.27 239.816 252.186 243.311 250.239 268.985 296.323ZM237.295 255.88 236.697 242.477 247.778 250.039Z" fill="#FFA225"/><path d="M271.218 304.614C267.235 306.833 262.208 305.403 259.989 301.42 257.77 297.437 259.2 292.41 263.183 290.191 267.165 287.972 272.193 289.402 274.412 293.385 276.631 297.368 275.201 302.395 271.218 304.614Z" stroke="#042433" stroke-width="2" stroke-miterlimit="8" fill="#FFFFFF" fill-rule="evenodd"/><text fill="#FFA225" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(177.167 658)">𝑥</text><path d="M5.75391-16.8984 9.73828-13.207 9.10547-12.5391 4.96875-15.3867 4.83984-15.3867 0.632812-12.5391 0-13.207 4.18359-16.8984Z" fill="#FFA225" transform="translate(180.081 658)"/><text fill="#000000" fill-opacity="0" font-family="Arial,Arial_MSFontService,sans-serif" font-size="4.35938" y="-13.084"></text><text fill="#A648D9" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(92.7606 588)">𝑦</text><path d="M5.75391-16.8984 9.73828-13.207 9.10547-12.5391 4.96875-15.3867 4.83984-15.3867 0.632812-12.5391 0-13.207 4.18359-16.8984Z" fill="#A648D9" transform="translate(95.9072 588)"/><text fill="#000000" fill-opacity="0" font-family="Arial,Arial_MSFontService,sans-serif" font-size="4.35938" y="-13.084"></text><text fill="#A648D9" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(196.853 328)">𝑦</text><path d="M5.75391-16.8984 9.73828-13.207 9.10547-12.5391 4.96875-15.3867 4.83984-15.3867 0.632812-12.5391 0-13.207 4.18359-16.8984Z" fill="#A648D9" transform="translate(200 328)"/><text fill="#000000" fill-opacity="0" font-family="Arial,Arial_MSFontService,sans-serif" font-size="4.35938" y="-13.084"></text><text fill="#A648D9" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(210.767 328)"></text><text fill="#FFA225" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(221.919 241)">𝑥</text><path d="M5.75391-16.8984 9.73828-13.207 9.10547-12.5391 4.96875-15.3867 4.83984-15.3867 0.632812-12.5391 0-13.207 4.18359-16.8984Z" fill="#FFA225" transform="translate(224.832 241)"/><text fill="#000000" fill-opacity="0" font-family="Arial,Arial_MSFontService,sans-serif" font-size="4.35938" y="-13.084"></text><text fill="#FFA225" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(235.479 241)"></text><text fill="#404040" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="48" transform="translate(298.733 265)">B</text><text fill="#FF0000" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="matrix(0.965926 0.258819 -0.258819 0.965926 327.86 374)">command</text><text fill="#215F9A" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="matrix(0.5 0.866025 -0.866025 0.5 154.06 195)">forwards</text><path d="M2.76252-1.16982 5.10217 4.35521-0.422871 6.69486-2.76252 1.16982ZM7.44182 9.88025 9.78146 15.4053 4.25643 17.7449 1.91678 12.2199ZM12.1211 20.9303 14.4608 26.4554 8.93572 28.795 6.59607 23.27ZM16.8004 31.9804 19.1401 37.5054 13.615 39.8451 11.2754 34.3201ZM21.4797 43.0305 23.8194 48.5555 18.2943 50.8952 15.9547 45.3701ZM26.159 54.0806 28.4987 59.6056 22.9736 61.9452 20.634 56.4202ZM30.8383 65.1306 33.178 70.6557 27.6529 72.9953 25.3133 67.4703ZM35.5176 76.1807 37.8573 81.7058 32.3322 84.0454 29.9926 78.5204ZM40.1969 87.2308 42.5365 92.7558 37.0115 95.0955 34.6719 89.5704ZM44.8762 98.2809 47.2158 103.806 41.6908 106.146 39.3512 100.621ZM49.5555 109.331 51.8951 114.856 46.3701 117.196 44.0305 111.671ZM54.2348 120.381 56.5744 125.906 51.0494 128.246 48.7098 122.721ZM58.9141 131.431 61.2537 136.956 55.7287 139.296 53.389 133.771ZM63.5934 142.481 65.933 148.006 60.408 150.346 58.0683 144.821ZM68.2727 153.531 70.6123 159.056 65.0873 161.396 62.7476 155.871ZM72.952 164.581 75.2916 170.106 69.7666 172.446 67.4269 166.921ZM77.6312 175.631 79.9709 181.156 74.4459 183.496 72.1062 177.971ZM82.3105 186.681 84.6502 192.207 79.1252 194.546 76.7855 189.021ZM86.9898 197.732 89.3295 203.257 83.8044 205.596 81.4648 200.071ZM91.6691 208.782 94.0088 214.307 88.4837 216.646 86.1441 211.121ZM96.3484 219.832 98.6881 225.357 93.163 227.696 90.8234 222.171ZM101.028 230.882 103.367 236.407 97.8423 238.746 95.5027 233.221ZM105.707 241.932 108.047 247.457 102.522 249.797 100.182 244.272ZM110.386 252.982 112.726 258.507 107.201 260.847 104.861 255.322ZM115.066 264.032 117.405 269.557 111.88 271.897 109.541 266.372ZM119.745 275.082 122.085 280.607 116.56 282.947 114.22 277.422ZM124.424 286.132 126.764 291.657 121.239 293.997 118.899 288.472ZM129.104 297.182 131.443 302.707 125.918 305.047 123.578 299.522ZM133.783 308.232 136.122 313.757 130.597 316.097 128.258 310.572ZM138.462 319.282 139.408 321.516 133.883 323.856 132.937 321.622ZM143.763 316.414 142.495 336.498 127.188 323.433Z" fill="#00B050" transform="matrix(1 0 0 -1 123 635.498)"/><text fill="#00B050" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="24" transform="matrix(0.379086 -0.925361 0.925361 0.379086 136.056 580)">robot.data.root_pos_w</text><path d="M266.843 287.935 265.855 282.017 271.773 281.029 272.761 286.947ZM264.867 276.099 263.879 270.181 269.797 269.193 270.785 275.111ZM262.891 264.262 261.904 258.344 267.822 257.357 268.81 263.275ZM260.916 252.426 259.928 246.508 265.846 245.52 266.834 251.438ZM258.94 240.59 257.952 234.672 263.87 233.684 264.858 239.602ZM256.964 228.754 255.977 222.836 261.895 221.848 262.882 227.766ZM254.989 216.918 254.001 210.999 259.919 210.012 260.907 215.93ZM253.013 205.081 252.025 199.163 257.943 198.175 258.931 204.093ZM251.037 193.245 250.049 187.327 255.968 186.339 256.955 192.257ZM249.062 181.409 248.074 175.491 253.992 174.503 254.98 180.421ZM247.086 169.573 246.098 163.654 252.016 162.667 253.004 168.585ZM245.11 157.736 244.122 151.818 250.04 150.83 251.028 156.748ZM243.134 145.9 242.147 139.982 248.065 138.994 249.053 144.912ZM241.159 134.064 240.171 128.146 246.089 127.158 247.077 133.076ZM239.183 122.228 238.195 116.309 244.113 115.322 245.101 121.24ZM237.207 110.391 236.22 104.473 242.138 103.485 243.126 109.403ZM235.232 98.5551 234.244 92.6369 240.162 91.6491 241.15 97.5672ZM233.256 86.7188 232.268 80.8007 238.186 79.8129 239.174 85.731ZM231.28 74.8826 230.292 68.9645 236.211 67.9766 237.198 73.8947ZM229.305 63.0464 228.511 58.289 234.429 57.3012 235.223 62.0585ZM223.086 62.2361 229 43 240.841 59.2726Z" fill="#61CBF4"/><text fill="#00B0F0" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(254.176 56)">robot.data.root_com_vel_w[:,:3]</text><path d="M312 289 631.248 289 631.248 293 312 293ZM629.248 285 641.248 291 629.248 297Z" fill="#FFA225"/><text fill="#FFA225" font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(626.304 282)">𝑥</text><path d="M5.75391-16.8984 9.73828-13.207 9.10547-12.5391 4.96875-15.3867 4.83984-15.3867 0.632812-12.5391 0-13.207 4.18359-16.8984Z" fill="#FFA225" transform="translate(629.217 282)"/><text fill="#000000" fill-opacity="0" font-family="Arial,Arial_MSFontService,sans-serif" font-size="4.35938" y="-13.084"></text><path d="M453.528 358.071 624.359 404.815 623.303 408.673 452.472 361.929ZM623.486 400.429 633.477 409.383 620.318 412.003Z" fill="#FF0000"/><path d="M565.203 294.741 568.203 299.775 570.623 305.133 572.413 310.67 573.584 316.342 574.149 322.103 574.118 327.911 573.505 333.722 572.321 339.493 570.579 345.184 568.291 350.753 565.467 356.158 562.12 361.358 558.26 366.311 553.874 371.001 551.646 372.857 549.086 369.782 551.219 368.007 551.038 368.178 555.277 363.645 555.16 363.781 558.91 358.97 558.805 359.117 562.055 354.069 561.964 354.226 564.703 348.982 564.626 349.148 566.844 343.75 566.782 343.925 568.469 338.413 568.422 338.597 569.567 333.013 569.537 333.205 570.13 327.59 570.119 327.79 570.148 322.185 570.158 322.391 569.614 316.837 569.646 317.047 568.518 311.586 568.573 311.797 566.851 306.469 566.932 306.677 564.604 301.525 564.709 301.725 561.767 296.788ZM555.696 374.724 542.594 377.613 548.144 365.398Z" fill="#FF7E1D"/><text fill="#FF7E1D" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(584.455 340)">yaw</text><path d="M897.812 28.6953C984.274 52.1183 1035.38 141.197 1011.95 227.659 988.577 313.952 899.779 365.056 813.422 341.917L855.401 185.248Z" fill="url(#fill1)" fill-rule="evenodd"/><path d="M811.366 341.888C724.905 318.465 673.802 229.386 697.225 142.924 720.602 56.6311 809.399 5.52706 895.757 28.6664L853.778 185.336Z" fill="url(#fill2)" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 822.551 169.949)"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 879.004 138.43)"/><path d="M821.857 169.023C819.262 164.549 820.785 158.818 825.26 156.223L857.664 137.427C862.138 134.831 867.869 136.355 870.465 140.829L895.965 184.792C898.56 189.266 897.037 194.997 892.563 197.593L860.158 216.389C855.684 218.984 849.953 217.461 847.358 212.986Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#008450" fill-rule="evenodd"/><path d="M856.565 228.678 855.002 214.037 860.165 211.059 863.292 240.342Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd"/><path d="M0 13.4645 5.96053 0 11.9211 0 0 26.929Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866253 0.499606 0.499606 0.866253 898.474 188.776)"/><path d="M845.068 138.894 847.668 147.962 839.333 152.78 832.772 146.002Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 822.541 169.697)"/><path d="M823.3 171.681 816.335 175.716" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M824.424 173.62 817.459 177.655" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M825.551 175.557 818.586 179.591" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M826.675 177.496 819.71 181.53" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M827.801 179.433 820.837 183.467" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M828.925 181.372 821.961 185.406" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M830.053 183.308 823.088 187.343" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M831.176 185.247 824.212 189.282" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M832.396 187.13 825.432 191.165" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M833.52 189.069 826.555 193.104" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M834.647 191.006 827.683 195.041" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M835.771 192.945 828.807 196.979" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M836.898 194.882 829.933 198.917" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M838.022 196.821 831.057 200.855" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M839.049 198.816 832.181 202.754" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M840.174 200.754 833.306 204.692" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M838.65 199.048 832.166 202.89" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M840.074 200.812 833.35 204.798" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M841.399 202.634 834.434 206.669" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 878.994 138.178)"/><path d="M879.753 140.162 872.788 144.197" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M880.877 142.101 873.912 146.136" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M882.004 144.038 875.04 148.073" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M883.128 145.977 876.163 150.011" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M884.255 147.914 877.29 151.949" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M885.379 149.853 878.414 153.887" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M886.506 151.79 879.541 155.824" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M887.63 153.728 880.665 157.763" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M888.849 155.612 881.885 159.646" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M889.973 157.55 883.009 161.585" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M891.101 159.487 884.136 163.522" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M892.225 161.426 885.26 165.461" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M893.351 163.363 886.387 167.398" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M894.475 165.302 887.511 169.337" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M895.502 167.297 888.634 171.235" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M896.628 169.235 889.759 173.173" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M895.103 167.529 888.619 171.371" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M896.528 169.293 889.803 173.279" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M897.852 171.115 890.888 175.15" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M821.847 168.77C819.252 164.296 820.775 158.565 825.25 155.97L857.654 137.174C862.128 134.579 867.859 136.102 870.454 140.576L895.955 184.539C898.55 189.014 897.027 194.745 892.553 197.34L860.148 216.136C855.674 218.731 849.943 217.208 847.348 212.734Z" fill="#008450" fill-rule="evenodd"/><path d="M827.591 166.764 865.535 144.766 891.409 189.395 853.465 211.394Z" fill="#434343" fill-rule="evenodd"/><path d="M834.805 167.335 865.256 149.563 887.379 187.471 856.928 205.242Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M853.327 181.882C849.812 175.887 851.823 168.178 857.818 164.663 863.813 161.148 871.522 163.159 875.037 169.154 878.552 175.149 876.541 182.858 870.546 186.373 864.551 189.887 856.842 187.877 853.327 181.882Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#999999" fill-rule="evenodd"/><path d="M856.555 228.426 854.991 213.784 860.155 210.806 863.282 240.089Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd"/><path d="M0 13.4645 5.96053 0 11.9211 0 0 26.929Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866253 0.499606 0.499606 0.866253 898.464 188.524)"/><path d="M830.949 157.413 856.262 142.812 857.88 145.617 832.567 160.218Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#999999" fill-rule="evenodd"/><path d="M845.058 138.641 847.658 147.709 839.323 152.527 832.762 145.749Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M827.78 172.47 841.277 164.689 845.848 172.617 832.351 180.398Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M833.536 182.383 847.032 174.602 851.603 182.53 838.106 190.311Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M839.061 191.9 852.558 184.119 857.129 192.048 843.632 199.829Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M844.817 201.813 858.314 194.032 862.884 201.96 849.388 209.741Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M858.582 174.087 1018.78 222.815 1017.62 226.642 857.418 177.913ZM1018.03 218.406 1027.77 227.639 1014.54 229.887Z" fill="#FF0000"/><path d="M856.724 175.595 777.12 32.709 780.614 30.7622 860.218 173.648ZM774.599 36.403 774 23 785.082 30.5628Z" fill="#215F9A"/><path d="M710.992 485.831C736.601 399.991 826.948 351.165 912.788 376.774 998.46 402.333 1047.29 492.398 1021.97 578.141L866.418 532.2Z" fill="url(#fill3)" fill-rule="evenodd"/><path d="M1021.89 580.195C996.282 666.034 905.935 714.861 820.095 689.252 734.423 663.693 685.588 573.627 710.911 487.885L866.465 533.825Z" fill="url(#fill4)" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 827.701 525.024)"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 884.154 493.505)"/><path d="M827.007 524.098C824.412 519.623 825.935 513.892 830.409 511.297L862.814 492.501C867.288 489.906 873.019 491.429 875.614 495.904L901.115 539.867C903.71 544.341 902.187 550.072 897.713 552.667L865.308 571.463C860.834 574.058 855.103 572.535 852.508 568.061Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#008450" fill-rule="evenodd"/><path d="M861.715 583.753 860.151 569.112 865.315 566.134 868.442 595.417Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd"/><path d="M0 13.4645 5.96053 0 11.9211 0 0 26.929Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866253 0.499606 0.499606 0.866253 903.624 543.851)"/><path d="M850.218 493.968 852.818 503.036 844.483 507.855 837.922 501.077Z" stroke="#FFFFFF" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 827.69 524.771)"/><path d="M828.449 526.756 821.485 530.79" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M829.573 528.695 822.609 532.729" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M830.701 530.632 823.736 534.666" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M831.825 532.57 824.86 536.605" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M832.951 534.508 825.987 538.542" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M834.075 536.446 827.111 540.481" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M835.202 538.383 828.238 542.418" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M836.326 540.322 829.362 544.357" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M837.546 542.205 830.581 546.24" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M838.67 544.144 831.705 548.179" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M839.797 546.081 832.832 550.115" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M840.921 548.02 833.956 552.054" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M842.048 549.957 835.083 553.991" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M843.172 551.896 836.207 555.93" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M844.199 553.891 837.33 557.829" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M845.324 555.829 838.456 559.767" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M843.8 554.122 837.315 557.965" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M845.224 555.887 838.5 559.873" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M846.549 557.709 839.584 561.743" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M0 1.34843C0 0.603712 0.603714 -4.7579e-16 1.34843 -6.34386e-16L6.7419 0C7.48662 -1.58597e-16 8.09033 0.603712 8.09033 1.34843L8.09033 38.5146C8.09033 39.2593 7.48662 39.863 6.7419 39.863L1.34843 39.863C0.603714 39.863 -1.58597e-16 39.2593 0 38.5146Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866778 0.498694 0.498694 0.866778 884.144 493.253)"/><path d="M884.903 495.237 877.938 499.272" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M886.027 497.176 879.062 501.21" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M887.154 499.113 880.189 503.147" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M888.278 501.052 881.313 505.086" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M889.405 502.989 882.44 507.023" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M890.529 504.928 883.564 508.962" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M891.656 506.865 884.691 510.899" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M892.78 508.803 885.815 512.838" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M893.999 510.686 887.035 514.721" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M895.123 512.625 888.159 516.66" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M896.25 514.562 889.286 518.596" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M897.374 516.501 890.41 520.535" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M898.501 518.438 891.536 522.473" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M899.625 520.377 892.66 524.411" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M900.652 522.372 893.784 526.31" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M901.777 524.31 894.909 528.248" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M900.253 522.604 893.769 526.446" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M901.677 524.368 894.953 528.354" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M903.002 526.19 896.038 530.224" stroke="#999999" stroke-linejoin="round" stroke-miterlimit="10" fill="none" fill-rule="evenodd"/><path d="M826.997 523.845C824.402 519.371 825.925 513.64 830.399 511.045L862.804 492.249C867.278 489.653 873.009 491.177 875.604 495.651L901.105 539.614C903.7 544.089 902.177 549.819 897.702 552.415L865.298 571.211C860.824 573.806 855.093 572.283 852.498 567.808Z" fill="#008450" fill-rule="evenodd"/><path d="M832.741 521.839 870.685 499.841 896.559 544.47 858.615 566.468Z" fill="#434343" fill-rule="evenodd"/><path d="M839.955 522.409 870.406 504.638 892.529 542.546 862.077 560.317Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M858.477 536.957C854.962 530.961 856.973 523.252 862.968 519.737 868.963 516.223 876.672 518.233 880.187 524.228 883.702 530.223 881.691 537.933 875.696 541.447 869.701 544.962 861.992 542.952 858.477 536.957Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#999999" fill-rule="evenodd"/><path d="M861.705 583.5 860.141 568.859 865.305 565.881 868.432 595.164Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd"/><path d="M0 13.4645 5.96053 0 11.9211 0 0 26.929Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#434343" fill-rule="evenodd" transform="matrix(-0.866253 0.499606 0.499606 0.866253 903.614 543.598)"/><path d="M836.099 512.488 861.411 497.887 863.029 500.692 837.717 515.293Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#999999" fill-rule="evenodd"/><path d="M850.208 493.716 852.807 502.784 844.473 507.602 837.912 500.824Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#666666" fill-rule="evenodd"/><path d="M832.93 527.545 846.427 519.764 850.997 527.692 837.501 535.473Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M838.685 537.457 852.182 529.676 856.753 537.605 843.256 545.386Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M844.211 546.975 857.708 539.194 862.279 547.122 848.782 554.903Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M849.967 556.887 863.463 549.106 868.034 557.035 854.538 564.816Z" stroke="#0E2841" stroke-linejoin="round" stroke-miterlimit="10" fill="#CCCCCC" fill-rule="evenodd"/><path d="M863.582 529.087 1023.78 577.815 1022.62 581.642 862.418 532.913ZM1023.03 573.406 1032.77 582.639 1019.54 584.887Z" fill="#FF0000"/><path d="M861.724 530.595 782.12 387.709 785.614 385.762 865.218 528.648ZM779.599 391.403 779 378 790.082 385.563Z" fill="#215F9A"/><text fill="#215F9A" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(447.939 194)">forwards</text><text fill="#FF0000" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(569.086 194)">command</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(447.939 577)">(</text><text fill="#215F9A" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(454.939 577)">forwards</text><text fill="#FF0000" font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(576.086 577)">command</text><text font-family="Aptos,Aptos_MSFontService,sans-serif" font-weight="700" font-size="24" transform="translate(686.753 577)">)</text><path d="M561.138 566.727 564.808 570.396 568.477 566.727 569.241 567.49 565.571 571.16 569.241 574.829 568.477 575.593 564.808 571.923 561.138 575.593 560.375 574.829 564.044 571.16 560.375 567.49Z" stroke="#000000" stroke-width="2" stroke-miterlimit="8" fill-rule="evenodd"/><text font-family="Cambria Math,Cambria Math_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(421.489 577)">𝑧</text><path d="M-2.51953-16.8984 0-13.207-0.820312-12.5391-3.30469-15.3867-3.43359-15.3867-5.91797-12.5391-6.71484-13.207-4.08984-16.8984Z" transform="translate(432.549 577)"/><text fill="#000000" fill-opacity="0" font-family="Arial,Arial_MSFontService,sans-serif" font-size="4.35938" x="-6.71484" y="-13.084">Ƹ</text><path d="M437 571C437 569.895 438.119 569 439.5 569 440.881 569 442 569.895 442 571 442 572.105 440.881 573 439.5 573 438.119 573 437 572.105 437 571Z" stroke="#000000" stroke-width="2" stroke-miterlimit="8" fill-rule="evenodd"/><path d="M553 189.5C553 188.119 554.119 187 555.5 187 556.881 187 558 188.119 558 189.5 558 190.881 556.881 192 555.5 192 554.119 192 553 190.881 553 189.5Z" stroke="#000000" stroke-width="2" stroke-miterlimit="8" fill-rule="evenodd"/></g></svg>
...@@ -73,7 +73,7 @@ Here, we print both the net contact force and the filtered force matrix for each ...@@ -73,7 +73,7 @@ Here, we print both the net contact force and the filtered force matrix for each
Received contact force of: tensor([[[1.3529e-05, 0.0000e+00, 1.0069e+02]]], device='cuda:0') Received contact force of: tensor([[[1.3529e-05, 0.0000e+00, 1.0069e+02]]], device='cuda:0')
.. figure:: ../../_static/overview/overview_sensors_contact_visualization.jpg .. figure:: ../../../_static/overview/sensors/contact_visualization.jpg
:align: center :align: center
:figwidth: 100% :figwidth: 100%
:alt: The contact sensor visualization :alt: The contact sensor visualization
......
.. _walkthrough_api_env_design:
Classes and Configs
====================================
To begin, navigate to the task: ``source/isaac_lab_tutorial/isaac_lab_tutorial/tasks/direct/isaac_lab_tutorial``, and take a look
and the contents of ``isaac_lab_tutorial_env_cfg.py``. You should see something that looks like the following
.. code-block:: python
from isaaclab_assets.robots.cartpole import CARTPOLE_CFG
from isaaclab.assets import ArticulationCfg
from isaaclab.envs import DirectRLEnvCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg
from isaaclab.utils import configclass
@configclass
class IsaacLabTutorialEnvCfg(DirectRLEnvCfg):
# Some useful fields
.
.
.
# simulation
sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=2)
# robot(s)
robot_cfg: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="/World/envs/env_.*/Robot")
# scene
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=4096, env_spacing=4.0, replicate_physics=True)
# Some more useful fields
.
.
.
This is the default configuration for a simple cartpole environment that comes with the template and defines the ``self`` scope
for anything you do within the corresponding environment.
.. currentmodule:: isaaclab.envs
The first thing to note is the presence of the ``@configclass`` decorator. This defines a class as a configuration class, which holds
a special place in Isaac Lab. Configuration classes are part of how Isaac Lab determines what to "care" about when it comes to cloning
the environment to scale up training. Isaac Lab provides different base configuration classes depending on your goals, and in this
case we are using the :class:`DirectRLEnvCfg` class because we are interested in performing reinforcement learning in the direct workflow.
.. currentmodule:: isaaclab.sim
The second thing to note is the content of the configuration class. As the author, you can specify any fields you desire but, generally speaking, there are three things you
will always define here: The **sim**, the **scene**, and the **robot**. Notice that these fields are also configuration classes! Configuration classes
are compositional in this way as a solution for cloning arbitrarily complex environments.
The **sim** is an instance of :class:`SimulationCfg`, and this is the config that controls the nature of the simulated reality we are building. This field is a member
of the base class, ``DirecRLEnvCfg``, but has a default sim configuration, so it's *technically* optional. The ``SimulationCfg`` dictates
how finely to step through time (dt), the direction of gravity, and even how physics should be simulated. In this case we only specify the time step and the render interval, with the
former indicating that each step through time should simulate :math:`1/120`th of a second, and the latter being how many steps we should take before we render a frame (a value of 2 means
render every other frame).
.. currentmodule:: isaaclab.scene
The **scene** is an instance of :class:`InteractiveSceneCfg`. The scene describes what goes "on the stage" and manages those simulation entities to be cloned across environments.
The scene is also a member of the base class ``DirectRLEnvCfg``, but unlike the sim it has no default and must be defined in every ``DirectRLEnvCfg``. The ``InteractiveSceneCfg``
describes how many copies of the scene we want to create for training purposes, as well as how far apart they should be spaced on the stage.
.. currentmodule:: isaaclab.assets
Finally we have the **robot** definition, which is an instance of :class:`ArticulationCfg`. An environment could have multiple articulations, and so the presence of
an ``ArticulationCfg`` is not strictly required in order to define a ``DirectRLEnv``. Instead, the usual workflow is to define a regex path to the robot, and replace
the ``prim_path`` attribute in the base configuration. In this case, ``CARTPOLE_CFG`` is a configuration defined in ``isaaclab_assets.robots.cartpole`` and by replacing
the prim path with ``/World/envs/env_.*/Robot`` we are implicitly saying that every copy of the scene will have a robot named ``Robot``.
The Environment
-----------------
Next, let's take a look at the contents of the other python file in our task directory: ``isaac_lab_tutorial_env_cfg.py``
.. code-block:: python
#imports
.
.
.
from .isaac_lab_tutorial_env_cfg import IsaacLabTutorialEnvCfg
class IsaacLabTutorialEnv(DirectRLEnv):
cfg: IsaacLabTutorialEnvCfg
def __init__(self, cfg: IsaacLabTutorialEnvCfg, render_mode: str | None = None, **kwargs):
super().__init__(cfg, render_mode, **kwargs)
. . .
def _setup_scene(self):
self.robot = Articulation(self.cfg.robot_cfg)
# add ground plane
spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg())
# add articulation to scene
self.scene.articulations["robot"] = self.robot
# clone and replicate
self.scene.clone_environments(copy_from_source=False)
# add lights
light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
light_cfg.func("/World/Light", light_cfg)
def _pre_physics_step(self, actions: torch.Tensor) -> None:
. . .
def _apply_action(self) -> None:
. . .
def _get_observations(self) -> dict:
. . .
def _get_rewards(self) -> torch.Tensor:
total_reward = compute_rewards(...)
return total_reward
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
. . .
def _reset_idx(self, env_ids: Sequence[int] | None):
. . .
@torch.jit.script
def compute_rewards(...):
. . .
return total_reward
.. currentmodule:: isaaclab.envs
Some of the code has been omitted for clarity, in order to aid in discussion. This is where the actual "meat" of the
direct workflow exists and where most of our modifications will take place as we tweak the template to suit our needs.
Currently, all of the member functions of ``IsaacLabTutorialEnv`` are directly inherited from the :class:`DirectRLEnv`. This
known interface is how Isaac Lab and its supported RL frameworks interact with the environment.
When the environment is initialized, it receives its own config as an argument, which is then immediately passed to super in order
to initialize the ``DirectRLEnv``. This super call also calls ``_setup_scene``, which actually constructs the scene and clones
it appropriately. Notably is how the robot is created and registered to the scene in ``_setup_scene``. First, the robot articulation
is created by using the ``robot_config`` we defined in ``IsaacLabTutorialEnvCfg``: it doesn't exist before this point! When the
articulation is created, the robot exists on the stage at ``/World/envs/env_0/Robot``. The call to ``scene.clone_environments`` then
copies ``env_0`` appropriately. At this point the robot exists as many copies on the stage, so all that's left is to notify the ``scene``
object of the existence of this articulation to be tracked. The articulations of the scene are kept as a dictionary, so ``scene.articulations["robot"] = self.robot``
creates a new ``robot`` element of the ``articulations`` dictionary and sets the value to be ``self.robot``.
Notice also that the remaining functions do not take additional arguments except ``_reset_idx``. This is because the environment only manages the application of
actions to the agent being simulated, and then updating the sim. This is what the ``_pre_physics_step`` and ``_apply_action`` steps are for: we set the drive commands
to the robot so that when the simulation steps forward, the actions are applied and the joints are driven to new targets. This process is broken into steps like this
in order to ensure systematic control over how the environment is executed, and is especially important in the manager workflow. A similar relationship exists between the
``_get_dones`` function and ``_reset_idx``. The former, ``_get_dones`` determines if each of the environments is in a terminal state, and populates tensors of boolean
values to indicate which environments terminated due to entering a terminal state vs time out (the two returned tensors of the function). The latter, ``_reset_idx`` takes a
list environment index values (integers) and then actually resets those environments. It is important that things like updating drive targets or resetting environments
do not happen **during** the physics or rendering steps, and breaking up the interface in this way helps prevent that.
.. _walkthrough_concepts_env_design:
Environment Design Background
==============================
Now that we have our project installed, we can start designing the environment. In the traditional description
of a reinforcement learning (RL) problem, the environment is responsible for using the actions produced by the agent to
update the state of the "world", and finally compute and return the observations and the reward signal. However, there are
some additional concepts that are unique to Isaac Sim and Lab regarding the mechanics of the simulation itself.
The traditional description of a reinforcement learning problem presumes a "world", but we get no such luxury; we must define
the world ourselves, and success depends on understanding on how to construct that world and how it will fit into the simulation.
App, Sim, World, Stage, and Scene
----------------------------------
.. figure:: ../../_static/setup/walkthrough_sim_stage_scene.svg
:align: center
:figwidth: 100%
:alt: How the sim is organized.
The **World** is defined by the origin of a cartesian coordinate system and the units that define it. How big or how small? How
near or how far? The answers to questions like these can only be defined *relative* to some contextual reference frame, and that
reference frame is what defines the world.
"Above" the world in structure is the **Sim**\ ulation and the **App**\ lication. The **Application** is "the thing responsible for
everything else": It governs all resource management as well as launching and destroying the simulation when we are done with it.
When we :ref:`launched training with the template<walkthrough_project_setup>`, the window that appears with the viewport of cartpoles
training is the Application window. The application is not defined by the GUI however, and even when running in headless mode all
simulations have an application that governs them.
The **Simulation** controls the "rules" of the world. It defines the laws of physics, such as how time and gravity should work, and how frequently to perform
rendering. If the application holds the sim, then the sim holds the world. The simulation governs a single step through time by dividing it into many different
sub-steps, each devoted to a specific aspect of updating the world into a state. Many of the APIs in Isaac Lab are written to specifically hook into
these various steps and you will often see functions named like ``_pre_XYZ_step`` and ``_post_XYZ_step`` where ``XYZ_step`` is the name of one of these sub-steps of
the simulation, such as the ``physics_step`` or the ``render_step``.
"Below" the world in structure is the **Stage** and the **Scene**. If the world provides spatial context to the sim, then
the **Stage** provides the *compositional context* for the world. Suppose we want to simulate a table set for a meal in a room:
the room is the "world" in this case, and we choose the origin of the world to be one of the corners of the room. The position of the
table in the room is defined as a vector from the origin of the world to some point on the table that we choose to be the origin of a *new* coordinate
system, fixed to the table. It's not useful to us, *the agent*\ , to talk about the location of the food and the utensils on the table with respect to the
corner of the room: instead it is preferable to use the coordinates defined with respect to the table. However, the simulation needs to know
these global coordinates in order to properly simulate the next time step, so we must define how these two coordinate systems are *composed* together.
This is what the stage accomplishes: everything in the simulation is a `USD primitive <https://openusd.org/release/glossary.html#usdglossary-prim>`_ and the
stage represents the relationships between these primitives as a tree, with the context being defined by the relative path in the tree. Every prim on the stage
has a name and therefore a path in this tree, such as ``/room/table/food`` or ``room/table/utensils``. Relationships are defined by the "parents" and "children"
of a given node in this tree: the ``table`` is a child of the ``room`` but a parent of ``food``. Compositional properties of the parent are applied to all of its
children, but child prims have the ability to override parent properties if necessary, as is often the case for materials.
.. figure:: ../../_static/setup/walkthrough_stage_context.svg
:align: center
:figwidth: 100%
:alt: How the stage organizes context
Armed with this vocabulary, we can finally talk about the **Scene**, one of the most critical elements to understand about Isaac Lab. Deep learning, in
all its forms, is rooted in the analysis of data. This is true even in robot learning, where data is acquired through the sensors of the robot being trained.
The time required to setup the robot, collect data, and reset the robot to collect more, is a fundamental bottleneck in teaching robots to do *anything*, with any method.
Isaac Sim gives us access to robots without the need for literal physical robots, but Isaac Lab gives us access to *vectorization*: the ability to simulate many copies
of a training procedure efficiently, thus multiplying the rate of data generation and accelerating training proportionally. The scene governs those primitives on the stage
that matter to this vectorization process, known as **simulation entities**.
Suppose the reason why you want to simulate a table set for a meal is because you would like to train a robot to place the table settings for you! The robot, the table,
and all the things on it can be registered to the scene of an environment. We can then specify how many copies we want and the scene will automatically
construct and run those copies on the stage. These copies are placed at new coordinates on the stage, defining a new reference frame from which observations
and rewards can be computed. Every copy of the scene exists on the stage and is being simulated by the same world. This is much more efficient
than running unique simulations for each copy, but it does open up the possibility of unwanted interactions between copies of the scene, so it's important
to keep this in mind while debugging.
Now that we have a grasp on the mechanics, we can take a look at the code generated for our template project!
.. _walkthrough:
Walkthrough
========================
So you finished installing Isaac Sim and Isaac Lab, and you verified that everything is working as expected...
Now what?
The following walkthrough will guide you through setting up an Isaac Lab extension project, adding a new robot to lab, designing an environment, and training a policy for that robot.
For this walkthrough, we will be starting with the Jetbot, a simple two wheeled differential base robot with a camera mounted on top, but the intent is for these guides to be general enough that you can use them to add your own robots and environments to Isaac Lab!
The end result of this walkthrough can be found in our tutorial project repository `here <https://github.com/isaac-sim/IsaacLabTutorial/tree/main>`_. Each branch of this repository
represents a different stage of modifying the default template project to achieve our goals.
.. toctree::
:maxdepth: 1
:titlesonly:
project_setup
concepts_env_design
api_env_design
technical_env_design
training_jetbot_gt
training_jetbot_reward_exploration
.. _walkthrough_project_setup:
Isaac Lab Project Setup
========================
The best way to create a new project is to use the :ref:`Template Generator<template-generator>`. Generating the template
for this tutorial series is done by calling the ``isaaclab`` script from the root directory of the repository
.. code-block:: bash
./isaaclab.sh --new
Be sure to select ``External`` and ``Direct | single agent``. For the frameworks, select ``skrl`` and both ``PPO`` and ``AMP`` on the following menu. You can
select other frameworks if you like, but this tutorial will detail ``skrl`` specifically. The configuration process for other frameworks is similar. You
can get a copy of this code directly by checking out the `initial branch of the tutorial repository <https://github.com/isaac-sim/IsaacLabTutorial/tree/initial>`_!
This will create an extension project with the specified name at the chosen path. For this tutorial, we chose the name ``isaac_lab_tutorial``.
.. note::
The template generator expects the project name to respect "snake_case": all lowercase with underscores separating words. However, we have renamed the
sample project to "IsaacLabTutorial" to more closely match the naming convention GitHub and our other projects. If you are following along with the example
repository, note this minor difference as some superficial path names may change. If you are following along by building the project yourself, then you can ignore this note.
Next, we must install the project as a python module. Navigate to the directory that was just created
(it will contain the ``source`` and ``scripts`` directories for the project) and then run the following to install the module.
.. code-block:: bash
python -m pip install -e source/isaac_lab_tutorial
To verify that things have been setup properly, run
.. code-block:: bash
python scripts/list_envs.py
from the root directory of your new project. This should generate a table that looks something like the following
.. code-block:: bash
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Available Environments in Isaac Lab |
+--------+---------------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| S. No. | Task Name | Entry Point | Config |
+--------+---------------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
| 1 | Template-Isaac-Lab-Tutorial-Direct-v0 | isaac_lab_tutorial.tasks.direct.isaac_lab_tutorial.isaac_lab_tutorial_env:IsaacLabTutorialEnv | isaac_lab_tutorial.tasks.direct.isaac_lab_tutorial.isaac_lab_tutorial_env_cfg:IsaacLabTutorialEnvCfg |
+--------+---------------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+
We can now use the task name to run the environment.
.. code-block:: bash
python scripts/skrl/train.py --task=Template-Isaac-Lab-Tutorial-Direct-v0
and by default, this should start a cartpole training environment.
Let the training finish and then run the following command to see the trained policy in action!
.. code-block:: bash
python scripts/skrl/play.py --task=Template-Isaac-Lab-Tutorial-Direct-v0
Notice that you did not need to specify the path for the checkpoint file! This is because Isaac Lab handles much of the minute details
like checkpoint saving, loading, and logging. In this case, the ``train.py`` script will create two directories: **logs** and **output**, which are
used as the default output directories for tasks run by this project.
Project Structure
------------------------------
There are four nested structures you need to be aware of when working in the direct workflow with an Isaac Lab template
project: the **Project**, the **Extension**, the **Modules**, and the **Task**.
.. figure:: ../../_static/setup/walkthrough_project_setup.svg
:align: center
:figwidth: 100%
:alt: The structure of the isaac lab template project.
The **Project** is the root directory of the generated template. It contains the source and scripts directories, as well as
a ``README.md`` file. When we created the template, we named the project *IsaacLabTutorial* and this defined the root directory
of a git repository. If you examine the project root with hidden files visible you will see a number of files defining
the behavior of the project with respect to git. The ``scripts`` directory contains the ``train.py`` and ``play.py`` scripts for the
various RL libraries you chose when generating the template, while the source directory contains the python packages for the project.
The **Extension** is the name of the python package we installed via pip. By default, the template generates a project
with a single extension of the same name. A project can have multiple extensions, and so they are kept in a common ``source``
directory. Traditional python packages are defined by the presence of a ``pyproject.toml`` file that describes the package
metadata, but packages using Isaac Lab must also be Isaac Sim extensions and so require a ``config`` directory and an accompanying
``extension.toml`` file that describes the metadata needed by the Isaac Sim extension manager. Finally, because the template
is intended to be installed via pip, it needs a ``setup.py`` file to complete the setup procedure using the ``extension.toml``
config. A project can have multiple extensions, as evidenced by the Isaac Lab repository itself!
The **Modules** are what actually gets loaded by Isaac Lab to run training (the meat of the code). By default, the template
generates an extension with a single module that is named the same as the project. The structure of the various sub-modules
in the extension is what determines the ``entry_point`` for an environment in Isaac Lab. This is why our template project needed
to be installed before we could call ``train.py``: the path to the necessary components to run the task needed to be exposed
to python for Isaac Lab to find them.
Finally, the **Task** is the heart of the direct workflow. By default, the template generates a single task with the same name
as the project. The environment and configuration files are stored here, as well as placeholder, RL library dependent ``agents``.
Critically, note the contents of the ``__init__.py``! Specifically, the ``gym.register`` function needs to be called at least once
before an environment and task can be used with the Isaac Lab ``train.py`` and ``play.py`` scripts.
This function should be included in one of the module ``__init__.py`` files so it is called at installation. The path to
this init file is what defines the entry point for the task!
For the template, ``gym.register`` is called within ``isaac_lab_tutorial/source/isaac_lab_tutorial/isaac_lab_tutorial/tasks/direct/isaac_lab_tutorial/__init__.py``.
The repeated name is a consequence of needing default names for the template, but now we can see the structure of the project.
**Project**/source/**Extension**/**Module**/tasks/direct/**Task**/__init__.py
.. _walkthrough_technical_env_design:
Environment Design
====================
Armed with our understanding of the project and its structure, we are ready to start modifying the code to suit our Jetbot training needs.
Our template is set up for the **direct** workflow, which means the environment class will manage all of these details
centrally. We will need to write the code that will...
#. Define the robot
#. Define the training simulation and manage cloning
#. Apply the actions from the agent to the robot
#. Calculate and return the rewards and observations
#. Manage resetting and terminal states
As a first step, our goal will be to get the environment training pipeline to load and run. We will use a dummy reward signal
for the purposes of this part of the walkthrough. You can find the code for these modifications `here <https://github.com/isaac-sim/IsaacLabTutorial/tree/jetbot-intro-1-1>`_!
Define the Robot
------------------
As our project grows, we may have many robots that we want to train. With malice aforethought we will add a new ``module`` to our
tutorial ``extension`` named ``robots`` where we will keep the definitions for robots as individual python scripts. Navigate
to ``isaac_lab_tutorial/source/isaac_lab_tutorial/isaac_lab_tutorial`` and create a new folder called ``robots``. Within this folder
create two files: ``__init__.py`` and ``jetbot.py``. The ``__init__.py`` file marks this directory as a python module and we will
be able to import the contents of ``jetbot.py`` in the usual way.
The contents of ``jetbot.py`` is fairly minimal
.. code-block:: python
import isaaclab.sim as sim_utils
from isaaclab.assets import ArticulationCfg
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
JETBOT_CONFIG = ArticulationCfg(
spawn=sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Jetbot/jetbot.usd"),
actuators={"wheel_acts": ImplicitActuatorCfg(joint_names_expr=[".*"], damping=None, stiffness=None)},
)
The only purpose of this file is to define a unique scope in which to save our configurations. The details of robot configurations
can be explored in :ref:`this tutorial <tutorial-add-new-robot>` but most noteworthy for this walkthrough is the ``usd_path`` for the ``spawn``
argument of this ``ArticulationCfg``. The Jetbot asset is available to the public via a hosted nucleus server, and that path is defined by
``ISAAC_NUCLEUS_DIR``, however any path to a USD file is valid, including local ones!
Environment Configuration
---------------------------
Navigate to the environment configuration, ``isaac_lab_tutorial/source/isaac_lab_tutorial/isaac_lab_tutorial/tasks/direct/isaac_lab_tutorial/isaac_lab_tutorial_env_cfg.py``, and
replace its contents with the following
.. code-block:: python
from isaac_lab_tutorial.robots.jetbot import JETBOT_CONFIG
from isaaclab.assets import ArticulationCfg
from isaaclab.envs import DirectRLEnvCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg
from isaaclab.utils import configclass
@configclass
class IsaacLabTutorialEnvCfg(DirectRLEnvCfg):
# env
decimation = 2
episode_length_s = 5.0
# - spaces definition
action_space = 2
observation_space = 3
state_space = 0
# simulation
sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation)
# robot(s)
robot_cfg: ArticulationCfg = JETBOT_CONFIG.replace(prim_path="/World/envs/env_.*/Robot")
# scene
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=100, env_spacing=4.0, replicate_physics=True)
dof_names = ["left_wheel_joint", "right_wheel_joint"]
Here we have, effectively, the same environment configuration as before, but with the Jetbot instead of the cartpole. The
parameters ``decimation``, ``episode_length_s``, ``action_space``, ``observation_space``, and ``state_space`` are members of
the base class, ``DirectRLEnvCfg``, and must be defined for every ``DirectRLEnv``. The space parameters are interpreted as vectors of
the given integer dimension, but they can also be defined as `gymnasium spaces <https://gymnasium.farama.org/api/spaces/>`_!
Notice the difference in the action and observation spaces. As the designers of the environment, we get to choose these. For the Jetbot, we want to
directly control the joints of the robot, of which only two are actuated (hence the action space of two). The observation space is *chosen* to be
3 because we are just going to feed the agent the linear velocity of the Jetbot, for now. We will change these later as we develop the environment. Our policy isn't going
to need an internal state maintained, so our state space is zero.
Attack of the clones
---------------------
With the config defined, it's time to fill in the details of the environment, starting with the initialization and setup.
Navigate to the environment definition, ``isaac_lab_tutorial/source/isaac_lab_tutorial/isaac_lab_tutorial/tasks/direct/isaac_lab_tutorial/isaac_lab_tutorial_env.py``, and
replace the contents of the ``__init__`` and ``_setup_scene`` methods with the following.
.. code-block:: python
class IsaacLabTutorialEnv(DirectRLEnv):
cfg: IsaacLabTutorialEnvCfg
def __init__(self, cfg: IsaacLabTutorialEnvCfg, render_mode: str | None = None, **kwargs):
super().__init__(cfg, render_mode, **kwargs)
self.dof_idx, _ = self.robot.find_joints(self.cfg.dof_names)
def _setup_scene(self):
self.robot = Articulation(self.cfg.robot_cfg)
# add ground plane
spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg())
# clone and replicate
self.scene.clone_environments(copy_from_source=False)
# add articulation to scene
self.scene.articulations["robot"] = self.robot
# add lights
light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
light_cfg.func("/World/Light", light_cfg)
Notice that the ``_setup_scene`` method doesn't change and the ``_init__`` method is simply grabbing the joint indices from the robot (remember, setup is called in super).
The next thing our environment needs is the definitions for how to handle actions, observations, and rewards. First, replace the contents of ``_pre_physics_step`` and
``_apply_action`` with the following.
.. code-block:: python
def _pre_physics_step(self, actions: torch.Tensor) -> None:
self.actions = actions.clone()
def _apply_action(self) -> None:
self.robot.set_joint_velocity_target(self.actions, joint_ids=self.dof_idx)
Here the act of applying actions to the robot in the environment is broken into two steps: ``_pre_physics_step`` and ``_apply_action``. The physics
simulation is decimated with respect to querying the policy for actions, meaning that multiple physics steps may occur per action taken by the policy.
The ``_pre_physics_step`` method is called just before this simulation step takes place and lets us detach the process of getting data from the
policy being trained and applying updates to the physics simulation. The ``_apply_action`` method is where those actions are actually applied to the robots
on the stage, after which the simulation is actually stepped forward in time.
Next is the observations and rewards, which is just going to depend on the linear velocity of the Jetbot in the body frame of the robot. Replace the contents of ``_get_observations``
and ``_get_rewards`` with the following.
.. code-block:: python
def _get_observations(self) -> dict:
self.velocity = self.robot.data.root_com_lin_vel_b
observations = {"policy": self.velocity}
return observations
def _get_rewards(self) -> torch.Tensor:
total_reward = torch.linalg.norm(self.velocity, dim=-1, keepdim=True)
return total_reward
The robot exists as an Articulation object within the Isaac Lab API. That object carries a data class, the ``ArticulationData``, which contains all the data for **specific** robots on the stage.
When we talk about a scene entity like the robot, we can either be talking about the robot broadly, as an entity that exists in every scene, or we can be describing a specific, singular clone
of the robot on the stage. The ``ArticulationData`` contains the data for those individual clones. This includes things like various kinematic vectors (like ``root_com_lin_vel_b``) and reference
vectors (like ``robot.data.FORWARD_VEC_B``).
Notice how in the ``_apply_action`` method, we are calling a method of ``self.robot`` which is a method of ``Articulation``. The actions being applied are in the form of a 2D tensor
of shape ``[num_envs, num_actions]``. We are applying actions to **all** robots on the stage at once! Here, when we need to get the observations, we need the body frame velocity for all robots on the
stage, and so access ``self.robot.data`` to get that information. The ``root_com_lin_vel_b`` is a property of the ``ArticulationData`` that handles the conversion of the center-of-mass linear velocity from the world frame
to the body frame for us. Finally, Isaac Lab expects the observations to be returned as a dictionary, with ``policy`` defining those observations for the policy model and ``critic`` defining those observations for
the critic model (in the case of asymmetric actor critic training). Since we are not doing asymmetric actor critic, we only need to define ``policy``.
The rewards are more straightforward. For each clone of the scene, we need to compute a reward value and return it as a tensor of shape ``[num_envs, 1]``. As a place holder, we will make the reward the
magnitude of the linear velocity of the Jetbot in the body frame. With this reward and observation space, the agent should learn to drive the Jetbot forward or backward, with the direction determined at random
shortly after training starts.
Finally, we can write the parts of the environment to handle termination and resetting. Replace the contents of ``_get_dones`` and ``_reset_idx`` with the following.
.. code-block:: python
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
time_out = self.episode_length_buf >= self.max_episode_length - 1
return False, time_out
def _reset_idx(self, env_ids: Sequence[int] | None):
if env_ids is None:
env_ids = self.robot._ALL_INDICES
super()._reset_idx(env_ids)
default_root_state = self.robot.data.default_root_state[env_ids]
default_root_state[:, :3] += self.scene.env_origins[env_ids]
self.robot.write_root_state_to_sim(default_root_state, env_ids)
Like the actions, termination and resetting are handled in two parts. First is the ``_get_dones`` method, the goal of which is simply to mark which environments need to be reset and why.
Traditionally in reinforcement learning, an "episode" ends in one of two ways: either the agent reaches a terminal state, or the episode reaches a maximum duration.
Isaac Lab is kind to us, because it manages all of this episode duration tracking behind the scenes. The configuration parameter ``episode_length_s`` defines this maximum episode length in
seconds and the parameters ``episode_length_buff`` and ``max_episode_length`` contain the number of steps taken by individual scenes (allowing for asynchronous running of the environment) and the
maximum length of the episode as converted from ``episode_length_s``. The boolean operation computing ``time_out`` just compares the current buffer size to the max and returns true if it's greater, thus
indicating which scenes are at the episode length limit. Since our current environment is a dummy, we don't define terminal states and so just return ``False`` for the first tensor (this gets projected automatically
to the correct shape through the power of pytorch).
Finally, the ``_reset_idx`` method accepts a tensor of booleans indicating which scenes need to be reset, and resets them. Notice that this is the only other method of ``DirectRLEnv`` that directly calls
``super``, which is done so here to manage the internal buffers related to episode length. For those environments indicated by ``env_ids`` we retrieve the root default state, and reset the robot to that state while
also offsetting the position of each robot according to the origin of the corresponding scene. This is a consequence of the cloning procedure, which starts with a single robot and a single default state defined in the world
frame. Don't forget this step for your own custom environments!
With these changes complete, you should see the Jetbot slowly learn to drive forward when you launch the task with the template ``train.py`` script.
.. figure:: ../../_static/setup/walkthrough_1_1_result.jpg
:align: center
:figwidth: 100%
:alt: The Jetbot invasion begins!
.. _walkthrough_training_jetbot_gt:
Training the Jetbot: Ground Truth
======================================
With the environment defined, we can now start modifying our observations and rewards in order to train a policy
to act as a controller for the Jetbot. As a user, we would like to be able to specify the desired direction for the Jetbot to drive,
and have the wheels turn such that the robot drives in that specified direction as fast as possible. How do we achieve this with
Reinforcement Learning (RL)? If you want to cut to the end and checkout the result of this stage of the walk through, checkout
`this branch of the tutorial repository <https://github.com/isaac-sim/IsaacLabTutorial/tree/jetbot-intro-1-2>`_!
Expanding the Environment
--------------------------
The very first thing we need to do is create the logic for setting commands for each Jetbot on the stage. Each command will be a unit vector, and
we need one for every clone of the robot on the stage, which means a tensor of shape ``[num_envs, 3]``. Even though the Jetbot only navigates in the
2D plane, by working with 3D vectors we get to make use of all the math utilities provided by Isaac Lab.
It would also be a good idea to setup visualizations, so we can more easily tell what the policy is doing during training and inference.
In this case, we will define two arrow ``VisualizationMarkers``: one to represent the "forward" direction of the robot, and one to
represent the command direction. When the policy is fully trained, these arrows should be aligned! Having these visualizations in place
early helps us avoid "silent bugs": issues in the code that do not cause it to crash.
To begin, we need to define the marker config and then instantiate the markers with that config. Add the following to the global scope of ``isaac_lab_tutorial_env.py``
.. code-block:: python
from isaaclab.markers import VisualizationMarkers, VisualizationMarkersCfg
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
import isaaclab.utils.math as math_utils
def define_markers() -> VisualizationMarkers:
"""Define markers with various different shapes."""
marker_cfg = VisualizationMarkersCfg(
prim_path="/Visuals/myMarkers",
markers={
"forward": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd",
scale=(0.25, 0.25, 0.5),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 1.0)),
),
"command": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd",
scale=(0.25, 0.25, 0.5),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
),
},
)
return VisualizationMarkers(cfg=marker_cfg)
The ``VisualizationMarkersCfg`` defines USD prims to serve as the "marker". Any prim will do, but generally you want to keep markers as simple as possible because the cloning of markers occurs at runtime on every time step.
This is because the purpose of these markers is for *debug visualization only* and not to be a part of the simulation: the user has full control over how many markers to draw when and where.
NVIDIA provides several simple meshes on our public nucleus server, located at ``ISAAC_NUCLEUS_DIR``, and for obvious reasons we choose to use ``arrow_x.usd``.
For a more detailed example of using ``VisualizationMarkers`` checkout the ``markers.py`` demo!
.. dropdown:: Code for the markers.py demo
:icon: code
.. literalinclude:: ../../../../scripts/demos/markers.py
:language: python
:linenos:
Next, we need to expand the initialization and setup steps to construct the data we need for tracking the commands as well as the marker positions and rotations. Replace the contents of
``_setup_scene`` with the following
.. code-block:: python
def _setup_scene(self):
self.robot = Articulation(self.cfg.robot_cfg)
# add ground plane
spawn_ground_plane(prim_path="/World/ground", cfg=GroundPlaneCfg())
# clone and replicate
self.scene.clone_environments(copy_from_source=False)
# add articulation to scene
self.scene.articulations["robot"] = self.robot
# add lights
light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
light_cfg.func("/World/Light", light_cfg)
self.visualization_markers = define_markers()
# setting aside useful variables for later
self.up_dir = torch.tensor([0.0, 0.0, 1.0]).cuda()
self.yaws = torch.zeros((self.cfg.scene.num_envs, 1)).cuda()
self.commands = torch.randn((self.cfg.scene.num_envs, 3)).cuda()
self.commands[:,-1] = 0.0
self.commands = self.commands/torch.linalg.norm(self.commands, dim=1, keepdim=True)
# offsets to account for atan range and keep things on [-pi, pi]
ratio = self.commands[:,1]/(self.commands[:,0]+1E-8)
gzero = torch.where(self.commands > 0, True, False)
lzero = torch.where(self.commands < 0, True, False)
plus = lzero[:,0]*gzero[:,1]
minus = lzero[:,0]*lzero[:,1]
offsets = torch.pi*plus - torch.pi*minus
self.yaws = torch.atan(ratio).reshape(-1,1) + offsets.reshape(-1,1)
self.marker_locations = torch.zeros((self.cfg.scene.num_envs, 3)).cuda()
self.marker_offset = torch.zeros((self.cfg.scene.num_envs, 3)).cuda()
self.marker_offset[:,-1] = 0.5
self.forward_marker_orientations = torch.zeros((self.cfg.scene.num_envs, 4)).cuda()
self.command_marker_orientations = torch.zeros((self.cfg.scene.num_envs, 4)).cuda()
Most of this is setting up the book keeping for the commands and markers, but the command initialization and the yaw calculations are worth diving into. The commands
are sampled from a multivariate normal distribution via ``torch.randn`` with the z component fixed to zero and then normalized to unit length. In order to point our
command markers along these vectors, we need to rotate the base arrow mesh appropriately. This means we need to define a `quaternion <https://en.wikipedia.org/wiki/Quaternion>`_` that will rotate the arrow
prim about the z axis by some angle defined by the command. By convention, rotations about the z axis are called a "yaw" rotation (akin to roll and pitch).
Luckily for us, Isaac Lab provides a utility to generate a quaternion from an axis of rotation and an angle: :func:`isaaclab.utils.math.quat_from_axis_angle`, so the only
tricky part now is determining that angle.
.. figure:: ../../_static/setup/walkthrough_training_vectors.svg
:align: center
:figwidth: 100%
:alt: Useful vector definitions for training
The yaw is defined about the z axis, with a yaw of 0 aligning with the x axis and positive angles opening counterclockwise. The x and y components of the command vector
define the tangent of this angle, and so we need the *arctangent* of that ratio to get the yaw.
Now, consider two commands: Command A is in quadrant 2 at (-x, y), while command B is in quadrant 4 at (x, -y). The ratio of the
y component to the x component is identical for both A and B. If we do not account for this, then some of our command arrows will be
pointing in the opposite direction of the command! Essentially, our commands are defined on ``[-pi, pi]`` but ``arctangent`` is
only defined on ``[-pi/2, pi/2]``.
To remedy this, we add or subtract ``pi`` from the yaw depending on the quadrant of the command.
.. code-block:: python
ratio = self.commands[:,1]/(self.commands[:,0]+1E-8) #in case the x component is zero
gzero = torch.where(self.commands > 0, True, False)
lzero = torch.where(self.commands < 0, True, False)
plus = lzero[:,0]*gzero[:,1]
minus = lzero[:,0]*lzero[:,1]
offsets = torch.pi*plus - torch.pi*minus
self.yaws = torch.atan(ratio).reshape(-1,1) + offsets.reshape(-1,1)
Boolean expressions involving tensors can have ambiguous definitions and pytorch will throw errors regarding this. Pytorch provides
various methods to make the definitions explicit. The method ``torch.where`` produces a tensor with the same shape as the input
with each element of the output is determined by the evaluation of that expression on only that element. A reliable way to handle
boolean operations with tensors is to simply produce boolean indexing tensors and then represent the operation algebraically, with ``AND``
as multiplication and ``OR`` as addition, which is what we do above. This is equivalent to the pseudocode:
.. code-block:: python
yaws = torch.atan(ratio)
yaws[commands[:,0] < 0 and commands[:,1] > 0] += torch.pi
yaws[commands[:,0] < 0 and commands[:,1] < 0] -= torch.pi
Next we have the method for actually visualizing the markers. Remember, these markers aren't scene entities! We need to "draw" them whenever we
want to see them.
.. code-block:: python
def _visualize_markers(self):
# get marker locations and orientations
self.marker_locations = self.robot.data.root_pos_w
self.forward_marker_orientations = self.robot.data.root_quat_w
self.command_marker_orientations = math_utils.quat_from_angle_axis(self.yaws, self.up_dir).squeeze()
# offset markers so they are above the jetbot
loc = self.marker_locations + self.marker_offset
loc = torch.vstack((loc, loc))
rots = torch.vstack((self.forward_marker_orientations, self.command_marker_orientations))
# render the markers
all_envs = torch.arange(self.cfg.scene.num_envs)
indices = torch.hstack((torch.zeros_like(all_envs), torch.ones_like(all_envs)))
self.visualization_markers.visualize(loc, rots, marker_indices=indices)
The ``visualize`` method of ``VisualizationMarkers`` is like this "draw" function. It accepts tensors for the spatial
transformations of the markers, and a ``marker_indices`` tensor to specify which marker prototype to use for each marker. So
long as the first dimension of all of these tensors match, this function will draw those markers with the specified transformations.
This is why we stack the locations, rotations, and indices.
Now we just need to call ``_visualize_markers`` on the pre physics step to make the arrows visible. Replace ``_pre_physics_step`` with the following
.. code-block:: python
def _pre_physics_step(self, actions: torch.Tensor) -> None:
self.actions = actions.clone()
self._visualize_markers()
The last major modification before we dig into the RL training is to update the ``_reset_idx`` method to account for the commands and markers. Whenever we reset an environment,
we need to generate a new command and reset the markers. The logic for this is already covered above. Replace the contents of ``_reset_idx`` with the following:
.. code-block:: python
def _reset_idx(self, env_ids: Sequence[int] | None):
if env_ids is None:
env_ids = self.robot._ALL_INDICES
super()._reset_idx(env_ids)
# pick new commands for reset envs
self.commands[env_ids] = torch.randn((len(env_ids), 3)).cuda()
self.commands[env_ids,-1] = 0.0
self.commands[env_ids] = self.commands[env_ids]/torch.linalg.norm(self.commands[env_ids], dim=1, keepdim=True)
# recalculate the orientations for the command markers with the new commands
ratio = self.commands[env_ids][:,1]/(self.commands[env_ids][:,0]+1E-8)
gzero = torch.where(self.commands[env_ids] > 0, True, False)
lzero = torch.where(self.commands[env_ids]< 0, True, False)
plus = lzero[:,0]*gzero[:,1]
minus = lzero[:,0]*lzero[:,1]
offsets = torch.pi*plus - torch.pi*minus
self.yaws[env_ids] = torch.atan(ratio).reshape(-1,1) + offsets.reshape(-1,1)
# set the root state for the reset envs
default_root_state = self.robot.data.default_root_state[env_ids]
default_root_state[:, :3] += self.scene.env_origins[env_ids]
self.robot.write_root_state_to_sim(default_root_state, env_ids)
self._visualize_markers()
And that's it! We now generate commands and can visualize it the heading of the Jetbot. We are ready to start tinkering with the observations and rewards.
.. figure:: ../../_static/setup/walkthrough_1_2_arrows.jpg
:align: center
:figwidth: 100%
:alt: Visualization of the command markers
.. _walkthrough_training_jetbot_reward_exploration:
Exploring the RL problem
=========================
The command to the Jetbot is a unit vector in specifying the desired drive direction and we must make the agent aware of this somehow
so it can adjust its actions accordingly. There are many possible ways to do this, with the "zeroth order" approach to simply change the observation space to include
this command. To start, **edit the ``IsaacLabTutorialEnvCfg`` to set the observation space to 9**: the world velocity vector contains the linear and angular velocities
of the robot, which is 6 dimensions and if we append the command to this vector, that's 9 dimensions for the observation space in total.
Next, we just need to do that appending when we get the observations. We also need to calculate our forward vectors for later use. The forward vector for the Jetbot is
the x axis, so we apply the ``root_link_quat_w`` to ``[1,0,0]`` to get the forward vector in the world frame. Replace the ``_get_observations`` method with the following:
.. code-block:: python
def _get_observations(self) -> dict:
self.velocity = self.robot.data.root_com_vel_w
self.forwards = math_utils.quat_apply(self.robot.data.root_link_quat_w, self.robot.data.FORWARD_VEC_B)
obs = torch.hstack((self.velocity, self.commands))
observations = {"policy": obs}
return observations
So now what should the reward be?
When the robot is behaving as desired, it will be driving at full speed in the direction of the command. If we reward both
"driving forward" and "alignment to the command", then maximizing that combined signal should result in driving to the command... right?
Let's give it a try! Replace the ``_get_rewards`` method with the following:
.. code-block:: python
def _get_rewards(self) -> torch.Tensor:
forward_reward = self.robot.data.root_com_lin_vel_b[:,0].reshape(-1,1)
alignment_reward = torch.sum(self.forwards * self.commands, dim=-1, keepdim=True)
total_reward = forward_reward + alignment_reward
return total_reward
The ``forward_reward`` is the x component of the linear center of mass velocity of the robot in the body frame. We know that
the x direction is the forward direction for the asset, so this should be equivalent to inner product between the forward vector and
the linear velocity in the world frame. The alignment term is the inner product between the forward vector and the command vector: when they are
pointing in the same direction this term will be 1, but in the opposite direction it will be -1. We add them together to get the combined reward and
we can finally run training! Let's see what happens!
.. code-block:: bash
python scripts/skrl/train.py --task=Template-Isaac-Lab-Tutorial-Direct-v0
.. figure:: https://download.isaacsim.omniverse.nvidia.com/isaaclab/images/walkthrough_naive_webp.webp
:align: center
:figwidth: 100%
:alt: Naive results
Surely we can do better!
Reward and Observation Tuning
-------------------------------
When tuning an environment for training, as a rule of thumb, you want to keep the observation space as small as possible. This is to
reduce the number parameters in the model (the literal interpretation of Occam's razor) and thus improve training time. In this case we
need to somehow encode our alignment to the command and our forward speed. One way to do this is to exploit the dot and cross products
from linear algebra! Replace the contents of ``_get_observations`` with the following:
.. code-block:: python
def _get_observations(self) -> dict:
self.velocity = self.robot.data.root_com_vel_w
self.forwards = math_utils.quat_apply(self.robot.data.root_link_quat_w, self.robot.data.FORWARD_VEC_B)
dot = torch.sum(self.forwards * self.commands, dim=-1, keepdim=True)
cross = torch.cross(self.forwards, self.commands, dim=-1)[:,-1].reshape(-1,1)
forward_speed = self.robot.data.root_com_lin_vel_b[:,0].reshape(-1,1)
obs = torch.hstack((dot, cross, forward_speed))
observations = {"policy": obs}
return observations
The dot or inner product tells us how aligned two vectors are as a single scalar quantity. If they are very aligned and pointed in the same direction, then the inner
product will be large and positive, but if they are aligned and in opposite directions, it will be large and negative. If two vectors are
perpendicular, the inner product is zero. This means that the inner product between the forward vector and the command vector can tell us
how much we are facing towards or away from the command, but not which direction we need to turn to improve alignment.
The cross product also tells us how aligned two vectors are, but it expresses this relationship as a vector. The cross product between any
two vectors defines an axis that is perpendicular to the plane containing the two argument vectors, where the direction of the result vector along this axis is
determined by the chirality (dimension ordering, or handedness) of the coordinate system. In our case, we can exploit the fact that we are operating in 2D to only
examine the z component of the result of :math:`\vec{forward} \times \vec{command}`. This component will be zero if the vectors are colinear, positive if the
command vector is to the left of forward, and negative if it's to the right.
Finally, the x component of the center of mass linear velocity tells us our forward speed, with positive being forward and negative being backwards. We stack these together
"horizontally" (along dim 1) to generate the observations for each Jetbot. This alone improves performance!
.. figure:: https://download.isaacsim.omniverse.nvidia.com/isaaclab/images/walkthrough_improved_webp.webp
:align: center
:figwidth: 100%
:alt: Improved results
It seems to qualitatively train better, and the Jetbots are somewhat inching forward... Surely we can do better still!
Another rule of thumb for training is to reduce and simplify the reward function as much as possible. Terms in the reward behave similarly to
the logical "OR" operation. In our case, we are rewarding driving forward and being aligned to the command by adding them together, so our agent
can be reward for driving forward OR being aligned to the command. To force the agent to learn to drive in the direction of the command, we should only
reward the agent driving forward AND being aligned. Logical AND suggests multiplication and therefore the following reward function:
.. code-block:: python
def _get_rewards(self) -> torch.Tensor:
forward_reward = self.robot.data.root_com_lin_vel_b[:,0].reshape(-1,1)
alignment_reward = torch.sum(self.forwards * self.commands, dim=-1, keepdim=True)
total_reward = forward_reward*alignment_reward
return total_reward
Now we will only get rewarded for driving forward if our alignment reward is non zero. Let's see what kind of result this produces!
.. figure:: https://download.isaacsim.omniverse.nvidia.com/isaaclab/images/walkthrough_tuned_webp.webp
:align: center
:figwidth: 100%
:alt: Tuned results
It definitely trains faster, but the Jetbots have learned to drive in reverse if the command is pointed behind them. This may be desirable in our
case, but it shows just how dependent the policy behavior is on the reward function. In this case, there are **degenerate solutions** to our
reward function: The reward is maximized for driving forward and aligned to the command, but if the Jetbot drives in reverse, then the forward
term is negative, and if its driving in reverse towards the command, then the alignment term is **also negative**, meaning hat the reward is positive!
When you design your own environments, you will run into degenerate solutions like this and a significant amount of reward engineering is devoted to
suppressing or supporting these behaviors by modifying the reward function.
Let's say, in our case, we don't want this behavior. In our case, the alignment term has a domain of ``[-1, 1]``, but we would much prefer it to be mapped
only to positive values. We don't want to *eliminate* the sign on the alignment term, rather, we would like large negative values to be near zero, so if we
are misaligned, we don't get rewarded. The exponential function accomplishes this!
.. code-block:: python
def _get_rewards(self) -> torch.Tensor:
forward_reward = self.robot.data.root_com_lin_vel_b[:,0].reshape(-1,1)
alignment_reward = torch.sum(self.forwards * self.commands, dim=-1, keepdim=True)
total_reward = forward_reward*torch.exp(alignment_reward)
return total_reward
Now when we train, the Jetbots will turn to always drive towards the command in the forward direction!
.. figure:: https://download.isaacsim.omniverse.nvidia.com/isaaclab/images/walkthrough_directed_webp.webp
:align: center
:figwidth: 100%
:alt: Directed results
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment