├── .env ├── .github └── workflows │ ├── publish.yml │ ├── test.yml │ └── update_stompy_s3.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── getting_started_colab_sim.ipynb ├── gpr_standing.kinfer ├── gpr_walking.kinfer ├── new.kinfer ├── randomization.kinfer ├── walking_policy.pt ├── zbot_walking.kinfer └── zbot_walking_armature_friction.kinfer ├── pyproject.toml ├── setup.cfg ├── setup.py ├── sim ├── __init__.py ├── algo │ ├── __init__.py │ ├── ppo │ │ ├── __init__.py │ │ ├── actor_critic.py │ │ ├── on_policy_runner.py │ │ ├── ppo.py │ │ └── rollout_storage.py │ └── vec_env.py ├── env.py ├── envs │ ├── __init__.py │ ├── base │ │ ├── __init__.py │ │ ├── base_config.py │ │ ├── base_task.py │ │ ├── legged_robot.py │ │ ├── legged_robot_config.py │ │ └── legged_robot_latency.py │ └── humanoids │ │ ├── __init__.py │ │ ├── dora_config.py │ │ ├── dora_env.py │ │ ├── g1_config.py │ │ ├── g1_env.py │ │ ├── gpr_config.py │ │ ├── gpr_env.py │ │ ├── gpr_headless_config.py │ │ ├── gpr_headless_env.py │ │ ├── gpr_headless_latency_config.py │ │ ├── gpr_headless_latency_env.py │ │ ├── gpr_latency_config.py │ │ ├── gpr_latency_env.py │ │ ├── gpr_vel_config.py │ │ ├── gpr_vel_env.py │ │ ├── h1_config.py │ │ ├── h1_env.py │ │ ├── xbot_config.py │ │ ├── xbot_env.py │ │ ├── zbot2_config.py │ │ └── zbot2_env.py ├── h5_logger.py ├── model_export.py ├── mujoco_eval.py ├── play.py ├── produce_sim_data.py ├── py.typed ├── ref.py ├── requirements-dev.txt ├── requirements.txt ├── resources │ ├── dora │ │ ├── __init__.py │ │ ├── joints.py │ │ ├── mjcf │ │ │ ├── dora2.xml │ │ │ └── dora2_terrain.xml │ │ └── robot_fixed.urdf │ ├── g1 │ │ ├── __init__.py │ │ ├── joints.py │ │ └── robot_fixed.urdf │ ├── gpr │ │ ├── README.md │ │ ├── __init__.py │ │ ├── joints.py │ │ ├── meshes │ │ │ ├── arm1_top.stl │ │ │ ├── arm1_top_2.stl │ │ │ ├── arm2_shell.stl │ │ │ ├── arm2_shell_2.stl │ │ │ ├── arm3_shell.stl │ │ │ ├── arm3_shell2.stl │ │ │ ├── body1-part.stl │ │ │ ├── foot1.stl │ │ │ ├── foot3.stl │ │ │ ├── hand_shell.stl │ │ │ ├── hand_shell_2.stl │ │ │ ├── leg0_shell.stl │ │ │ ├── leg0_shell_2.stl │ │ │ ├── leg1_shell.stl │ │ │ ├── leg1_shell3.stl │ │ │ ├── leg2_shell.stl │ │ │ ├── leg2_shell_2.stl │ │ │ ├── shoulder.stl │ │ │ └── shoulder_2.stl │ │ ├── robot_fixed.urdf │ │ ├── robot_fixed.xml │ │ ├── robot_fixed_old.urdf │ │ └── robot_fixed_terrain.xml │ ├── gpr_headless │ │ ├── README.md │ │ ├── __init__.py │ │ ├── joints.py │ │ ├── old_real │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── joints.py │ │ │ ├── old │ │ │ │ ├── robot_fixed.urdf │ │ │ │ ├── robot_fixed.xml │ │ │ │ ├── robot_fixed_old.urdf │ │ │ │ └── robot_fixed_terrain.xml │ │ │ ├── robot_fixed.urdf │ │ │ ├── robot_fixed.xml │ │ │ ├── robot_fixed_new.urdf │ │ │ └── robot_fixed_terrain.xml │ │ ├── robot.mjcf │ │ ├── robot.urdf │ │ └── robot_fixed.urdf │ ├── gpr_vel │ │ ├── README.md │ │ ├── __init__.py │ │ ├── headless │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── joints.py │ │ │ └── robot_fixed.urdf │ │ ├── joints.py │ │ ├── robot_fixed.urdf │ │ ├── robot_fixed.xml │ │ └── robot_fixed_terrain.xml │ ├── h1_2 │ │ ├── __init__.py │ │ ├── h1.xml │ │ ├── joints.py │ │ ├── robot_fixed.urdf │ │ └── scene.xml │ ├── xbot │ │ ├── __init__.py │ │ ├── joints.py │ │ ├── robot_fixed.urdf │ │ └── robot_fixed.xml │ ├── zbot2 │ │ ├── joints.py │ │ ├── meshes │ │ │ ├── 3215_1Flange.stl │ │ │ ├── 3215_1Flange_2.stl │ │ │ ├── 3215_BothFlange.stl │ │ │ ├── 3215_BothFlange_2.stl │ │ │ ├── 3215_BothFlange_3.stl │ │ │ ├── 3215_BothFlange_4.stl │ │ │ ├── 3215_BothFlange_5.stl │ │ │ ├── 3215_BothFlange_6.stl │ │ │ ├── FINGER_1.stl │ │ │ ├── FINGER_1_2.stl │ │ │ ├── FOOT.stl │ │ │ ├── FOOT_2.stl │ │ │ ├── L-ARM-MIRROR_1.stl │ │ │ ├── R-ARM-MIRROR-1.stl │ │ │ ├── U-HIP-L.stl │ │ │ ├── U-HIP-R.stl │ │ │ ├── Z-BOT2-MASTER-SHOULDER2.stl │ │ │ ├── Z-BOT2-MASTER-SHOULDER2_2.stl │ │ │ └── Z-BOT2_MASTER-BODY-SKELETON.stl │ │ ├── robot.urdf │ │ ├── robot_fixed.urdf │ │ └── robot_fixed.xml │ └── zeroth │ │ ├── __init__.py │ │ ├── joints.py │ │ ├── meshes │ │ ├── Part_76.stl │ │ ├── Robot_-_STS3250_v1.stl │ │ ├── Robot_-_STS3250_v1_2.stl │ │ ├── Rotor.stl │ │ ├── Rotor_2.stl │ │ ├── foot_bracket_for_5dof_leg_v9.stl │ │ ├── foot_bracket_for_5dof_leg_v9_2.stl │ │ ├── leg_top_bracket_v8_1.stl │ │ ├── leg_top_bracket_v8_1_2.stl │ │ ├── leg_top_bracket_v9.stl │ │ └── leg_top_bracket_v9_2.stl │ │ ├── robot.urdf │ │ └── robot_fixed.urdf ├── scripts │ ├── __init__.py │ ├── calibration_isaac.py │ ├── calibration_mujoco.py │ ├── create_fixed_torso.py │ ├── imu_data_comparison.py │ ├── install_isaac_dependencies.sh │ ├── print_joints.py │ ├── push_standing_tests.py │ ├── run_headless.sh │ ├── simulate_mjcf.py │ └── simulate_urdf.py ├── sim2sim.py ├── train.py └── utils │ ├── __init__.py │ ├── calculate_gait.py │ ├── helpers.py │ ├── logger.py │ ├── math.py │ ├── resources.py │ ├── task_registry.py │ └── terrain.py └── third_party └── expressive_humanoid ├── .gitignore ├── Dockerfile ├── LICENSE ├── download_assets.sh ├── fbx_importer_all_2.py ├── install_fbx.sh ├── install_fbx_bindings.sh ├── parse_cmu_mocap_all_2.py └── test_fbx.py /.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/.env -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | id-token: write 11 | 12 | concurrency: 13 | group: "publish" 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | publish: 18 | timeout-minutes: 10 19 | name: Build and publish 20 | 21 | # We don't need to run on all platforms since this package is 22 | # platform-agnostic. The output wheel is something like 23 | # "monotonic_attention--py3-none-any.whl". 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v3 29 | 30 | - name: Set up Python 31 | uses: actions/setup-python@v4 32 | with: 33 | python-version: "3.10" 34 | 35 | - name: Install dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install build wheel 39 | 40 | - name: Build package 41 | run: python -m build --sdist --wheel --outdir dist/ . 42 | 43 | - name: Publish package 44 | uses: pypa/gh-action-pypi-publish@release/v1 45 | # with: 46 | # user: __token__ 47 | # password: ${{ secrets.PYPI_API_TOKEN }} 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Python Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | types: 11 | - opened 12 | - reopened 13 | - synchronize 14 | - ready_for_review 15 | 16 | concurrency: 17 | group: tests-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | run-base-tests: 22 | timeout-minutes: 10 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Check out repository 26 | uses: actions/checkout@v3 27 | 28 | - name: Set up Python 29 | uses: actions/setup-python@v4 30 | with: 31 | python-version: "3.8.18" 32 | 33 | - name: Restore cache 34 | id: restore-cache 35 | uses: actions/cache/restore@v3 36 | with: 37 | path: | 38 | ${{ env.pythonLocation }} 39 | .mypy_cache/ 40 | key: python-requirements-${{ env.pythonLocation }}-${{ github.event.pull_request.base.sha || github.sha }} 41 | restore-keys: | 42 | python-requirements-${{ env.pythonLocation }} 43 | python-requirements- 44 | 45 | - name: Install package 46 | run: | 47 | pip install --upgrade --upgrade-strategy eager --extra-index-url https://download.pytorch.org/whl/cpu -e '.[dev]' 48 | 49 | - name: Run static checks 50 | run: | 51 | mkdir -p .mypy_cache 52 | make static-checks 53 | 54 | - name: Run unit tests 55 | run: | 56 | make test 57 | 58 | - name: Save cache 59 | uses: actions/cache/save@v3 60 | if: github.ref == 'refs/heads/master' 61 | with: 62 | path: | 63 | ${{ env.pythonLocation }} 64 | .mypy_cache/ 65 | key: ${{ steps.restore-cache.outputs.cache-primary-key }} 66 | -------------------------------------------------------------------------------- /.github/workflows/update_stompy_s3.yml: -------------------------------------------------------------------------------- 1 | name: Update Stompy S3 Model 2 | 3 | on: 4 | release: 5 | types: [created] 6 | schedule: 7 | - cron: "30 10 * * *" 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | id-token: write 13 | 14 | concurrency: 15 | group: "stompy-s3-model" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | publish: 20 | name: Update Stompy S3 Model 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 30 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v3 27 | 28 | - name: Set up Python 29 | uses: actions/setup-python@v4 30 | with: 31 | python-version: "3.11" 32 | 33 | - name: Restore cache 34 | id: restore-cache 35 | uses: actions/cache/restore@v3 36 | with: 37 | path: | 38 | ${{ env.pythonLocation }} 39 | .mypy_cache/ 40 | key: python-requirements-${{ env.pythonLocation }}-${{ github.event.pull_request.base.sha || github.sha }} 41 | restore-keys: | 42 | python-requirements-${{ env.pythonLocation }} 43 | python-requirements- 44 | 45 | - name: Install dependencies 46 | run: | 47 | pip install -e '.[dev]' 48 | 49 | - name: Configure AWS credentials 50 | uses: aws-actions/configure-aws-credentials@v1 51 | with: 52 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 53 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 54 | aws-region: us-east-1 55 | 56 | - name: Upload to S3 57 | env: 58 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 59 | run: | 60 | for file in stompy/*.tar.gz; do 61 | aws s3 cp "$file" s3://${AWS_S3_BUCKET}/$(basename "$file") 62 | done 63 | for file in stompy_arm/*.tar.gz; do 64 | aws s3 cp "$file" s3://${AWS_S3_BUCKET}/arm_$(basename "$file") 65 | done 66 | 67 | - name: Save cache 68 | uses: actions/cache/save@v3 69 | if: github.ref == 'refs/heads/master' 70 | with: 71 | path: | 72 | ${{ env.pythonLocation }} 73 | .mypy_cache/ 74 | key: ${{ steps.restore-cache.outputs.cache-primary-key }} 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | # Robot Parts 3 | *.stl 4 | *.dae 5 | # Local files 6 | .env 7 | *.DS_Store 8 | # Python 9 | *.py[oc] 10 | __pycache__/ 11 | *.egg-info 12 | .eggs/ 13 | .mypy_cache/* 14 | .pyre/ 15 | .pytest_cache/ 16 | .ruff_cache/ 17 | .dmypy.json 18 | # Databases 19 | *.db 20 | *meshes* 21 | logs/* 22 | # Build artifacts 23 | build/ 24 | dist/ 25 | *.so 26 | out*/ 27 | 28 | # Training artifacts 29 | wandb/ 30 | runs/ 31 | isaacgym/ 32 | *.h5 33 | 34 | # dev 35 | ref/ 36 | *.csv 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/isaacgym"] 2 | path = third_party/isaacgym 3 | url = ../../kscalelabs/isaacgym.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | To get started, check out the open [Issues](https://github.com/kscalelabs/sim/issues). 4 | We publish there the most pressing issues to contribute. Feel free to post a new one if you see 5 | an issue or you want to add an enhancement. 6 | 7 | > [!NOTE] 8 | > You should develop the backend using Python 3.10 or later. 9 | 10 | When creating a new pull request please add the issue number. 11 | 12 | 13 | ## Lint and formatting 14 | When you submit the PR we automatically run some checks using black, ruff and mypy. 15 | You can check the logic [here](https://github.com/kscalelabs/sim/blob/master/pyproject.toml). 16 | You can locally run commands below to test the formatting: 17 | ``` 18 | make format 19 | make static-checks 20 | ``` 21 | 22 | ## Adding a new robot 23 | Creating a new embodiment is quite straightforward. The best way is to copy an existing robot and modify it. 24 | The best starting point would be [stompymini](https://github.com/kscalelabs/sim/tree/master/sim/resources/stompymini). 25 | 1. Create a folder with a new robot [here](https://github.com/kscalelabs/sim/tree/master/sim/resources). 26 | 2. Add `joint.py` file setting up basic properties and joint configuration - see an [example](https://github.com/kscalelabs/sim/blob/master/sim/resources/stompymini/joints.py). 27 | 3. Update [height](https://github.com/kscalelabs/sim/blob/aa97ddfddcaadcac64d4f83d986548cc7fc451a6/sim/resources/stompymini/joints.py#L102) and default rotation of your humanoid. 28 | 4. Add a new embodiment configuration and environment [here](https://github.com/kscalelabs/sim/tree/master/sim/envs/humanoids). 29 | 5. Add a new embodiment to the [registry](https://github.com/kscalelabs/sim/blob/master/sim/envs/__init__.py). 30 | To run things make sure that the robot name in your config file is the same as the folder name. 31 | ``` 32 | - sim 33 | - resources 34 | - NEW_HUMANOID 35 | - joints.py 36 | - urdf 37 | - ... 38 | - envs 39 | - humanoids 40 | - NEW_HUMANOID_config.py 41 | - NEW_HUMANOID_env.py 42 | ``` 43 | and kick off the training: 44 | ``` 45 | python sim/train.py --env-id NEW_HUMANOID --num-envs 4 46 | ``` 47 | 48 | ## Making it stand 49 | 1. The best way to start is to make your new robot stand. In this case you want to comment out 50 | the rewards that are responsible for making the robot walk. See an example of a [reward config](https://github.com/kscalelabs/sim/blob/aa97ddfddcaadcac64d4f83d986548cc7fc451a6/sim/envs/humanoids/stompymini_config.py#L163). 51 | 2. If the robot flies away, inspect your joint limits. During training, we introduce a lot of noise to the default joint positions 52 | as well as masses. You might need either adapt the limits or the [noise level](https://github.com/kscalelabs/sim/blob/aa97ddfddcaadcac64d4f83d986548cc7fc451a6/sim/envs/humanoids/stompy_config.py#L213). 53 | 3. Isaac Sim often hits velocities nans when some joints are hitting their limits - you can change the [engine parameters](https://github.com/kscalelabs/sim/blob/aa97ddfddcaadcac64d4f83d986548cc7fc451a6/sim/envs/humanoids/stompy_config.py#L108) to fix this issue. 54 | 4. The revolute joints cannot have 0 velocity in the URDF definition - otherwise, engine will go nans as well. 55 | 5. Observe the reward for orientation and default joint position. The model should just farm these two rewards. 56 | 6. If the robot still struggles at keeping the standing pose, you can also change the urdf definition using the [script](https://github.com/kscalelabs/sim/blob/master/sim/scripts/create_fixed_torso.py) to fix the upper body or change joint limits. 57 | 58 | ## Making it walk 59 | We set up the logic so that your new robot should start to walk with basic configuration after some modifications. 60 | 1. To get things going it's best to start from the good standing pose, with knees slightly bent. 61 | See an example [Stompy's pose](https://github.com/kscalelabs/sim/blob/aa97ddfddcaadcac64d4f83d986548cc7fc451a6/sim/resources/stompymini/joints.py#L113). 62 | 2. The [gait reward](https://github.com/kscalelabs/sim/blob/aa97ddfddcaadcac64d4f83d986548cc7fc451a6/sim/envs/humanoids/stompy_config.py#L146) is the most crucial single reward. You have to adapt the hyperparameters to your robot design to get it to work. 63 | 3. If the robots tends to jump, use only one limb you will have to adapt the overall rewards schema to enforce proper behavior. 64 | 4. If the setup is correct, you should see positive results after first 200-300 epochs. However, the robust policy will requires 2000-3000 epochs. 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 KScale Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | define HELP_MESSAGE 4 | kscale-sim-library 5 | 6 | # Installing 7 | 8 | 1. Create a new Conda environment: `conda create --name kscale-sim-library python=3.8.19` 9 | 2. Activate the environment: `conda activate kscale-sim-library` 10 | 3. Install the package: `make install-dev` 11 | 12 | # Running Tests 13 | 14 | 1. Run autoformatting: `make format` 15 | 2. Run static checks: `make static-checks` 16 | 3. Run unit tests: `make test` 17 | 18 | endef 19 | export HELP_MESSAGE 20 | 21 | all: 22 | @echo "$$HELP_MESSAGE" 23 | .PHONY: all 24 | 25 | # ------------------------ # 26 | # Train # 27 | # ------------------------ # 28 | 29 | train-one-vis: 30 | @python -m sim.train --task gpr --num_envs 1 31 | 32 | train-many-vis: 33 | @python -m sim.train --task gpr --num_envs 16 34 | 35 | train: 36 | @python -m sim.train --task gpr --num_envs 4096 --headless 37 | 38 | play: 39 | @python -m sim.play --task gpr 40 | 41 | # ------------------------ # 42 | # Build # 43 | # ------------------------ # 44 | 45 | install: 46 | @pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu121 47 | @pip install --verbose -e . 48 | .PHONY: install 49 | 50 | install-dev: 51 | @pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu121 52 | @pip install --verbose -e '.[dev]' 53 | .PHONY: install 54 | 55 | install-third-party: 56 | @git submodule update --init --recursive 57 | @cd third_party/isaacgym/python/ && pip install --verbose -e . 58 | 59 | install-third-party-external: 60 | @conda install -y nvidia/label/cuda-12.0.0::cuda-nvrtc 61 | @cd ${ISAACGYM_PATH}/python/ && pip install --verbose -e . 62 | 63 | build-ext: 64 | @python setup.py build_ext --inplace 65 | .PHONY: build-ext 66 | 67 | clean: 68 | rm -rf build dist *.so **/*.so **/*.pyi **/*.pyc **/*.pyd **/*.pyo **/__pycache__ *.egg-info .eggs/ .ruff_cache/ 69 | .PHONY: clean 70 | 71 | # ------------------------ # 72 | # Static Checks # 73 | # ------------------------ # 74 | 75 | format: 76 | @isort --profile black sim 77 | @black sim 78 | @ruff format sim 79 | .PHONY: format 80 | 81 | format-cpp: 82 | @clang-format -i $(shell find . -name '*.cpp' -o -name '*.h') 83 | @cmake-format -i $(shell find . -name 'CMakeLists.txt' -o -name '*.cmake') 84 | .PHONY: format-cpp 85 | 86 | static-checks: 87 | @isort --profile black --check --diff sim 88 | @black --diff --check sim 89 | @ruff check sim 90 | @mypy --install-types --non-interactive sim 91 | .PHONY: lint 92 | 93 | # ------------------------ # 94 | # Unit tests # 95 | # ------------------------ # 96 | 97 | test: 98 | # python -m pytest 99 | @echo "Unit tests not implemented" 100 | .PHONY: test 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/kscalelabs/onshape/blob/main/LICENSE) 4 | [![Discord](https://img.shields.io/discord/1224056091017478166)](https://discord.gg/k5mSvCkYQh) 5 | [![Wiki](https://img.shields.io/badge/wiki-humanoids-black)](https://humanoids.wiki) 6 |
7 | [![python](https://img.shields.io/badge/-Python_3.8-blue?logo=python&logoColor=white)](https://github.com/pre-commit/pre-commit) 8 | [![black](https://img.shields.io/badge/Code%20Style-Black-black.svg?labelColor=gray)](https://black.readthedocs.io/en/stable/) 9 | [![ruff](https://img.shields.io/badge/Linter-Ruff-red.svg?labelColor=gray)](https://github.com/charliermarsh/ruff) 10 |
11 | [![Python Checks](https://github.com/kscalelabs/sim/actions/workflows/test.yml/badge.svg)](https://github.com/kscalelabs/sim/actions/workflows/test.yml) 12 | 13 |
14 | 15 | # K-Scale Sim Library 16 | 17 | This repository implements training for robotic control policies in Nvidia's Isaac simulator, using [Humanoid Gym](https://sites.google.com/view/humanoid-gym/). For more information, see the [documentation](https://docs.kscale.dev/simulation/isaac). 18 | -------------------------------------------------------------------------------- /examples/getting_started_colab_sim.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "sRtNitKlw1zw" 7 | }, 8 | "source": [ 9 | "This Notebook works on L4 and A100 GPUs" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": { 16 | "id": "iYqTHUm8hv1F" 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "from google.colab import drive\n", 21 | "\n", 22 | "drive.mount(\"/content/drive\", force_remount=True)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "id": "16UZ4d3Nm1PY" 30 | }, 31 | "outputs": [], 32 | "source": [ 33 | "%cd /content/drive/MyDrive\n", 34 | "!git clone https://github.com/kscalelabs/sim.git" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": { 41 | "id": "pQ7Ev2668RqY" 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "cd /content/drive/MyDrive/sim" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": { 52 | "id": "dmwa4ir7OfMU" 53 | }, 54 | "outputs": [], 55 | "source": [ 56 | "!apt-get update -y\n", 57 | "!apt-get install python3.8 python3.8-distutils\n", 58 | "!update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1\n", 59 | "!update-alternatives --config python3\n", 60 | "!apt-get install python3-pip\n", 61 | "!python3 -m pip install --upgrade pip --user" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": { 68 | "id": "TrLr6NG20IN4" 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "!sudo apt-get install libpython3.8-dev\n", 73 | "!pip install numpy" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": { 80 | "id": "HGAYjhc33_Ad" 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "!make install-dev" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": { 91 | "id": "xl-_w0pIwgZH" 92 | }, 93 | "outputs": [], 94 | "source": [ 95 | "# Manually download IsaacGym_Preview_4_Package.tar.gz from https://developer.nvidia.com/isaac-gym\n", 96 | "# And place it in your sim folder in google drive.\n", 97 | "# OR use this slower upload option below\n", 98 | "from google.colab import files\n", 99 | "\n", 100 | "uploaded = files.upload()" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": { 107 | "id": "H52XAPEPOZ0k" 108 | }, 109 | "outputs": [], 110 | "source": [ 111 | "!tar -xvf IsaacGym_Preview_4_Package.tar.gz\n", 112 | "%env ISAACGYM_PATH=`pwd`/isaacgym\n", 113 | "!export ISAACGYM_PATH=$(pwd)/isaacgym\n", 114 | "!make install-third-party-external" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 10, 120 | "metadata": { 121 | "id": "fVReUppMRDEm" 122 | }, 123 | "outputs": [], 124 | "source": [ 125 | "!wget https://media.kscale.dev/stompy.tar.gz && tar -xzvf stompy.tar.gz\n", 126 | "!python sim/scripts/create_fixed_torso.py" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": { 133 | "id": "j_7WABU14nHI" 134 | }, 135 | "outputs": [], 136 | "source": [ 137 | "%env MODEL_DIR=stompy" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": { 144 | "id": "xR7Wpx8aSJQX" 145 | }, 146 | "outputs": [], 147 | "source": [ 148 | "!python sim/train.py --task=stompymini --num_envs=4096 --headless" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "metadata": { 155 | "id": "Gzgj4q5vV0TY" 156 | }, 157 | "outputs": [], 158 | "source": [ 159 | "# Untested\n", 160 | "!python sim/play.py --task stompymini --sim_device cpu" 161 | ] 162 | } 163 | ], 164 | "metadata": { 165 | "accelerator": "GPU", 166 | "colab": { 167 | "gpuType": "L4", 168 | "provenance": [] 169 | }, 170 | "kernelspec": { 171 | "display_name": "Python 3", 172 | "name": "python3" 173 | }, 174 | "language_info": { 175 | "name": "python" 176 | } 177 | }, 178 | "nbformat": 4, 179 | "nbformat_minor": 0 180 | } 181 | -------------------------------------------------------------------------------- /examples/gpr_standing.kinfer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/gpr_standing.kinfer -------------------------------------------------------------------------------- /examples/gpr_walking.kinfer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/gpr_walking.kinfer -------------------------------------------------------------------------------- /examples/new.kinfer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/new.kinfer -------------------------------------------------------------------------------- /examples/randomization.kinfer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/randomization.kinfer -------------------------------------------------------------------------------- /examples/walking_policy.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/walking_policy.pt -------------------------------------------------------------------------------- /examples/zbot_walking.kinfer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/zbot_walking.kinfer -------------------------------------------------------------------------------- /examples/zbot_walking_armature_friction.kinfer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/examples/zbot_walking_armature_friction.kinfer -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | 3 | line-length = 120 4 | target-version = ["py38"] 5 | include = '\.pyi?$' 6 | 7 | [tool.pytest.ini_options] 8 | 9 | addopts = "-rx -rf -x -q --full-trace" 10 | testpaths = ["tests"] 11 | 12 | markers = [ 13 | "slow: Marks test as being slow", 14 | ] 15 | 16 | [tool.mypy] 17 | ignore_missing_imports = true 18 | pretty = true 19 | show_column_numbers = true 20 | show_error_context = true 21 | show_error_codes = true 22 | show_traceback = true 23 | disallow_untyped_defs = true 24 | strict_equality = true 25 | allow_redefinition = true 26 | 27 | warn_unused_ignores = true 28 | warn_redundant_casts = true 29 | 30 | incremental = true 31 | namespace_packages = false 32 | 33 | exclude = ["sim/envs/", "sim/algo/", "sim/utils/", "sim/sim2sim.py", 34 | "sim/scripts/create_mjcf.py", "sim/scripts/create_fixed_torso.py", 35 | "sim/sim2sim_old.py", "sim/play_old.py"] 36 | 37 | [[tool.mypy.overrides]] 38 | 39 | module = [ 40 | "isaacgym.*", 41 | "mujoco.*", 42 | "sim.envs.*", 43 | "sim.utils.*", 44 | ] 45 | 46 | ignore_errors = true 47 | 48 | [tool.isort] 49 | 50 | profile = "black" 51 | known_third_party = ["wandb"] 52 | 53 | [tool.ruff] 54 | 55 | line-length = 120 56 | target-version = "py38" 57 | 58 | exclude = ["sim/envs/", "sim/algo/", "sim/play.py", 59 | "sim/sim2sim.py","sim/utils", "sim/scripts/create_mjcf.py", 60 | "sim/play_old.py", "sim/sim2sim_old.py"] 61 | 62 | [tool.ruff.lint] 63 | 64 | select = ["ANN", "D", "E", "F", "I", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "W"] 65 | 66 | ignore = [ 67 | "ANN101", "ANN102", 68 | "D101", "D102", "D103", "D104", "D105", "D106", "D107", 69 | "N812", "N817", 70 | "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", 71 | "PLW0603", "PLW2901", 72 | ] 73 | 74 | 75 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 76 | 77 | [tool.ruff.lint.per-file-ignores] 78 | 79 | "__init__.py" = ["E402", "F401", "F403", "F811"] 80 | 81 | [tool.ruff.lint.isort] 82 | 83 | known-first-party = ["kscale-sim-library", "tests"] 84 | combine-as-imports = true 85 | 86 | [tool.ruff.lint.pydocstyle] 87 | 88 | convention = "google" 89 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [options] 2 | 3 | packages = find: 4 | 5 | [options.packages.find] 6 | 7 | exclude = 8 | tests -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="import-untyped" 2 | #!/usr/bin/env python 3 | """Setup script for the project.""" 4 | 5 | import re 6 | from typing import List 7 | 8 | from setuptools import setup 9 | 10 | with open("README.md", "r", encoding="utf-8") as f: 11 | long_description: str = f.read() 12 | 13 | 14 | with open("sim/requirements.txt", "r", encoding="utf-8") as f: 15 | requirements: List[str] = f.read().splitlines() 16 | 17 | 18 | with open("sim/requirements-dev.txt", "r", encoding="utf-8") as f: 19 | requirements_dev: List[str] = f.read().splitlines() 20 | 21 | 22 | with open("sim/__init__.py", "r", encoding="utf-8") as fh: 23 | version_re = re.search(r"^__version__ = \"([^\"]*)\"", fh.read(), re.MULTILINE) 24 | assert version_re is not None, "Could not find version in sim/__init__.py" 25 | version: str = version_re.group(1) 26 | 27 | 28 | setup( 29 | name="kscale-sim-library", 30 | version=version, 31 | description="Training models in simulation", 32 | author="Benjamin Bolte, Pawel Budzianowski, Allen Wu", 33 | url="https://github.com/kscalelabs/sim", 34 | long_description=long_description, 35 | long_description_content_type="text/markdown", 36 | python_requires=">=3.8, <3.9", 37 | install_requires=requirements, 38 | tests_require=requirements_dev, 39 | extras_require={"dev": requirements_dev}, 40 | ) 41 | -------------------------------------------------------------------------------- /sim/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | __version__ = "0.1.0" 4 | 5 | ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 6 | -------------------------------------------------------------------------------- /sim/algo/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | -------------------------------------------------------------------------------- /sim/algo/ppo/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | -------------------------------------------------------------------------------- /sim/algo/ppo/actor_critic.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | 32 | import torch 33 | import torch.nn as nn 34 | from torch.distributions import Normal 35 | 36 | 37 | class ActorCritic(nn.Module): 38 | def __init__( 39 | self, 40 | num_actor_obs, 41 | num_critic_obs, 42 | num_actions, 43 | actor_hidden_dims=[256, 256, 256], 44 | critic_hidden_dims=[256, 256, 256], 45 | init_noise_std=1.0, 46 | activation=nn.ELU(), 47 | **kwargs, 48 | ): 49 | if kwargs: 50 | print( 51 | "ActorCritic.__init__ got unexpected arguments, which will be ignored: " 52 | + str([key for key in kwargs.keys()]) 53 | ) 54 | super(ActorCritic, self).__init__() 55 | 56 | mlp_input_dim_a = num_actor_obs 57 | mlp_input_dim_c = num_critic_obs 58 | # Policy 59 | actor_layers = [] 60 | actor_layers.append(nn.Linear(mlp_input_dim_a, actor_hidden_dims[0])) 61 | actor_layers.append(activation) 62 | for l in range(len(actor_hidden_dims)): 63 | if l == len(actor_hidden_dims) - 1: 64 | actor_layers.append(nn.Linear(actor_hidden_dims[l], num_actions)) 65 | else: 66 | actor_layers.append(nn.Linear(actor_hidden_dims[l], actor_hidden_dims[l + 1])) 67 | actor_layers.append(activation) 68 | self.actor = nn.Sequential(*actor_layers) 69 | 70 | # Value function 71 | critic_layers = [] 72 | critic_layers.append(nn.Linear(mlp_input_dim_c, critic_hidden_dims[0])) 73 | critic_layers.append(activation) 74 | for l in range(len(critic_hidden_dims)): 75 | if l == len(critic_hidden_dims) - 1: 76 | critic_layers.append(nn.Linear(critic_hidden_dims[l], 1)) 77 | else: 78 | critic_layers.append(nn.Linear(critic_hidden_dims[l], critic_hidden_dims[l + 1])) 79 | critic_layers.append(activation) 80 | self.critic = nn.Sequential(*critic_layers) 81 | 82 | print(f"Actor MLP: {self.actor}") 83 | print(f"Critic MLP: {self.critic}") 84 | 85 | # Action noise 86 | self.std = nn.Parameter(init_noise_std * torch.ones(num_actions)) 87 | self.distribution = None 88 | # disable args validation for speedup 89 | Normal.set_default_validate_args = False 90 | 91 | @staticmethod 92 | # not used at the moment 93 | def init_weights(sequential, scales): 94 | [ 95 | torch.nn.init.orthogonal_(module.weight, gain=scales[idx]) 96 | for idx, module in enumerate(mod for mod in sequential if isinstance(mod, nn.Linear)) 97 | ] 98 | 99 | def reset(self, dones=None): 100 | pass 101 | 102 | def forward(self): 103 | raise NotImplementedError 104 | 105 | @property 106 | def action_mean(self): 107 | return self.distribution.mean 108 | 109 | @property 110 | def action_std(self): 111 | return self.distribution.stddev 112 | 113 | @property 114 | def entropy(self): 115 | return self.distribution.entropy().sum(dim=-1) 116 | 117 | def update_distribution(self, observations): 118 | mean = self.actor(observations) 119 | self.distribution = Normal(mean, mean * 0.0 + self.std) 120 | 121 | def act(self, observations, **kwargs): 122 | self.update_distribution(observations) 123 | return self.distribution.sample() 124 | 125 | def get_actions_log_prob(self, actions): 126 | return self.distribution.log_prob(actions).sum(dim=-1) 127 | 128 | def act_inference(self, observations): 129 | actions_mean = self.actor(observations) 130 | return actions_mean 131 | 132 | def evaluate(self, critic_observations, **kwargs): 133 | value = self.critic(critic_observations) 134 | return value 135 | -------------------------------------------------------------------------------- /sim/algo/vec_env.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | 32 | from abc import ABC, abstractmethod 33 | from typing import Tuple, Union 34 | 35 | import torch 36 | 37 | 38 | # minimal interface of the environment 39 | class VecEnv(ABC): 40 | num_envs: int 41 | num_obs: int 42 | num_privileged_obs: int 43 | num_actions: int 44 | max_episode_length: int 45 | privileged_obs_buf: torch.Tensor 46 | obs_buf: torch.Tensor 47 | rew_buf: torch.Tensor 48 | reset_buf: torch.Tensor 49 | episode_length_buf: torch.Tensor # current episode duration 50 | extras: dict 51 | device: torch.device 52 | 53 | @abstractmethod 54 | def step( 55 | self, actions: torch.Tensor 56 | ) -> Tuple[torch.Tensor, Union[torch.Tensor, None], torch.Tensor, torch.Tensor, dict]: 57 | pass 58 | 59 | @abstractmethod 60 | def reset(self, env_ids: Union[list, torch.Tensor]): 61 | pass 62 | 63 | @abstractmethod 64 | def get_observations(self) -> torch.Tensor: 65 | pass 66 | 67 | @abstractmethod 68 | def get_privileged_observations(self) -> Union[torch.Tensor, None]: 69 | pass 70 | -------------------------------------------------------------------------------- /sim/env.py: -------------------------------------------------------------------------------- 1 | """Defines common environment parameters.""" 2 | 3 | import os 4 | from pathlib import Path 5 | 6 | from dotenv import load_dotenv 7 | 8 | load_dotenv() 9 | 10 | 11 | def model_dir(robotname: str) -> Path: 12 | print(os.getcwd()) 13 | 14 | return Path(os.environ.get("MODEL_DIR", "sim/resources/" + robotname)) 15 | 16 | 17 | def run_dir() -> Path: 18 | return Path(os.environ.get("RUN_DIR", "runs")) 19 | 20 | 21 | def robot_urdf_path(robotname: str, legs_only: bool = False) -> Path: 22 | if legs_only: 23 | robot_path = model_dir(robotname) / "robot_fixed.urdf" 24 | else: 25 | robot_path = model_dir(robotname) / "robot_fixed.urdf" 26 | 27 | if not robot_path.exists(): 28 | raise FileNotFoundError(f"URDF file not found: {robot_path}") 29 | 30 | return robot_path.resolve() 31 | 32 | 33 | def robot_mjcf_path(robotname: str, legs_only: bool = False) -> Path: 34 | if legs_only: 35 | robot_path = model_dir(robotname) / "robot_fixed.xml" 36 | else: 37 | robot_path = model_dir(robotname) / "robot_fixed.xml" 38 | 39 | if not robot_path.exists(): 40 | raise FileNotFoundError(f"MJCF file not found: {robot_path}") 41 | 42 | return robot_path.resolve() 43 | -------------------------------------------------------------------------------- /sim/envs/__init__.py: -------------------------------------------------------------------------------- 1 | """Registers the tasks in the task registry. 2 | 3 | For other people who might be looking at this in the future - my preferred way 4 | of doing config management is to use dataclasses (see the `mlfab` or `xax` 5 | packages for examples of what I mean). This plays a lot better with type 6 | checkers and VSCode. I am just doing it this way to get something working 7 | quickly. 8 | """ 9 | 10 | # mypy: ignore-errors 11 | from sim.envs.humanoids.dora_config import DoraCfg, DoraCfgPPO 12 | from sim.envs.humanoids.dora_env import DoraFreeEnv 13 | from sim.envs.humanoids.g1_config import G1Cfg, G1CfgPPO 14 | from sim.envs.humanoids.g1_env import G1FreeEnv 15 | from sim.envs.humanoids.gpr_config import GprCfg, GprCfgPPO, GprStandingCfg 16 | from sim.envs.humanoids.gpr_env import GprFreeEnv 17 | from sim.envs.humanoids.gpr_headless_config import GprHeadlessCfg, GprHeadlessCfgPPO 18 | from sim.envs.humanoids.gpr_headless_env import GprHeadlessEnv 19 | from sim.envs.humanoids.gpr_headless_latency_config import ( 20 | GprHeadlessLatencyCfg, 21 | GprHeadlessLatencyCfgPPO, 22 | ) 23 | from sim.envs.humanoids.gpr_headless_latency_env import GprHeadlessLatencyEnv 24 | from sim.envs.humanoids.gpr_headless_pos_config import ( 25 | GprHeadlessPosCfg, 26 | GprHeadlessPosCfgPPO, 27 | GprHeadlessPosStandingCfg, 28 | ) 29 | from sim.envs.humanoids.gpr_headless_pos_env import GprHeadlessPosEnv 30 | from sim.envs.humanoids.gpr_latency_config import ( 31 | GprLatencyCfg, 32 | GprLatencyCfgPPO, 33 | GprLatencyStandingCfg, 34 | ) 35 | from sim.envs.humanoids.gpr_latency_env import GprLatencyEnv 36 | from sim.envs.humanoids.gpr_vel_config import GprVelCfg, GprVelCfgPPO 37 | from sim.envs.humanoids.gpr_vel_env import GprVelEnv 38 | from sim.envs.humanoids.h1_config import H1Cfg, H1CfgPPO 39 | from sim.envs.humanoids.h1_env import H1FreeEnv 40 | from sim.envs.humanoids.xbot_config import XBotCfg, XBotCfgPPO 41 | from sim.envs.humanoids.xbot_env import XBotLFreeEnv 42 | from sim.envs.humanoids.zbot2_config import ZBot2Cfg, ZBot2CfgPPO, ZBot2StandingCfg 43 | from sim.envs.humanoids.zbot2_env import ZBot2Env 44 | from sim.utils.task_registry import TaskRegistry # noqa: E402 45 | 46 | task_registry = TaskRegistry() 47 | task_registry.register("gpr", GprFreeEnv, GprCfg(), GprCfgPPO()) 48 | task_registry.register("gpr_standing", GprFreeEnv, GprStandingCfg(), GprCfgPPO()) 49 | task_registry.register("dora", DoraFreeEnv, DoraCfg(), DoraCfgPPO()) 50 | task_registry.register("h1", H1FreeEnv, H1Cfg(), H1CfgPPO()) 51 | task_registry.register("g1", G1FreeEnv, G1Cfg(), G1CfgPPO()) 52 | task_registry.register("XBotL_free", XBotLFreeEnv, XBotCfg(), XBotCfgPPO()) 53 | task_registry.register("zbot2", ZBot2Env, ZBot2Cfg(), ZBot2CfgPPO()) 54 | task_registry.register("zbot2_standing", ZBot2Env, ZBot2StandingCfg(), ZBot2CfgPPO()) 55 | task_registry.register("gpr_headless", GprHeadlessEnv, GprHeadlessCfg(), GprHeadlessCfgPPO()) 56 | task_registry.register("gpr_latency", GprLatencyEnv, GprLatencyCfg(), GprLatencyCfgPPO()) 57 | task_registry.register("gpr_latency_standing", GprLatencyEnv, GprLatencyStandingCfg(), GprLatencyCfgPPO()) 58 | task_registry.register( 59 | "gpr_headless_latency", GprHeadlessLatencyEnv, GprHeadlessLatencyCfg(), GprHeadlessLatencyCfgPPO() 60 | ) 61 | task_registry.register("gpr_vel", GprVelEnv, GprVelCfg(), GprVelCfgPPO()) 62 | task_registry.register("gpr_headless_pos", GprHeadlessPosEnv, GprHeadlessPosCfg(), GprHeadlessPosCfgPPO()) 63 | task_registry.register( 64 | "gpr_headless_pos_standing", GprHeadlessPosEnv, GprHeadlessPosStandingCfg(), GprHeadlessPosCfgPPO() 65 | ) 66 | -------------------------------------------------------------------------------- /sim/envs/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/envs/base/__init__.py -------------------------------------------------------------------------------- /sim/envs/base/base_config.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | 32 | import inspect 33 | 34 | 35 | class BaseConfig: 36 | def __init__(self) -> None: 37 | """Initializes all member classes recursively. Ignores all namse starting with '__' (buit-in methods).""" 38 | self.init_member_classes(self) 39 | 40 | @staticmethod 41 | def init_member_classes(obj): 42 | # iterate over all attributes names 43 | for key in dir(obj): 44 | # disregard builtin attributes 45 | # if key.startswith("__"): 46 | if key == "__class__": 47 | continue 48 | # get the corresponding attribute object 49 | var = getattr(obj, key) 50 | # check if it the attribute is a class 51 | if inspect.isclass(var): 52 | # instantate the class 53 | i_var = var() 54 | # set the attribute to the instance instead of the type 55 | setattr(obj, key, i_var) 56 | # recursively init members of the attribute 57 | BaseConfig.init_member_classes(i_var) 58 | -------------------------------------------------------------------------------- /sim/envs/base/base_task.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | 32 | import sys 33 | 34 | from isaacgym import gymapi # isort: skip 35 | from isaacgym import gymutil # isort: skip 36 | import torch # isort: skip 37 | 38 | # Base class for RL tasks 39 | 40 | 41 | class BaseTask: 42 | def __init__(self, cfg, sim_params, physics_engine, sim_device, headless): 43 | self.gym = gymapi.acquire_gym() 44 | 45 | self.sim_params = sim_params 46 | self.physics_engine = physics_engine 47 | self.sim_device = sim_device 48 | sim_device_type, self.sim_device_id = gymutil.parse_device_str(self.sim_device) 49 | self.headless = headless 50 | 51 | # env device is GPU only if sim is on GPU and use_gpu_pipeline=True, otherwise returned tensors are copied to CPU by physX. 52 | if sim_device_type == "cuda" and sim_params.use_gpu_pipeline: 53 | self.device = self.sim_device 54 | else: 55 | self.device = "cpu" 56 | 57 | # graphics device for rendering, -1 for no rendering 58 | if self.headless: 59 | self.graphics_device_id = -1 60 | else: 61 | self.graphics_device_id = self.sim_device_id 62 | 63 | self.num_envs = cfg.env.num_envs 64 | self.num_obs = cfg.env.num_observations 65 | self.num_privileged_obs = cfg.env.num_privileged_obs 66 | self.num_actions = cfg.env.num_actions 67 | self.num_joints = cfg.env.num_joints 68 | 69 | # optimization flags for pytorch JIT 70 | torch._C._jit_set_profiling_mode(False) 71 | torch._C._jit_set_profiling_executor(False) 72 | 73 | # allocate buffers 74 | self.obs_buf = torch.zeros(self.num_envs, self.num_obs, device=self.device, dtype=torch.float) 75 | self.rew_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.float) 76 | # new reward buffers for exp rewrads 77 | self.neg_reward_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.float) 78 | self.pos_reward_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.float) 79 | 80 | self.reset_buf = torch.ones(self.num_envs, device=self.device, dtype=torch.long) 81 | self.episode_length_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.long) 82 | self.time_out_buf = torch.zeros(self.num_envs, device=self.device, dtype=torch.bool) 83 | if self.num_privileged_obs is not None: 84 | self.privileged_obs_buf = torch.zeros( 85 | self.num_envs, self.num_privileged_obs, device=self.device, dtype=torch.float 86 | ) 87 | else: 88 | self.privileged_obs_buf = None 89 | 90 | self.extras = {} 91 | 92 | # create envs, sim and viewer 93 | self.create_sim() 94 | self.gym.prepare_sim(self.sim) 95 | self.enable_viewer_sync = True 96 | self.viewer = None 97 | 98 | # if running with a viewer, set up keyboard shortcuts and camera 99 | if self.headless == False: 100 | # subscribe to keyboard shortcuts 101 | self.viewer = self.gym.create_viewer(self.sim, gymapi.CameraProperties()) 102 | self.gym.subscribe_viewer_keyboard_event(self.viewer, gymapi.KEY_ESCAPE, "QUIT") 103 | self.gym.subscribe_viewer_keyboard_event(self.viewer, gymapi.KEY_V, "toggle_viewer_sync") 104 | 105 | camera_properties = gymapi.CameraProperties() 106 | camera_properties.width = 720 107 | camera_properties.height = 480 108 | camera_handle = self.gym.create_camera_sensor(self.envs[0], camera_properties) 109 | self.camera_handle = camera_handle 110 | else: 111 | # pass 112 | camera_properties = gymapi.CameraProperties() 113 | camera_properties.width = 720 114 | camera_properties.height = 480 115 | camera_handle = self.gym.create_camera_sensor(self.envs[0], camera_properties) 116 | self.camera_handle = camera_handle 117 | 118 | def get_observations(self): 119 | return self.obs_buf 120 | 121 | def get_privileged_observations(self): 122 | return self.privileged_obs_buf 123 | 124 | def get_rma_observations(self): 125 | return self.rma_obs_buf 126 | 127 | def reset_idx(self, env_ids): 128 | """Reset selected robots""" 129 | raise NotImplementedError 130 | 131 | def reset(self): 132 | """Reset all robots""" 133 | self.reset_idx(torch.arange(self.num_envs, device=self.device)) 134 | obs, privileged_obs, _, _, _ = self.step( 135 | torch.zeros(self.num_envs, self.num_actions, device=self.device, requires_grad=False) 136 | ) 137 | return obs, privileged_obs 138 | 139 | def step(self, actions): 140 | raise NotImplementedError 141 | 142 | def render(self, sync_frame_time=True): 143 | if self.viewer: 144 | # check for window closed 145 | if self.gym.query_viewer_has_closed(self.viewer): 146 | sys.exit() 147 | 148 | # check for keyboard events 149 | for evt in self.gym.query_viewer_action_events(self.viewer): 150 | if evt.action == "QUIT" and evt.value > 0: 151 | sys.exit() 152 | elif evt.action == "toggle_viewer_sync" and evt.value > 0: 153 | self.enable_viewer_sync = not self.enable_viewer_sync 154 | 155 | # fetch results 156 | if self.device != "cpu": 157 | self.gym.fetch_results(self.sim, True) 158 | 159 | # step graphics 160 | if self.enable_viewer_sync: 161 | self.gym.step_graphics(self.sim) 162 | self.gym.draw_viewer(self.viewer, self.sim, True) 163 | if sync_frame_time: 164 | self.gym.sync_frame_time(self.sim) 165 | else: 166 | self.gym.poll_viewer_events(self.viewer) 167 | -------------------------------------------------------------------------------- /sim/envs/humanoids/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/envs/humanoids/__init__.py -------------------------------------------------------------------------------- /sim/envs/humanoids/g1_config.py: -------------------------------------------------------------------------------- 1 | """Defines the environment configuration for the walking task""" 2 | 3 | from sim.env import robot_urdf_path 4 | from sim.envs.base.legged_robot_config import ( # type: ignore 5 | LeggedRobotCfg, 6 | LeggedRobotCfgPPO, 7 | ) 8 | from sim.resources.g1.joints import Robot 9 | 10 | NUM_JOINTS = len(Robot.all_joints()) # 33 11 | 12 | 13 | class G1Cfg(LeggedRobotCfg): 14 | """Configuration class for the Legs humanoid robot.""" 15 | 16 | class env(LeggedRobotCfg.env): 17 | # change the observation dim 18 | 19 | frame_stack = 15 20 | c_frame_stack = 3 21 | num_single_obs = 11 + NUM_JOINTS * 3 22 | num_observations = int(frame_stack * num_single_obs) 23 | single_num_privileged_obs = 25 + NUM_JOINTS * 4 24 | num_privileged_obs = int(c_frame_stack * single_num_privileged_obs) 25 | num_actions = NUM_JOINTS 26 | num_envs = 4096 27 | episode_length_s = 24 # episode length in seconds 28 | use_ref_actions = False 29 | 30 | class safety(LeggedRobotCfg.safety): 31 | # safety factors 32 | pos_limit = 1.0 33 | vel_limit = 1.0 34 | torque_limit = 0.85 35 | 36 | class asset(LeggedRobotCfg.asset): 37 | name = "g1" 38 | file = str(robot_urdf_path(name)) 39 | 40 | foot_name = "ankle_roll" 41 | knee_name = "knee_link" 42 | 43 | termination_height = 0.55 44 | default_feet_height = 0.0 45 | 46 | penalize_contacts_on = [] 47 | self_collisions = 1 # 1 to disable, 0 to enable...bitwise filter 48 | flip_visual_attachments = False 49 | replace_cylinder_with_capsule = False 50 | fix_base_link = False 51 | 52 | class terrain(LeggedRobotCfg.terrain): 53 | mesh_type = "plane" 54 | # mesh_type = 'trimesh' 55 | curriculum = False 56 | # rough terrain only: 57 | measure_heights = False 58 | static_friction = 0.6 59 | dynamic_friction = 0.6 60 | terrain_length = 8.0 61 | terrain_width = 8.0 62 | num_rows = 10 # number of terrain rows (levels) 63 | num_cols = 10 # number of terrain cols (types) 64 | max_init_terrain_level = 10 # starting curriculum state 65 | # plane; obstacles; uniform; slope_up; slope_down, stair_up, stair_down 66 | terrain_proportions = [0.2, 0.2, 0.4, 0.1, 0.1, 0, 0] 67 | restitution = 0.0 68 | 69 | class noise: 70 | add_noise = True 71 | noise_level = 0.6 # scales other values 72 | 73 | class noise_scales: 74 | dof_pos = 0.05 75 | dof_vel = 0.5 76 | ang_vel = 0.1 77 | lin_vel = 0.05 78 | quat = 0.03 79 | height_measurements = 0.1 80 | 81 | class init_state(LeggedRobotCfg.init_state): 82 | pos = [0.0, 0.0, Robot.height] 83 | default_joint_angles = {k: 0.0 for k in Robot.all_joints()} 84 | 85 | default_positions = Robot.default_standing() 86 | for joint in default_positions: 87 | default_joint_angles[joint] = default_positions[joint] 88 | 89 | class control(LeggedRobotCfg.control): 90 | # PD Drive parameters: 91 | stiffness = Robot.stiffness() 92 | damping = Robot.damping() 93 | # action scale: target angle = actionScale * action + defaultAngle 94 | action_scale = 0.25 95 | # decimation: Number of control action updates @ sim DT per policy DT 96 | decimation = 4 # 100hz 97 | 98 | class sim(LeggedRobotCfg.sim): 99 | dt = 0.002 # 1000 Hz 100 | substeps = 1 # 2 101 | up_axis = 1 # 0 is y, 1 is z 102 | 103 | class physx(LeggedRobotCfg.sim.physx): 104 | num_threads = 12 105 | solver_type = 1 # 0: pgs, 1: tgs 106 | num_position_iterations = 4 107 | num_velocity_iterations = 1 108 | contact_offset = 0.01 # [m] 109 | rest_offset = 0.0 # [m] 110 | bounce_threshold_velocity = 0.1 # [m/s] 111 | max_depenetration_velocity = 1.0 112 | max_gpu_contact_pairs = 2**23 # 2**24 -> needed for 8000 envs and more 113 | default_buffer_size_multiplier = 5 114 | # 0: never, 1: last sub-step, 2: all sub-steps (default=2) 115 | contact_collection = 2 116 | 117 | class domain_rand(LeggedRobotCfg.domain_rand): 118 | randomize_friction = True 119 | friction_range = [0.1, 2.0] 120 | 121 | randomize_base_mass = True 122 | added_mass_range = [-1.0, 1.0] 123 | push_robots = True 124 | push_interval_s = 4 125 | max_push_vel_xy = 0.2 126 | max_push_ang_vel = 0.4 127 | # dynamic randomization 128 | action_delay = 0.5 129 | action_noise = 0.02 130 | 131 | class commands(LeggedRobotCfg.commands): 132 | # Vers: lin_vel_x, lin_vel_y, ang_vel_yaw, heading (in heading mode ang_vel_yaw is recomputed from heading error) 133 | num_commands = 4 134 | resampling_time = 8.0 # time before command are changed[s] 135 | heading_command = True # if true: compute ang vel command from heading error 136 | 137 | class ranges: 138 | lin_vel_x = [-0.3, 0.6] # min max [m/s] 139 | lin_vel_y = [-0.3, 0.3] # min max [m/s] 140 | ang_vel_yaw = [-0.3, 0.3] # min max [rad/s] 141 | heading = [-0.14, 0.14] 142 | 143 | class rewards: 144 | # quite important to keep it right 145 | base_height_target = 0.78 146 | min_dist = 0.2 147 | max_dist = 0.5 148 | # put some settings here for LLM parameter tuning 149 | target_joint_pos_scale = 0.17 # rad 150 | target_feet_height = 0.06 # m 151 | cycle_time = 0.64 # sec 152 | # if true negative total rewards are clipped at zero (avoids early termination problems) 153 | only_positive_rewards = True 154 | # tracking reward = exp(error*sigma) 155 | tracking_sigma = 5 156 | max_contact_force = 400 # forces above this value are penalized 157 | 158 | class scales: 159 | # reference motion tracking 160 | joint_pos = 1.6 161 | feet_clearance = 1.0 162 | feet_contact_number = 1.2 163 | # gait 164 | feet_air_time = 1.0 165 | foot_slip = -0.05 166 | feet_distance = 0.2 167 | knee_distance = 0.2 168 | # contact 169 | feet_contact_forces = -0.01 170 | # vel tracking 171 | tracking_lin_vel = 1.2 172 | tracking_ang_vel = 1.1 173 | vel_mismatch_exp = 0.5 # lin_z; ang x,y 174 | low_speed = 0.2 175 | track_vel_hard = 0.5 176 | 177 | # above this was removed 178 | # base pos 179 | default_joint_pos = 0.5 180 | orientation = 1 181 | base_height = 0.2 182 | base_acc = 0.2 183 | # energy 184 | action_smoothness = -0.002 185 | torques = -1e-5 186 | dof_vel = -5e-4 187 | dof_acc = -1e-7 188 | collision = -1.0 189 | 190 | class normalization: 191 | class obs_scales: 192 | lin_vel = 2.0 193 | ang_vel = 1.0 194 | dof_pos = 1.0 195 | dof_vel = 0.05 196 | quat = 1.0 197 | height_measurements = 5.0 198 | 199 | clip_observations = 18.0 200 | clip_actions = 18.0 201 | 202 | class viewer: 203 | ref_env = 0 204 | pos = [4, -4, 2] 205 | lookat = [0, -2, 0] 206 | 207 | 208 | class G1CfgPPO(LeggedRobotCfgPPO): 209 | seed = 5 210 | runner_class_name = "OnPolicyRunner" # DWLOnPolicyRunner 211 | 212 | class policy: 213 | init_noise_std = 1.0 214 | actor_hidden_dims = [512, 256, 128] 215 | critic_hidden_dims = [768, 256, 128] 216 | 217 | class algorithm(LeggedRobotCfgPPO.algorithm): 218 | entropy_coef = 0.001 219 | learning_rate = 1e-5 220 | num_learning_epochs = 2 221 | gamma = 0.994 222 | lam = 0.9 223 | num_mini_batches = 4 224 | 225 | class runner: 226 | policy_class_name = "ActorCritic" 227 | algorithm_class_name = "PPO" 228 | num_steps_per_env = 60 # per iteration 229 | max_iterations = 3001 # number of policy updates 230 | 231 | # logging 232 | save_interval = 100 # check for potential saves every this many iterations 233 | experiment_name = "g1" 234 | run_name = "" 235 | # load and resume 236 | resume = False 237 | load_run = -1 # -1 = last run 238 | checkpoint = -1 # -1 = last saved model 239 | resume_path = None # updated from load_run and chkpt 240 | -------------------------------------------------------------------------------- /sim/envs/humanoids/xbot_config.py: -------------------------------------------------------------------------------- 1 | """Defines the environment configuration for the walking task""" 2 | 3 | from sim.env import robot_urdf_path 4 | from sim.envs.base.legged_robot_config import ( # type: ignore 5 | LeggedRobotCfg, 6 | LeggedRobotCfgPPO, 7 | ) 8 | from sim.resources.xbot.joints import Robot 9 | 10 | NUM_JOINTS = len(Robot.all_joints()) # 12 11 | 12 | 13 | class XBotCfg(LeggedRobotCfg): 14 | """Configuration class for the Legs humanoid robot.""" 15 | 16 | class env(LeggedRobotCfg.env): 17 | # change the observation dim 18 | frame_stack = 15 19 | c_frame_stack = 3 20 | num_single_obs = 11 + NUM_JOINTS * 3 21 | num_observations = int(frame_stack * num_single_obs) 22 | single_num_privileged_obs = 25 + NUM_JOINTS * 4 23 | num_privileged_obs = int(c_frame_stack * single_num_privileged_obs) 24 | num_actions = NUM_JOINTS 25 | num_envs = 4096 26 | episode_length_s = 24 # episode length in seconds 27 | use_ref_actions = False 28 | 29 | class safety(LeggedRobotCfg.safety): 30 | # safety factors 31 | pos_limit = 1.0 32 | vel_limit = 1.0 33 | torque_limit = 0.85 34 | 35 | class asset(LeggedRobotCfg.asset): 36 | name = "xbot" 37 | file = str(robot_urdf_path(name)) 38 | 39 | foot_name = "ankle_roll" 40 | knee_name = "knee" 41 | 42 | termination_height = 0.35 43 | default_feet_height = 0.05 44 | terminate_after_contacts_on = ["base_link"] 45 | penalize_contacts_on = ["base_link"] 46 | self_collisions = 0 # 1 to disable, 0 to enable...bitwise filter 47 | flip_visual_attachments = False 48 | replace_cylinder_with_capsule = False 49 | fix_base_link = False 50 | 51 | class terrain(LeggedRobotCfg.terrain): 52 | mesh_type = "plane" 53 | # mesh_type = 'trimesh' 54 | curriculum = False 55 | # rough terrain only: 56 | measure_heights = False 57 | static_friction = 0.6 58 | dynamic_friction = 0.6 59 | terrain_length = 8.0 60 | terrain_width = 8.0 61 | num_rows = 10 # number of terrain rows (levels) 62 | num_cols = 10 # number of terrain cols (types) 63 | max_init_terrain_level = 10 # starting curriculum state 64 | # plane; obstacles; uniform; slope_up; slope_down, stair_up, stair_down 65 | terrain_proportions = [0.2, 0.2, 0.4, 0.1, 0.1, 0, 0] 66 | restitution = 0.0 67 | 68 | class noise: 69 | add_noise = True 70 | noise_level = 0.6 # scales other values 71 | 72 | class noise_scales: 73 | dof_pos = 0.05 74 | dof_vel = 0.5 75 | ang_vel = 0.1 76 | lin_vel = 0.05 77 | quat = 0.03 78 | height_measurements = 0.1 79 | 80 | class init_state(LeggedRobotCfg.init_state): 81 | pos = [0.0, 0.0, Robot.height] 82 | rot = Robot.rotation 83 | 84 | default_joint_angles = {k: 0.0 for k in Robot.all_joints()} 85 | 86 | default_positions = Robot.default_standing() 87 | for joint in default_positions: 88 | default_joint_angles[joint] = default_positions[joint] 89 | 90 | class control(LeggedRobotCfg.control): 91 | # PD Drive parameters: 92 | stiffness = Robot.stiffness() 93 | damping = Robot.damping() 94 | # action scale: target angle = actionScale * action + defaultAngle 95 | action_scale = 0.25 96 | # decimation: Number of control action updates @ sim DT per policy DT 97 | decimation = 10 # 100hz 98 | 99 | class sim(LeggedRobotCfg.sim): 100 | dt = 0.001 # 1000 Hz 101 | substeps = 1 # 2 102 | up_axis = 1 # 0 is y, 1 is z 103 | 104 | class physx(LeggedRobotCfg.sim.physx): 105 | num_threads = 10 106 | solver_type = 1 # 0: pgs, 1: tgs 107 | num_position_iterations = 4 108 | num_velocity_iterations = 1 109 | contact_offset = 0.01 # [m] 110 | rest_offset = 0.0 # [m] 111 | bounce_threshold_velocity = 0.1 # [m/s] 112 | max_depenetration_velocity = 1.0 113 | max_gpu_contact_pairs = 2**23 # 2**24 -> needed for 8000 envs and more 114 | default_buffer_size_multiplier = 5 115 | # 0: never, 1: last sub-step, 2: all sub-steps (default=2) 116 | contact_collection = 2 117 | 118 | class domain_rand(LeggedRobotCfg.domain_rand): 119 | randomize_friction = True 120 | friction_range = [0.1, 2.0] 121 | 122 | randomize_base_mass = True 123 | added_mass_range = [-1.0, 1.0] 124 | push_robots = True 125 | push_interval_s = 4 126 | max_push_vel_xy = 0.2 127 | max_push_ang_vel = 0.4 128 | # dynamic randomization 129 | action_delay = 0.5 130 | action_noise = 0.02 131 | 132 | class commands(LeggedRobotCfg.commands): 133 | # Vers: lin_vel_x, lin_vel_y, ang_vel_yaw, heading (in heading mode ang_vel_yaw is recomputed from heading error) 134 | num_commands = 4 135 | resampling_time = 8.0 # time before command are changed[s] 136 | heading_command = True # if true: compute ang vel command from heading error 137 | 138 | class ranges: 139 | lin_vel_x = [-0.3, 0.6] # min max [m/s] 140 | lin_vel_y = [-0.3, 0.3] # min max [m/s] 141 | ang_vel_yaw = [-0.3, 0.3] # min max [rad/s] 142 | heading = [-3.14, 3.14] 143 | 144 | class rewards: 145 | base_height_target = 0.89 146 | min_dist = 0.25 147 | max_dist = 0.5 148 | # put some settings here for LLM parameter tuning 149 | target_joint_pos_scale = 0.17 # rad 150 | target_feet_height = 0.06 # m 151 | cycle_time = 0.64 # sec 152 | # if true negative total rewards are clipped at zero (avoids early termination problems) 153 | only_positive_rewards = True 154 | # tracking reward = exp(error*sigma) 155 | tracking_sigma = 5.0 156 | max_contact_force = 700 # forces above this value are penalized 157 | 158 | class scales: 159 | # reference motion tracking 160 | joint_pos = 1.6 161 | feet_clearance = 1.0 162 | feet_contact_number = 1.2 163 | # gait 164 | feet_air_time = 1.0 165 | foot_slip = -0.05 166 | feet_distance = 0.2 167 | knee_distance = 0.2 168 | # contact 169 | feet_contact_forces = -0.01 170 | # vel tracking 171 | tracking_lin_vel = 1.2 172 | tracking_ang_vel = 1.1 173 | vel_mismatch_exp = 0.5 # lin_z; ang x,y 174 | low_speed = 0.2 175 | track_vel_hard = 0.5 176 | 177 | # base pos 178 | default_joint_pos = 0.5 179 | orientation = 1 180 | base_height = 0.2 181 | base_acc = 0.2 182 | # energy 183 | action_smoothness = -0.002 184 | torques = -1e-5 185 | dof_vel = -5e-4 186 | dof_acc = -1e-7 187 | collision = -1.0 188 | 189 | class normalization: 190 | class obs_scales: 191 | lin_vel = 2.0 192 | ang_vel = 1.0 193 | dof_pos = 1.0 194 | dof_vel = 0.05 195 | quat = 1.0 196 | height_measurements = 5.0 197 | 198 | clip_observations = 18.0 199 | clip_actions = 18.0 200 | 201 | class viewer: 202 | ref_env = 0 203 | pos = [4, -4, 2] 204 | lookat = [0, -2, 0] 205 | 206 | 207 | class XBotCfgPPO(LeggedRobotCfgPPO): 208 | seed = 5 209 | runner_class_name = "OnPolicyRunner" # DWLOnPolicyRunner 210 | 211 | class policy: 212 | init_noise_std = 1.0 213 | actor_hidden_dims = [512, 256, 128] 214 | critic_hidden_dims = [768, 256, 128] 215 | 216 | class algorithm(LeggedRobotCfgPPO.algorithm): 217 | entropy_coef = 0.001 218 | learning_rate = 1e-5 219 | num_learning_epochs = 2 220 | gamma = 0.994 221 | lam = 0.9 222 | num_mini_batches = 4 223 | 224 | class runner: 225 | policy_class_name = "ActorCritic" 226 | algorithm_class_name = "PPO" 227 | num_steps_per_env = 60 # per iteration 228 | max_iterations = 5001 # number of policy updates 229 | 230 | # logging 231 | save_interval = 100 # check for potential saves every this many iterations 232 | experiment_name = "g1" 233 | run_name = "" 234 | # load and resume 235 | resume = False 236 | load_run = -1 # -1 = last run 237 | checkpoint = -1 # -1 = last saved model 238 | resume_path = None # updated from load_run and chkpt 239 | -------------------------------------------------------------------------------- /sim/h5_logger.py: -------------------------------------------------------------------------------- 1 | """Logger for logging data to HDF5 files.""" 2 | 3 | import os 4 | import uuid 5 | from datetime import datetime 6 | from typing import Dict, Tuple 7 | 8 | import h5py 9 | import matplotlib.pyplot as plt # dependency issues with python 3.8 10 | import numpy as np 11 | 12 | 13 | class HDF5Logger: 14 | def __init__( 15 | self, 16 | data_name: str, 17 | num_actions: int, 18 | max_timesteps: int, 19 | num_observations: int, 20 | h5_out_dir: str = "sim/resources/", 21 | ) -> None: 22 | self.data_name = data_name 23 | self.num_actions = num_actions 24 | self.max_timesteps = max_timesteps 25 | self.num_observations = num_observations 26 | self.max_threshold = 1e3 # Adjust this threshold as needed 27 | self.h5_out_dir = h5_out_dir 28 | self.h5_file, self.h5_dict = self._create_h5_file() 29 | self.current_timestep = 0 30 | 31 | def _create_h5_file(self) -> Tuple[h5py.File, Dict[str, h5py.Dataset]]: 32 | # Create a unique file ID 33 | idd = str(uuid.uuid4()) 34 | timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") 35 | 36 | curr_h5_out_dir = f"{self.h5_out_dir}/{self.data_name}/h5_out/" 37 | os.makedirs(curr_h5_out_dir, exist_ok=True) 38 | 39 | h5_file_path = f"{curr_h5_out_dir}/{timestamp}__{idd}.h5" 40 | print(f"Saving HDF5 data to {h5_file_path}") 41 | h5_file = h5py.File(h5_file_path, "w") 42 | 43 | # Create datasets for logging actions and observations 44 | dset_prev_actions = h5_file.create_dataset( 45 | "prev_actions", (self.max_timesteps, self.num_actions), dtype=np.float32 46 | ) 47 | dset_2d_command = h5_file.create_dataset("observations/2D_command", (self.max_timesteps, 2), dtype=np.float32) 48 | dset_3d_command = h5_file.create_dataset("observations/3D_command", (self.max_timesteps, 3), dtype=np.float32) 49 | dset_q = h5_file.create_dataset("observations/q", (self.max_timesteps, self.num_actions), dtype=np.float32) 50 | dset_dq = h5_file.create_dataset("observations/dq", (self.max_timesteps, self.num_actions), dtype=np.float32) 51 | dset_ang_vel = h5_file.create_dataset("observations/ang_vel", (self.max_timesteps, 3), dtype=np.float32) 52 | dset_euler = h5_file.create_dataset("observations/euler", (self.max_timesteps, 3), dtype=np.float32) 53 | dset_t = h5_file.create_dataset("observations/t", (self.max_timesteps, 1), dtype=np.float32) 54 | dset_buffer = h5_file.create_dataset( 55 | "observations/buffer", (self.max_timesteps, self.num_observations), dtype=np.float32 56 | ) 57 | dset_curr_actions = h5_file.create_dataset( 58 | "curr_actions", (self.max_timesteps, self.num_actions), dtype=np.float32 59 | ) 60 | 61 | # Map datasets for easy access 62 | h5_dict = { 63 | "prev_actions": dset_prev_actions, 64 | "curr_actions": dset_curr_actions, 65 | "2D_command": dset_2d_command, 66 | "3D_command": dset_3d_command, 67 | "joint_pos": dset_q, 68 | "joint_vel": dset_dq, 69 | "ang_vel": dset_ang_vel, 70 | "euler_rotation": dset_euler, 71 | "t": dset_t, 72 | "buffer": dset_buffer, 73 | } 74 | return h5_file, h5_dict 75 | 76 | def log_data(self, data: Dict[str, np.ndarray]) -> None: 77 | if self.current_timestep >= self.max_timesteps: 78 | print(f"Warning: Exceeded maximum timesteps ({self.max_timesteps})") 79 | return 80 | 81 | for key, dataset in self.h5_dict.items(): 82 | if key in data: 83 | dataset[self.current_timestep] = data[key] 84 | 85 | self.current_timestep += 1 86 | 87 | def close(self) -> None: 88 | for key, dataset in self.h5_dict.items(): 89 | max_val = np.max(np.abs(dataset[:])) 90 | if max_val > self.max_threshold: 91 | print(f"Warning: Found very large values in {key}: {max_val}") 92 | print("File will not be saved to prevent corrupted data") 93 | self.h5_file.close() 94 | # Delete the file 95 | os.remove(self.h5_file.filename) 96 | return 97 | 98 | self.h5_file.close() 99 | 100 | @staticmethod 101 | def visualize_h5(h5_file_path: str) -> None: 102 | """Visualizes the data from an HDF5 file by plotting each variable one by one. 103 | 104 | Args: 105 | h5_file_path (str): Path to the HDF5 file. 106 | """ 107 | try: 108 | # Open the HDF5 file 109 | with h5py.File(h5_file_path, "r") as h5_file: 110 | # Extract all datasets 111 | for key in h5_file.keys(): 112 | group = h5_file[key] 113 | if isinstance(group, h5py.Group): 114 | for subkey in group.keys(): 115 | dataset = group[subkey][:] 116 | HDF5Logger._plot_dataset(f"{key}/{subkey}", dataset) 117 | else: 118 | dataset = group[:] 119 | HDF5Logger._plot_dataset(key, dataset) 120 | 121 | except Exception as e: 122 | print(f"Failed to visualize HDF5 file: {e}") 123 | 124 | @staticmethod 125 | def _plot_dataset(name: str, data: np.ndarray) -> None: 126 | """Helper method to plot a single dataset. 127 | 128 | Args: 129 | name (str): Name of the dataset. 130 | data (np.ndarray): Data to be plotted. 131 | """ 132 | plt.figure(figsize=(10, 5)) 133 | if data.ndim == 2: # Handle multi-dimensional data 134 | for i in range(data.shape[1]): 135 | plt.plot(data[:, i], label=f"{name}[{i}]") 136 | else: 137 | plt.plot(data, label=name) 138 | 139 | plt.title(f"Visualization of {name}") 140 | plt.xlabel("Timesteps") 141 | plt.ylabel("Values") 142 | plt.legend(loc="upper right") 143 | plt.grid(True) 144 | plt.tight_layout() 145 | plt.show() 146 | -------------------------------------------------------------------------------- /sim/produce_sim_data.py: -------------------------------------------------------------------------------- 1 | """Produce simulation data for training.""" 2 | 3 | import argparse 4 | import multiprocessing as mp 5 | import subprocess 6 | import time 7 | 8 | from tqdm import tqdm 9 | 10 | 11 | def run_simulation(sim_idx: int, args: argparse.Namespace) -> None: 12 | """Run a single simulation instance with the given parameters.""" 13 | cmd = [ 14 | "python", 15 | "sim/sim2sim.py", 16 | "--embodiment", 17 | args.embodiment, 18 | "--load_model", 19 | args.load_model, 20 | "--log_h5", 21 | "--h5_out_dir", 22 | args.h5_out_dir, 23 | "--no_render", 24 | ] 25 | 26 | try: 27 | subprocess.run(cmd, check=True) 28 | except subprocess.CalledProcessError as e: 29 | print(f"Simulation {sim_idx} failed with error: {e}") 30 | 31 | 32 | def run_parallel_sims(num_threads: int, args: argparse.Namespace) -> None: 33 | """Run multiple simulation instances in parallel with a delay between each start.""" 34 | processes = [] 35 | delay_between_starts = 0.1 # Adjust the delay (in seconds) as needed 36 | 37 | for idx in range(num_threads): 38 | p = mp.Process(target=run_simulation, args=(idx, args)) 39 | p.start() 40 | processes.append(p) 41 | time.sleep(delay_between_starts) # Introduce a delay before starting the next process 42 | 43 | # Wait for all processes to finish with a progress bar 44 | for p in tqdm(processes, desc="Running parallel simulations"): 45 | p.join() 46 | 47 | 48 | if __name__ == "__main__": 49 | parser = argparse.ArgumentParser(description="Run parallel simulations.") 50 | parser.add_argument("--num_threads", type=int, default=10, help="Number of parallel simulations to run") 51 | parser.add_argument("--embodiment", default="stompypro", type=str, help="Embodiment name") 52 | parser.add_argument("--load_model", default="examples/walking_pro.onnx", type=str, help="Path to model to load") 53 | parser.add_argument("--num_examples", default=10000, type=int, help="Number of examples to run") 54 | parser.add_argument("--h5_out_dir", default="sim/resources", type=str, help="Directory to save HDF5 files") 55 | args = parser.parse_args() 56 | 57 | # Run 100 examples total, in parallel batches 58 | num_batches = (args.num_examples + args.num_threads - 1) // args.num_threads 59 | 60 | for batch in range(num_batches): 61 | examples_remaining = args.num_examples - (batch * args.num_threads) 62 | threads_this_batch = min(args.num_threads, examples_remaining) 63 | print(f"\nRunning batch {batch + 1}/{num_batches} ({threads_this_batch} simulations)") 64 | run_parallel_sims(threads_this_batch, args) 65 | -------------------------------------------------------------------------------- /sim/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/py.typed -------------------------------------------------------------------------------- /sim/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # requirements-dev.txt 2 | 3 | black==24.8.0 4 | darglint==1.8.1 5 | h5py==3.11.0 6 | imageio==2.35.1 7 | matplotlib==3.3.4 8 | mediapy==1.2.2 9 | mujoco==2.3.6 10 | mujoco_python_viewer==0.1.4 11 | onnx 12 | onnxruntime 13 | pandas==1.4.4 14 | Pillow>6.2.0 15 | poselib==2.0.4 16 | pygame==2.6.1 17 | python-dotenv==1.0.1 18 | ruamel.base==1.0.0 19 | scipy>1.10.0 20 | setuptools==75.1.0 21 | tqdm==4.67.0 22 | wandb==0.18.7 23 | mypy==1.10.0 24 | pytest==8.3.3 25 | ruff==0.7.4 26 | isort==5.13.2 27 | -------------------------------------------------------------------------------- /sim/requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements.txt 2 | 3 | mediapy 4 | torch 5 | python-dotenv 6 | h5py 7 | gdown 8 | matplotlib==3.3.4 9 | numpy==1.21.6 10 | wandb 11 | tensorboard==2.14.0 12 | onnxscript 13 | onnx 14 | mujoco==2.3.6 15 | kinfer==0.0.5 16 | opencv-python 17 | gpytorch>=1.13 18 | -------------------------------------------------------------------------------- /sim/resources/dora/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/dora/__init__.py -------------------------------------------------------------------------------- /sim/resources/dora/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names. 2 | 3 | The best way to re-generate this snippet for a new robot is to use the 4 | `sim/scripts/print_joints.py` script. This script will print out a hierarchical 5 | tree of the various joint names in the robot. 6 | """ 7 | 8 | import textwrap 9 | from abc import ABC 10 | from typing import Dict, List, Tuple, Union 11 | 12 | 13 | class Node(ABC): 14 | @classmethod 15 | def children(cls) -> List["Union[Node, str]"]: 16 | return [ 17 | attr 18 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 19 | if isinstance(attr, (Node, str)) 20 | ] 21 | 22 | @classmethod 23 | def joints(cls) -> List[str]: 24 | return [ 25 | attr 26 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 27 | if isinstance(attr, str) 28 | ] 29 | 30 | @classmethod 31 | def joints_motors(cls) -> List[Tuple[str, str]]: 32 | joint_names: List[Tuple[str, str]] = [] 33 | for attr in dir(cls): 34 | if not attr.startswith("__"): 35 | attr2 = getattr(cls, attr) 36 | if isinstance(attr2, str): 37 | joint_names.append((attr, attr2)) 38 | 39 | return joint_names 40 | 41 | @classmethod 42 | def all_joints(cls) -> List[str]: 43 | leaves = cls.joints() 44 | for child in cls.children(): 45 | if isinstance(child, Node): 46 | leaves.extend(child.all_joints()) 47 | return leaves 48 | 49 | def __str__(self) -> str: 50 | parts = [str(child) for child in self.children()] 51 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 52 | return f"[{self.__class__.__name__}]{parts_str}" 53 | 54 | 55 | class LeftLeg(Node): 56 | hip_roll = "left hip roll" 57 | hip_yaw = "left hip yaw" 58 | hip_pitch = "left hip pitch" 59 | knee_pitch = "left knee pitch" 60 | ankle_pitch = "left ankle pitch" 61 | ankle_roll = "left ankle roll" 62 | 63 | 64 | class RightLeg(Node): 65 | hip_roll = "right hip roll" 66 | hip_yaw = "right hip yaw" 67 | hip_pitch = "right hip pitch" 68 | knee_pitch = "right knee pitch" 69 | ankle_pitch = "right ankle pitch" 70 | ankle_roll = "right ankle roll" 71 | 72 | 73 | class Legs(Node): 74 | left = LeftLeg() 75 | right = RightLeg() 76 | 77 | 78 | class Robot(Node): 79 | legs = Legs() 80 | 81 | height = 0.75 82 | rotation = [0.0, 0.0, 0, 1] 83 | 84 | @classmethod 85 | def default_positions(cls) -> Dict[str, float]: 86 | return {} 87 | 88 | @classmethod 89 | def default_standing(cls) -> Dict[str, float]: 90 | return { 91 | Robot.legs.left.hip_pitch: 0.325, 92 | Robot.legs.left.hip_yaw: 0, 93 | Robot.legs.left.hip_roll: 0, 94 | Robot.legs.left.knee_pitch: -0.259, 95 | Robot.legs.left.ankle_pitch: -0.0556, 96 | Robot.legs.left.ankle_roll: 0, 97 | Robot.legs.right.hip_pitch: 0.325, 98 | Robot.legs.right.hip_yaw: 0, 99 | Robot.legs.right.hip_roll: 0, 100 | Robot.legs.right.knee_pitch: -0.259, 101 | Robot.legs.right.ankle_pitch: -0.0556, 102 | Robot.legs.right.ankle_roll: 0, 103 | } 104 | 105 | @classmethod 106 | def default_limits(cls) -> Dict[str, Dict[str, float]]: 107 | return { 108 | Robot.legs.left.hip_pitch: { 109 | "lower": 0.5, 110 | "upper": 2.69, 111 | }, 112 | Robot.legs.left.hip_yaw: { 113 | "lower": 0.5, 114 | "upper": 1.19, 115 | }, 116 | Robot.legs.left.hip_roll: { 117 | "lower": -0.5, 118 | "upper": 0.5, 119 | }, 120 | Robot.legs.left.knee_pitch: { 121 | "lower": -2.14, 122 | "upper": -1.0, 123 | }, 124 | Robot.legs.left.ankle_pitch: { 125 | "lower": -0.8, 126 | "upper": 0.6, 127 | }, 128 | Robot.legs.left.ankle_roll: { 129 | "lower": 1, 130 | "upper": 2.3, 131 | }, 132 | Robot.legs.right.hip_pitch: { 133 | "lower": -1, 134 | "upper": 1, 135 | }, 136 | Robot.legs.right.hip_yaw: { 137 | "lower": -2.6, 138 | "upper": -1.5, 139 | }, 140 | Robot.legs.right.hip_roll: { 141 | "lower": -2.39, 142 | "upper": -1, 143 | }, 144 | Robot.legs.right.knee_pitch: { 145 | "lower": 2.09, 146 | "upper": 3.2, 147 | }, 148 | Robot.legs.right.ankle_pitch: { 149 | "lower": 0, 150 | "upper": 1.5, 151 | }, 152 | Robot.legs.right.ankle_roll: { 153 | "lower": 1, 154 | "upper": 2.3, 155 | }, 156 | } 157 | 158 | # p_gains 159 | @classmethod 160 | def stiffness(cls) -> Dict[str, float]: 161 | return { 162 | "hip pitch": 250, 163 | "hip yaw": 150, 164 | "hip roll": 150, 165 | "knee pitch": 150, 166 | "ankle pitch": 150, 167 | "ankle roll": 150, 168 | } 169 | 170 | # d_gains 171 | @classmethod 172 | def damping(cls) -> Dict[str, float]: 173 | return { 174 | "hip pitch": 15, 175 | "hip yaw": 10, 176 | "hip roll": 10, 177 | "knee pitch": 10, 178 | "ankle pitch": 5, 179 | "ankle roll": 5, 180 | } 181 | 182 | # pos_limits 183 | @classmethod 184 | def effort(cls) -> Dict[str, float]: 185 | return { 186 | "hip pitch": 150, 187 | "hip yaw": 90, 188 | "hip roll": 90, 189 | "knee pitch": 90, 190 | "ankle pitch": 24, 191 | "ankle roll": 24, 192 | } 193 | 194 | # vel_limits 195 | @classmethod 196 | def velocity(cls) -> Dict[str, float]: 197 | return { 198 | "hip pitch": 40, 199 | "hip yaw": 40, 200 | "hip roll": 40, 201 | "knee pitch": 40, 202 | "ankle pitch": 24, 203 | "ankle roll": 24, 204 | } 205 | 206 | @classmethod 207 | def friction(cls) -> Dict[str, float]: 208 | return { 209 | "hip pitch": 0.0, 210 | "hip yaw": 0.0, 211 | "hip roll": 0.0, 212 | "knee pitch": 0.0, 213 | "ankle pitch": 0.0, 214 | "ankle roll": 0.1, 215 | } 216 | 217 | 218 | def print_joints() -> None: 219 | joints = Robot.all_joints() 220 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 221 | print(Robot()) 222 | 223 | 224 | if __name__ == "__main__": 225 | # python -m sim.Robot.joints 226 | print_joints() 227 | -------------------------------------------------------------------------------- /sim/resources/g1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/g1/__init__.py -------------------------------------------------------------------------------- /sim/resources/g1/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names. 2 | 3 | The best way to re-generate this snippet for a new robot is to use the 4 | `sim/scripts/print_joints.py` script. This script will print out a hierarchical 5 | tree of the various joint names in the robot. 6 | """ 7 | 8 | import textwrap 9 | from abc import ABC 10 | from typing import Dict, List, Tuple, Union 11 | 12 | 13 | class Node(ABC): 14 | @classmethod 15 | def children(cls) -> List["Union[Node, str]"]: 16 | return [ 17 | attr 18 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 19 | if isinstance(attr, (Node, str)) 20 | ] 21 | 22 | @classmethod 23 | def joints(cls) -> List[str]: 24 | return [ 25 | attr 26 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 27 | if isinstance(attr, str) 28 | ] 29 | 30 | @classmethod 31 | def joints_motors(cls) -> List[Tuple[str, str]]: 32 | joint_names: List[Tuple[str, str]] = [] 33 | for attr in dir(cls): 34 | if not attr.startswith("__"): 35 | attr2 = getattr(cls, attr) 36 | if isinstance(attr2, str): 37 | joint_names.append((attr, attr2)) 38 | 39 | return joint_names 40 | 41 | @classmethod 42 | def all_joints(cls) -> List[str]: 43 | leaves = cls.joints() 44 | for child in cls.children(): 45 | if isinstance(child, Node): 46 | leaves.extend(child.all_joints()) 47 | return leaves 48 | 49 | def __str__(self) -> str: 50 | parts = [str(child) for child in self.children()] 51 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 52 | return f"[{self.__class__.__name__}]{parts_str}" 53 | 54 | 55 | class LeftLeg(Node): 56 | hip_roll = "left_hip_roll_joint" 57 | hip_yaw = "left_hip_yaw_joint" 58 | hip_pitch = "left_hip_pitch_joint" 59 | knee_pitch = "left_knee_joint" 60 | ankle_pitch = "left_ankle_pitch_joint" 61 | ankle_roll = "left_ankle_roll_joint" 62 | 63 | 64 | class RightLeg(Node): 65 | hip_roll = "right_hip_roll_joint" 66 | hip_yaw = "right_hip_yaw_joint" 67 | hip_pitch = "right_hip_pitch_joint" 68 | knee_pitch = "right_knee_joint" 69 | ankle_pitch = "right_ankle_pitch_joint" 70 | ankle_roll = "right_ankle_roll_joint" 71 | 72 | 73 | class Legs(Node): 74 | left = LeftLeg() 75 | right = RightLeg() 76 | 77 | 78 | class Robot(Node): 79 | legs = Legs() 80 | 81 | height = 0.78 82 | rotation = [0.0, 0.0, 0.7071068, 0.7071068] 83 | 84 | @classmethod 85 | def default_standing(cls) -> Dict[str, float]: 86 | return { # = target angles [rad] when action = 0.0 87 | # left leg 88 | Robot.legs.left.hip_yaw: 0, 89 | Robot.legs.left.hip_roll: 0, 90 | Robot.legs.left.hip_pitch: -0.1, 91 | Robot.legs.left.knee_pitch: 0.3, 92 | Robot.legs.left.ankle_pitch: -0.2, 93 | Robot.legs.left.ankle_roll: 0.0, 94 | # right leg 95 | Robot.legs.right.hip_yaw: 0, 96 | Robot.legs.right.hip_roll: 0, 97 | Robot.legs.right.hip_pitch: -0.1, 98 | Robot.legs.right.knee_pitch: 0.3, 99 | Robot.legs.right.ankle_pitch: -0.2, 100 | Robot.legs.right.ankle_roll: 0.0, 101 | } 102 | 103 | # p_gains 104 | @classmethod 105 | def stiffness(cls) -> Dict[str, float]: 106 | return { 107 | "hip_pitch": 150, 108 | "hip_yaw": 150, 109 | "hip_roll": 150, 110 | "knee": 300, 111 | "ankle": 40, 112 | } 113 | 114 | # d_gains 115 | @classmethod 116 | def damping(cls) -> Dict[str, float]: 117 | return { 118 | "hip_pitch": 2, 119 | "hip_yaw": 2, 120 | "hip_roll": 2, 121 | "knee": 4, 122 | "ankle": 2, 123 | } 124 | 125 | 126 | def print_joints() -> None: 127 | joints = Robot.all_joints() 128 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 129 | print(Robot()) 130 | 131 | 132 | if __name__ == "__main__": 133 | # python -m sim.Robot.joints 134 | print_joints() 135 | -------------------------------------------------------------------------------- /sim/resources/gpr/README.md: -------------------------------------------------------------------------------- 1 | # URDF/XML development: 2 | 1. kol run https://cad.onshape.com/documents/bc3a557e2f92bdcea4099f0d/w/09713ac603d461fc1dff6b5d/e/5a4acdbffe6f8ed7c4e34970 --config-path config_example.json # noqa: E501 3 | 2. python sim/scripts/create_fixed_torso.py 4 | 3. Rotate first link 5 | 6 | 7 | 8 | 9 | 10 | 11 | 4. Fix left leg axes to align with expected directions 12 | 5. urf2mjcf robot.urdf -o robot.xml 13 | -------------------------------------------------------------------------------- /sim/resources/gpr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/__init__.py -------------------------------------------------------------------------------- /sim/resources/gpr/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names.""" 2 | 3 | import textwrap 4 | from abc import ABC 5 | from typing import Dict, List, Tuple, Union 6 | 7 | 8 | class Node(ABC): 9 | @classmethod 10 | def children(cls) -> List["Union[Node, str]"]: 11 | return [ 12 | attr 13 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 14 | if isinstance(attr, (Node, str)) 15 | ] 16 | 17 | @classmethod 18 | def joints(cls) -> List[str]: 19 | return [ 20 | attr 21 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 22 | if isinstance(attr, str) 23 | ] 24 | 25 | @classmethod 26 | def joints_motors(cls) -> List[Tuple[str, str]]: 27 | joint_names: List[Tuple[str, str]] = [] 28 | for attr in dir(cls): 29 | if not attr.startswith("__"): 30 | attr2 = getattr(cls, attr) 31 | if isinstance(attr2, str): 32 | joint_names.append((attr, attr2)) 33 | 34 | return joint_names 35 | 36 | @classmethod 37 | def all_joints(cls) -> List[str]: 38 | leaves = cls.joints() 39 | for child in cls.children(): 40 | if isinstance(child, Node): 41 | leaves.extend(child.all_joints()) 42 | return leaves 43 | 44 | def __str__(self) -> str: 45 | parts = [str(child) for child in self.children()] 46 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 47 | return f"[{self.__class__.__name__}]{parts_str}" 48 | 49 | 50 | class LeftLeg(Node): 51 | hip_pitch = "left_hip_pitch_04" 52 | hip_yaw = "left_hip_yaw_03" 53 | hip_roll = "left_hip_roll_03" 54 | knee_pitch = "left_knee_04" 55 | ankle_pitch = "left_ankle_02" 56 | 57 | 58 | class RightLeg(Node): 59 | hip_pitch = "right_hip_pitch_04" 60 | hip_yaw = "right_hip_yaw_03" 61 | hip_roll = "right_hip_roll_03" 62 | knee_pitch = "right_knee_04" 63 | ankle_pitch = "right_ankle_02" 64 | 65 | 66 | class Legs(Node): 67 | left = LeftLeg() 68 | right = RightLeg() 69 | 70 | 71 | class Robot(Node): 72 | legs = Legs() 73 | 74 | # height = 1.05 #- 0.151 #maybe? 75 | # standing_height = 1.05 + 0.025 #- 0.151 76 | 77 | height = 0.79 78 | standing_height = 0.79 + 0.025 79 | 80 | # 1.3 m and 1.149m 81 | rotation = [0, 0, 0, 1] 82 | 83 | @classmethod 84 | def default_positions(cls) -> Dict[str, float]: 85 | return { 86 | Robot.legs.left.hip_pitch: 0.0, 87 | Robot.legs.left.hip_yaw: 0.0, 88 | Robot.legs.left.hip_roll: 0.0, 89 | Robot.legs.left.knee_pitch: 0.0, 90 | Robot.legs.left.ankle_pitch: 0.0, 91 | Robot.legs.right.hip_pitch: 0.0, 92 | Robot.legs.right.hip_yaw: 0.0, 93 | Robot.legs.right.hip_roll: 0.0, 94 | Robot.legs.right.knee_pitch: 0.0, 95 | Robot.legs.right.ankle_pitch: 0.0, 96 | } 97 | 98 | # CONTRACT - this should be ordered according to how the policy is trained. 99 | # E.g. the first entry should be the angle of the first joint in the policy. 100 | @classmethod 101 | def default_standing(cls) -> Dict[str, float]: 102 | return { 103 | Robot.legs.left.hip_pitch: 0.23, 104 | Robot.legs.left.hip_yaw: 0.0, 105 | Robot.legs.left.hip_roll: 0.0, 106 | Robot.legs.left.knee_pitch: -0.441, 107 | Robot.legs.left.ankle_pitch: 0.195, # negated 108 | Robot.legs.right.hip_pitch: -0.23, 109 | Robot.legs.right.hip_yaw: 0.0, 110 | Robot.legs.right.hip_roll: 0.0, 111 | Robot.legs.right.knee_pitch: 0.441, # negated 112 | Robot.legs.right.ankle_pitch: 0.195, # negated 113 | } 114 | 115 | # CONTRACT - this should be ordered according to how the policy is trained. 116 | # E.g. the first entry should be the name of the first joint in the policy. 117 | @classmethod 118 | def joint_names(cls) -> List[str]: 119 | return list(cls.default_standing().keys()) 120 | 121 | # p_gains 122 | @classmethod 123 | def stiffness(cls) -> Dict[str, float]: 124 | return { 125 | "04": 300, 126 | "03": 120, 127 | "02": 40, 128 | } 129 | 130 | @classmethod 131 | def stiffness_mapping(cls) -> Dict[str, float]: 132 | mapping = {} 133 | stiffness = cls.stiffness() 134 | for joint in cls.joint_names(): 135 | mapping[joint] = stiffness[joint[-2:]] 136 | return mapping 137 | 138 | # d_gains 139 | @classmethod 140 | def damping(cls) -> Dict[str, float]: 141 | return { 142 | "04": 5, 143 | "03": 5, 144 | "02": 5, 145 | } 146 | 147 | @classmethod 148 | def damping_mapping(cls) -> Dict[str, float]: 149 | mapping = {} 150 | damping = cls.damping() 151 | for joint in cls.joint_names(): 152 | mapping[joint] = damping[joint[-2:]] 153 | print(mapping) 154 | return mapping 155 | 156 | # effort_limits 157 | @classmethod 158 | def effort(cls) -> Dict[str, float]: 159 | return { 160 | "04": 60, 161 | "03": 40, 162 | "02": 40, 163 | } 164 | 165 | @classmethod 166 | def effort_mapping(cls) -> Dict[str, float]: 167 | mapping = {} 168 | effort = cls.effort() 169 | for joint in cls.joint_names(): 170 | mapping[joint] = effort[joint[-2:]] 171 | return mapping 172 | 173 | 174 | def print_joints() -> None: 175 | joints = Robot.all_joints() 176 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 177 | print(Robot()) 178 | print(len(joints)) 179 | 180 | 181 | if __name__ == "__main__": 182 | # python -m sim.Robot.joints 183 | print_joints() 184 | -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/arm1_top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/arm1_top.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/arm1_top_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/arm1_top_2.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/arm2_shell.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/arm2_shell.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/arm2_shell_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/arm2_shell_2.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/arm3_shell.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/arm3_shell.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/arm3_shell2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/arm3_shell2.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/body1-part.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/body1-part.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/foot1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/foot1.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/foot3.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/foot3.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/hand_shell.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/hand_shell.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/hand_shell_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/hand_shell_2.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/leg0_shell.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/leg0_shell.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/leg0_shell_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/leg0_shell_2.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/leg1_shell.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/leg1_shell.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/leg1_shell3.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/leg1_shell3.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/leg2_shell.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/leg2_shell.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/leg2_shell_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/leg2_shell_2.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/shoulder.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/shoulder.stl -------------------------------------------------------------------------------- /sim/resources/gpr/meshes/shoulder_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr/meshes/shoulder_2.stl -------------------------------------------------------------------------------- /sim/resources/gpr_headless/README.md: -------------------------------------------------------------------------------- 1 | # URDF/XML development: 2 | 1. kol run https://cad.onshape.com/documents/bc3a557e2f92bdcea4099f0d/w/09713ac603d461fc1dff6b5d/e/5a4acdbffe6f8ed7c4e34970 --config-path config_example.json # noqa: E501 3 | 2. python sim/scripts/create_fixed_torso.py 4 | 3. Rotate first link 5 | 6 | 7 | 8 | 9 | 10 | 11 | 4. Fix left leg axes to align with expected directions 12 | 5. urf2mjcf robot.urdf -o robot.xml 13 | -------------------------------------------------------------------------------- /sim/resources/gpr_headless/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr_headless/__init__.py -------------------------------------------------------------------------------- /sim/resources/gpr_headless/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names.""" 2 | 3 | import textwrap 4 | from abc import ABC 5 | from typing import Dict, List, Tuple, Union 6 | 7 | 8 | class Node(ABC): 9 | @classmethod 10 | def children(cls) -> List["Union[Node, str]"]: 11 | return [ 12 | attr 13 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 14 | if isinstance(attr, (Node, str)) 15 | ] 16 | 17 | @classmethod 18 | def joints(cls) -> List[str]: 19 | return [ 20 | attr 21 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 22 | if isinstance(attr, str) 23 | ] 24 | 25 | @classmethod 26 | def joints_motors(cls) -> List[Tuple[str, str]]: 27 | joint_names: List[Tuple[str, str]] = [] 28 | for attr in dir(cls): 29 | if not attr.startswith("__"): 30 | attr2 = getattr(cls, attr) 31 | if isinstance(attr2, str): 32 | joint_names.append((attr, attr2)) 33 | 34 | return joint_names 35 | 36 | @classmethod 37 | def all_joints(cls) -> List[str]: 38 | leaves = cls.joints() 39 | for child in cls.children(): 40 | if isinstance(child, Node): 41 | leaves.extend(child.all_joints()) 42 | return leaves 43 | 44 | def __str__(self) -> str: 45 | parts = [str(child) for child in self.children()] 46 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 47 | return f"[{self.__class__.__name__}]{parts_str}" 48 | 49 | 50 | class LeftLeg(Node): 51 | hip_pitch = "left_hip_pitch_04" 52 | hip_yaw = "left_hip_yaw_03" 53 | hip_roll = "left_hip_roll_03" 54 | knee_pitch = "left_knee_04" 55 | ankle_pitch = "left_ankle_02" 56 | 57 | 58 | class RightLeg(Node): 59 | hip_pitch = "right_hip_pitch_04" 60 | hip_yaw = "right_hip_yaw_03" 61 | hip_roll = "right_hip_roll_03" 62 | knee_pitch = "right_knee_04" 63 | ankle_pitch = "right_ankle_02" 64 | 65 | 66 | class Legs(Node): 67 | left = LeftLeg() 68 | right = RightLeg() 69 | 70 | 71 | class Robot(Node): 72 | legs = Legs() 73 | 74 | height = 1.05 75 | standing_height = 1.05 + 0.025 76 | rotation = [0, 0, 0, 1] 77 | 78 | @classmethod 79 | def isaac_to_real_signs(cls) -> Dict[str, int]: 80 | return { 81 | Robot.legs.left.hip_pitch: 1, 82 | Robot.legs.left.hip_yaw: 1, 83 | Robot.legs.left.hip_roll: 1, 84 | Robot.legs.left.knee_pitch: 1, 85 | Robot.legs.left.ankle_pitch: 1, 86 | Robot.legs.right.hip_pitch: 1, 87 | Robot.legs.right.hip_yaw: 1, 88 | Robot.legs.right.hip_roll: 1, 89 | Robot.legs.right.knee_pitch: 1, 90 | Robot.legs.right.ankle_pitch: 1, 91 | } 92 | 93 | @classmethod 94 | def isaac_to_mujoco_signs(cls) -> Dict[str, int]: 95 | return { 96 | Robot.legs.left.hip_pitch: 1, 97 | Robot.legs.left.hip_yaw: 1, 98 | Robot.legs.left.hip_roll: 1, 99 | Robot.legs.left.knee_pitch: 1, 100 | Robot.legs.left.ankle_pitch: 1, 101 | Robot.legs.right.hip_pitch: 1, 102 | Robot.legs.right.hip_yaw: 1, 103 | Robot.legs.right.hip_roll: 1, 104 | Robot.legs.right.knee_pitch: 1, 105 | Robot.legs.right.ankle_pitch: 1, 106 | } 107 | 108 | @classmethod 109 | def default_positions(cls) -> Dict[str, float]: 110 | return { 111 | Robot.legs.left.hip_pitch: 0.0, 112 | Robot.legs.left.hip_yaw: 0.0, 113 | Robot.legs.left.hip_roll: 0.0, 114 | Robot.legs.left.knee_pitch: 0.0, 115 | Robot.legs.left.ankle_pitch: 0.0, 116 | Robot.legs.right.hip_pitch: 0.0, 117 | Robot.legs.right.hip_yaw: 0.0, 118 | Robot.legs.right.hip_roll: 0.0, 119 | Robot.legs.right.knee_pitch: 0.0, 120 | Robot.legs.right.ankle_pitch: 0.0, 121 | } 122 | 123 | # CONTRACT - this should be ordered according to how the policy is trained. 124 | # E.g. the first entry should be the angle of the first joint in the policy. 125 | @classmethod 126 | def default_standing(cls) -> Dict[str, float]: 127 | return { 128 | Robot.legs.left.hip_pitch: 0.23, 129 | Robot.legs.left.hip_yaw: 0.0, 130 | Robot.legs.left.hip_roll: 0.0, 131 | Robot.legs.left.knee_pitch: 0.441, # negated 132 | Robot.legs.left.ankle_pitch: -0.195, 133 | Robot.legs.right.hip_pitch: -0.23, 134 | Robot.legs.right.hip_yaw: 0.0, 135 | Robot.legs.right.hip_roll: 0.0, 136 | Robot.legs.right.knee_pitch: -0.441, 137 | Robot.legs.right.ankle_pitch: 0.195, # negated 138 | } 139 | 140 | # CONTRACT - this should be ordered according to how the policy is trained. 141 | # E.g. the first entry should be the name of the first joint in the policy. 142 | @classmethod 143 | def joint_names(cls) -> List[str]: 144 | return list(cls.default_standing().keys()) 145 | 146 | @classmethod 147 | def default_limits(cls) -> Dict[str, Dict[str, float]]: 148 | return { 149 | Robot.legs.left.knee_pitch: {"lower": -1.57, "upper": 0}, 150 | Robot.legs.right.knee_pitch: {"lower": -1.57, "upper": 0}, 151 | } 152 | 153 | # p_gains 154 | @classmethod 155 | def stiffness(cls) -> Dict[str, float]: 156 | return { 157 | "04": 80, 158 | "03": 40, 159 | "02": 30, 160 | } 161 | 162 | @classmethod 163 | def stiffness_mapping(cls) -> Dict[str, float]: 164 | mapping = {} 165 | stiffness = cls.stiffness() 166 | for joint in cls.joint_names(): 167 | mapping[joint] = stiffness[joint[-2:]] 168 | return mapping 169 | 170 | # d_gains 171 | @classmethod 172 | def damping(cls) -> Dict[str, float]: 173 | return { 174 | "04": 5, 175 | "03": 4, 176 | "02": 1, 177 | } 178 | 179 | @classmethod 180 | def damping_mapping(cls) -> Dict[str, float]: 181 | mapping = {} 182 | damping = cls.damping() 183 | for joint in cls.joint_names(): 184 | mapping[joint] = damping[joint[-2:]] 185 | print(mapping) 186 | return mapping 187 | 188 | # effort_limits 189 | @classmethod 190 | def effort(cls) -> Dict[str, float]: 191 | return { 192 | "04": 80, 193 | "03": 40, 194 | "02": 17, 195 | } 196 | 197 | @classmethod 198 | def effort_mapping(cls) -> Dict[str, float]: 199 | mapping = {} 200 | effort = cls.effort() 201 | for joint in cls.joint_names(): 202 | mapping[joint] = effort[joint[-2:]] 203 | return mapping 204 | 205 | @classmethod 206 | def velocity(cls) -> Dict[str, float]: 207 | return { 208 | "04": 18.0, 209 | "03": 18.0, 210 | "02": 18.0, 211 | } 212 | 213 | @classmethod 214 | def friction(cls) -> Dict[str, float]: 215 | return { 216 | "04": 0, 217 | "03": 0, 218 | "02": 0.1, 219 | } 220 | 221 | 222 | def print_joints() -> None: 223 | joints = Robot.all_joints() 224 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 225 | print(Robot()) 226 | print(len(joints)) 227 | 228 | 229 | if __name__ == "__main__": 230 | # python -m sim.Robot.joints 231 | print_joints() 232 | -------------------------------------------------------------------------------- /sim/resources/gpr_headless/old_real/README.md: -------------------------------------------------------------------------------- 1 | # URDF/XML development: 2 | 1. kol run https://cad.onshape.com/documents/bc3a557e2f92bdcea4099f0d/w/09713ac603d461fc1dff6b5d/e/5a4acdbffe6f8ed7c4e34970 --config-path config_example.json # noqa: E501 3 | 2. python sim/scripts/create_fixed_torso.py 4 | 3. Rotate first link 5 | 6 | 7 | 8 | 9 | 10 | 11 | 4. Fix left leg axes to align with expected directions 12 | 5. urf2mjcf robot.urdf -o robot.xml 13 | -------------------------------------------------------------------------------- /sim/resources/gpr_headless/old_real/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr_headless/old_real/__init__.py -------------------------------------------------------------------------------- /sim/resources/gpr_headless/old_real/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names.""" 2 | 3 | import textwrap 4 | from abc import ABC 5 | from typing import Dict, List, Tuple, Union 6 | 7 | 8 | class Node(ABC): 9 | @classmethod 10 | def children(cls) -> List["Union[Node, str]"]: 11 | return [ 12 | attr 13 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 14 | if isinstance(attr, (Node, str)) 15 | ] 16 | 17 | @classmethod 18 | def joints(cls) -> List[str]: 19 | return [ 20 | attr 21 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 22 | if isinstance(attr, str) 23 | ] 24 | 25 | @classmethod 26 | def joints_motors(cls) -> List[Tuple[str, str]]: 27 | joint_names: List[Tuple[str, str]] = [] 28 | for attr in dir(cls): 29 | if not attr.startswith("__"): 30 | attr2 = getattr(cls, attr) 31 | if isinstance(attr2, str): 32 | joint_names.append((attr, attr2)) 33 | 34 | return joint_names 35 | 36 | @classmethod 37 | def all_joints(cls) -> List[str]: 38 | leaves = cls.joints() 39 | for child in cls.children(): 40 | if isinstance(child, Node): 41 | leaves.extend(child.all_joints()) 42 | return leaves 43 | 44 | def __str__(self) -> str: 45 | parts = [str(child) for child in self.children()] 46 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 47 | return f"[{self.__class__.__name__}]{parts_str}" 48 | 49 | 50 | class LeftLeg(Node): 51 | hip_pitch = "left_hip_pitch_04" 52 | hip_yaw = "left_hip_yaw_03" 53 | hip_roll = "left_hip_roll_03" 54 | knee_pitch = "left_knee_04" 55 | ankle_pitch = "left_ankle_02" 56 | 57 | 58 | class RightLeg(Node): 59 | hip_pitch = "right_hip_pitch_04" 60 | hip_yaw = "right_hip_yaw_03" 61 | hip_roll = "right_hip_roll_03" 62 | knee_pitch = "right_knee_04" 63 | ankle_pitch = "right_ankle_02" 64 | 65 | 66 | class Legs(Node): 67 | left = LeftLeg() 68 | right = RightLeg() 69 | 70 | 71 | class Robot(Node): 72 | legs = Legs() 73 | 74 | # height = 1.05 #- 0.151 #maybe? 75 | # standing_height = 1.05 + 0.025 #- 0.151 76 | 77 | height = 1.04 78 | standing_height = 1.04 + 0.025 79 | 80 | # 1.3 m and 1.149m 81 | rotation = [0, 0, 0, 1] 82 | 83 | @classmethod 84 | def default_positions(cls) -> Dict[str, float]: 85 | return { 86 | Robot.legs.left.hip_pitch: 0.0, 87 | Robot.legs.left.hip_yaw: 0.0, 88 | Robot.legs.left.hip_roll: 0.0, 89 | Robot.legs.left.knee_pitch: 0.0, 90 | Robot.legs.left.ankle_pitch: 0.0, 91 | Robot.legs.right.hip_pitch: 0.0, 92 | Robot.legs.right.hip_yaw: 0.0, 93 | Robot.legs.right.hip_roll: 0.0, 94 | Robot.legs.right.knee_pitch: 0.0, 95 | Robot.legs.right.ankle_pitch: 0.0, 96 | } 97 | 98 | # CONTRACT - this should be ordered according to how the policy is trained. 99 | # E.g. the first entry should be the angle of the first joint in the policy. 100 | @classmethod 101 | def default_standing(cls) -> Dict[str, float]: 102 | return { 103 | Robot.legs.left.hip_pitch: 0.23, 104 | Robot.legs.left.hip_yaw: 0.0, 105 | Robot.legs.left.hip_roll: 0.0, 106 | Robot.legs.left.knee_pitch: 0.441, # negated 107 | Robot.legs.left.ankle_pitch: -0.195, # negated 108 | Robot.legs.right.hip_pitch: -0.23, 109 | Robot.legs.right.hip_yaw: 0.0, 110 | Robot.legs.right.hip_roll: 0.0, 111 | Robot.legs.right.knee_pitch: -0.441, # negated 112 | Robot.legs.right.ankle_pitch: -0.195, # negated 113 | } 114 | 115 | # CONTRACT - this should be ordered according to how the policy is trained. 116 | # E.g. the first entry should be the name of the first joint in the policy. 117 | @classmethod 118 | def joint_names(cls) -> List[str]: 119 | return list(cls.default_standing().keys()) 120 | 121 | # p_gains 122 | @classmethod 123 | def stiffness(cls) -> Dict[str, float]: 124 | return { 125 | "04": 300, 126 | "03": 120, 127 | "02": 40, 128 | } 129 | 130 | @classmethod 131 | def stiffness_mapping(cls) -> Dict[str, float]: 132 | mapping = {} 133 | stiffness = cls.stiffness() 134 | for joint in cls.joint_names(): 135 | mapping[joint] = stiffness[joint[-2:]] 136 | return mapping 137 | 138 | # d_gains 139 | @classmethod 140 | def damping(cls) -> Dict[str, float]: 141 | return { 142 | "04": 5, 143 | "03": 5, 144 | "02": 5, 145 | } 146 | 147 | @classmethod 148 | def damping_mapping(cls) -> Dict[str, float]: 149 | mapping = {} 150 | damping = cls.damping() 151 | for joint in cls.joint_names(): 152 | mapping[joint] = damping[joint[-2:]] 153 | print(mapping) 154 | return mapping 155 | 156 | # effort_limits 157 | @classmethod 158 | def effort(cls) -> Dict[str, float]: 159 | return { 160 | "04": 80, 161 | "03": 40, 162 | "02": 20, 163 | } 164 | 165 | @classmethod 166 | def effort_mapping(cls) -> Dict[str, float]: 167 | mapping = {} 168 | effort = cls.effort() 169 | for joint in cls.joint_names(): 170 | mapping[joint] = effort[joint[-2:]] 171 | return mapping 172 | 173 | 174 | def print_joints() -> None: 175 | joints = Robot.all_joints() 176 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 177 | print(Robot()) 178 | print(len(joints)) 179 | 180 | 181 | if __name__ == "__main__": 182 | # python -m sim.Robot.joints 183 | print_joints() 184 | -------------------------------------------------------------------------------- /sim/resources/gpr_vel/README.md: -------------------------------------------------------------------------------- 1 | # URDF/XML development: 2 | 1. kol run https://cad.onshape.com/documents/bc3a557e2f92bdcea4099f0d/w/09713ac603d461fc1dff6b5d/e/5a4acdbffe6f8ed7c4e34970 --config-path config_example.json # noqa: E501 3 | 2. python sim/scripts/create_fixed_torso.py 4 | 3. Rotate first link 5 | 6 | 7 | 8 | 9 | 10 | 11 | 4. Fix left leg axes to align with expected directions 12 | 5. urf2mjcf robot.urdf -o robot.xml 13 | -------------------------------------------------------------------------------- /sim/resources/gpr_vel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr_vel/__init__.py -------------------------------------------------------------------------------- /sim/resources/gpr_vel/headless/README.md: -------------------------------------------------------------------------------- 1 | # URDF/XML development: 2 | 1. kol run https://cad.onshape.com/documents/bc3a557e2f92bdcea4099f0d/w/09713ac603d461fc1dff6b5d/e/5a4acdbffe6f8ed7c4e34970 --config-path config_example.json # noqa: E501 3 | 2. python sim/scripts/create_fixed_torso.py 4 | 3. Rotate first link 5 | 6 | 7 | 8 | 9 | 10 | 11 | 4. Fix left leg axes to align with expected directions 12 | 5. urf2mjcf robot.urdf -o robot.xml 13 | -------------------------------------------------------------------------------- /sim/resources/gpr_vel/headless/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/gpr_vel/headless/__init__.py -------------------------------------------------------------------------------- /sim/resources/gpr_vel/headless/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names.""" 2 | 3 | import textwrap 4 | from abc import ABC 5 | from typing import Dict, List, Tuple, Union 6 | 7 | 8 | class Node(ABC): 9 | @classmethod 10 | def children(cls) -> List["Union[Node, str]"]: 11 | return [ 12 | attr 13 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 14 | if isinstance(attr, (Node, str)) 15 | ] 16 | 17 | @classmethod 18 | def joints(cls) -> List[str]: 19 | return [ 20 | attr 21 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 22 | if isinstance(attr, str) 23 | ] 24 | 25 | @classmethod 26 | def joints_motors(cls) -> List[Tuple[str, str]]: 27 | joint_names: List[Tuple[str, str]] = [] 28 | for attr in dir(cls): 29 | if not attr.startswith("__"): 30 | attr2 = getattr(cls, attr) 31 | if isinstance(attr2, str): 32 | joint_names.append((attr, attr2)) 33 | 34 | return joint_names 35 | 36 | @classmethod 37 | def all_joints(cls) -> List[str]: 38 | leaves = cls.joints() 39 | for child in cls.children(): 40 | if isinstance(child, Node): 41 | leaves.extend(child.all_joints()) 42 | return leaves 43 | 44 | def __str__(self) -> str: 45 | parts = [str(child) for child in self.children()] 46 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 47 | return f"[{self.__class__.__name__}]{parts_str}" 48 | 49 | 50 | class LeftLeg(Node): 51 | hip_pitch = "left_hip_pitch_04" 52 | hip_yaw = "left_hip_yaw_03" 53 | hip_roll = "left_hip_roll_03" 54 | knee_pitch = "left_knee_04" 55 | ankle_pitch = "left_ankle_02" 56 | 57 | 58 | class RightLeg(Node): 59 | hip_pitch = "right_hip_pitch_04" 60 | hip_yaw = "right_hip_yaw_03" 61 | hip_roll = "right_hip_roll_03" 62 | knee_pitch = "right_knee_04" 63 | ankle_pitch = "right_ankle_02" 64 | 65 | 66 | class Legs(Node): 67 | left = LeftLeg() 68 | right = RightLeg() 69 | 70 | 71 | class Robot(Node): 72 | legs = Legs() 73 | 74 | height = 1.05 # - 0.151 #maybe? 75 | standing_height = 1.05 + 0.025 # - 0.151 76 | 77 | # height = 1.04 78 | # standing_height = 1.04 + 0.025 79 | 80 | # 1.3 m and 1.149m 81 | rotation = [0, 0, 0, 1] 82 | 83 | @classmethod 84 | def default_positions(cls) -> Dict[str, float]: 85 | return { 86 | Robot.legs.left.hip_pitch: 0.0, 87 | Robot.legs.left.hip_yaw: 0.0, 88 | Robot.legs.left.hip_roll: 0.0, 89 | Robot.legs.left.knee_pitch: 0.0, 90 | Robot.legs.left.ankle_pitch: 0.0, 91 | Robot.legs.right.hip_pitch: 0.0, 92 | Robot.legs.right.hip_yaw: 0.0, 93 | Robot.legs.right.hip_roll: 0.0, 94 | Robot.legs.right.knee_pitch: 0.0, 95 | Robot.legs.right.ankle_pitch: 0.0, 96 | } 97 | 98 | # CONTRACT - this should be ordered according to how the policy is trained. 99 | # E.g. the first entry should be the angle of the first joint in the policy. 100 | @classmethod 101 | def default_standing(cls) -> Dict[str, float]: 102 | return { 103 | Robot.legs.left.hip_pitch: 0.23, 104 | Robot.legs.left.hip_yaw: 0.0, 105 | Robot.legs.left.hip_roll: 0.0, 106 | Robot.legs.left.knee_pitch: 0.441, # negated 107 | Robot.legs.left.ankle_pitch: -0.195, # negated 108 | Robot.legs.right.hip_pitch: -0.23, 109 | Robot.legs.right.hip_yaw: 0.0, 110 | Robot.legs.right.hip_roll: 0.0, 111 | Robot.legs.right.knee_pitch: -0.441, # negated 112 | Robot.legs.right.ankle_pitch: 0.195, # negated 113 | } 114 | 115 | # CONTRACT - this should be ordered according to how the policy is trained. 116 | # E.g. the first entry should be the name of the first joint in the policy. 117 | @classmethod 118 | def joint_names(cls) -> List[str]: 119 | return list(cls.default_standing().keys()) 120 | 121 | # p_gains 122 | @classmethod 123 | def stiffness(cls) -> Dict[str, float]: 124 | return { 125 | "04": 85, 126 | "03": 40, 127 | "02": 30, 128 | } 129 | 130 | @classmethod 131 | def stiffness_mapping(cls) -> Dict[str, float]: 132 | mapping = {} 133 | stiffness = cls.stiffness() 134 | for joint in cls.joint_names(): 135 | mapping[joint] = stiffness[joint[-2:]] 136 | return mapping 137 | 138 | # d_gains 139 | @classmethod 140 | def damping(cls) -> Dict[str, float]: 141 | return { 142 | "04": 5, 143 | "03": 4, 144 | "02": 1, 145 | } 146 | 147 | @classmethod 148 | def damping_mapping(cls) -> Dict[str, float]: 149 | mapping = {} 150 | damping = cls.damping() 151 | for joint in cls.joint_names(): 152 | mapping[joint] = damping[joint[-2:]] 153 | print(mapping) 154 | return mapping 155 | 156 | # effort_limits 157 | @classmethod 158 | def effort(cls) -> Dict[str, float]: 159 | return { 160 | "04": 100, 161 | "03": 50, 162 | "02": 17, 163 | } 164 | 165 | @classmethod 166 | def effort_mapping(cls) -> Dict[str, float]: 167 | mapping = {} 168 | effort = cls.effort() 169 | for joint in cls.joint_names(): 170 | mapping[joint] = effort[joint[-2:]] 171 | return mapping 172 | 173 | 174 | def print_joints() -> None: 175 | joints = Robot.all_joints() 176 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 177 | print(Robot()) 178 | print(len(joints)) 179 | 180 | 181 | if __name__ == "__main__": 182 | # python -m sim.Robot.joints 183 | print_joints() 184 | -------------------------------------------------------------------------------- /sim/resources/gpr_vel/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names.""" 2 | 3 | import textwrap 4 | from abc import ABC 5 | from typing import Dict, List, Tuple, Union 6 | 7 | 8 | class Node(ABC): 9 | @classmethod 10 | def children(cls) -> List["Union[Node, str]"]: 11 | return [ 12 | attr 13 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 14 | if isinstance(attr, (Node, str)) 15 | ] 16 | 17 | @classmethod 18 | def joints(cls) -> List[str]: 19 | return [ 20 | attr 21 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 22 | if isinstance(attr, str) 23 | ] 24 | 25 | @classmethod 26 | def joints_motors(cls) -> List[Tuple[str, str]]: 27 | joint_names: List[Tuple[str, str]] = [] 28 | for attr in dir(cls): 29 | if not attr.startswith("__"): 30 | attr2 = getattr(cls, attr) 31 | if isinstance(attr2, str): 32 | joint_names.append((attr, attr2)) 33 | 34 | return joint_names 35 | 36 | @classmethod 37 | def all_joints(cls) -> List[str]: 38 | leaves = cls.joints() 39 | for child in cls.children(): 40 | if isinstance(child, Node): 41 | leaves.extend(child.all_joints()) 42 | return leaves 43 | 44 | def __str__(self) -> str: 45 | parts = [str(child) for child in self.children()] 46 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 47 | return f"[{self.__class__.__name__}]{parts_str}" 48 | 49 | 50 | class LeftLeg(Node): 51 | hip_pitch = "L_hip_y" 52 | hip_yaw = "L_hip_x" 53 | hip_roll = "L_hip_z" 54 | knee_pitch = "L_knee" 55 | ankle_pitch = "L_ankle" 56 | 57 | 58 | class RightLeg(Node): 59 | hip_pitch = "R_hip_y" 60 | hip_yaw = "R_hip_x" 61 | hip_roll = "R_hip_z" 62 | knee_pitch = "R_knee" 63 | ankle_pitch = "R_ankle" 64 | 65 | 66 | class Legs(Node): 67 | left = LeftLeg() 68 | right = RightLeg() 69 | 70 | 71 | class Robot(Node): 72 | legs = Legs() 73 | 74 | height = 1.05 75 | standing_height = 1.05 + 0.025 76 | rotation = [0, 0, 0, 1] 77 | 78 | @classmethod 79 | def isaac_to_real_signs(cls) -> Dict[str, int]: 80 | return { 81 | Robot.legs.left.hip_pitch: 1, 82 | Robot.legs.left.hip_yaw: 1, 83 | Robot.legs.left.hip_roll: 1, 84 | Robot.legs.left.knee_pitch: 1, 85 | Robot.legs.left.ankle_pitch: 1, 86 | Robot.legs.right.hip_pitch: -1, 87 | Robot.legs.right.hip_yaw: -1, 88 | Robot.legs.right.hip_roll: 1, 89 | Robot.legs.right.knee_pitch: -1, 90 | Robot.legs.right.ankle_pitch: 1, 91 | } 92 | 93 | @classmethod 94 | def isaac_to_mujoco_signs(cls) -> Dict[str, int]: 95 | return { 96 | Robot.legs.left.hip_pitch: 1, 97 | Robot.legs.left.hip_yaw: 1, 98 | Robot.legs.left.hip_roll: 1, 99 | Robot.legs.left.knee_pitch: 1, 100 | Robot.legs.left.ankle_pitch: 1, 101 | Robot.legs.right.hip_pitch: 1, 102 | Robot.legs.right.hip_yaw: 1, 103 | Robot.legs.right.hip_roll: 1, 104 | Robot.legs.right.knee_pitch: 1, 105 | Robot.legs.right.ankle_pitch: 1, 106 | } 107 | 108 | @classmethod 109 | def default_positions(cls) -> Dict[str, float]: 110 | return { 111 | Robot.legs.left.hip_pitch: 0.0, 112 | Robot.legs.left.hip_yaw: 0.0, 113 | Robot.legs.left.hip_roll: 0.0, 114 | Robot.legs.left.knee_pitch: 0.0, 115 | Robot.legs.left.ankle_pitch: 0.0, 116 | Robot.legs.right.hip_pitch: 0.0, 117 | Robot.legs.right.hip_yaw: 0.0, 118 | Robot.legs.right.hip_roll: 0.0, 119 | Robot.legs.right.knee_pitch: 0.0, 120 | Robot.legs.right.ankle_pitch: 0.0, 121 | } 122 | 123 | # CONTRACT - this should be ordered according to how the policy is trained. 124 | # E.g. the first entry should be the angle of the first joint in the policy. 125 | @classmethod 126 | def default_standing(cls) -> Dict[str, float]: 127 | return { 128 | Robot.legs.left.hip_pitch: 0.23, 129 | Robot.legs.left.hip_yaw: 0.0, 130 | Robot.legs.left.hip_roll: 0.0, 131 | Robot.legs.left.knee_pitch: -0.441, 132 | Robot.legs.left.ankle_pitch: -0.195, 133 | Robot.legs.right.hip_pitch: -0.23, 134 | Robot.legs.right.hip_yaw: 0.0, 135 | Robot.legs.right.hip_roll: 0.0, 136 | Robot.legs.right.knee_pitch: -0.441, 137 | Robot.legs.right.ankle_pitch: -0.195, 138 | } 139 | 140 | # CONTRACT - this should be ordered according to how the policy is trained. 141 | # E.g. the first entry should be the name of the first joint in the policy. 142 | @classmethod 143 | def joint_names(cls) -> List[str]: 144 | return list(cls.default_standing().keys()) 145 | 146 | @classmethod 147 | def default_limits(cls) -> Dict[str, Dict[str, float]]: 148 | return { 149 | Robot.legs.left.knee_pitch: {"lower": -1.57, "upper": 0}, 150 | Robot.legs.right.knee_pitch: {"lower": -1.57, "upper": 0}, 151 | } 152 | 153 | # p_gains 154 | @classmethod 155 | def stiffness(cls) -> Dict[str, float]: 156 | return { 157 | "hip_y": 85, 158 | "hip_x": 40, 159 | "hip_z": 40, 160 | "knee": 85, 161 | "ankle": 30, 162 | } 163 | 164 | @classmethod 165 | def stiffness_mapping(cls) -> Dict[str, float]: 166 | mapping = {} 167 | stiffness = cls.stiffness() 168 | for side in ["L", "R"]: 169 | for joint, value in stiffness.items(): 170 | mapping[f"{side}_{joint}"] = value 171 | return mapping 172 | 173 | # d_gains 174 | @classmethod 175 | def damping(cls) -> Dict[str, float]: 176 | return { 177 | "hip_y": 5, 178 | "hip_x": 4, 179 | "hip_z": 4, 180 | "knee": 5, 181 | "ankle": 1, 182 | } 183 | 184 | @classmethod 185 | def damping_mapping(cls) -> Dict[str, float]: 186 | mapping = {} 187 | damping = cls.damping() 188 | for side in ["L", "R"]: 189 | for joint, value in damping.items(): 190 | mapping[f"{side}_{joint}"] = value 191 | return mapping 192 | 193 | # effort_limits 194 | @classmethod 195 | def effort(cls) -> Dict[str, float]: 196 | return { 197 | "hip_y": 60, 198 | "hip_x": 40, 199 | "hip_z": 40, 200 | "knee": 60, 201 | "ankle": 17, 202 | } 203 | 204 | @classmethod 205 | def effort_mapping(cls) -> Dict[str, float]: 206 | mapping = {} 207 | effort = cls.effort() 208 | for side in ["L", "R"]: 209 | for joint, value in effort.items(): 210 | mapping[f"{side}_{joint}"] = value 211 | return mapping 212 | 213 | # vel_limits 214 | @classmethod 215 | def velocity(cls) -> Dict[str, float]: 216 | return {"hip": 1000, "knee": 1000, "ankle": 1000} 217 | 218 | @classmethod 219 | def friction(cls) -> Dict[str, float]: 220 | return { 221 | "hip": 0, 222 | "knee": 0, 223 | "ankle": 0.1, 224 | } 225 | 226 | 227 | def print_joints() -> None: 228 | joints = Robot.all_joints() 229 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 230 | print(Robot()) 231 | print(len(joints)) 232 | 233 | 234 | if __name__ == "__main__": 235 | # python -m sim.Robot.joints 236 | print_joints() 237 | -------------------------------------------------------------------------------- /sim/resources/h1_2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/h1_2/__init__.py -------------------------------------------------------------------------------- /sim/resources/h1_2/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names. 2 | 3 | The best way to re-generate this snippet for a new robot is to use the 4 | `sim/scripts/print_joints.py` script. This script will print out a hierarchical 5 | tree of the various joint names in the robot. 6 | """ 7 | 8 | import textwrap 9 | from abc import ABC 10 | from typing import Dict, List, Tuple, Union 11 | 12 | 13 | class Node(ABC): 14 | @classmethod 15 | def children(cls) -> List["Union[Node, str]"]: 16 | return [ 17 | attr 18 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 19 | if isinstance(attr, (Node, str)) 20 | ] 21 | 22 | @classmethod 23 | def joints(cls) -> List[str]: 24 | return [ 25 | attr 26 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 27 | if isinstance(attr, str) 28 | ] 29 | 30 | @classmethod 31 | def joints_motors(cls) -> List[Tuple[str, str]]: 32 | joint_names: List[Tuple[str, str]] = [] 33 | for attr in dir(cls): 34 | if not attr.startswith("__"): 35 | attr2 = getattr(cls, attr) 36 | if isinstance(attr2, str): 37 | joint_names.append((attr, attr2)) 38 | 39 | return joint_names 40 | 41 | @classmethod 42 | def all_joints(cls) -> List[str]: 43 | leaves = cls.joints() 44 | for child in cls.children(): 45 | if isinstance(child, Node): 46 | leaves.extend(child.all_joints()) 47 | return leaves 48 | 49 | def __str__(self) -> str: 50 | parts = [str(child) for child in self.children()] 51 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 52 | return f"[{self.__class__.__name__}]{parts_str}" 53 | 54 | 55 | class Torso(Node): 56 | roll = "torso_joint" 57 | 58 | 59 | class LeftArm(Node): 60 | shoulder_yaw = "left_shoulder_yaw_joint" 61 | shoulder_pitch = "left_shoulder_pitch_joint" 62 | shoulder_roll = "left_shoulder_roll_joint" 63 | elbow_pitch = "left_elbow_pitch_joint" 64 | 65 | 66 | class RightArm(Node): 67 | shoulder_yaw = "right_shoulder_yaw_joint" 68 | shoulder_pitch = "right_shoulder_pitch_joint" 69 | shoulder_roll = "right_shoulder_roll_joint" 70 | elbow_pitch = "right_elbow_pitch_joint" 71 | 72 | 73 | class LeftLeg(Node): 74 | hip_roll = "left_hip_roll_joint" 75 | hip_yaw = "left_hip_yaw_joint" 76 | hip_pitch = "left_hip_pitch_joint" 77 | knee_pitch = "left_knee_joint" 78 | ankle_pitch = "left_ankle_pitch_joint" 79 | ankle_roll = "left_ankle_roll_joint" 80 | 81 | 82 | class RightLeg(Node): 83 | hip_roll = "right_hip_roll_joint" 84 | hip_yaw = "right_hip_yaw_joint" 85 | hip_pitch = "right_hip_pitch_joint" 86 | knee_pitch = "right_knee_joint" 87 | ankle_pitch = "right_ankle_pitch_joint" 88 | ankle_roll = "right_ankle_roll_joint" 89 | 90 | 91 | class Legs(Node): 92 | left = LeftLeg() 93 | right = RightLeg() 94 | 95 | 96 | class Robot(Node): 97 | height = 0.9 98 | rotation = [0, 0, 0, 1] 99 | 100 | torso = Torso() 101 | left_arm = LeftArm() 102 | right_arm = RightArm() 103 | legs = Legs() 104 | 105 | @classmethod 106 | def default_standing(cls) -> Dict[str, float]: 107 | return { # = target angles [rad] when action = 0.0 108 | # left leg 109 | Robot.legs.left.hip_yaw: 0, 110 | Robot.legs.left.hip_roll: 0, 111 | Robot.legs.left.hip_pitch: -0.6, 112 | Robot.legs.left.knee_pitch: 1.2, 113 | Robot.legs.left.ankle_pitch: -0.6, 114 | Robot.legs.left.ankle_roll: 0.0, 115 | # right leg 116 | Robot.legs.right.hip_yaw: 0, 117 | Robot.legs.right.hip_roll: 0, 118 | Robot.legs.right.hip_pitch: -0.6, 119 | Robot.legs.right.knee_pitch: 1.2, 120 | Robot.legs.right.ankle_pitch: -0.6, 121 | Robot.legs.right.ankle_roll: 0.0, 122 | # torso 123 | Robot.torso.roll: 0, 124 | # left arm 125 | Robot.left_arm.shoulder_pitch: 0.4, 126 | Robot.left_arm.shoulder_roll: 0, 127 | Robot.left_arm.shoulder_yaw: 0, 128 | Robot.left_arm.elbow_pitch: 0.3, 129 | # right arm 130 | Robot.right_arm.shoulder_pitch: 0.4, 131 | Robot.right_arm.shoulder_roll: 0, 132 | Robot.right_arm.shoulder_yaw: 0, 133 | Robot.right_arm.elbow_pitch: 0.3, 134 | } 135 | 136 | # p_gains 137 | @classmethod 138 | def stiffness(cls) -> Dict[str, float]: 139 | return { 140 | "hip_pitch": 200, 141 | "hip_yaw": 200, 142 | "hip_roll": 200, 143 | "knee_joint": 300, 144 | "ankle_pitch": 40, 145 | "ankle_roll": 40, 146 | "shoulder_pitch": 80, 147 | "shoulder_yaw": 40, 148 | "shoulder_roll": 80, 149 | "elbow_pitch": 60, 150 | "torso_joint": 600, 151 | } 152 | 153 | # d_gains 154 | @classmethod 155 | def damping(cls) -> Dict[str, float]: 156 | return { 157 | "hip_pitch": 5, 158 | "hip_yaw": 5, 159 | "hip_roll": 5, 160 | "knee_joint": 7.5, 161 | "ankle_pitch": 1, 162 | "ankle_roll": 0.3, 163 | "shoulder_pitch": 2, 164 | "shoulder_yaw": 1, 165 | "shoulder_roll": 2, 166 | "elbow_pitch": 1, 167 | "torso_joint": 15, 168 | } 169 | 170 | 171 | def print_joints() -> None: 172 | joints = Robot.all_joints() 173 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 174 | print(Robot()) 175 | 176 | 177 | if __name__ == "__main__": 178 | # python -m sim.Robot.joints 179 | print_joints() 180 | -------------------------------------------------------------------------------- /sim/resources/h1_2/scene.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sim/resources/xbot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/xbot/__init__.py -------------------------------------------------------------------------------- /sim/resources/xbot/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names. 2 | 3 | The best way to re-generate this snippet for a new robot is to use the 4 | `sim/scripts/print_joints.py` script. This script will print out a hierarchical 5 | tree of the various joint names in the robot. 6 | """ 7 | 8 | import textwrap 9 | from abc import ABC 10 | from typing import Dict, List, Tuple, Union 11 | 12 | 13 | class Node(ABC): 14 | @classmethod 15 | def children(cls) -> List["Union[Node, str]"]: 16 | return [ 17 | attr 18 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 19 | if isinstance(attr, (Node, str)) 20 | ] 21 | 22 | @classmethod 23 | def joints(cls) -> List[str]: 24 | return [ 25 | attr 26 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 27 | if isinstance(attr, str) 28 | ] 29 | 30 | @classmethod 31 | def joints_motors(cls) -> List[Tuple[str, str]]: 32 | joint_names: List[Tuple[str, str]] = [] 33 | for attr in dir(cls): 34 | if not attr.startswith("__"): 35 | attr2 = getattr(cls, attr) 36 | if isinstance(attr2, str): 37 | joint_names.append((attr, attr2)) 38 | 39 | return joint_names 40 | 41 | @classmethod 42 | def all_joints(cls) -> List[str]: 43 | leaves = cls.joints() 44 | for child in cls.children(): 45 | if isinstance(child, Node): 46 | leaves.extend(child.all_joints()) 47 | return leaves 48 | 49 | def __str__(self) -> str: 50 | parts = [str(child) for child in self.children()] 51 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 52 | return f"[{self.__class__.__name__}]{parts_str}" 53 | 54 | 55 | class LeftLeg(Node): 56 | hip_roll = "left_leg_roll_joint" 57 | hip_yaw = "left_leg_yaw_joint" 58 | hip_pitch = "left_leg_pitch_joint" 59 | knee_pitch = "left_knee_joint" 60 | ankle_pitch = "left_ankle_pitch_joint" 61 | ankle_roll = "left_ankle_roll_joint" 62 | 63 | 64 | class RightLeg(Node): 65 | hip_roll = "right_leg_roll_joint" 66 | hip_yaw = "right_leg_yaw_joint" 67 | hip_pitch = "right_leg_pitch_joint" 68 | knee_pitch = "right_knee_joint" 69 | ankle_pitch = "right_ankle_pitch_joint" 70 | ankle_roll = "right_ankle_roll_joint" 71 | 72 | 73 | class Legs(Node): 74 | left = LeftLeg() 75 | right = RightLeg() 76 | 77 | 78 | class Robot(Node): 79 | height = 0.95 80 | rotation = [0, 0, 0, 1] 81 | 82 | legs = Legs() 83 | 84 | @classmethod 85 | def default_standing(cls) -> Dict[str, float]: 86 | return { 87 | # legs 88 | Robot.legs.left.hip_pitch: 0, 89 | Robot.legs.left.hip_roll: 0, 90 | Robot.legs.left.hip_yaw: 0, 91 | Robot.legs.left.knee_pitch: 0, 92 | Robot.legs.left.ankle_pitch: 0, 93 | Robot.legs.left.ankle_roll: 0, 94 | Robot.legs.right.hip_pitch: 0, 95 | Robot.legs.right.hip_roll: 0, 96 | Robot.legs.right.hip_yaw: 0, 97 | Robot.legs.right.knee_pitch: 0, 98 | Robot.legs.right.ankle_pitch: 0, 99 | Robot.legs.right.ankle_roll: 0, 100 | } 101 | 102 | @classmethod 103 | def default_limits(cls) -> Dict[str, Dict[str, float]]: 104 | return { 105 | # left leg 106 | Robot.legs.left.hip_pitch: { 107 | "lower": -1.57, 108 | "upper": 1.31, 109 | }, 110 | Robot.legs.left.hip_roll: { 111 | "lower": -0.44, 112 | "upper": 1.57, 113 | }, 114 | Robot.legs.left.hip_yaw: { 115 | "lower": -1.05, 116 | "upper": 1.05, 117 | }, 118 | Robot.legs.left.knee_pitch: { 119 | "lower": -1.05, 120 | "upper": 1.1, 121 | }, 122 | Robot.legs.left.ankle_pitch: { 123 | "lower": -0.7, 124 | "upper": 0.87, 125 | }, 126 | Robot.legs.left.ankle_roll: { 127 | "lower": -0.44, 128 | "upper": 0.44, 129 | }, 130 | # right leg 131 | Robot.legs.right.hip_pitch: {"lower": -1.31, "upper": 1.57}, 132 | Robot.legs.right.hip_roll: { 133 | "lower": -1.57, 134 | "upper": 0.44, 135 | }, 136 | Robot.legs.right.hip_yaw: { 137 | "lower": -1.05, 138 | "upper": 1.05, 139 | }, 140 | Robot.legs.right.knee_pitch: { 141 | "lower": -1.1, 142 | "upper": 1.05, 143 | }, 144 | Robot.legs.right.ankle_pitch: { 145 | "lower": -0.87, 146 | "upper": 0.7, 147 | }, 148 | Robot.legs.right.ankle_roll: { 149 | "lower": -0.44, 150 | "upper": 0.44, 151 | }, 152 | } 153 | 154 | # p_gains 155 | @classmethod 156 | def stiffness(cls) -> Dict[str, float]: 157 | return { 158 | "leg_pitch": 350, 159 | "leg_yaw": 200, 160 | "leg_roll": 200, 161 | "knee": 350, 162 | "ankle_pitch": 15, 163 | "ankle_roll": 15, 164 | } 165 | 166 | # d_gains 167 | @classmethod 168 | def damping(cls) -> Dict[str, float]: 169 | return { 170 | "leg_pitch": 10, 171 | "leg_yaw": 10, 172 | "leg_roll": 10, 173 | "knee": 10, 174 | "ankle_pitch": 10, 175 | "ankle_roll": 10, 176 | } 177 | 178 | # pos_limits 179 | @classmethod 180 | def effort(cls) -> Dict[str, float]: 181 | return { 182 | "leg_pitch": 250, 183 | "leg_yaw": 100, 184 | "leg_roll": 100, 185 | "knee": 250, 186 | "ankle_pitch": 100, 187 | "ankle_roll": 100, 188 | } 189 | 190 | # vel_limits 191 | @classmethod 192 | def velocity(cls) -> Dict[str, float]: 193 | return { 194 | "leg_pitch": 12, 195 | "leg_yaw": 12, 196 | "leg_roll": 12, 197 | "knee": 12, 198 | "ankle_pitch": 12, 199 | "ankle_roll": 12, 200 | } 201 | 202 | @classmethod 203 | def friction(cls) -> Dict[str, float]: 204 | return { 205 | "leg_pitch": 0.0, 206 | "leg_yaw": 0.0, 207 | "leg_roll": 0.0, 208 | "knee": 0.0, 209 | "ankle_pitch": 0.0, 210 | "ankle_roll": 0.1, 211 | } 212 | 213 | 214 | def print_joints() -> None: 215 | joints = Robot.all_joints() 216 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 217 | print(Robot()) 218 | 219 | 220 | if __name__ == "__main__": 221 | # python -m sim.Robot.joints 222 | print_joints() 223 | -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_1Flange.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_1Flange.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_1Flange_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_1Flange_2.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_BothFlange.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_BothFlange.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_BothFlange_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_BothFlange_2.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_BothFlange_3.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_BothFlange_3.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_BothFlange_4.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_BothFlange_4.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_BothFlange_5.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_BothFlange_5.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/3215_BothFlange_6.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/3215_BothFlange_6.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/FINGER_1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/FINGER_1.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/FINGER_1_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/FINGER_1_2.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/FOOT.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/FOOT.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/FOOT_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/FOOT_2.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/L-ARM-MIRROR_1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/L-ARM-MIRROR_1.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/R-ARM-MIRROR-1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/R-ARM-MIRROR-1.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/U-HIP-L.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/U-HIP-L.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/U-HIP-R.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/U-HIP-R.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/Z-BOT2-MASTER-SHOULDER2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/Z-BOT2-MASTER-SHOULDER2.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/Z-BOT2-MASTER-SHOULDER2_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/Z-BOT2-MASTER-SHOULDER2_2.stl -------------------------------------------------------------------------------- /sim/resources/zbot2/meshes/Z-BOT2_MASTER-BODY-SKELETON.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zbot2/meshes/Z-BOT2_MASTER-BODY-SKELETON.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/__init__.py -------------------------------------------------------------------------------- /sim/resources/zeroth/joints.py: -------------------------------------------------------------------------------- 1 | """Defines a more Pythonic interface for specifying the joint names. 2 | 3 | The best way to re-generate this snippet for a new robot is to use the 4 | `sim/scripts/print_joints.py` script. This script will print out a hierarchical 5 | tree of the various joint names in the robot. 6 | """ 7 | 8 | import textwrap 9 | from abc import ABC 10 | from typing import Dict, List, Tuple, Union 11 | 12 | 13 | class Node(ABC): 14 | @classmethod 15 | def children(cls) -> List["Union[Node, str]"]: 16 | return [ 17 | attr 18 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 19 | if isinstance(attr, (Node, str)) 20 | ] 21 | 22 | @classmethod 23 | def joints(cls) -> List[str]: 24 | return [ 25 | attr 26 | for attr in (getattr(cls, attr) for attr in dir(cls) if not attr.startswith("__")) 27 | if isinstance(attr, str) 28 | ] 29 | 30 | @classmethod 31 | def joints_motors(cls) -> List[Tuple[str, str]]: 32 | joint_names: List[Tuple[str, str]] = [] 33 | for attr in dir(cls): 34 | if not attr.startswith("__"): 35 | attr2 = getattr(cls, attr) 36 | if isinstance(attr2, str): 37 | joint_names.append((attr, attr2)) 38 | 39 | return joint_names 40 | 41 | @classmethod 42 | def all_joints(cls) -> List[str]: 43 | leaves = cls.joints() 44 | for child in cls.children(): 45 | if isinstance(child, Node): 46 | leaves.extend(child.all_joints()) 47 | return leaves 48 | 49 | def __str__(self) -> str: 50 | parts = [str(child) for child in self.children()] 51 | parts_str = textwrap.indent("\n" + "\n".join(parts), " ") 52 | return f"[{self.__class__.__name__}]{parts_str}" 53 | 54 | 55 | class LeftLeg(Node): 56 | hip_roll = "left_hip_roll" 57 | hip_yaw = "left_hip_yaw" 58 | hip_pitch = "left_hip_pitch" 59 | knee_pitch = "left_knee_pitch" 60 | ankle_pitch = "left_ankle_pitch" 61 | 62 | 63 | class RightLeg(Node): 64 | hip_roll = "right_hip_roll" 65 | hip_yaw = "right_hip_yaw" 66 | hip_pitch = "right_hip_pitch" 67 | knee_pitch = "right_knee_pitch" 68 | ankle_pitch = "right_ankle_pitch" 69 | 70 | 71 | class Legs(Node): 72 | left = LeftLeg() 73 | right = RightLeg() 74 | 75 | 76 | class Robot(Node): 77 | height = 0.34 78 | rotation = [0, 0, 0, 1.0] 79 | 80 | legs = Legs() 81 | 82 | @classmethod 83 | def default_standing(cls) -> Dict[str, float]: 84 | return { 85 | # Legs 86 | cls.legs.left.hip_pitch: -0.267, 87 | cls.legs.left.knee_pitch: -0.741, 88 | cls.legs.left.hip_yaw: 0, 89 | cls.legs.left.hip_roll: 0, 90 | cls.legs.left.ankle_pitch: 0.581, 91 | cls.legs.right.hip_pitch: -0.267, 92 | cls.legs.right.knee_pitch: 0.741, 93 | cls.legs.right.ankle_pitch: -0.581, 94 | cls.legs.right.hip_yaw: 0, 95 | cls.legs.right.hip_roll: 0, 96 | } 97 | 98 | @classmethod 99 | def default_limits(cls) -> Dict[str, Dict[str, float]]: 100 | return { 101 | Robot.legs.left.knee_pitch: { 102 | "lower": -1.57, 103 | "upper": 0, 104 | }, 105 | Robot.legs.right.knee_pitch: { 106 | "lower": 0, 107 | "upper": 1.57, 108 | }, 109 | } 110 | 111 | # p_gains 112 | @classmethod 113 | def stiffness(cls) -> Dict[str, float]: 114 | return { 115 | "hip_pitch": 17.681462808698132, 116 | "hip_yaw": 17.681462808698132, 117 | "hip_roll": 17.681462808698132, 118 | "knee_pitch": 17.681462808698132, 119 | "ankle_pitch": 17.681462808698132, 120 | } 121 | 122 | # d_gains 123 | @classmethod 124 | def damping(cls) -> Dict[str, float]: 125 | return { 126 | "hip_pitch": 0.5354656169048285, 127 | "hip_yaw": 0.5354656169048285, 128 | "hip_roll": 0.5354656169048285, 129 | "knee_pitch": 0.5354656169048285, 130 | "ankle_pitch": 0.5354656169048285, 131 | } 132 | 133 | # pos_limits 134 | @classmethod 135 | def effort(cls) -> Dict[str, float]: 136 | return { 137 | "hip_pitch": 10, 138 | "hip_yaw": 10, 139 | "hip_roll": 10, 140 | "knee_pitch": 10, 141 | "ankle_pitch": 10, 142 | } 143 | 144 | # vel_limits 145 | @classmethod 146 | def velocity(cls) -> Dict[str, float]: 147 | return { 148 | "hip_pitch": 10, 149 | "hip_yaw": 10, 150 | "hip_roll": 10, 151 | "knee_pitch": 10, 152 | "ankle_pitch": 10, 153 | } 154 | 155 | @classmethod 156 | def friction(cls) -> Dict[str, float]: 157 | return { 158 | "ankle_pitch": 0.01, 159 | } 160 | 161 | 162 | def print_joints() -> None: 163 | joints = Robot.all_joints() 164 | assert len(joints) == len(set(joints)), "Duplicate joint names found!" 165 | print(Robot()) 166 | 167 | 168 | if __name__ == "__main__": 169 | # python -m sim.Robot.joints 170 | print_joints() 171 | -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/Part_76.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/Part_76.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/Robot_-_STS3250_v1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/Robot_-_STS3250_v1.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/Robot_-_STS3250_v1_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/Robot_-_STS3250_v1_2.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/Rotor.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/Rotor.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/Rotor_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/Rotor_2.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/foot_bracket_for_5dof_leg_v9.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/foot_bracket_for_5dof_leg_v9.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/foot_bracket_for_5dof_leg_v9_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/foot_bracket_for_5dof_leg_v9_2.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/leg_top_bracket_v8_1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/leg_top_bracket_v8_1.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/leg_top_bracket_v8_1_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/leg_top_bracket_v8_1_2.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/leg_top_bracket_v9.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/leg_top_bracket_v9.stl -------------------------------------------------------------------------------- /sim/resources/zeroth/meshes/leg_top_bracket_v9_2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/resources/zeroth/meshes/leg_top_bracket_v9_2.stl -------------------------------------------------------------------------------- /sim/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/scripts/__init__.py -------------------------------------------------------------------------------- /sim/scripts/calibration_mujoco.py: -------------------------------------------------------------------------------- 1 | """Calibrate the joint position of the robot. 2 | 3 | Run: 4 | python sim/scripts/calibration_mujoco.py --embodiment stompypro 5 | """ 6 | 7 | import argparse 8 | import math 9 | import os 10 | from copy import deepcopy 11 | from typing import Any 12 | 13 | import matplotlib.pyplot as plt 14 | import mujoco 15 | import mujoco_viewer 16 | import numpy as np 17 | from scipy.spatial.transform import Rotation as R 18 | 19 | from sim.scripts.create_fixed_torso import load_embodiment 20 | 21 | import torch # isort: skip 22 | 23 | 24 | def get_obs(data: mujoco.MjData) -> tuple: 25 | """Extracts an observation from the mujoco data structure. 26 | 27 | Args: 28 | data: The mujoco data structure. 29 | 30 | Returns: 31 | A tuple containing the joint positions, velocities, 32 | quaternions, linear velocities, angular velocities, and gravity vector. 33 | """ 34 | q = data.qpos.astype(np.double) 35 | dq = data.qvel.astype(np.double) 36 | quat = data.sensor("orientation").data[[1, 2, 3, 0]].astype(np.double) 37 | r = R.from_quat(quat) 38 | v = r.apply(data.qvel[:3], inverse=True).astype(np.double) # In the base frame 39 | omega = data.sensor("angular-velocity").data.astype(np.double) 40 | gvec = r.apply(np.array([0.0, 0.0, -1.0]), inverse=True).astype(np.double) 41 | return (q, dq, quat, v, omega, gvec) 42 | 43 | 44 | def pd_control( 45 | target_q: np.ndarray, 46 | q: np.ndarray, 47 | kp: np.ndarray, 48 | target_dq: np.ndarray, 49 | dq: np.ndarray, 50 | kd: np.ndarray, 51 | default: np.ndarray, 52 | ) -> np.ndarray: 53 | """Calculates torques from position commands. 54 | 55 | Args: 56 | target_q: The target joint positions. 57 | q: The current joint positions. 58 | kp: The position gain. 59 | target_dq: The target joint velocities. 60 | dq: The current joint velocities. 61 | kd: The velocity gain. 62 | default: The default joint positions. 63 | 64 | Returns: 65 | The calculated torques. 66 | """ 67 | return kp * (target_q + default - q) - kd * dq 68 | 69 | 70 | def run_mujoco(cfg: Any, joint_id: int = 0, steps: int = 1000) -> None: # noqa: ANN401 71 | """Run the Mujoco motor identification. 72 | 73 | Args: 74 | cfg: The configuration object containing simulation settings. 75 | joint_id: The joint id to calibrate. 76 | steps: The number of steps to run the simulation. 77 | 78 | Returns: 79 | None 80 | """ 81 | model_dir = os.environ.get("MODEL_DIR") 82 | mujoco_model_path = f"{model_dir}/{args.embodiment}/robot_calibration.xml" 83 | model = mujoco.MjModel.from_xml_path(mujoco_model_path) 84 | model.opt.timestep = cfg.sim_config.dt 85 | data = mujoco.MjData(model) 86 | joint_names = [model.joint(joint_id).name for joint_id in range(model.njnt)] 87 | print("Joint names:", joint_names) 88 | 89 | try: 90 | data.qpos = model.keyframe("default").qpos 91 | default = deepcopy(model.keyframe("default").qpos)[-cfg.num_actions :] 92 | print("Default position:", default) 93 | except Exception as _: 94 | print("No default position found, using zero initialization") 95 | default = np.zeros(cfg.num_actions) # 3 for pos, 4 for quat, cfg.num_actions for joints 96 | 97 | mujoco.mj_step(model, data) 98 | 99 | data.qvel = np.zeros_like(data.qvel) 100 | data.qacc = np.zeros_like(data.qacc) 101 | viewer = mujoco_viewer.MujocoViewer(model, data) 102 | 103 | target_q = np.zeros((cfg.num_actions), dtype=np.double) 104 | 105 | # Parameters for calibration 106 | step = 0 107 | dt = cfg.sim_config.dt 108 | joint_id = 0 109 | # Lists to store time and position data for plotting 110 | time_data = [] 111 | position_data = [] 112 | 113 | while step < steps: 114 | q, dq, _, _, _, _ = get_obs(data) # type: ignore[misc] 115 | q = q[-cfg.num_actions :] 116 | dq = dq[-cfg.num_actions :] 117 | 118 | sin_pos = torch.tensor(math.sin(2 * math.pi * step * dt / cfg.sim_config.cycle_time)) 119 | 120 | # N hz control loop 121 | if step % cfg.sim_config.decimation == 0: 122 | target_q = default 123 | target_q[joint_id] = sin_pos 124 | 125 | time_data.append(step) 126 | position_data.append(q[joint_id] - default[joint_id]) 127 | 128 | # Generate PD control 129 | tau = pd_control(target_q, q, cfg.robot_config.kps, default, dq, cfg.robot_config.kds, default) # Calc torques 130 | 131 | tau = np.clip(tau, -cfg.robot_config.tau_limit, cfg.robot_config.tau_limit) # Clamp torques 132 | 133 | data.ctrl = tau 134 | 135 | mujoco.mj_step(model, data) 136 | step += 1 137 | viewer.render() 138 | 139 | # Create the plot 140 | plt.figure(figsize=(10, 6)) 141 | plt.plot(time_data, position_data) 142 | plt.title("Joint Position over Time") 143 | plt.xlabel("Step (10hz)") 144 | plt.ylabel("Joint Position") 145 | plt.grid(True) 146 | plt.savefig("joint_position_plot.png") 147 | plt.show() 148 | 149 | viewer.close() 150 | 151 | 152 | if __name__ == "__main__": 153 | parser = argparse.ArgumentParser(description="Deployment script.") 154 | parser.add_argument("--embodiment", type=str, required=True, help="embodiment") 155 | parser.add_argument("--joint_id", type=int, default=1, help="joint_id") 156 | args = parser.parse_args() 157 | 158 | robot = load_embodiment(args.embodiment) 159 | 160 | class Sim2simCfg: 161 | num_actions = len(robot.all_joints()) 162 | 163 | class env: # noqa: N801 164 | num_actions = len(robot.all_joints()) 165 | frame_stack = 15 166 | c_frame_stack = 3 167 | num_single_obs = 11 + num_actions * c_frame_stack 168 | num_observations = int(frame_stack * num_single_obs) 169 | 170 | class sim_config: # noqa: N801 171 | sim_duration = 60.0 172 | dt = 0.001 173 | decimation = 20 174 | cycle_time = 0.64 175 | 176 | class robot_config: # noqa: N801 177 | tau_factor = 0.85 178 | tau_limit = np.array(list(robot.stiffness().values()) + list(robot.stiffness().values())) * tau_factor 179 | kps = tau_limit 180 | kds = np.array(list(robot.damping().values()) + list(robot.damping().values())) 181 | 182 | class normalization: # noqa: N801 183 | class obs_scales: # noqa: N801 184 | lin_vel = 2.0 185 | ang_vel = 1.0 186 | dof_pos = 1.0 187 | dof_vel = 0.05 188 | 189 | clip_observations = 18.0 190 | clip_actions = 18.0 191 | 192 | class control: # noqa: N801 193 | action_scale = 0.25 194 | 195 | run_mujoco(Sim2simCfg(), joint_id=0) 196 | -------------------------------------------------------------------------------- /sim/scripts/create_fixed_torso.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="valid-newtype" 2 | """This script updates the URDF file to fix the joints of the robot.""" 3 | 4 | import argparse 5 | import importlib 6 | import xml.etree.ElementTree as ET 7 | from pathlib import Path 8 | from typing import Any 9 | 10 | 11 | def load_embodiment(embodiment: str) -> Any: # noqa: ANN401 12 | # Dynamically import embodiment based on MODEL_DIR 13 | module_name = f"sim.resources.{embodiment}.joints" 14 | module = importlib.import_module(module_name) 15 | robot = getattr(module, "Robot") 16 | print(robot) 17 | return robot 18 | 19 | 20 | def update_urdf(model_path: str, embodiment: str) -> None: 21 | tree = ET.parse(Path(model_path) / "robot.urdf") 22 | root = tree.getroot() 23 | robot = load_embodiment(embodiment) 24 | print(robot.default_standing()) 25 | revolute_joints = set(robot.default_standing().keys()) 26 | 27 | joint_limits = robot.default_limits() 28 | effort = robot.effort() 29 | velocity = robot.velocity() 30 | friction = robot.friction() 31 | 32 | for joint in root.findall("joint"): 33 | joint_name = joint.get("name") 34 | 35 | if joint_name not in revolute_joints: 36 | joint.set("type", "fixed") 37 | else: 38 | limit = joint.find("limit") 39 | if limit is not None: 40 | limits = joint_limits.get(joint_name, {}) 41 | if limits: 42 | lower = str(limits.get("lower", 0.0)) 43 | upper = str(limits.get("upper", 0.0)) 44 | limit.set("lower", lower) 45 | limit.set("upper", upper) 46 | for key, value in effort.items(): 47 | if key in joint_name: # type: ignore[operator] 48 | limit.set("effort", str(value)) 49 | for key, value in velocity.items(): 50 | if key in joint_name: # type: ignore[operator] 51 | limit.set("velocity", str(value)) 52 | dynamics = joint.find("dynamics") 53 | if dynamics is not None: 54 | for key, value in friction.items(): 55 | if key in joint_name: # type: ignore[operator] 56 | dynamics.set("friction", str(value)) 57 | else: 58 | # Create and add new dynamics element 59 | dynamics = ET.SubElement(joint, "dynamics") 60 | dynamics.set("damping", "0.0") 61 | # Set friction if exists for this joint 62 | for key, value in friction.items(): 63 | if key in joint_name: # type: ignore[operator] 64 | dynamics.set("friction", str(value)) 65 | break 66 | else: 67 | dynamics.set("friction", "0.0") 68 | 69 | # Save the modified URDF to a new file 70 | tree.write(Path(model_path) / "robot_fixed.urdf", xml_declaration=False) 71 | 72 | 73 | def main() -> None: 74 | parser = argparse.ArgumentParser(description="Update URDF file to fix robot joints.") 75 | parser.add_argument("--model_path", type=str, help="Path to the model directory", default="sim/resources/gpr") 76 | parser.add_argument("--embodiment", type=str, help="Embodiment to use", default="gpr") 77 | args = parser.parse_args() 78 | 79 | update_urdf(args.model_path, args.embodiment) 80 | # create_mjcf(Path(args.model_path) / "robot") 81 | 82 | 83 | if __name__ == "__main__": 84 | main() 85 | -------------------------------------------------------------------------------- /sim/scripts/install_isaac_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Script to set up Isaac Sim on a cluster. 3 | # This script was adapted from here: 4 | # https://docs.omniverse.nvidia.com/isaacsim/latest/installation/install_container.html 5 | 6 | set -e 7 | 8 | # Create a directory for installation artifacts. 9 | build_dir=$(pwd)build/sim/ 10 | mkdir -p ${build_dir} 11 | 12 | showmsg() { 13 | echo -e "\e[1;32m$1\e[0m" 14 | } 15 | 16 | showmsg "Updating and installing dependencies..." 17 | sudo apt-get update 18 | sudo apt install build-essential -y 19 | 20 | # Install NVIDIA drivers. This shouldn't be necessary since the cluster 21 | # already has NVIDIA drivers installed. 22 | # if [[ ! -f ${build_dir}/NVIDIA-Linux-x86_64-525.85.05.run ]]; then 23 | # showmsg "Downloading NVIDIA drivers..." 24 | # wget https://us.download.nvidia.com/XFree86/Linux-x86_64/525.85.05/NVIDIA-Linux-x86_64-525.85.05.run -P ${build_dir} 25 | # chmod +x ${build_dir}/NVIDIA-Linux-x86_64-525.85.05.run 26 | # sudo ${build_dir}/NVIDIA-Linux-x86_64-525.85.05.run 27 | # fi 28 | 29 | # Docker installation using the convenience script 30 | if [[ ! -f /usr/bin/docker ]]; then 31 | showmsg "Installing Docker..." 32 | curl -fsSL https://get.docker.com -o ${build_dir}get-docker.sh 33 | sudo sh ${build_dir}get-docker.sh 34 | 35 | showmsg "Post-install steps for Docker..." 36 | sudo groupadd docker 37 | sudo usermod -aG docker $USER 38 | newgrp docker 39 | 40 | showmsg "Verifying Docker installation..." 41 | docker run hello-world 42 | fi 43 | 44 | # Configure the repository 45 | if [[ ! -f /etc/apt/sources.list.d/nvidia-container-toolkit.list ]]; then 46 | showmsg "Configuring the NVIDIA Container Toolkit repository..." 47 | if [[ ! -f /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg ]]; then 48 | curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \ 49 | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg 50 | fi 51 | curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ 52 | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ 53 | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list 54 | 55 | showmsg "Updating the package list..." 56 | sudo apt-get update 57 | 58 | showmsg "Installing NVIDIA Container Toolkit..." 59 | sudo apt-get install -y nvidia-container-toolkit 60 | sudo systemctl restart docker 61 | fi 62 | 63 | showmsg "Verifying NVIDIA Container Toolkit..." 64 | docker run --rm --gpus all ubuntu nvidia-smi 65 | 66 | showmsg "Installing NVIDIA Container Runtime..." 67 | docker login nvcr.io 68 | docker pull nvcr.io/nvidia/isaac-sim:2023.1.1 69 | -------------------------------------------------------------------------------- /sim/scripts/print_joints.py: -------------------------------------------------------------------------------- 1 | """Parses the URDF file and prints the joint names and types.""" 2 | 3 | import argparse 4 | import xml.etree.ElementTree as ET 5 | from typing import Dict, List 6 | 7 | 8 | def main() -> None: 9 | parser = argparse.ArgumentParser(description="Gets the links and joints for a URDF") 10 | parser.add_argument("urdf_path", help="The path to the URDF file") 11 | parser.add_argument("--ignore-joint-type", nargs="*", default=["fixed"], help="The joint types to ignore") 12 | args = parser.parse_args() 13 | 14 | ignore_joint_type = set(args.ignore_joint_type) 15 | 16 | with open(args.urdf_path, "r") as urdf_file: 17 | urdf = ET.parse(urdf_file).getroot() 18 | 19 | # Gets the relevant joint names. 20 | joint_names: List[str] = [] 21 | for joint in urdf.findall("joint"): 22 | joint_type = joint.attrib["type"] 23 | if joint_type in ignore_joint_type: 24 | continue 25 | joint_name = joint.attrib["name"] 26 | joint_names.append(joint_name) 27 | 28 | # Makes a "tree" of the joints using common prefixes. 29 | joint_names.sort() 30 | joint_tree: Dict = {} 31 | for joint_name in joint_names: 32 | parts = joint_name.split("_") 33 | current_tree = joint_tree 34 | for part in parts: 35 | current_tree = current_tree.setdefault(part, {}) 36 | 37 | # Collapses nodes with just one child. 38 | def collapse_tree(tree: Dict) -> None: 39 | for key, value in list(tree.items()): 40 | collapse_tree(value) 41 | if len(value) == 1: 42 | child_key, child_value = next(iter(value.items())) 43 | tree[key + "_" + child_key] = child_value 44 | del tree[key] 45 | 46 | collapse_tree(joint_tree) 47 | 48 | # Replaces leaf nodes with their full names. 49 | def replace_leaves(tree: Dict, prefix: str) -> None: 50 | for key, value in list(tree.items()): 51 | if not value: 52 | tree[key] = prefix + key 53 | else: 54 | replace_leaves(value, prefix + key + "_") 55 | 56 | replace_leaves(joint_tree, "") 57 | 58 | # Prints the tree. 59 | def print_tree(tree: Dict, depth: int = 0) -> None: 60 | for key, value in tree.items(): 61 | if isinstance(value, dict): 62 | print(" " * depth + key) 63 | print_tree(value, depth + 1) 64 | else: 65 | print(" " * depth + key + ": " + value) 66 | 67 | print_tree(joint_tree) 68 | 69 | 70 | if __name__ == "__main__": 71 | # python -m sim.scripts.print_joints 72 | main() 73 | -------------------------------------------------------------------------------- /sim/scripts/push_standing_tests.py: -------------------------------------------------------------------------------- 1 | """Id and standing test. 2 | 3 | Run: 4 | python sim/scripts/push_standing_tests.py --load_model kinfer.onnx --embodiment zbot2 5 | """ 6 | 7 | import argparse 8 | import os 9 | from copy import deepcopy 10 | from dataclasses import dataclass 11 | from typing import List, TypedDict 12 | 13 | import mediapy as media 14 | import mujoco 15 | import mujoco_viewer 16 | import numpy as np 17 | from tqdm import tqdm 18 | 19 | 20 | @dataclass 21 | class ModelInfo(TypedDict): 22 | sim_dt: float 23 | tau_factor: float 24 | num_actions: int 25 | num_observations: int 26 | robot_effort: List[float] 27 | robot_stiffness: List[float] 28 | robot_damping: List[float] 29 | 30 | 31 | def pd_control( 32 | target_q: np.ndarray, 33 | q: np.ndarray, 34 | kp: np.ndarray, 35 | dq: np.ndarray, 36 | kd: np.ndarray, 37 | default: np.ndarray, 38 | ) -> np.ndarray: 39 | """Calculates torques from position commands. 40 | 41 | Args: 42 | target_q: The target position. 43 | q: The current position. 44 | kp: The proportional gain. 45 | dq: The current velocity. 46 | kd: The derivative gain. 47 | default: The default position. 48 | """ 49 | return kp * (target_q + default - q) - kd * dq 50 | 51 | 52 | def run_test( 53 | embodiment: str, 54 | kp: float = 1.0, 55 | kd: float = 1.0, 56 | push_force: float = 1.0, 57 | sim_duration: float = 3.0, 58 | effort: float = 5.0, 59 | ) -> None: 60 | """Run the Mujoco simulation using the provided policy and configuration. 61 | 62 | Args: 63 | embodiment: The embodiment name. 64 | kp: The proportional gain. 65 | kd: The derivative gain. 66 | push_force: The force to apply to the robot. 67 | sim_duration: The duration of the simulation. 68 | effort: The effort to apply to the robot. 69 | """ 70 | model_info: ModelInfo = { 71 | "sim_dt": 0.001, 72 | "tau_factor": 2, 73 | "num_actions": 10, 74 | "num_observations": 10, 75 | "robot_effort": [effort] * 10, 76 | "robot_stiffness": [kp] * 10, 77 | "robot_damping": [kd] * 10, 78 | } 79 | frames = [] 80 | framerate = 30 81 | model_dir = os.environ.get("MODEL_DIR", "sim/resources") 82 | mujoco_model_path = f"{model_dir}/{embodiment}/robot_fixed.xml" 83 | 84 | model = mujoco.MjModel.from_xml_path(mujoco_model_path) 85 | model.opt.timestep = model_info["sim_dt"] 86 | data = mujoco.MjData(model) 87 | 88 | tau_limit = np.array(list(model_info["robot_effort"])) * model_info["tau_factor"] 89 | kps = np.array(model_info["robot_stiffness"]) 90 | kds = np.array(model_info["robot_damping"]) 91 | print(kps) 92 | print(kds) 93 | print(tau_limit) 94 | 95 | data.qpos = model.keyframe("standing").qpos 96 | default = deepcopy(model.keyframe("standing").qpos)[-model_info["num_actions"] :] 97 | print("Default position:", default) 98 | 99 | mujoco.mj_step(model, data) 100 | for ii in range(len(data.ctrl) + 1): 101 | print(data.joint(ii).id, data.joint(ii).name) 102 | 103 | data.qvel = np.zeros_like(data.qvel) 104 | data.qacc = np.zeros_like(data.qacc) 105 | 106 | target_q = np.zeros((model_info["num_actions"]), dtype=np.double) 107 | viewer = mujoco_viewer.MujocoViewer(model, data, "offscreen") 108 | 109 | force_application_interval = 1000 # Apply force every 1000 steps (1 second at 1000Hz) 110 | force_magnitude_range = (-push_force, push_force) # Force range in Newtons 111 | force_duration = 100 # Duration of force application in timesteps 112 | force_timer = 0 113 | 114 | for timestep in tqdm(range(int(sim_duration / model_info["sim_dt"])), desc="Simulating..."): 115 | # Obtain an observation 116 | q = data.qpos.astype(np.double)[-model_info["num_actions"] :] 117 | dq = data.qvel.astype(np.double)[-model_info["num_actions"] :] 118 | 119 | # Generate PD control 120 | tau = pd_control(target_q, q, kps, dq, kds, default) # Calc torques 121 | tau = np.clip(tau, -tau_limit, tau_limit) # Clamp torques 122 | 123 | data.ctrl = tau 124 | 125 | # Apply random forces periodically 126 | if timestep % force_application_interval == 0: 127 | print("Applying force") 128 | # Generate random force vector 129 | random_force = np.random.uniform(force_magnitude_range[0], force_magnitude_range[1], size=3) 130 | force_timer = force_duration 131 | 132 | # Apply force if timer is active 133 | if force_timer > 0: 134 | data.xfrc_applied[1] = np.concatenate([random_force, np.zeros(3)]) 135 | force_timer -= 1 136 | else: 137 | data.xfrc_applied[1] = np.zeros(6) 138 | 139 | mujoco.mj_step(model, data) 140 | if timestep % 100 == 0: 141 | img = viewer.read_pixels() 142 | frames.append(img) 143 | 144 | # viewer.render() 145 | breakpoint() 146 | media.write_video("push_tests.mp4", frames, fps=framerate) 147 | 148 | 149 | if __name__ == "__main__": 150 | parser = argparse.ArgumentParser(description="Deployment script.") 151 | parser.add_argument("--embodiment", type=str, required=True, help="Embodiment name.") 152 | parser.add_argument("--kp", type=float, default=17.0, help="Path to run to load from.") 153 | parser.add_argument("--kd", type=float, default=1.0, help="Path to run to load from.") 154 | parser.add_argument("--push_force", type=float, default=1.0, help="Path to run to load from.") 155 | args = parser.parse_args() 156 | 157 | run_test(args.embodiment, args.kp, args.kd, args.push_force) 158 | -------------------------------------------------------------------------------- /sim/scripts/run_headless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Runs Isaac in headless mode. 3 | 4 | docker run \ 5 | --name isaac-sim \ 6 | --entrypoint bash \ 7 | -it \ 8 | --gpus all \ 9 | -e "ACCEPT_EULA=Y" \ 10 | --rm --network=host \ 11 | -e "PRIVACY_CONSENT=Y" \ 12 | -v ~/docker/isaac-sim/cache/kit:/isaac-sim/kit/cache:rw \ 13 | -v ~/docker/isaac-sim/cache/ov:/root/.cache/ov:rw \ 14 | -v ~/docker/isaac-sim/cache/pip:/root/.cache/pip:rw \ 15 | -v ~/docker/isaac-sim/cache/glcache:/root/.cache/nvidia/GLCache:rw \ 16 | -v ~/docker/isaac-sim/cache/computecache:/root/.nv/ComputeCache:rw \ 17 | -v ~/docker/isaac-sim/logs:/root/.nvidia-omniverse/logs:rw \ 18 | -v ~/docker/isaac-sim/data:/root/.local/share/ov/data:rw \ 19 | -v ~/docker/isaac-sim/documents:/root/Documents:rw \ 20 | nvcr.io/nvidia/isaac-sim:2023.1.1 21 | -------------------------------------------------------------------------------- /sim/scripts/simulate_mjcf.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="valid-newtype" 2 | """Defines a simple demo script for simulating a MJCF to observe the physics. 3 | 4 | Run with mjpython: 5 | mjpython sim/scripts/simulate_mjcf.py --record 6 | """ 7 | 8 | import argparse 9 | import logging 10 | import time 11 | from pathlib import Path 12 | from typing import List, Union 13 | 14 | import mediapy as media 15 | import mujoco 16 | import mujoco.viewer 17 | import numpy as np 18 | 19 | from sim.env import robot_mjcf_path 20 | from sim.logging import configure_logging 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | DEFAULT_ROBOT_NAME = "stompymini" 25 | 26 | 27 | def simulate(model_path: Union[str, Path], duration: float, framerate: float, record_video: bool) -> None: 28 | frames: List[np.ndarray] = [] 29 | model = mujoco.MjModel.from_xml_path(model_path) 30 | data = mujoco.MjData(model) 31 | renderer = mujoco.Renderer(model) 32 | 33 | with mujoco.viewer.launch_passive(model, data) as viewer: 34 | while data.time < duration: 35 | step_start = time.time() 36 | mujoco.mj_step(model, data) 37 | viewer.sync() 38 | time_until_next_step = model.opt.timestep - (time.time() - step_start) 39 | if time_until_next_step > 0: 40 | time.sleep(time_until_next_step) 41 | 42 | if record_video and (len(frames) < data.time * framerate): 43 | renderer.update_scene(data) 44 | pixels = renderer.render() 45 | frames.append(pixels) 46 | 47 | if record_video: 48 | video_path = "mjcf_simulation.mp4" 49 | media.write_video(video_path, frames, fps=framerate) 50 | # print(f"Video saved to {video_path}") 51 | logger.info("Video saved to %s", video_path) 52 | 53 | 54 | if __name__ == "__main__": 55 | configure_logging() 56 | 57 | parser = argparse.ArgumentParser(description="MuJoCo Simulation") 58 | parser.add_argument( 59 | "--model_path", 60 | type=str, 61 | default=str(robot_mjcf_path(DEFAULT_ROBOT_NAME, legs_only=True)), 62 | help="Path to the MuJoCo XML file", 63 | ) 64 | parser.add_argument("--duration", type=int, default=3, help="Duration of the simulation in seconds") 65 | parser.add_argument("--framerate", type=int, default=30, help="Frame rate for video recording") 66 | parser.add_argument("--record", action="store_true", help="Flag to record video") 67 | args = parser.parse_args() 68 | 69 | simulate(args.model_path, args.duration, args.framerate, args.record) 70 | -------------------------------------------------------------------------------- /sim/train.py: -------------------------------------------------------------------------------- 1 | """Trains a humanoid to stand up.""" 2 | 3 | # ruff: noqa 4 | # mypy: ignore-errors 5 | 6 | import argparse 7 | 8 | from sim.envs import task_registry # noqa: E402 9 | from sim.utils.helpers import get_args # noqa: E402 10 | 11 | 12 | def train(args: argparse.Namespace) -> None: 13 | env, _ = task_registry.make_env(name=args.task, args=args) 14 | ppo_runner, train_cfg = task_registry.make_alg_runner(env=env, name=args.task, args=args) 15 | ppo_runner.learn(num_learning_iterations=train_cfg.runner.max_iterations, init_at_random_ep_len=True) 16 | 17 | 18 | # Puts this import down here so that the environments are registered 19 | 20 | if __name__ == "__main__": 21 | # python -m sim.humanoid_gym.train 22 | train(get_args()) 23 | -------------------------------------------------------------------------------- /sim/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscalelabs/sim/e03e74d83a513ccc32ca34a7b100f39a9fadc9b9/sim/utils/__init__.py -------------------------------------------------------------------------------- /sim/utils/calculate_gait.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | 32 | import matplotlib.pyplot as plt 33 | import numpy as np 34 | from scipy.optimize import fsolve 35 | 36 | 37 | def get_coefficients(h0, hswing, v0, vswing, hmax, swing_time): 38 | def equations(coeffs): 39 | a5, a4, a3, a2, a1, a0 = coeffs 40 | 41 | # Height at t=0 should be h0 42 | eq1 = a0 - h0 43 | 44 | # Height at t=swing_time should be hswing 45 | eq2 = ( 46 | a5 * swing_time**5 47 | + a4 * swing_time**4 48 | + a3 * swing_time**3 49 | + a2 * swing_time**2 50 | + a1 * swing_time 51 | + a0 52 | - hswing 53 | ) 54 | 55 | # Velocity at t=0 should be v0 56 | eq3 = a1 - v0 57 | 58 | # Velocity at t=swing_time should be vswing 59 | eq4 = ( 60 | 5 * a5 * swing_time**4 + 4 * a4 * swing_time**3 + 3 * a3 * swing_time**2 + 2 * a2 * swing_time + a1 - vswing 61 | ) 62 | 63 | # Height at t=swing_time/2 should be hmax 64 | eq5 = ( 65 | a5 * (swing_time / 2) ** 5 66 | + a4 * (swing_time / 2) ** 4 67 | + a3 * (swing_time / 2) ** 3 68 | + a2 * (swing_time / 2) ** 2 69 | + a1 * (swing_time / 2) 70 | + a0 71 | - hmax 72 | ) 73 | 74 | # Return the deviations from the expected values. These will be minimized by fsolve. 75 | return (eq1, eq2, eq3, eq4, eq5, a5 + a4 + a3 + a2 + a1 + a0) 76 | 77 | # Solve for the coefficients using the equations above 78 | return fsolve(equations, (1, 1, 1, 1, 1, 1)) 79 | 80 | 81 | def plot_curves(coeffs, swing_time): 82 | a5, a4, a3, a2, a1, a0 = coeffs 83 | 84 | def h(t): 85 | return a5 * t**5 + a4 * t**4 + a3 * t**3 + a2 * t**2 + a1 * t + a0 86 | 87 | def v(t): 88 | return 5 * a5 * t**4 + 4 * a4 * t**3 + 3 * a3 * t**2 + 2 * a2 * t + a1 89 | 90 | # Define the acceleration function based on the coefficients 91 | def a(t): 92 | return 20 * a5 * t**3 + 12 * a4 * t**2 + 6 * a3 * t + 2 * a2 93 | 94 | t_values = np.linspace(0, swing_time, 500) 95 | h_values = h(t_values) 96 | v_values = v(t_values) 97 | a_values = a(t_values) # Compute acceleration values 98 | 99 | discrete_t_values = np.linspace(0, swing_time, 14) 100 | 101 | plt.figure(figsize=(12, 9)) 102 | 103 | plt.subplot(3, 1, 1) 104 | plt.plot(t_values, h_values, label="Height (h(t))") 105 | plt.scatter(discrete_t_values, h(discrete_t_values), color="black", label="Discrete Height") 106 | plt.title("Height Curve") 107 | plt.grid(True) 108 | plt.legend() 109 | 110 | plt.subplot(3, 1, 2) 111 | plt.plot(t_values, v_values, label="Velocity (v(t))", color="red") 112 | plt.scatter(discrete_t_values, v(discrete_t_values), color="black", label="Discrete Velocity") 113 | # print(v(discrete_t_values)) 114 | plt.title("Velocity Curve") 115 | plt.grid(True) 116 | plt.legend() 117 | 118 | # Plotting the acceleration curve 119 | plt.subplot(3, 1, 3) 120 | plt.plot(t_values, a_values / 50, label="Acceleration (a(t))", color="green") 121 | plt.scatter(discrete_t_values, a(discrete_t_values) / 50, color="black", label="Discrete Acceleration") 122 | # print(a(discrete_t_values)/50) 123 | plt.title("Acceleration Curve") 124 | plt.grid(True) 125 | plt.legend() 126 | 127 | plt.tight_layout() 128 | plt.show() 129 | 130 | 131 | # Set the constraints and swing time 132 | coeffs = get_coefficients(0, 0, 0, -0.1, 0.04, 0.26) 133 | 134 | print("Coefficients (a5, a4, a3, a2, a1, a0):") 135 | print(f"a5 = {coeffs[0]:.15f}") 136 | print(f"a4 = {coeffs[1]:.15f}") 137 | print(f"a3 = {coeffs[2]:.15f}") 138 | print(f"a2 = {coeffs[3]:.15f}") 139 | print(f"a1 = {coeffs[4]:.15f}") 140 | print(f"a0 = {coeffs[5]:.15f}") 141 | 142 | plot_curves(coeffs, 0.26) 143 | -------------------------------------------------------------------------------- /sim/utils/logger.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | # mypy: ignore-errors 32 | from collections import defaultdict 33 | from multiprocessing import Process, Value 34 | 35 | # import matplotlib.pyplot as plt 36 | import numpy as np 37 | 38 | 39 | class Logger: 40 | def __init__(self, dt): 41 | self.state_log = defaultdict(list) 42 | self.rew_log = defaultdict(list) 43 | self.dt = dt 44 | self.num_episodes = 0 45 | self.plot_process = None 46 | 47 | def log_state(self, key, value): 48 | self.state_log[key].append(value) 49 | 50 | def log_states(self, dict): 51 | for key, value in dict.items(): 52 | self.log_state(key, value) 53 | 54 | def log_rewards(self, dict, num_episodes): 55 | for key, value in dict.items(): 56 | if "rew" in key: 57 | self.rew_log[key].append(value.item() * num_episodes) 58 | self.num_episodes += num_episodes 59 | 60 | def reset(self): 61 | self.state_log.clear() 62 | self.rew_log.clear() 63 | 64 | def plot_states(self): 65 | self.plot_process = Process(target=self._plot) 66 | self.plot_process.start() 67 | 68 | def _plot(self): 69 | nb_rows = 3 70 | nb_cols = 3 71 | fig, axs = plt.subplots(nb_rows, nb_cols) 72 | for key, value in self.state_log.items(): 73 | time = np.linspace(0, len(value) * self.dt, len(value)) 74 | break 75 | log = self.state_log 76 | # plot joint targets and measured positions 77 | a = axs[1, 0] 78 | if log["dof_pos"]: 79 | a.plot(time, log["dof_pos"], label="measured") 80 | if log["dof_pos_target"]: 81 | a.plot(time, log["dof_pos_target"], label="target") 82 | a.set(xlabel="time [s]", ylabel="Position [rad]", title="DOF Position") 83 | a.legend() 84 | # plot joint velocity 85 | a = axs[1, 1] 86 | if log["dof_vel"]: 87 | a.plot(time, log["dof_vel"], label="measured") 88 | if log["dof_vel_target"]: 89 | a.plot(time, log["dof_vel_target"], label="target") 90 | a.set(xlabel="time [s]", ylabel="Velocity [rad/s]", title="Joint Velocity") 91 | a.legend() 92 | # plot base vel x 93 | a = axs[0, 0] 94 | if log["base_vel_x"]: 95 | a.plot(time, log["base_vel_x"], label="measured") 96 | if log["command_x"]: 97 | a.plot(time, log["command_x"], label="commanded") 98 | a.set(xlabel="time [s]", ylabel="base lin vel [m/s]", title="Base velocity x") 99 | a.legend() 100 | # plot base vel y 101 | a = axs[0, 1] 102 | if log["base_vel_y"]: 103 | a.plot(time, log["base_vel_y"], label="measured") 104 | if log["command_y"]: 105 | a.plot(time, log["command_y"], label="commanded") 106 | a.set(xlabel="time [s]", ylabel="base lin vel [m/s]", title="Base velocity y") 107 | a.legend() 108 | # plot base vel yaw 109 | a = axs[0, 2] 110 | if log["base_vel_yaw"]: 111 | a.plot(time, log["base_vel_yaw"], label="measured") 112 | if log["command_yaw"]: 113 | a.plot(time, log["command_yaw"], label="commanded") 114 | a.set(xlabel="time [s]", ylabel="base ang vel [rad/s]", title="Base velocity yaw") 115 | a.legend() 116 | # plot base vel z 117 | a = axs[1, 2] 118 | if log["base_vel_z"]: 119 | a.plot(time, log["base_vel_z"], label="measured") 120 | a.set(xlabel="time [s]", ylabel="base lin vel [m/s]", title="Base velocity z") 121 | a.legend() 122 | # plot contact forces 123 | a = axs[2, 0] 124 | if log["contact_forces_z"]: 125 | forces = np.array(log["contact_forces_z"]) 126 | for i in range(forces.shape[1]): 127 | a.plot(time, forces[:, i], label=f"force {i}") 128 | a.set(xlabel="time [s]", ylabel="Forces z [N]", title="Vertical Contact forces") 129 | a.legend() 130 | # plot torque/vel curves 131 | a = axs[2, 1] 132 | if log["dof_vel"] != [] and log["dof_torque"] != []: 133 | a.plot(log["dof_vel"], log["dof_torque"], "x", label="measured") 134 | a.set(xlabel="Joint vel [rad/s]", ylabel="Joint Torque [Nm]", title="Torque/velocity curves") 135 | a.legend() 136 | # plot torques 137 | a = axs[2, 2] 138 | if log["dof_torque"] != []: 139 | a.plot(time, log["dof_torque"], label="measured") 140 | a.set(xlabel="time [s]", ylabel="Joint Torque [Nm]", title="Torque") 141 | a.legend() 142 | plt.show() 143 | 144 | def print_rewards(self): 145 | print("Average rewards per second:") 146 | for key, values in self.rew_log.items(): 147 | mean = np.sum(np.array(values)) / self.num_episodes 148 | print(f" - {key}: {mean}") 149 | print(f"Total number of episodes: {self.num_episodes}") 150 | 151 | def __del__(self): 152 | if self.plot_process is not None: 153 | self.plot_process.kill() 154 | -------------------------------------------------------------------------------- /sim/utils/math.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 ETH Zurich, Nikita Rudin 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # Copyright (c) 2024 Beijing RobotEra TECHNOLOGY CO.,LTD. All rights reserved. 31 | 32 | 33 | import numpy as np 34 | 35 | from isaacgym.torch_utils import quat_apply, normalize, get_euler_xyz # isort: skip 36 | import torch # isort: skip 37 | 38 | 39 | # @ torch.jit.script 40 | def quat_apply_yaw(quat, vec): 41 | quat_yaw = quat.clone().view(-1, 4) 42 | quat_yaw[:, :2] = 0.0 43 | quat_yaw = normalize(quat_yaw) 44 | return quat_apply(quat_yaw, vec) 45 | 46 | 47 | # @ torch.jit.script 48 | def wrap_to_pi(angles): 49 | angles %= 2 * np.pi 50 | angles -= 2 * np.pi * (angles > np.pi) 51 | return angles 52 | 53 | 54 | # @ torch.jit.script 55 | def torch_rand_sqrt_float(lower, upper, shape, device): 56 | # type: (float, float, Tuple[int, int], str) -> Tensor 57 | r = 2 * torch.rand(*shape, device=device) - 1 58 | r = torch.where(r < 0.0, -torch.sqrt(-r), torch.sqrt(r)) 59 | r = (r + 1.0) / 2.0 60 | return (upper - lower) * r + lower 61 | 62 | 63 | def get_euler_xyz_tensor(quat): 64 | r, p, w = get_euler_xyz(quat) 65 | # stack r, p, w in dim1 66 | euler_xyz = torch.stack((r, p, w), dim=1) 67 | euler_xyz[euler_xyz > np.pi] -= 2 * np.pi 68 | return euler_xyz 69 | -------------------------------------------------------------------------------- /sim/utils/resources.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | from typing import Any 3 | 4 | 5 | def load_embodiment(embodiment: str) -> Any: # noqa: ANN401 6 | # Dynamically import embodiment 7 | module_name = f"sim.resources.{embodiment}.joints" 8 | module = importlib.import_module(module_name) 9 | robot = getattr(module, "Robot") 10 | return robot 11 | -------------------------------------------------------------------------------- /third_party/expressive_humanoid/.gitignore: -------------------------------------------------------------------------------- 1 | sip-4.19.3.tar.gz 2 | fbx202032_fbxpythonbindings_linux.tar.gz 3 | fbx202032_fbxsdk_linux.tar.gz 4 | CMU_fbx.zip -------------------------------------------------------------------------------- /third_party/expressive_humanoid/Dockerfile: -------------------------------------------------------------------------------- 1 | # docker build -t fbx_image . 2 | # docker run -it fbx_image 3 | ARG TARGETPLATFORM 4 | FROM --platform=${TARGETPLATFORM:-linux/amd64} ubuntu:20.04 AS base 5 | 6 | # Set environment variables 7 | ENV DEBIAN_FRONTEND=noninteractive \ 8 | HOME="/home/user" \ 9 | FBX_SDK_VERSION="2020.3.2" \ 10 | SIP_VERSION="4.19.3" \ 11 | PYTHON_VERSION="3.8" \ 12 | FBX_SDK_PATH="$HOME/fbx_setup/fbx_sdk" \ 13 | FBX_PYTHON_BINDING_PATH="$HOME/fbx_setup/fbx_python_bindings" \ 14 | SIP_ROOT="$HOME/fbx_setup/sip" \ 15 | FBXSDK_ROOT="$FBX_SDK_PATH" \ 16 | SIP_PATH="$SIP_ROOT/sip-$SIP_VERSION" \ 17 | PYTHON_EXECUTABLE="/home/user/miniconda/envs/humanoid/bin/python" 18 | 19 | # Update and install dependencies 20 | RUN apt-get update && \ 21 | apt-get install -y --no-install-recommends \ 22 | build-essential \ 23 | libxml2-dev \ 24 | zlib1g-dev \ 25 | wget \ 26 | python${PYTHON_VERSION} \ 27 | python${PYTHON_VERSION}-dev \ 28 | python3-pip \ 29 | libssl-dev \ 30 | vim \ 31 | libcurl4-openssl-dev && \ 32 | rm -rf /var/lib/apt/lists/* 33 | 34 | # Create necessary directories 35 | RUN mkdir -p $FBX_SDK_PATH $FBX_PYTHON_BINDING_PATH $SIP_ROOT 36 | 37 | # Install Miniconda 38 | RUN wget -qO- https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh && \ 39 | /bin/bash miniconda.sh -b -p $HOME/miniconda && \ 40 | rm miniconda.sh && \ 41 | $HOME/miniconda/bin/conda init bash 42 | 43 | FROM base AS install_fbx 44 | 45 | # Make sure the base environment is activated by default 46 | SHELL ["/bin/bash", "-c"] 47 | 48 | # Install packages inside the Conda environment 49 | RUN /bin/bash -c "source ~/.bashrc && \ 50 | conda create -n humanoid python=3.8 -y && \ 51 | conda activate humanoid && \ 52 | conda install -y pytorch==1.10.0 torchvision==0.11.1 torchaudio==0.10.0 cudatoolkit=11.3 -c pytorch && \ 53 | conda install -y 'numpy<1.24' && \ 54 | conda install -c conda-forge gxx_linux-64=12.1.0 -y && \ 55 | pip install pydelatin wandb tqdm opencv-python ipdb pyfqmr flask dill gdown pandas matplotlib ruamel.yaml xlrd scipy" 56 | 57 | FROM install_fbx AS base_conda 58 | 59 | # Install FBX SDK 60 | WORKDIR $FBX_SDK_PATH 61 | COPY fbx202032_fbxsdk_linux.tar.gz $FBX_SDK_PATH/ 62 | RUN tar xzf fbx202032_fbxsdk_linux.tar.gz && \ 63 | chmod +x fbx202032_fbxsdk_linux 64 | COPY install_fbx.sh . 65 | RUN chmod +x install_fbx.sh && ./install_fbx.sh 66 | 67 | FROM base_conda AS fbx_python_bindings 68 | 69 | # Install FBX Python Bindings 70 | WORKDIR $FBX_PYTHON_BINDING_PATH 71 | COPY fbx202032_fbxpythonbindings_linux.tar.gz $FBX_PYTHON_BINDING_PATH/ 72 | RUN tar xzf fbx202032_fbxpythonbindings_linux.tar.gz && \ 73 | chmod +x fbx202032_fbxpythonbindings_linux 74 | COPY install_fbx_bindings.sh . 75 | RUN chmod +x install_fbx_bindings.sh && ./install_fbx_bindings.sh 76 | 77 | FROM fbx_python_bindings AS sip 78 | 79 | # Install SIP 80 | WORKDIR $SIP_ROOT 81 | RUN wget "https://sourceforge.net/projects/pyqt/files/sip/sip-${SIP_VERSION}/sip-${SIP_VERSION}.tar.gz/download" -O "sip-${SIP_VERSION}.tar.gz" && \ 82 | tar xzf "sip-${SIP_VERSION}.tar.gz" && \ 83 | cd sip-${SIP_VERSION} && \ 84 | $PYTHON_EXECUTABLE configure.py && \ 85 | make && \ 86 | make install 87 | 88 | ENV SIP_ROOT="$SIP_ROOT/sip-$SIP_VERSION" 89 | 90 | FROM sip AS build_fbx 91 | 92 | # Build the SDK 93 | WORKDIR $FBX_PYTHON_BINDING_PATH 94 | RUN $PYTHON_EXECUTABLE PythonBindings.py "Python3_x64" buildsip 95 | 96 | # Modify the Makefile to fix linking order 97 | WORKDIR $FBX_PYTHON_BINDING_PATH/build/Python38_x64 98 | RUN make clean && \ 99 | sed -i 's|\(LIBS = -L[^ ]*\) -lz -lxml2 \([^ ]*\)|\1 \2 -lz -lxml2|' Makefile 100 | 101 | # Build and install 102 | RUN make install 103 | 104 | # Build the SDK 105 | WORKDIR $FBX_PYTHON_BINDING_PATH 106 | RUN $PYTHON_EXECUTABLE PythonBindings.py "Python3_x64" buildsip 107 | 108 | FROM build_fbx AS rebuild_fbx 109 | 110 | # Install git 111 | WORKDIR $HOME 112 | RUN apt-get update && apt-get install -y git unzip 113 | RUN git clone https://github.com/Ke-Wang1017/expressive_humanoid.git expressive_humanoid 114 | 115 | WORKDIR $HOME/expressive_humanoid/ASE/ase/poselib/data/ 116 | COPY CMU_fbx.zip . 117 | RUN mkdir -p cmu_fbx_all && unzip CMU_fbx.zip -d cmu_fbx_all/ 118 | 119 | WORKDIR $HOME/expressive_humanoid/ASE/ase/poselib/ 120 | COPY test_fbx.py . 121 | RUN $PYTHON_EXECUTABLE test_fbx.py && echo "FBX works" 122 | 123 | # Set the default command to bash 124 | COPY parse_cmu_mocap_all_2.py $HOME/expressive_humanoid/ASE/ase/poselib/ 125 | COPY fbx_importer_all_2.py $HOME/expressive_humanoid/ASE/ase/poselib/ 126 | 127 | FROM rebuild_fbx AS final 128 | 129 | RUN $PYTHON_EXECUTABLE parse_cmu_mocap_all_2.py 130 | RUN $PYTHON_EXECUTABLE fbx_importer_all_2.py 131 | RUN mkdir -p pkl retarget_npy 132 | RUN $PYTHON_EXECUTABLE retarget_motion_stompy_all.py 133 | 134 | WORKDIR $HOME/expressive_humanoid/legged_gym/legged_gym/scripts 135 | # Train for 1 iteration and kill the program to have a dummy model to load. 136 | RUN $PYTHON_EXECUTABLE train.py debug --task stompy_view --motion_name motions_debug.yaml --debug 137 | 138 | # Run the play script to visualize the motion 139 | RUN $PYTHON_EXECUTABLE play.py debug --task stompy_view --motion_name motions_debug.yaml 140 | 141 | RUN $PYTHON_EXECUTABLE train.py 060-40-some_descriptions_of_run --device cuda:0 --entity WANDB_ENTITY 142 | RUN $PYTHON_EXECUTABLE play.py 060-40 --task stompy_mimic --record_video 143 | 144 | FROM final AS amp 145 | 146 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /third_party/expressive_humanoid/LICENSE: -------------------------------------------------------------------------------- 1 | # This file is kept for experimental purposes. All software contained within complies with their respective licenses. 2 | # The following software is included: 3 | # - Expressive Humanoid (https://github.com/chengxuxin/expressive-humanoid) 4 | # - Expressive Humanoid (https://github.com/Ke-Wang1017/expressive_humanoid) 5 | # - CMU Motion Capture Data 6 | # - ASE 7 | # - FBX SDK 8 | # - SIP -------------------------------------------------------------------------------- /third_party/expressive_humanoid/download_assets.sh: -------------------------------------------------------------------------------- 1 | echo "Starting download of CMU_fbx.zip" 2 | aws s3 cp s3://kscale-public/expressive_humanoid/CMU_fbx.zip . & 3 | echo "Starting download of fbx202032_fbxpythonbindings_linux.tar.gz" 4 | aws s3 cp s3://kscale-public/expressive_humanoid/fbx202032_fbxpythonbindings_linux.tar.gz . & 5 | echo "Starting download of fbx202032_fbxsdk_linux.tar.gz" 6 | aws s3 cp s3://kscale-public/expressive_humanoid/fbx202032_fbxsdk_linux.tar.gz . & 7 | echo "Starting download of sip-4.19.3.tar.gz" 8 | aws s3 cp s3://kscale-public/expressive_humanoid/sip-4.19.3.tar.gz . & 9 | 10 | wait 11 | echo "All downloads completed" -------------------------------------------------------------------------------- /third_party/expressive_humanoid/fbx_importer_all_2.py: -------------------------------------------------------------------------------- 1 | """This script converts CMU Motion Capture data from FBX format to NPY format. """ 2 | import sys 3 | import os 4 | from tqdm import tqdm 5 | from poselib.skeleton.skeleton3d import SkeletonMotion 6 | import multiprocessing 7 | 8 | sys.path.append("/home/user/fbx_setup/fbx_python_bindings/build/Distrib/site-packages/fbx/") 9 | 10 | def process_file(i, fbx_file, all_fbx_path): 11 | if fbx_file.endswith(".fbx"): 12 | print(i, fbx_file) 13 | motion = SkeletonMotion.from_fbx( 14 | fbx_file_path=os.path.join(all_fbx_path, fbx_file), 15 | root_joint="Hips", 16 | fps=60 17 | ) 18 | motion.to_file(f"data/npy/{fbx_file[:-4]}.npy") 19 | 20 | def main(): 21 | all_fbx_path = "data/cmu_fbx_all/" 22 | all_fbx_files = sorted(os.listdir(all_fbx_path)) 23 | 24 | all_fbx_filtered = [] 25 | for fbx in all_fbx_files: 26 | npy = fbx.split(".")[0] + ".npy" 27 | target_motion_file = os.path.join(all_fbx_path, "../npy/", npy) 28 | if os.path.exists(target_motion_file): 29 | print("Already exists, skip: ", fbx) 30 | continue 31 | all_fbx_filtered.append(fbx) 32 | 33 | print(len(all_fbx_filtered)) 34 | 35 | for fbx_file in tqdm(all_fbx_filtered): 36 | process_file(None, fbx_file, all_fbx_path) 37 | 38 | n_workers = multiprocessing.cpu_count() 39 | with multiprocessing.Pool(n_workers) as pool: 40 | list(tqdm(pool.starmap(process_file, [(i, fbx_file, all_fbx_path) for i, fbx_file in enumerate(all_fbx_filtered)]), total=len(all_fbx_filtered))) 41 | 42 | if __name__ == "__main__": 43 | main() -------------------------------------------------------------------------------- /third_party/expressive_humanoid/install_fbx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./fbx202032_fbxsdk_linux <