Isaacsim and Mujoco simulator classes are added.

This commit is contained in:
Rooholla-KhorramBakht 2024-02-20 22:27:08 -05:00
parent caeaeca1dd
commit f2ee4f1526
44 changed files with 760938 additions and 10 deletions

View File

@ -0,0 +1,9 @@
import os
import platform
import sys
ASSETS_PATH = os.path.join(os.path.dirname(__file__), "assets")
GO2_USD_PATH = os.path.join(os.path.dirname(__file__), "assets/usd/go2.usd")
GO2_ISAACSIM_CFG_PATH = os.path.join(
os.path.dirname(__file__), "sim/isaacsim/sim_config.yaml"
)

View File

@ -0,0 +1,27 @@
Copyright (c) 2016-2022 HangZhou YuShu TECHNOLOGY CO.,LTD. ("Unitree Robotics")
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,35 @@
# Unitree Go2 Description (MJCF)
Requires MuJoCo 2.2.2 or later.
## Overview
This package contains a simplified robot description (MJCF) of the [Go2
Quadruped Robot](https://www.unitree.com/go2/) developed by [Unitree
Robotics](https://www.unitree.com/). It is derived from the [publicly available
URDF
description](https://github.com/unitreerobotics/unitree_ros/tree/master/robots/go2_description).
<p float="left">
<img src="go2.png" width="400">
</p>
## URDF → MJCF derivation steps
1. Converted the DAE [mesh
files](https://github.com/unitreerobotics/unitree_mujoco/tree/main/data/a1/meshes)
to OBJ format using [Blender](https://www.blender.org/).
- When exporting, ensure "up axis" is `+Z`, and "forward axis" is `+Y`.
2. Processed `.obj` files with [`obj2mjcf`](https://github.com/kevinzakka/obj2mjcf).
3. Added `<mujoco> <compiler discardvisual="false" strippath="false" fusestatic="false"/> </mujoco>` to the URDF's
`<robot>` clause in order to preserve visual geometries.
4. Loaded the URDF into MuJoCo and saved a corresponding MJCF.
5. Added a `<freejoint/>` to the base.
6. Manually edited the MJCF to extract common properties into the `<default>` section.
7. Softened the contacts of the feet to approximate the effect of rubber and
increased `impratio` to reduce slippage.
8. Added `scene.xml` which includes the robot, with a textured groundplane, skybox, and haze.
## License
This model is released under a [BSD-3-Clause License](LICENSE).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
Go2Py/assets/mujoco/go2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

211
Go2Py/assets/mujoco/go2.xml Normal file
View File

@ -0,0 +1,211 @@
<mujoco model="go2">
<compiler angle="radian" meshdir="assets" autolimits="true"/>
<option cone="elliptic" impratio="100"/>
<default>
<default class="go2">
<geom friction="0.6" margin="0.001" condim="1"/>
<joint axis="0 1 0" damping="0" armature="0.01" frictionloss="0.2"/>
<motor ctrlrange="-23.7 23.7"/>
<default class="abduction">
<joint axis="1 0 0" range="-1.0472 1.0472"/>
</default>
<default class="hip">
<default class="front_hip">
<joint range="-1.5708 3.4907"/>
</default>
<default class="back_hip">
<joint range="-0.5236 4.5379"/>
</default>
</default>
<default class="knee">
<joint range="-2.7227 -0.83776"/>
<motor ctrlrange="-45.43 45.43"/>
</default>
<default class="visual">
<geom type="mesh" contype="0" conaffinity="0" group="2"/>
</default>
<default class="collision">
<geom group="3"/>
<default class="foot">
<geom size="0.022" pos="-0.002 0 -0.213" priority="1" solimp="0.015 1 0.031" condim="6"
friction="0.8 0.02 0.01"/>
</default>
</default>
</default>
</default>
<asset>
<material name="metal" rgba=".9 .95 .95 1"/>
<material name="black" rgba="0 0 0 1"/>
<material name="white" rgba="1 1 1 1"/>
<material name="gray" rgba="0.671705 0.692426 0.774270 1"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/>
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300"/>
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/>
<mesh file="base_0.obj"/>
<mesh file="base_1.obj"/>
<mesh file="base_2.obj"/>
<mesh file="base_3.obj"/>
<mesh file="base_4.obj"/>
<mesh file="hip_0.obj"/>
<mesh file="hip_1.obj"/>
<mesh file="thigh_0.obj"/>
<mesh file="thigh_1.obj"/>
<mesh file="thigh_mirror_0.obj"/>
<mesh file="thigh_mirror_1.obj"/>
<mesh file="calf_0.obj"/>
<mesh file="calf_1.obj"/>
<mesh file="calf_mirror_0.obj"/>
<mesh file="calf_mirror_1.obj"/>
<mesh file="foot.obj"/>
</asset>
<worldbody>
<geom name="floor" size="0 0 0.05" type="plane" material="groundplane"/>
<body name="base" pos="0 0 0.445" childclass="go2">
<inertial pos="0.021112 0 -0.005366" quat="-0.000543471 0.713435 -0.00173769 0.700719" mass="6.921"
diaginertia="0.107027 0.0980771 0.0244531"/>
<freejoint/>
<geom mesh="base_0" material="black" class="visual"/>
<geom mesh="base_1" material="black" class="visual"/>
<geom mesh="base_2" material="black" class="visual"/>
<geom mesh="base_3" material="white" class="visual"/>
<geom mesh="base_4" material="gray" class="visual"/>
<geom size="0.1881 0.04675 0.057" type="box" class="collision"/>
<geom size="0.05 0.045" pos="0.285 0 0.01" type="cylinder" class="collision"/>
<geom size="0.047" pos="0.293 0 -0.06" class="collision"/>
<site name="imu" pos="-0.02557 0 0.04232"/>
<body name="FL_hip" pos="0.1934 0.0465 0">
<inertial pos="-0.0054 0.00194 -0.000105" quat="0.497014 0.499245 0.505462 0.498237" mass="0.678"
diaginertia="0.00088403 0.000596003 0.000479967"/>
<joint name="FL_hip_joint" class="abduction"/>
<geom mesh="hip_0" material="metal" class="visual"/>
<geom mesh="hip_1" material="gray" class="visual"/>
<geom size="0.046 0.02" pos="0 0.08 0" quat="1 1 0 0" type="cylinder" class="collision"/>
<body name="FL_thigh" pos="0 0.0955 0">
<inertial pos="-0.00374 -0.0223 -0.0327" quat="0.829533 0.0847635 -0.0200632 0.551623" mass="1.152"
diaginertia="0.00594973 0.00584149 0.000878787"/>
<joint name="FL_thigh_joint" class="front_hip"/>
<geom mesh="thigh_0" material="metal" class="visual"/>
<geom mesh="thigh_1" material="gray" class="visual"/>
<geom size="0.1065 0.01225 0.017" pos="0 0 -0.1065" quat="0.707107 0 0.707107 0" type="box" class="collision"/>
<body name="FL_calf" pos="0 0 -0.213">
<inertial pos="0.00629595 -0.000622121 -0.141417" quat="0.710672 0.00154099 -0.00450087 0.703508"
mass="0.241352" diaginertia="0.0014901 0.00146356 5.31397e-05"/>
<joint name="FL_calf_joint" class="knee"/>
<geom mesh="calf_0" material="gray" class="visual"/>
<geom mesh="calf_1" material="black" class="visual"/>
<geom size="0.012 0.06" pos="0.008 0 -0.06" quat="0.994493 0 -0.104807 0" type="cylinder" class="collision"/>
<geom size="0.011 0.0325" pos="0.02 0 -0.148" quat="0.999688 0 0.0249974 0" type="cylinder" class="collision"/>
<geom pos="0 0 -0.213" mesh="foot" class="visual" material="black"/>
<geom name="FL" class="foot"/>
</body>
</body>
</body>
<body name="FR_hip" pos="0.1934 -0.0465 0">
<inertial pos="-0.0054 -0.00194 -0.000105" quat="0.498237 0.505462 0.499245 0.497014" mass="0.678"
diaginertia="0.00088403 0.000596003 0.000479967"/>
<joint name="FR_hip_joint" class="abduction"/>
<geom mesh="hip_0" material="metal" class="visual" quat="4.63268e-05 1 0 0"/>
<geom mesh="hip_1" material="gray" class="visual" quat="4.63268e-05 1 0 0"/>
<geom size="0.046 0.02" pos="0 -0.08 0" quat="0.707107 0.707107 0 0" type="cylinder" class="collision"/>
<body name="FR_thigh" pos="0 -0.0955 0">
<inertial pos="-0.00374 0.0223 -0.0327" quat="0.551623 -0.0200632 0.0847635 0.829533" mass="1.152"
diaginertia="0.00594973 0.00584149 0.000878787"/>
<joint name="FR_thigh_joint" class="front_hip"/>
<geom mesh="thigh_mirror_0" material="metal" class="visual"/>
<geom mesh="thigh_mirror_1" material="gray" class="visual"/>
<geom size="0.1065 0.01225 0.017" pos="0 0 -0.1065" quat="0.707107 0 0.707107 0" type="box" class="collision"/>
<body name="FR_calf" pos="0 0 -0.213">
<inertial pos="0.00629595 0.000622121 -0.141417" quat="0.703508 -0.00450087 0.00154099 0.710672"
mass="0.241352" diaginertia="0.0014901 0.00146356 5.31397e-05"/>
<joint name="FR_calf_joint" class="knee"/>
<geom mesh="calf_mirror_0" material="gray" class="visual"/>
<geom mesh="calf_mirror_1" material="black" class="visual"/>
<geom size="0.013 0.06" pos="0.01 0 -0.06" quat="0.995004 0 -0.0998334 0" type="cylinder" class="collision"/>
<geom size="0.011 0.0325" pos="0.02 0 -0.148" quat="0.999688 0 0.0249974 0" type="cylinder" class="collision"/>
<geom pos="0 0 -0.213" mesh="foot" class="visual" material="black"/>
<geom name="FR" class="foot"/>
</body>
</body>
</body>
<body name="RL_hip" pos="-0.1934 0.0465 0">
<inertial pos="0.0054 0.00194 -0.000105" quat="0.505462 0.498237 0.497014 0.499245" mass="0.678"
diaginertia="0.00088403 0.000596003 0.000479967"/>
<joint name="RL_hip_joint" class="abduction"/>
<geom mesh="hip_0" material="metal" class="visual" quat="4.63268e-05 0 1 0"/>
<geom mesh="hip_1" material="gray" class="visual" quat="4.63268e-05 0 1 0"/>
<geom size="0.046 0.02" pos="0 0.08 0" quat="0.707107 0.707107 0 0" type="cylinder" class="collision"/>
<body name="RL_thigh" pos="0 0.0955 0">
<inertial pos="-0.00374 -0.0223 -0.0327" quat="0.829533 0.0847635 -0.0200632 0.551623" mass="1.152"
diaginertia="0.00594973 0.00584149 0.000878787"/>
<joint name="RL_thigh_joint" class="back_hip"/>
<geom mesh="thigh_0" material="metal" class="visual"/>
<geom mesh="thigh_1" material="gray" class="visual"/>
<geom size="0.1065 0.01225 0.017" pos="0 0 -0.1065" quat="0.707107 0 0.707107 0" type="box" class="collision"/>
<body name="RL_calf" pos="0 0 -0.213">
<inertial pos="0.00629595 -0.000622121 -0.141417" quat="0.710672 0.00154099 -0.00450087 0.703508"
mass="0.241352" diaginertia="0.0014901 0.00146356 5.31397e-05"/>
<joint name="RL_calf_joint" class="knee"/>
<geom mesh="calf_0" material="gray" class="visual"/>
<geom mesh="calf_1" material="black" class="visual"/>
<geom size="0.013 0.06" pos="0.01 0 -0.06" quat="0.995004 0 -0.0998334 0" type="cylinder" class="collision"/>
<geom size="0.011 0.0325" pos="0.02 0 -0.148" quat="0.999688 0 0.0249974 0" type="cylinder" class="collision"/>
<geom pos="0 0 -0.213" mesh="foot" class="visual" material="black"/>
<geom name="RL" class="foot"/>
</body>
</body>
</body>
<body name="RR_hip" pos="-0.1934 -0.0465 0">
<inertial pos="0.0054 -0.00194 -0.000105" quat="0.499245 0.497014 0.498237 0.505462" mass="0.678"
diaginertia="0.00088403 0.000596003 0.000479967"/>
<joint name="RR_hip_joint" class="abduction"/>
<geom mesh="hip_0" material="metal" class="visual" quat="2.14617e-09 4.63268e-05 4.63268e-05 -1"/>
<geom mesh="hip_1" material="gray" class="visual" quat="2.14617e-09 4.63268e-05 4.63268e-05 -1"/>
<geom size="0.046 0.02" pos="0 -0.08 0" quat="0.707107 0.707107 0 0" type="cylinder" class="collision"/>
<body name="RR_thigh" pos="0 -0.0955 0">
<inertial pos="-0.00374 0.0223 -0.0327" quat="0.551623 -0.0200632 0.0847635 0.829533" mass="1.152"
diaginertia="0.00594973 0.00584149 0.000878787"/>
<joint name="RR_thigh_joint" class="back_hip"/>
<geom mesh="thigh_mirror_0" material="metal" class="visual"/>
<geom mesh="thigh_mirror_1" material="gray" class="visual"/>
<geom size="0.1065 0.01225 0.017" pos="0 0 -0.1065" quat="0.707107 0 0.707107 0" type="box" class="collision"/>
<body name="RR_calf" pos="0 0 -0.213">
<inertial pos="0.00629595 0.000622121 -0.141417" quat="0.703508 -0.00450087 0.00154099 0.710672"
mass="0.241352" diaginertia="0.0014901 0.00146356 5.31397e-05"/>
<joint name="RR_calf_joint" class="knee"/>
<geom mesh="calf_mirror_0" material="gray" class="visual"/>
<geom mesh="calf_mirror_1" material="black" class="visual"/>
<geom size="0.013 0.06" pos="0.01 0 -0.06" quat="0.995004 0 -0.0998334 0" type="cylinder" class="collision"/>
<geom size="0.011 0.0325" pos="0.02 0 -0.148" quat="0.999688 0 0.0249974 0" type="cylinder" class="collision"/>
<geom pos="0 0 -0.213" mesh="foot" class="visual" material="black"/>
<geom name="RR" class="foot"/>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<motor class="abduction" name="FR_hip" joint="FR_hip_joint"/>
<motor class="hip" name="FR_thigh" joint="FR_thigh_joint"/>
<motor class="knee" name="FR_calf" joint="FR_calf_joint"/>
<motor class="abduction" name="FL_hip" joint="FL_hip_joint"/>
<motor class="hip" name="FL_thigh" joint="FL_thigh_joint"/>
<motor class="knee" name="FL_calf" joint="FL_calf_joint"/>
<motor class="abduction" name="RR_hip" joint="RR_hip_joint"/>
<motor class="hip" name="RR_thigh" joint="RR_thigh_joint"/>
<motor class="knee" name="RR_calf" joint="RR_calf_joint"/>
<motor class="abduction" name="RL_hip" joint="RL_hip_joint"/>
<motor class="hip" name="RL_thigh" joint="RL_thigh_joint"/>
<motor class="knee" name="RL_calf" joint="RL_calf_joint"/>
</actuator>
<keyframe>
<key name="home" qpos="0 0 0.27 1 0 0 0 0 0.9 -1.8 0 0.9 -1.8 0 0.9 -1.8 0 0.9 -1.8"
ctrl="0 0.9 -1.8 0 0.9 -1.8 0 0.9 -1.8 0 0.9 -1.8"/>
</keyframe>
</mujoco>

View File

@ -0,0 +1,23 @@
<mujoco model="go2 scene">
<include file="go2.xml"/>
<statistic center="0 0 0.1" extent="0.8"/>
<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/>
<rgba haze="0.15 0.25 0.35 1"/>
<global azimuth="-130" elevation="-20"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/>
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3"
markrgb="0.8 0.8 0.8" width="300" height="300"/>
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/>
</asset>
<worldbody>
<light pos="0 0 1.5" dir="0 0 -1" directional="true"/>
<geom name="floor" size="0 0 0.05" type="plane" material="groundplane"/>
</worldbody>
</mujoco>

View File

@ -8,7 +8,7 @@ Stephen Brawner (brawner@gmail.com)
Commit Version: 1.6.0-4-g7f85cfe Build Version: 1.6.7995.38578
For more information, please see http://wiki.ros.org/sw_urdf_exporter -->
<robot name="go2_description">
<link name="base">
<link name="base_link">
<inertial>
<origin rpy="0 0 0" xyz="0.021112 0 -0.005366"/>
<mass value="6.921"/>
@ -45,7 +45,7 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint dont_collapse="true" name="Head_upper_joint" type="fixed">
<origin rpy="0 0 0" xyz="0.285 0 0.01"/>
<parent link="base"/>
<parent link="base_link"/>
<child link="Head_upper"/>
<axis xyz="0 0 0"/>
</joint>
@ -92,7 +92,7 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint name="FL_hip_joint" type="revolute">
<origin rpy="0 0 0" xyz="0.1934 0.0465 0"/>
<parent link="base"/>
<parent link="base_link"/>
<child link="FL_hip"/>
<axis xyz="1 0 0"/>
<dynamics damping="0" friction="0"/>
@ -238,7 +238,7 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint name="FR_hip_joint" type="revolute">
<origin rpy="0 0 0" xyz="0.1934 -0.0465 0"/>
<parent link="base"/>
<parent link="base_link"/>
<child link="FR_hip"/>
<axis xyz="1 0 0"/>
<dynamics damping="0" friction="0"/>
@ -384,7 +384,7 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint name="RL_hip_joint" type="revolute">
<origin rpy="0 0 0" xyz="-0.1934 0.0465 0"/>
<parent link="base"/>
<parent link="base_link"/>
<child link="RL_hip"/>
<axis xyz="1 0 0"/>
<dynamics damping="0" friction="0"/>
@ -530,7 +530,7 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint name="RR_hip_joint" type="revolute">
<origin rpy="0 0 0" xyz="-0.1934 -0.0465 0"/>
<parent link="base"/>
<parent link="base_link"/>
<child link="RR_hip"/>
<axis xyz="1 0 0"/>
<dynamics damping="0" friction="0"/>
@ -652,7 +652,7 @@ Stephen Brawner (brawner@gmail.com)
<child link="RR_foot"/>
<axis xyz="0 0 0"/>
</joint>
<link name="imu">
<link name="imu_link">
<inertial>
<origin rpy="0 0 0" xyz="0 0 0"/>
<mass value="0"/>
@ -661,8 +661,8 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint name="imu_joint" type="fixed">
<origin rpy="0 0 0" xyz="-0.02557 0 0.04232"/>
<parent link="base"/>
<child link="imu"/>
<parent link="base_link"/>
<child link="imu_link"/>
<axis xyz="0 0 0"/>
</joint>
<link name="radar">
@ -674,7 +674,7 @@ Stephen Brawner (brawner@gmail.com)
</link>
<joint name="radar_joint" type="fixed">
<origin rpy="0 2.8782 0" xyz="0.28945 0 -0.046825"/>
<parent link="base"/>
<parent link="base_link"/>
<child link="radar"/>
<axis xyz="0 0 0"/>
</joint>

Binary file not shown.

BIN
Go2Py/assets/usd/go2.usd Normal file

Binary file not shown.

124
Go2Py/sim/interface.py Normal file
View File

@ -0,0 +1,124 @@
import time
import numpy as np
from B1Py import B1_ISAACSIM_CFG_PATH
from B1Py.lcm_types.unitree_lowlevel import UnitreeLowCommand, UnitreeLowState
from B1Py.sim.utils import LCMBridgeClient, NumpyMemMapDataPipe, load_config
class B1IsaacSim:
"""
Class for communication with the simulated Franka Emika FR3 robot in Isaac Sim.
It uses LCM to send joint velocity to the simulated robot and receive joint states
(joint angle, velocity, and torque) and camera images from the robot.
"""
def __init__(self, robot_id="b1"):
"""
@param robot_id: (str) The name of the robot in the Isaac Sim scene.
"""
self.lcm_bridge = LCMBridgeClient(robot_name=robot_id)
self.cfg = load_config(B1_ISAACSIM_CFG_PATH)
self.camera_pipes = {}
for camera in self.cfg["cameras"]:
for type in camera["type"]:
if type == "rgb":
shape = (camera["resolution"][1], camera["resolution"][0], 4)
else:
shape = (camera["resolution"][1], camera["resolution"][0])
self.camera_pipes[camera["name"] + "_" + type] = NumpyMemMapDataPipe(
camera["name"] + "_" + type, force=False, shape=shape
)
print(camera["name"] + "_" + type)
def LCMThreadFunc(self):
"""
Function that runs on a separate thread and handles LCM communication.
`lc.handle()` function is called when there are data available to be processed.
"""
while self.running:
rfds, wfds, efds = select.select([self.lc.fileno()], [], [], 0.5)
if rfds: # Handle only if there are data in the interface file
self.lc.handle()
def getStates(self):
"""
Get the current joint angle, velocity, and torque of the robot.
@return: (dict or None) The joint state {'q': (numpy.ndarray, shape=(n,)),
'dq': (numpy.ndarray, shape=(n,)), 'T': (numpy.ndarray, shape=(n,))}
if the latest update was received less than 0.2 seconds ago;
otherwise, return None.
"""
state = self.lcm_bridge.getStates(timeout=0.1)
if state is not None:
q = np.array(state.q)
dq = np.array(state.dq)
tau_est = np.array(state.tau_est)
contact_state = np.array(state.contact_state)
accel = np.array(state.accel)
gyro = np.array(state.gyro)
temperature = np.array(state.temperature)
quaternion = np.array(state.quaternion)
rpy = np.array(state.rpy)
gravity = np.array(state.gravity)
gt_pos = np.array(state.gt_pos)
gt_quat = np.array(state.gt_quat)
gt_lin_vel = np.array(state.gt_lin_vel)
gt_ang_vel = np.array(state.gt_ang_vel)
return {
"q": q,
"dq": dq,
"tau_est": tau_est,
"contact_state": contact_state,
"accel": accel,
"gyro": gyro,
"temperature": temperature,
"quaternion": quaternion,
"rpy": rpy,
"gravity": gravity,
"gt_pos": gt_pos,
"gt_quat": gt_quat,
"gt_lin_vel": gt_lin_vel,
"gt_ang_vel": gt_ang_vel,
}
else:
return None
def readCameras(self):
data = {key: pipe.read() for key, pipe in self.camera_pipes.items()}
return data
def sendCommands(self, kp, kd, q_des, dq_des, tau_ff):
"""
Send a joint velocity command to the robot.
@param kp: (numpy.ndarray, shape=(n,)) The proportional gain for the joint position control.
@param kd: (numpy.ndarray, shape=(n,)) The derivative gain for the joint position control.
@param q_des: (numpy.ndarray, shape=(n,)) The desired joint position.
@param dq_des: (numpy.ndarray, shape=(n,)) The desired joint velocity.
@param tau_ff: (numpy.ndarray, shape=(n,)) The feedforward torque.
"""
cmd_lcm = UnitreeLowCommand()
cmd_lcm.tau_ff = tau_ff.tolist()
cmd_lcm.kp = kp.tolist()
cmd_lcm.kd = kd.tolist()
cmd_lcm.q_des = q_des.tolist()
cmd_lcm.dq_des = dq_des.tolist()
self.lcm_bridge.sendCommands(cmd_lcm)
def close(self):
"""
Stop the LCM thread, unsubscribe from the LCM topic,
and effectively shut down the interface.
"""
self.lcm_bridge.close()
def reset(self):
time.sleep(0.3)
for i in range(100):
time.sleep(0.01)
self.sendCommands(np.zeros(9))
state = self.getStates()

View File

272
Go2Py/sim/isaacsim/go2.py Normal file
View File

@ -0,0 +1,272 @@
from collections import deque
from typing import Optional
import numpy as np
from omni.isaac.core.articulations import Articulation
from omni.isaac.core.utils.prims import define_prim
from omni.isaac.sensor import ContactSensor, IMUSensor
import Go2Py
from Go2Py.sim.isaacsim.lcm_types.unitree_lowlevel import UnitreeLowState
class UnitreeGo2(Articulation):
def __init__(
self,
prim_path: str,
name: str = "go2_quadruped",
physics_dt: Optional[float] = 1 / 400.0,
position: Optional[np.ndarray] = None,
orientation: Optional[np.ndarray] = None,
usd_path: Optional[str] = None,
) -> None:
"""
[Summary]
initialize robot, set up sensors and interfaces
Args:
prim_path {str} -- prim path of the robot on the stage
name {str} -- name of the quadruped
physics_dt {float} -- physics downtime of the controller
position {np.ndarray} -- position of the robot
orientation {np.ndarray} -- orientation of the robot
"""
self._prim_path = prim_path
prim = define_prim(self._prim_path, "Xform")
prim.GetReferences().AddReference(Go2Py.GO2_USD_PATH)
self.usd_path = usd_path
if self.usd_path is None:
prim.GetReferences().AddReference(Go2Py.GO2_USD_PATH)
else:
prim.GetReferences().AddReference(self.usd_path)
super().__init__(
prim_path=self._prim_path,
name=name,
position=position,
orientation=orientation,
)
# contact sensor setup
self.feet_order = ["FL", "RL", "FR", "RR"]
self.feet_path = [
self._prim_path + "/FL_foot",
self._prim_path + "/FR_foot",
self._prim_path + "/RL_foot",
self._prim_path + "/RR_foot",
]
self.color = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1), (1, 1, 0, 1)]
self._contact_sensors = [None] * 4
for i in range(4):
self._contact_sensors[i] = ContactSensor(
prim_path=self.feet_path[i] + "/sensor",
min_threshold=0,
max_threshold=1000000,
radius=0.043,
dt=physics_dt,
)
self.foot_force = np.zeros(4)
self.enable_foot_filter = True
self._FILTER_WINDOW_SIZE = 20
self._foot_filters = [deque(), deque(), deque(), deque()]
# imu sensor setup
self.imu_path = self._prim_path + "/imu_link"
self._imu_sensor = IMUSensor(
prim_path=self.imu_path + "/imu_sensor",
name="imu",
dt=physics_dt,
translation=np.array([0, 0, 0]),
orientation=np.array([1, 0, 0, 0]),
)
self.accel = np.zeros((3,))
self.gyro = np.zeros((3,))
# Translation maps between Isaac and Bullet
# self.bullet_joint_order = ['FL_hip_joint', 'FL_thigh_joint', 'FL_calf_joint',
# 'RL_hip_joint', 'RL_thigh_joint', 'RL_calf_joint',
# 'FR_hip_joint', 'FR_thigh_joint', 'FR_calf_joint',
# 'RR_hip_joint', 'RR_thigh_joint', 'RR_calf_joint']
self.bullet_joint_order = [
"FL_hip_joint",
"FL_thigh_joint",
"FL_calf_joint",
"FR_hip_joint",
"FR_thigh_joint",
"FR_calf_joint",
"RL_hip_joint",
"RL_thigh_joint",
"RL_calf_joint",
"RR_hip_joint",
"RR_thigh_joint",
"RR_calf_joint",
]
self.isaac_joint_order = [
"FL_hip_joint",
"FR_hip_joint",
"RL_hip_joint",
"RR_hip_joint",
"FL_thigh_joint",
"FR_thigh_joint",
"RL_thigh_joint",
"RR_thigh_joint",
"FL_calf_joint",
"FR_calf_joint",
"RL_calf_joint",
"RR_calf_joint",
]
self.isaac_name_2_index = {s: i for i, s in enumerate(self.isaac_joint_order)}
self.bullet_name_2_index = {s: i for i, s in enumerate(self.bullet_joint_order)}
self.to_bullet_index = np.array(
[self.isaac_name_2_index[id] for id in self.bullet_joint_order]
)
self.to_isaac_index = np.array(
[self.bullet_name_2_index[id] for id in self.isaac_joint_order]
)
self.tau_est = np.zeros((12,))
self.state = UnitreeLowState()
self.init_pos = np.array([0.0, 0.0, 0.6])
self.init_quat = np.array([0.0, 0.0, 0.0, 1.0])
self.init_joint_pos = np.array(
[0.2, 0.8, -1.5, -0.2, 0.8, -1.5, 0.2, 1.0, -1.6, -0.2, 1.0, -1.6]
)
return
def toIsaacOrder(self, x):
return x[self.to_isaac_index, ...]
def toBulletOrder(self, x):
return x[self.to_bullet_index, ...]
def setState(self, pos, quat, q) -> None:
"""[Summary]
Set the kinematic state of the robot.
Args:
pos {ndarray} -- The position of the robot (x, y, z)
quat {ndarray} -- The orientation of the robot (qx, qy, qz, qw)
q {ndarray} -- Joint angles of the robot in standard Pinocchio order
Raises:
RuntimeError: When the DC Toolbox interface has not been configured.
"""
self.set_world_pose(position=pos, orientation=quat[[3, 0, 1, 2]])
self.set_linear_velocity(np.zeros((3,)))
self.set_angular_velocity(np.zeros((3,)))
self.set_joint_positions(
positions=np.asarray(self.toIsaacOrder(q), dtype=np.float32)
)
self.set_joint_velocities(
velocities=np.asarray(self.toIsaacOrder(np.zeros((12,))), dtype=np.float32)
)
self.set_joint_efforts(np.zeros_like(q))
return
def initialize(self, physics_sim_view=None) -> None:
"""[summary]
initialize dc interface, set up drive mode and initial robot state
"""
super().initialize(physics_sim_view=physics_sim_view)
self.get_articulation_controller().set_effort_modes("force")
self.get_articulation_controller().switch_control_mode("effort")
self.setState(self.init_pos, self.init_quat, self.init_joint_pos)
for i in range(4):
self._contact_sensors[i].initialize()
return
def readContactSensor(self) -> None:
"""[summary]
Updates processed contact sensor data from the robot feet, store them in member variable foot_force
"""
# Order: FL, RL, FR, RR
for i in range(len(self.feet_path)):
frame = self._contact_sensors[i].get_current_frame()
if "force" in frame:
if self.enable_foot_filter:
self._foot_filters[i].append(frame["force"])
if len(self._foot_filters[i]) > self._FILTER_WINDOW_SIZE:
self._foot_filters[i].popleft()
self.foot_force[i] = np.mean(self._foot_filters[i])
else:
self.foot_force[i] = frame["force"]
return self.foot_force
def readIMUSensor(self) -> None:
"""[summary]
Updates processed imu sensor data from the robot body, store them in member variable base_lin and ang_vel
"""
frame = self._imu_sensor.get_current_frame()
self.accel = frame["lin_acc"]
self.gyro = frame["ang_vel"]
return self.accel, self.gyro
def readStates(self):
contact_forces = self.readContactSensor()
accel, gyro = self.readIMUSensor()
# joint pos and vel from the DC interface
joint_state = super().get_joints_state()
joint_pos = self.toBulletOrder(joint_state.positions)
joint_vel = self.toBulletOrder(joint_state.velocities)
# base frame
base_pose = self.get_world_pose()
base_pos = base_pose[0]
base_quat = base_pose[1][[1, 2, 3, 0]]
base_lin_vel = self.get_linear_velocity()
base_ang_vel = self.get_angular_velocity()
# assign to state objects
self.state.accel = accel.tolist()
self.state.gyro = gyro.tolist()
self.state.q = joint_pos.tolist()
self.state.dq = joint_vel.tolist()
self.state.tau_est = self.tau_est
self.state.quaternion = base_quat.tolist()
self.state.gt_pos = base_pos.tolist()
self.state.gt_quat = base_quat.tolist()
self.state.gt_lin_vel = base_lin_vel.tolist()
self.state.gt_ang_vel = base_ang_vel.tolist()
self.state.contact_state = contact_forces.tolist()
# projected_gravity = pp.SO3(base_quat).matrix().T @ np.array(
# [0.0, 0.0, -1.0]
# ).reshape(3, 1)
# self.state.gravity = (
# projected_gravity.squeeze().numpy().astype(np.float32).tolist()
# )
return self.state
def setCommands(self, cmd):
"""[summary]
sets the joint torques
Argument:
action {np.ndarray} -- Joint torque command
"""
q = np.array(self.state.q)
dq = np.array(self.state.dq)
kp = np.array(cmd.kp)
kd = np.array(cmd.kd)
tau_ff = np.array(cmd.tau_ff)
q_des = np.array(cmd.q_des)
dq_des = np.array(cmd.dq_des)
tau = (q_des - q) * kp + (dq_des - dq) * kd + tau_ff
self.set_joint_efforts(np.asarray(self.toIsaacOrder(tau), dtype=np.float32))
self.tau_est = tau
return
def step(self, cmd):
self.readStates()
self.setCommands(cmd)
return self.state

View File

@ -0,0 +1,213 @@
# launch Isaac Sim before any other imports
# default first two lines in any standalone application
import pickle
import sys
import time
import numpy as np
from omni.isaac.kit import SimulationApp
# sys.path.append("/home/vr-station/projects/lcm/build/python")
simulation_app = SimulationApp({"headless": False}) # we can also run as headless.
# import cv2
from omni.isaac.core import World
from omni.isaac.core.utils.nucleus import get_assets_root_path
from omni.isaac.core.utils.prims import define_prim, get_prim_at_path
from omni.isaac.sensor import RotatingLidarPhysX
import Go2Py
from Go2Py.sim.isaacsim.lcm_types.unitree_lowlevel import UnitreeLowCommand
from Go2Py.sim.isaacsim.go2 import UnitreeGo2
from Go2Py.sim.isaacsim.utils import AnnotatorManager
from Go2Py.sim.utils import (
NumpyMemMapDataPipe,
load_config,
simulationManager,
)
cfg = load_config(Go2Py.GO2_ISAACSIM_CFG_PATH)
robots = cfg["robots"]
cameras = cfg["cameras"]
env_cfg = cfg["environment"]
PHYSICS_DT = 1 / 200
RENDERING_DT = 1 / 200
# create the world
world = World(
physics_dt=cfg["environment"]["simulation_dt"],
rendering_dt=cfg["environment"]["rendering_dt"],
)
assets_root_path = get_assets_root_path()
if assets_root_path is None:
print("Could not find Isaac Sim assets folder")
prim = get_prim_at_path(env_cfg["prim_path"])
if not prim.IsValid():
prim = define_prim(env_cfg["prim_path"], "Xform")
asset_path = (
assets_root_path + env_cfg["usd_path"]
if env_cfg["buildin"] is True
else env_cfg["usd_path"]
)
prim.GetReferences().AddReference(asset_path)
breakpoint()
go2 = world.scene.add(
UnitreeGo2(
prim_path=robots[0]["prim_path"],
usd_path=(robots[0]["usd_path"] if robots[0]["usd_path"] != "" else None),
name=robots[0]["name"],
position=np.array(robots[0]["position"]),
physics_dt=cfg["environment"]["simulation_dt"],
)
)
# # Add Lidar
# lidar = world.scene.add(
# RotatingLidarPhysX(
# prim_path="/World/Env/GO2/imu_link/lidar",
# name="lidar",
# translation=[0.16, 0.0, 0.14],
# orientation=[0.0, 0.0, 0.0, 1.0],
# )
# )
# lidar.add_depth_data_to_frame()
# lidar.add_point_cloud_data_to_frame()
# lidar.set_rotation_frequency(0)
# lidar.set_resolution([0.4, 2])
# # lidar.enable_visualization()
# lidar.prim.GetAttribute("highLod").Set(True)
# lidar.prim.GetAttribute("highLod").Set(True)
# lidar_data_pipe = NumpyMemMapDataPipe(
# "lidar_data_pipe", force=True, dtype="float32", shape=(900, 16, 3)
# )
world.reset()
go2.initialize()
breakpoint()
# Add cameras
print("Adding cameras")
ann = AnnotatorManager(world)
for camera in cameras:
ann.registerCamera(
camera["prim_path"],
camera["name"],
translation=camera["translation"], # xyz
orientation=camera["orientation"], # xyzw
resolution=camera["resolution"],
)
for type in camera["type"]:
ann.registerAnnotator(type, camera["name"])
ann.setClippingRange(camera["name"], 0.2, 1000000.0)
ann.setFocalLength(camera["name"], 28)
# Add the shared memory data channels for image and LiDAR data
print("Creating shared memory data pipes")
camera_pipes = {}
for camera in cameras:
for type in camera["type"]:
if type == "pointcloud":
pipe = NumpyMemMapDataPipe(
camera["name"] + "_" + type,
force=True,
dtype="float32",
shape=(camera["resolution"][1] * camera["resolution"][0], 3),
)
camera_pipes[camera["name"] + "_" + type] = pipe
elif type == "rgb":
pipe = NumpyMemMapDataPipe(
camera["name"] + "_" + type,
force=True,
dtype="uint8",
shape=(camera["resolution"][1], camera["resolution"][0], 4),
)
camera_pipes[camera["name"] + "_" + type] = pipe
elif type == "distance_to_camera":
pipe = NumpyMemMapDataPipe(
camera["name"] + "_" + type,
force=True,
dtype="uint8",
shape=(camera["resolution"][1], camera["resolution"][0]),
)
camera_pipes[camera["name"] + "_" + type] = pipe
# Store simulation hyperparamters in shared memory
print("Storing simulation hyperparamters in shared memory")
meta_data = {
"camera_names": [camera["name"] for camera in cameras],
"camera_types": [camera["type"] for camera in cameras],
"camera_resolutions": [camera["resolution"] for camera in cameras],
"camera_intrinsics": [
ann.getCameraIntrinsics(camera["name"]) for camera in cameras
],
"camera_extrinsics": [
ann.getCameraExtrinsics(camera["name"]) for camera in cameras
],
}
with open("/dev/shm/fr3_sim_meta_data.pkl", "wb") as f:
pickle.dump(meta_data, f)
lcm_server = LCMBridgeServer(robot_name="b1")
cmd_stamp = time.time()
cmd_stamp_old = cmd_stamp
cmd = UnitreeLowCommand()
cmd.kd = 12 * [2.5]
cmd.kp = 12 * [100]
cmd.dq_des = 12 * [0]
cmd.q_des = b1.init_joint_pos
# sim_manager = simulationManager(
# robot=go2,
# lcm_server=lcm_server,
# default_cmd=cmd,
# physics_dt=PHYSICS_DT,
# lcm_timeout=1e-4,
# )
counter = 0
while simulation_app.is_running():
# sim_manager.step(counter*PHYSICS_DT)
# Step the world with rendering 50 times per second
# sim_manager.step(counter * PHYSICS_DT)
if counter % 4 == 0:
world.step(render=True)
pc = lidar.get_current_frame()["point_cloud"]
lidar_data_pipe.write(pc, match_length=True)
# Push the sensor data to the shared memory pipes
for camera in cameras:
for type in camera["type"]:
data = ann.getData(f"{camera['name']}:{type}")
if type == "pointcloud":
payload = data["data"]
if payload.shape[0]:
camera_pipes[camera["name"] + "_" + type].write(
np.zeros(
(camera["resolution"][1] * camera["resolution"][0], 3)
),
match_length=True,
)
camera_pipes[camera["name"] + "_" + type].write(
payload, match_length=True
)
else:
if data.shape[0]:
camera_pipes[camera["name"] + "_" + type].write(
data, match_length=False
)
else:
world.step(render=False)
counter += 1
simulation_app.close() # close Isaac Sim

View File

View File

@ -0,0 +1,9 @@
package unitree_lowlevel;
struct UnitreeLowCommand {
double stamp;
float tau_ff[12];
float q_des[12];
float dq_des[12];
float kp[12];
float kd[12];
}

View File

@ -0,0 +1,18 @@
package unitree_lowlevel;
struct UnitreeLowState {
double stamp;
float q[12];
float dq[12];
float tau_est[12];
float contact_state[4];
float accel[3];
float gyro[3];
float temperature;
float quaternion[4];
float rpy[3];
float gravity[3];
float gt_pos[3];
float gt_quat[4];
float gt_lin_vel[3];
float gt_ang_vel[3];
}

View File

@ -0,0 +1,12 @@
#!/bin/bash
GREEN='\033[0;32m'
NC='\033[0m' # No Color
echo -e "${GREEN} Starting LCM type generation...${NC}"
# Clean
rm -r unitree_lowlevel
# Make
lcm-gen -xp *.lcm
echo -e "${GREEN} Done with LCM type generation${NC}"

View File

@ -0,0 +1,193 @@
/** THIS IS AN AUTOMATICALLY GENERATED FILE. DO NOT MODIFY
* BY HAND!!
*
* Generated by lcm-gen
**/
#ifndef __unitree_lowlevel_UnitreeLowCommand_hpp__
#define __unitree_lowlevel_UnitreeLowCommand_hpp__
#include <lcm/lcm_coretypes.h>
namespace unitree_lowlevel
{
class UnitreeLowCommand
{
public:
double stamp;
float tau_ff[12];
float q_des[12];
float dq_des[12];
float kp[12];
float kd[12];
public:
/**
* Encode a message into binary form.
*
* @param buf The output buffer.
* @param offset Encoding starts at this byte offset into @p buf.
* @param maxlen Maximum number of bytes to write. This should generally be
* equal to getEncodedSize().
* @return The number of bytes encoded, or <0 on error.
*/
inline int encode(void *buf, int offset, int maxlen) const;
/**
* Check how many bytes are required to encode this message.
*/
inline int getEncodedSize() const;
/**
* Decode a message from binary form into this instance.
*
* @param buf The buffer containing the encoded message.
* @param offset The byte offset into @p buf where the encoded message starts.
* @param maxlen The maximum number of bytes to read while decoding.
* @return The number of bytes decoded, or <0 if an error occurred.
*/
inline int decode(const void *buf, int offset, int maxlen);
/**
* Retrieve the 64-bit fingerprint identifying the structure of the message.
* Note that the fingerprint is the same for all instances of the same
* message type, and is a fingerprint on the message type definition, not on
* the message contents.
*/
inline static int64_t getHash();
/**
* Returns "UnitreeLowCommand"
*/
inline static const char* getTypeName();
// LCM support functions. Users should not call these
inline int _encodeNoHash(void *buf, int offset, int maxlen) const;
inline int _getEncodedSizeNoHash() const;
inline int _decodeNoHash(const void *buf, int offset, int maxlen);
inline static uint64_t _computeHash(const __lcm_hash_ptr *p);
};
int UnitreeLowCommand::encode(void *buf, int offset, int maxlen) const
{
int pos = 0, tlen;
int64_t hash = getHash();
tlen = __int64_t_encode_array(buf, offset + pos, maxlen - pos, &hash, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = this->_encodeNoHash(buf, offset + pos, maxlen - pos);
if (tlen < 0) return tlen; else pos += tlen;
return pos;
}
int UnitreeLowCommand::decode(const void *buf, int offset, int maxlen)
{
int pos = 0, thislen;
int64_t msg_hash;
thislen = __int64_t_decode_array(buf, offset + pos, maxlen - pos, &msg_hash, 1);
if (thislen < 0) return thislen; else pos += thislen;
if (msg_hash != getHash()) return -1;
thislen = this->_decodeNoHash(buf, offset + pos, maxlen - pos);
if (thislen < 0) return thislen; else pos += thislen;
return pos;
}
int UnitreeLowCommand::getEncodedSize() const
{
return 8 + _getEncodedSizeNoHash();
}
int64_t UnitreeLowCommand::getHash()
{
static int64_t hash = static_cast<int64_t>(_computeHash(NULL));
return hash;
}
const char* UnitreeLowCommand::getTypeName()
{
return "UnitreeLowCommand";
}
int UnitreeLowCommand::_encodeNoHash(void *buf, int offset, int maxlen) const
{
int pos = 0, tlen;
tlen = __double_encode_array(buf, offset + pos, maxlen - pos, &this->stamp, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->tau_ff[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->q_des[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->dq_des[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->kp[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->kd[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
return pos;
}
int UnitreeLowCommand::_decodeNoHash(const void *buf, int offset, int maxlen)
{
int pos = 0, tlen;
tlen = __double_decode_array(buf, offset + pos, maxlen - pos, &this->stamp, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->tau_ff[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->q_des[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->dq_des[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->kp[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->kd[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
return pos;
}
int UnitreeLowCommand::_getEncodedSizeNoHash() const
{
int enc_size = 0;
enc_size += __double_encoded_array_size(NULL, 1);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 12);
return enc_size;
}
uint64_t UnitreeLowCommand::_computeHash(const __lcm_hash_ptr *)
{
uint64_t hash = 0x3fa55736ccae518dLL;
return (hash<<1) + ((hash>>63)&1);
}
}
#endif

View File

@ -0,0 +1,88 @@
"""LCM type definitions
This file automatically generated by lcm.
DO NOT MODIFY BY HAND!!!!
"""
try:
import cStringIO.StringIO as BytesIO
except ImportError:
from io import BytesIO
import struct
class UnitreeLowCommand:
__slots__ = ["stamp", "tau_ff", "q_des", "dq_des", "kp", "kd"]
__typenames__ = ["double", "float", "float", "float", "float", "float"]
__dimensions__ = [None, [12], [12], [12], [12], [12]]
def __init__(self):
self.stamp = 0.0
self.tau_ff = [0.0 for dim0 in range(12)]
self.q_des = [0.0 for dim0 in range(12)]
self.dq_des = [0.0 for dim0 in range(12)]
self.kp = [0.0 for dim0 in range(12)]
self.kd = [0.0 for dim0 in range(12)]
def encode(self):
buf = BytesIO()
buf.write(UnitreeLowCommand._get_packed_fingerprint())
self._encode_one(buf)
return buf.getvalue()
def _encode_one(self, buf):
buf.write(struct.pack(">d", self.stamp))
buf.write(struct.pack(">12f", *self.tau_ff[:12]))
buf.write(struct.pack(">12f", *self.q_des[:12]))
buf.write(struct.pack(">12f", *self.dq_des[:12]))
buf.write(struct.pack(">12f", *self.kp[:12]))
buf.write(struct.pack(">12f", *self.kd[:12]))
def decode(data):
if hasattr(data, "read"):
buf = data
else:
buf = BytesIO(data)
if buf.read(8) != UnitreeLowCommand._get_packed_fingerprint():
raise ValueError("Decode error")
return UnitreeLowCommand._decode_one(buf)
decode = staticmethod(decode)
def _decode_one(buf):
self = UnitreeLowCommand()
self.stamp = struct.unpack(">d", buf.read(8))[0]
self.tau_ff = struct.unpack(">12f", buf.read(48))
self.q_des = struct.unpack(">12f", buf.read(48))
self.dq_des = struct.unpack(">12f", buf.read(48))
self.kp = struct.unpack(">12f", buf.read(48))
self.kd = struct.unpack(">12f", buf.read(48))
return self
_decode_one = staticmethod(_decode_one)
def _get_hash_recursive(parents):
if UnitreeLowCommand in parents:
return 0
tmphash = (0x3FA55736CCAE518D) & 0xFFFFFFFFFFFFFFFF
tmphash = (
((tmphash << 1) & 0xFFFFFFFFFFFFFFFF) + (tmphash >> 63)
) & 0xFFFFFFFFFFFFFFFF
return tmphash
_get_hash_recursive = staticmethod(_get_hash_recursive)
_packed_fingerprint = None
def _get_packed_fingerprint():
if UnitreeLowCommand._packed_fingerprint is None:
UnitreeLowCommand._packed_fingerprint = struct.pack(
">Q", UnitreeLowCommand._get_hash_recursive([])
)
return UnitreeLowCommand._packed_fingerprint
_get_packed_fingerprint = staticmethod(_get_packed_fingerprint)
def get_hash(self):
"""Get the LCM hash of the struct"""
return struct.unpack(">Q", UnitreeLowCommand._get_packed_fingerprint())[0]

View File

@ -0,0 +1,274 @@
/** THIS IS AN AUTOMATICALLY GENERATED FILE. DO NOT MODIFY
* BY HAND!!
*
* Generated by lcm-gen
**/
#ifndef __unitree_lowlevel_UnitreeLowState_hpp__
#define __unitree_lowlevel_UnitreeLowState_hpp__
#include <lcm/lcm_coretypes.h>
namespace unitree_lowlevel
{
class UnitreeLowState
{
public:
double stamp;
float q[12];
float dq[12];
float tau_est[12];
float contact_state[4];
float accel[3];
float gyro[3];
float temperature;
float quaternion[4];
float rpy[3];
float gravity[3];
float gt_pos[3];
float gt_quat[4];
float gt_lin_vel[3];
float gt_ang_vel[3];
public:
/**
* Encode a message into binary form.
*
* @param buf The output buffer.
* @param offset Encoding starts at this byte offset into @p buf.
* @param maxlen Maximum number of bytes to write. This should generally be
* equal to getEncodedSize().
* @return The number of bytes encoded, or <0 on error.
*/
inline int encode(void *buf, int offset, int maxlen) const;
/**
* Check how many bytes are required to encode this message.
*/
inline int getEncodedSize() const;
/**
* Decode a message from binary form into this instance.
*
* @param buf The buffer containing the encoded message.
* @param offset The byte offset into @p buf where the encoded message starts.
* @param maxlen The maximum number of bytes to read while decoding.
* @return The number of bytes decoded, or <0 if an error occurred.
*/
inline int decode(const void *buf, int offset, int maxlen);
/**
* Retrieve the 64-bit fingerprint identifying the structure of the message.
* Note that the fingerprint is the same for all instances of the same
* message type, and is a fingerprint on the message type definition, not on
* the message contents.
*/
inline static int64_t getHash();
/**
* Returns "UnitreeLowState"
*/
inline static const char* getTypeName();
// LCM support functions. Users should not call these
inline int _encodeNoHash(void *buf, int offset, int maxlen) const;
inline int _getEncodedSizeNoHash() const;
inline int _decodeNoHash(const void *buf, int offset, int maxlen);
inline static uint64_t _computeHash(const __lcm_hash_ptr *p);
};
int UnitreeLowState::encode(void *buf, int offset, int maxlen) const
{
int pos = 0, tlen;
int64_t hash = getHash();
tlen = __int64_t_encode_array(buf, offset + pos, maxlen - pos, &hash, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = this->_encodeNoHash(buf, offset + pos, maxlen - pos);
if (tlen < 0) return tlen; else pos += tlen;
return pos;
}
int UnitreeLowState::decode(const void *buf, int offset, int maxlen)
{
int pos = 0, thislen;
int64_t msg_hash;
thislen = __int64_t_decode_array(buf, offset + pos, maxlen - pos, &msg_hash, 1);
if (thislen < 0) return thislen; else pos += thislen;
if (msg_hash != getHash()) return -1;
thislen = this->_decodeNoHash(buf, offset + pos, maxlen - pos);
if (thislen < 0) return thislen; else pos += thislen;
return pos;
}
int UnitreeLowState::getEncodedSize() const
{
return 8 + _getEncodedSizeNoHash();
}
int64_t UnitreeLowState::getHash()
{
static int64_t hash = static_cast<int64_t>(_computeHash(NULL));
return hash;
}
const char* UnitreeLowState::getTypeName()
{
return "UnitreeLowState";
}
int UnitreeLowState::_encodeNoHash(void *buf, int offset, int maxlen) const
{
int pos = 0, tlen;
tlen = __double_encode_array(buf, offset + pos, maxlen - pos, &this->stamp, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->q[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->dq[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->tau_est[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->contact_state[0], 4);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->accel[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->gyro[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->temperature, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->quaternion[0], 4);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->rpy[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->gravity[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->gt_pos[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->gt_quat[0], 4);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->gt_lin_vel[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_encode_array(buf, offset + pos, maxlen - pos, &this->gt_ang_vel[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
return pos;
}
int UnitreeLowState::_decodeNoHash(const void *buf, int offset, int maxlen)
{
int pos = 0, tlen;
tlen = __double_decode_array(buf, offset + pos, maxlen - pos, &this->stamp, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->q[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->dq[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->tau_est[0], 12);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->contact_state[0], 4);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->accel[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->gyro[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->temperature, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->quaternion[0], 4);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->rpy[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->gravity[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->gt_pos[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->gt_quat[0], 4);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->gt_lin_vel[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __float_decode_array(buf, offset + pos, maxlen - pos, &this->gt_ang_vel[0], 3);
if(tlen < 0) return tlen; else pos += tlen;
return pos;
}
int UnitreeLowState::_getEncodedSizeNoHash() const
{
int enc_size = 0;
enc_size += __double_encoded_array_size(NULL, 1);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 12);
enc_size += __float_encoded_array_size(NULL, 4);
enc_size += __float_encoded_array_size(NULL, 3);
enc_size += __float_encoded_array_size(NULL, 3);
enc_size += __float_encoded_array_size(NULL, 1);
enc_size += __float_encoded_array_size(NULL, 4);
enc_size += __float_encoded_array_size(NULL, 3);
enc_size += __float_encoded_array_size(NULL, 3);
enc_size += __float_encoded_array_size(NULL, 3);
enc_size += __float_encoded_array_size(NULL, 4);
enc_size += __float_encoded_array_size(NULL, 3);
enc_size += __float_encoded_array_size(NULL, 3);
return enc_size;
}
uint64_t UnitreeLowState::_computeHash(const __lcm_hash_ptr *)
{
uint64_t hash = 0x63fba9ef911d1605LL;
return (hash<<1) + ((hash>>63)&1);
}
}
#endif

View File

@ -0,0 +1,163 @@
"""LCM type definitions
This file automatically generated by lcm.
DO NOT MODIFY BY HAND!!!!
"""
try:
import cStringIO.StringIO as BytesIO
except ImportError:
from io import BytesIO
import struct
class UnitreeLowState:
__slots__ = [
"stamp",
"q",
"dq",
"tau_est",
"contact_state",
"accel",
"gyro",
"temperature",
"quaternion",
"rpy",
"gravity",
"gt_pos",
"gt_quat",
"gt_lin_vel",
"gt_ang_vel",
]
__typenames__ = [
"double",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
"float",
]
__dimensions__ = [
None,
[12],
[12],
[12],
[4],
[3],
[3],
None,
[4],
[3],
[3],
[3],
[4],
[3],
[3],
]
def __init__(self):
self.stamp = 0.0
self.q = [0.0 for dim0 in range(12)]
self.dq = [0.0 for dim0 in range(12)]
self.tau_est = [0.0 for dim0 in range(12)]
self.contact_state = [0.0 for dim0 in range(4)]
self.accel = [0.0 for dim0 in range(3)]
self.gyro = [0.0 for dim0 in range(3)]
self.temperature = 0.0
self.quaternion = [0.0 for dim0 in range(4)]
self.rpy = [0.0 for dim0 in range(3)]
self.gravity = [0.0 for dim0 in range(3)]
self.gt_pos = [0.0 for dim0 in range(3)]
self.gt_quat = [0.0 for dim0 in range(4)]
self.gt_lin_vel = [0.0 for dim0 in range(3)]
self.gt_ang_vel = [0.0 for dim0 in range(3)]
def encode(self):
buf = BytesIO()
buf.write(UnitreeLowState._get_packed_fingerprint())
self._encode_one(buf)
return buf.getvalue()
def _encode_one(self, buf):
buf.write(struct.pack(">d", self.stamp))
buf.write(struct.pack(">12f", *self.q[:12]))
buf.write(struct.pack(">12f", *self.dq[:12]))
buf.write(struct.pack(">12f", *self.tau_est[:12]))
buf.write(struct.pack(">4f", *self.contact_state[:4]))
buf.write(struct.pack(">3f", *self.accel[:3]))
buf.write(struct.pack(">3f", *self.gyro[:3]))
buf.write(struct.pack(">f", self.temperature))
buf.write(struct.pack(">4f", *self.quaternion[:4]))
buf.write(struct.pack(">3f", *self.rpy[:3]))
buf.write(struct.pack(">3f", *self.gravity[:3]))
buf.write(struct.pack(">3f", *self.gt_pos[:3]))
buf.write(struct.pack(">4f", *self.gt_quat[:4]))
buf.write(struct.pack(">3f", *self.gt_lin_vel[:3]))
buf.write(struct.pack(">3f", *self.gt_ang_vel[:3]))
def decode(data):
if hasattr(data, "read"):
buf = data
else:
buf = BytesIO(data)
if buf.read(8) != UnitreeLowState._get_packed_fingerprint():
raise ValueError("Decode error")
return UnitreeLowState._decode_one(buf)
decode = staticmethod(decode)
def _decode_one(buf):
self = UnitreeLowState()
self.stamp = struct.unpack(">d", buf.read(8))[0]
self.q = struct.unpack(">12f", buf.read(48))
self.dq = struct.unpack(">12f", buf.read(48))
self.tau_est = struct.unpack(">12f", buf.read(48))
self.contact_state = struct.unpack(">4f", buf.read(16))
self.accel = struct.unpack(">3f", buf.read(12))
self.gyro = struct.unpack(">3f", buf.read(12))
self.temperature = struct.unpack(">f", buf.read(4))[0]
self.quaternion = struct.unpack(">4f", buf.read(16))
self.rpy = struct.unpack(">3f", buf.read(12))
self.gravity = struct.unpack(">3f", buf.read(12))
self.gt_pos = struct.unpack(">3f", buf.read(12))
self.gt_quat = struct.unpack(">4f", buf.read(16))
self.gt_lin_vel = struct.unpack(">3f", buf.read(12))
self.gt_ang_vel = struct.unpack(">3f", buf.read(12))
return self
_decode_one = staticmethod(_decode_one)
def _get_hash_recursive(parents):
if UnitreeLowState in parents:
return 0
tmphash = (0x63FBA9EF911D1605) & 0xFFFFFFFFFFFFFFFF
tmphash = (
((tmphash << 1) & 0xFFFFFFFFFFFFFFFF) + (tmphash >> 63)
) & 0xFFFFFFFFFFFFFFFF
return tmphash
_get_hash_recursive = staticmethod(_get_hash_recursive)
_packed_fingerprint = None
def _get_packed_fingerprint():
if UnitreeLowState._packed_fingerprint is None:
UnitreeLowState._packed_fingerprint = struct.pack(
">Q", UnitreeLowState._get_hash_recursive([])
)
return UnitreeLowState._packed_fingerprint
_get_packed_fingerprint = staticmethod(_get_packed_fingerprint)
def get_hash(self):
"""Get the LCM hash of the struct"""
return struct.unpack(">Q", UnitreeLowState._get_packed_fingerprint())[0]

View File

@ -0,0 +1,7 @@
"""LCM package __init__.py file
This file automatically generated by lcm-gen.
DO NOT MODIFY BY HAND!!!!
"""
from .UnitreeLowCommand import UnitreeLowCommand
from .UnitreeLowState import UnitreeLowState

View File

@ -0,0 +1,28 @@
environment:
simulation_dt: 0.01
rendering_dt: 0.02
prim_path: "/World/Env"
buildin: true
usd_path: "/Isaac/Environments/Grid/default_environment.usd" # /Isaac/Environments/Simple_Warehouse/full_warehouse.usd
synchronous_mode: false # if true, the simulator will wait for the control commands from the LCM channel for 0.1 seconds
robots:
- prim_path: "/World/Env/GO2"
usd_path: ""
name: "go2"
usd_file: "assets/usd/go2.usd"
position: [0.0, 0.0, 0.8]
orientation: [0.0, 0.0, 0.0, 1.0]
cameras:
- prim_path: "/World/Env/GO2/imu_link"
name: "test_camera"
type: ["rgb", "distance_to_camera"]
resolution: [1280, 720]
interstice_parameters:
K: [1000, 1000, 500, 500]
D: [0.1, 0.2, 0.1, 0.0]
translation: [0.0, 0.0, 0.0] # x, y, z
orientation: [0.0, 0.0, 0.0, 1.0] # qx, qy, qz, qw

119
Go2Py/sim/isaacsim/utils.py Normal file
View File

@ -0,0 +1,119 @@
import numpy as np
import omni.replicator.core as rep
from omni.isaac.core.prims import BaseSensor
from omni.isaac.core.prims.xform_prim import XFormPrim
from pxr import UsdGeom
from scipy.spatial.transform import Rotation as R
class AnnotatorManager:
def __init__(self, world):
self.annotators = {}
self.render_products = {}
self.cameras = {}
self.annotator_types = [
"rgb",
"distance_to_camera",
"pointcloud",
"semantic_segmentation",
]
self.camera_prims = {}
self.camera_info = {}
self.world = world
def registerCamera(
self, parent_prim, name, translation, orientation, resolution=(1024, 600)
):
"""
Register a camera in the scene. The camera will be attached to the parent_prim.
parent_prim: The prim path of the parent prim. The camera will be attached to this prim.
name: The name of the camera.
translation: The translation of the camera relative to the parent prim in the form of (x, y, z).
orientation: The orientation of the camera relative to the parent prim in the form of (x, y, z, w).
resolution: The resolution of the camera in the form of (width, height).
"""
qx, qy, qz, qw = tuple(orientation)
xformprim = XFormPrim(
prim_path=parent_prim + "/" + name,
name=name,
translation=tuple(translation),
orientation=(qw, qx, qy, qz), # Omniverse core convention (w, x, y, z)
)
self.camera_prims[name] = xformprim
self.cameras[name] = self.world.stage.DefinePrim(
xformprim.prim_path + "/" + name, "Camera"
)
UsdGeom.Xformable(self.cameras[name]).AddTranslateOp().Set((0.0, 0.0, 0.0))
# Rotate around x for 180 degrees to make the convention consistent with opencv/ROS
UsdGeom.Xformable(self.cameras[name]).AddRotateXYZOp().Set((180.0, 0.0, 0.0))
self.camera_info[name] = {
"translation": translation,
"orientation": orientation,
"resolution": resolution,
}
self.render_products[name] = rep.create.render_product(
str(self.cameras[name].GetPrimPath()), resolution
)
def registerAnnotator(self, type, camera_name):
assert (
type in self.annotator_types
), "Requested replicator type not available. Choose from: " + str(
self.annotator_types
)
assert (
camera_name in self.cameras.keys()
), "The requested camera is not registered. Please register the camera first."
self.annotators[camera_name + ":" + type] = rep.AnnotatorRegistry.get_annotator(
type
)
self.annotators[camera_name + ":" + type].attach(
[self.render_products[camera_name]]
)
def getData(self, annotator_name):
assert (
annotator_name in self.annotators.keys()
), "The requested annotator is not registered. Please register the annotator first."
return self.annotators[annotator_name].get_data()
def getStreams(self):
return list(self.annotators.keys())
def setFocalLength(self, name, value):
assert (
name in self.cameras.keys()
), "The requested camera is not registered. Please register the camera first."
self.cameras[name].GetAttribute("focalLength").Set(value)
def setClippingRange(self, name, min=0.2, max=1000000.0):
assert (
name in self.cameras.keys()
), "The requested camera is not registered. Please register the camera first."
self.cameras[name].GetAttribute("clippingRange").Set((min, max))
# TODO: implement these functions
def getCameraIntrinsics(self, name):
return None
def getCameraExtrinsics(self, name):
return None
def getCameraPose(self, name):
"""
Get the camera pose in the world frame
@param name: (str) The name of the camera
@return: (t, q) The camera pose (np.array([x, y, z]), np.array([qw, qx, qy, qz]))
"""
t, q = self.camera_prims[name].get_world_pose()
return t, q
def setCameraPose(self, name, t, q):
"""
Get the camera pose in the world frame
@param name: (str) The name of the camera
@param t: (mp.array([x, y, z])) The translation of the camera in the world frame
@param q: (mp.array([qw, qx, qy, qz])) The orientation of the camera in the world frame
@return: None
"""
self.camera_prims[name].set_local_pose(t, q)

61
Go2Py/sim/mujoco.py Normal file
View File

@ -0,0 +1,61 @@
import time
from copy import deepcopy
import matplotlib.pyplot as plt
import mujoco
import mujoco.viewer
import numpy as np
import pinocchio as pin
from pinocchio.robot_wrapper import RobotWrapper
from Go2Py import ASSETS_PATH
import os
class Go2Sim:
def __init__(self, render=True, dt=0.002):
self.model = mujoco.MjModel.from_xml_path(
os.path.join(ASSETS_PATH, 'mujoco/go2.xml')
)
self.data = mujoco.MjData(self.model)
self.dt = dt
_render_dt = 1/60
self.render_ds_ratio = max(1, _render_dt//dt)
if render:
self.viewer = mujoco.viewer.launch_passive(self.model, self.data)
self.render = True
self.viewer.cam.distance = 3.0
self.viewer.cam.azimuth = 90
self.viewer.cam.elevation = -45
self.viewer.cam.lookat[:] = np.array([0.0, -0.25, 0.824])
else:
self.render = False
self.model.opt.gravity[2] = -9.81
self.model.opt.timestep = dt
self.renderer = None
self.render = render
self.step_counter = 0
def reset(self):
self.q_nominal = np.array(
12*[0.]
)
for i in range(12):
self.data.qpos[i] = self.q_nominal[i]
self.data.qpos[7] = 0.0
self.data.qpos[8] = 0.0
def step(self, tau):
self.step_counter += 1
self.data.ctrl[:] = tau
mujoco.mj_step(self.model, self.data)
# Render every render_ds_ratio steps (60Hz GUI update)
if self.render and (self.step_counter%self.render_ds_ratio)==0:
self.viewer.sync()
return self.data.qpos, self.data.qvel
def close(self):
self.viewer.close()

74
Go2Py/sim/utils.py Normal file
View File

@ -0,0 +1,74 @@
import select
import numpy as np
import yaml
from Go2Py.sim.isaacsim.lcm_types.unitree_lowlevel import UnitreeLowCommand, UnitreeLowState
def load_config(file_path):
with open(file_path, "r") as file:
config = yaml.safe_load(file)
return config
class NumpyMemMapDataPipe:
def __init__(self, channel_name, force=False, dtype="uint8", shape=(640, 480, 3)):
self.channel_name = channel_name
self.force = force
self.dtype = dtype
self.shape = shape
if force:
self.shm = np.memmap(
"/dev/shm/" + channel_name, dtype=self.dtype, mode="w+", shape=shape
)
else:
self.shm = np.memmap(
"/dev/shm/" + channel_name, dtype=self.dtype, mode="r+", shape=shape
)
def write(self, data, match_length=True):
if match_length:
self.shm[: data.shape[0], ...] = data
else:
assert (
data.shape == self.shape
), "The data and the shape of the shared memory must match"
self.shm[:] = data
def read(self):
return self.shm.copy()
class simulationManager:
def __init__(self, robot, lcm_server, default_cmd, physics_dt, lcm_timeout=0.01):
self.robot = robot
self.lcm_server = lcm_server
self.missed_ticks = 0
self.default_cmd = default_cmd
self.robot.initialize()
self.reset_required = True
self.lcm_timeout = lcm_timeout
self.physics_dt = physics_dt
self.robot.setCommands(self.default_cmd)
def step(self, timestamp):
# Read the robot's state and send it to the LCM client
state = self.robot.readStates()
state.stamp = timestamp
self.lcm_server.sendStates(state)
# Wait for a response from the LCM client timeout=0.1 second
lcm_cmd = self.lcm_server.getCommands(timeout=self.lcm_timeout)
if lcm_cmd is not None:
self.missed_ticks = 0
# Reset the robot if the communication has been off for too long
if self.reset_required:
self.robot.initialize()
self.robot.setCommands(self.default_cmd)
self.reset_required = False
self.robot.setCommands(lcm_cmd)
else:
self.missed_ticks += 1
if self.missed_ticks > 0.2 / (
self.lcm_timeout + self.physics_dt
): # stopped for more than a second?
self.reset_required = True
self.robot.setCommands(self.default_cmd)

101
examples/mujoco_test.ipynb Normal file
View File

@ -0,0 +1,101 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from Go2Py.sim.mujoco import Go2Sim\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"env = Go2Sim()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"env.step_counter+=1\n",
"env.render and (env.step_counter%env.render_ds_ratio)==0\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"import mujoco\n",
"import time\n",
"freq_list = []\n",
"for i in range(100000):\n",
" tick = time.time()\n",
" q,dq = env.step(np.zeros(12))\n",
" tock = time.time()\n",
" freq_list.append(1/(tock-tick))"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using matplotlib backend: TkAgg\n"
]
}
],
"source": [
"\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib\n",
"_ = plt.hist(freq_list, bins=100)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "b1-env",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

180
go2py.sh Executable file
View File

@ -0,0 +1,180 @@
#!/bin/bash
# Note: This shell script is inspired by the Orbit simulator: https://isaac-B1Py.github.io/
#==
# Configurations
#==
# Exits if error occurs
set -e
# Set tab-spaces
tabs 4
# get source directory
export GO2PY_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
#==
# Helper functions
#==
# extract the python from isaacsim
extract_python_exe() {
# Check if IsaacSim directory manually specified
# Note: for manually build isaacsim, this: _build/linux-x86_64/release
if [ ! -z ${ISAACSIM_PATH} ];
then
# Use local build
build_path=${ISAACSIM_PATH}
else
# Use TeamCity build
build_path=${GO2PY_PATH}/_isaac_sim
fi
# check if using conda
if ! [[ -z "${CONDA_PREFIX}" ]]; then
# use conda python
local python_exe=${CONDA_PREFIX}/bin/python
else
# use python from kit
local python_exe=${build_path}/python.sh
fi
# check if there is a python path available
if [ ! -f "${python_exe}" ]; then
echo "[ERROR] No python executable found at path: ${build_path}" >&2
exit 1
fi
# return the result
echo ${python_exe}
}
# check if input directory is a python extension and install the module
install_B1Py_extension() {
# retrieve the python executable
python_exe=$(extract_python_exe)
# if the directory contains setup.py then install the python module
if [ -f "$1/setup.py" ];
then
echo -e "\t module: $1"
${python_exe} -m pip install --editable $1
fi
}
# update the vscode settings from template and isaac sim settings
update_vscode_settings() {
echo "[INFO] Setting up vscode settings..."
# retrieve the python executable
python_exe=$(extract_python_exe)
# run the setup script
${python_exe} ${GO2PY_PATH}/.vscode/tools/setup_vscode.py
}
# print the usage description
print_help () {
echo -e "\nusage: $(basename "$0") [-h] [-i] [-e] [-f] [-p] [-s] [-v] [-d] [-c] -- Utility to manage extensions in Orbit."
echo -e "\noptional arguments:"
echo -e "\t-h, --help Display the help content."
echo -e "\t-i, --install Install the extensions inside Isaac B1Py."
echo -e "\t-f, --format Run pre-commit to format the code and check lints."
echo -e "\t-p, --python Run the python executable (python.sh) provided by Isaac Sim."
# echo -e "\t-s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim."
echo -e "\t-s, --sim Run the B1Py Isaac Sim simulation node."
echo -e "\t-v, --vscode Generate the VSCode settings file from template."
echo -e "\n" >&2
}
#==
# Main
#==
# check argument provided
if [ -z "$*" ]; then
echo "[Error] No arguments provided." >&2;
print_help
exit 1
fi
# pass the arguments
while [[ $# -gt 0 ]]; do
# read the key
case "$1" in
-i|--install)
# install the python packages for supported reinforcement learning frameworks
echo "[INFO] Installing extra requirements such as learning frameworks..."
python_exe=$(extract_python_exe)
# install the rl-frameworks specified
${python_exe} -m pip install -e ${GO2PY_PATH}
shift # past argument
;;
-f|--format)
# reset the python path to avoid conflicts with pre-commit
# this is needed because the pre-commit hooks are installed in a separate virtual environment
# and it uses the system python to run the hooks
if [ -n "${CONDA_DEFAULT_ENV}" ]; then
cache_pythonpath=${PYTHONPATH}
export PYTHONPATH=""
fi
# run the formatter over the repository
# check if pre-commit is installed
if ! command -v pre-commit &>/dev/null; then
echo "[INFO] Installing pre-commit..."
pip install pre-commit
fi
# always execute inside the Orbit directory
echo "[INFO] Formatting the repository..."
cd ${GO2PY_PATH}
pre-commit run --all-files
cd - > /dev/null
# set the python path back to the original value
if [ -n "${CONDA_DEFAULT_ENV}" ]; then
export PYTHONPATH=${cache_pythonpath}
fi
shift # past argument
# exit neatly
break
;;
-p|--python)
# run the python provided by isaacsim
python_exe=$(extract_python_exe)
echo "[INFO] Using python from: ${python_exe}"
shift # past argument
${python_exe} $@
# exit neatly
break
;;
# -s|--sim)
# # run the simulator exe provided by isaacsim
# isaacsim_exe=$(extract_isaacsim_exe)
# echo "[INFO] Running isaac-sim from: ${isaacsim_exe}"
# shift # past argument
# ${isaacsim_exe} --ext-folder ${GO2PY_PATH}/source/extensions $@
# # exit neatly
# break
# ;;
-s|--sim)
# run the simulator exe provided by isaacsim
python_exe=$(extract_python_exe)
# install the rl-frameworks specified
${python_exe} ${GO2PY_PATH}/B1Py/sim/isaac/isaacsim_node.py
# exit neatly
break
;;
-v|--vscode)
# update the vscode settings
update_vscode_settings
shift # past argument
# exit neatly
break
;;
-h|--help)
print_help
exit 1
;;
*) # unknown option
echo "[Error] Invalid argument provided: $1"
print_help
exit 1
;;
esac
done