This commit is contained in:
wty-yy
2025-11-27 16:04:14 +08:00
parent 14ce318bdd
commit fc7e184741
48 changed files with 759751 additions and 2 deletions

4
robogauge/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from pathlib import Path
__version__ = "0.1.0"
ROBOGAUGE_ROOT_DIR = str(Path(__file__).parent)

Binary file not shown.

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

View File

@@ -0,0 +1,291 @@
<mujoco model="go2">
<compiler angle="radian" meshdir="assets" autolimits="true" />
<option cone="elliptic" impratio="100" />
<default>
<default class="go2">
<geom friction="0.4" margin="0.001" condim="1"/>
<joint axis="0 1 0" damping="0.1" 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" condim="6"
friction="0.4 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="white" rgba="1 1 0 1" /> -->
<material name="gray" rgba="0.671705 0.692426 0.774270 1" />
<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>
<body name="base_link" pos="0 0 0.445" childclass="go2">
<!-- <body name="base_link" pos="-5 1.2 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 name="FL_foot" pos="0 0 -0.213" />
</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 name="FR_foot" pos="0 0 -0.213" />
</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 name="RL_foot" pos="0 0 -0.213" />
</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 name="RR_foot" pos="0 0 -0.213" />
</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>
<sensor>
<jointpos name="FR_hip_pos" joint="FR_hip_joint" />
<jointpos name="FR_thigh_pos" joint="FR_thigh_joint" />
<jointpos name="FR_calf_pos" joint="FR_calf_joint" />
<jointpos name="FL_hip_pos" joint="FL_hip_joint" />
<jointpos name="FL_thigh_pos" joint="FL_thigh_joint" />
<jointpos name="FL_calf_pos" joint="FL_calf_joint" />
<jointpos name="RR_hip_pos" joint="RR_hip_joint" />
<jointpos name="RR_thigh_pos" joint="RR_thigh_joint" />
<jointpos name="RR_calf_pos" joint="RR_calf_joint" />
<jointpos name="RL_hip_pos" joint="RL_hip_joint" />
<jointpos name="RL_thigh_pos" joint="RL_thigh_joint" />
<jointpos name="RL_calf_pos" joint="RL_calf_joint" />
<jointvel name="FR_hip_vel" joint="FR_hip_joint" />
<jointvel name="FR_thigh_vel" joint="FR_thigh_joint" />
<jointvel name="FR_calf_vel" joint="FR_calf_joint" />
<jointvel name="FL_hip_vel" joint="FL_hip_joint" />
<jointvel name="FL_thigh_vel" joint="FL_thigh_joint" />
<jointvel name="FL_calf_vel" joint="FL_calf_joint" />
<jointvel name="RR_hip_vel" joint="RR_hip_joint" />
<jointvel name="RR_thigh_vel" joint="RR_thigh_joint" />
<jointvel name="RR_calf_vel" joint="RR_calf_joint" />
<jointvel name="RL_hip_vel" joint="RL_hip_joint" />
<jointvel name="RL_thigh_vel" joint="RL_thigh_joint" />
<jointvel name="RL_calf_vel" joint="RL_calf_joint" />
<jointactuatorfrc name="FR_hip_torque" joint="FR_hip_joint" noise="0.01" />
<jointactuatorfrc name="FR_thigh_torque" joint="FR_thigh_joint" noise="0.01" />
<jointactuatorfrc name="FR_calf_torque" joint="FR_calf_joint" noise="0.01" />
<jointactuatorfrc name="FL_hip_torque" joint="FL_hip_joint" noise="0.01" />
<jointactuatorfrc name="FL_thigh_torque" joint="FL_thigh_joint" noise="0.01" />
<jointactuatorfrc name="FL_calf_torque" joint="FL_calf_joint" noise="0.01" />
<jointactuatorfrc name="RR_hip_torque" joint="RR_hip_joint" noise="0.01" />
<jointactuatorfrc name="RR_thigh_torque" joint="RR_thigh_joint" noise="0.01" />
<jointactuatorfrc name="RR_calf_torque" joint="RR_calf_joint" noise="0.01" />
<jointactuatorfrc name="RL_hip_torque" joint="RL_hip_joint" noise="0.01" />
<jointactuatorfrc name="RL_thigh_torque" joint="RL_thigh_joint" noise="0.01" />
<jointactuatorfrc name="RL_calf_torque" joint="RL_calf_joint" noise="0.01" />
<framequat name="imu_quat" objtype="site" objname="imu" />
<gyro name="imu_gyro" site="imu" />
<accelerometer name="imu_acc" site="imu" />
<framepos name="frame_pos" objtype="site" objname="imu" />
<framelinvel name="frame_vel" objtype="site" objname="imu" />
</sensor>
<!-- <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,22 @@
<mujoco model="flat">
<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"/>
<material name="sponge_mat" rgba="0.9 0.9 0.2 1" reflectance="0.1"/>
</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>

19
robogauge/scripts/run.py Normal file
View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
'''
@File : run.py
@Time : 2025/11/27 15:54:47
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Run Robogauge Pipeline
'''
from robogauge.tasks import *
from robogauge.utils.task_register import task_register
from robogauge.utils.helpers import parse_args
from robogauge.utils.logger import logger
if __name__ == '__main__':
args = parse_args()
logger.create(args.experiment_name)
pipeline: BasePipeline = task_register.make_pipeline(args.task_name, args=args)
pipeline.run()

View File

@@ -0,0 +1,7 @@
from robogauge.utils.task_register import task_register
from robogauge.tasks.simulator.mujoco_config import MujocoConfig
from robogauge.tasks.robots import RobotConfig
from robogauge.tasks.pipeline import BasePipeline
from robogauge.tasks.gauge import BaseGaugeConfig
task_register.register('base', BasePipeline, MujocoConfig, BaseGaugeConfig, RobotConfig)

View File

@@ -0,0 +1,4 @@
from .base_gauge import BaseGauge
from .base_gauge_config import BaseGaugeConfig
from .flat.flat_gauge import FlatGauge
from .flat.flat_gauge_config import FlatGaugeConfig

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
'''
@File : base_gauge.py
@Time : 2025/11/27 15:55:19
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Base Gauge for Robogauge
'''
from robogauge.tasks.robots.base_robot_config import RobotConfig
from robogauge.tasks.gauge.base_gauge_config import BaseGaugeConfig
class BaseGauge:
def __init__(self, cfg: BaseGaugeConfig):
self.cfg = cfg
def is_reset(self) -> bool:
return False
def is_done(self) -> bool:
return False
def get_goal(self) -> dict:
goal = {}
return goal
def update_metrics(self, sim_info: dict):
...

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
'''
@File : base_gauge_config.py
@Time : 2025/11/27 15:55:11
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Base Gauge Configuration
'''
from robogauge.utils.config import Config
class BaseGaugeConfig(Config):
gauge_class = 'BaseGauge'
class assets:
terrain_xml = '{ROBOGAUGE_ROOT_DIR}/resources/terrains/flat.xml'
terrain_spawn_xy = [0, 0] # x y [m]
class metrics:
dof_limits = True
class commands:
stance = True
max_lin_vel = True
diagonal_lin_vel = True

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
'''
@File : flat_gauge.py
@Time : 2025/11/27 16:03:11
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Flat Gauge Implementation
'''
from robogauge.tasks.gauge.base_gauge import BaseGauge
class FlatGauge(BaseGauge):
...

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
'''
@File : flat_gauge_config.py
@Time : 2025/11/27 16:03:02
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Flat Gauge Configuration
'''
from robogauge.tasks.gauge.base_gauge_config import BaseGaugeConfig
class FlatGaugeConfig(BaseGaugeConfig):
...

View File

@@ -0,0 +1 @@
from .base_pipeline import BasePipeline

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
'''
@File : base_pipeline.py
@Time : 2025/11/27 15:53:26
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Base Pipeline for Robogauge
'''
import traceback
from robogauge.utils.logger import logger
from robogauge.tasks.simulator import MujocoSimulator, MujocoConfig
from robogauge.tasks.robots import BaseRobot, RobotConfig
from robogauge.tasks.gauge import BaseGauge, BaseGaugeConfig
class BasePipeline:
def __init__(self,
simulator_cfg: MujocoConfig,
robot_cfg: RobotConfig,
gauge_cfg: BaseGaugeConfig
):
self.simulator_cfg = simulator_cfg
self.robot_cfg = robot_cfg
self.gauge_cfg = gauge_cfg
self.sim: MujocoSimulator = eval(simulator_cfg.simulator_class)(simulator_cfg)
self.robot: BaseRobot = eval(robot_cfg.robot_class)(robot_cfg)
self.gauge: BaseGauge = eval(gauge_cfg.gauge_class)(gauge_cfg)
def load(self):
self.sim.load(
self.gauge_cfg.assets.terrain_xml,
self.robot_cfg.assets.robot_xml,
self.gauge_cfg.assets.terrain_spawn_xy,
self.robot_cfg.assets.robot_spawn_height
)
def run(self):
try:
self.load()
info = self.sim.step()
frame_skip = int(self.robot_cfg.control.control_dt / self.simulator_cfg.physics.simulation_dt)
logger.info(f"Sim FPS: {1.0 / self.simulator_cfg.physics.simulation_dt:.2f}, Control FPS: {1.0 / self.robot_cfg.control.control_dt:.2f}, Frame Skip: {frame_skip:d}")
logger.info("Starting pipeline...")
while not self.gauge.is_done():
goal = self.gauge.get_goal()
obs = self.robot.build_observation(info, goal)
action = self.robot.get_action(obs)
for _ in range(frame_skip):
self.sim.apply_action(action)
info = self.sim.step()
self.gauge.update_metrics(info)
if self.gauge.is_reset():
self.sim.reset()
info = self.sim.step()
finally:
self.sim.close_viewer()
logger.info("Pipeline execution finished.")

View File

@@ -0,0 +1,4 @@
from .base_robot_config import RobotConfig
from .base_robot import BaseRobot
# from .go2.go2_config import Go2Config
# from .go2.go2_controller import Go2Controller

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
'''
@File : base_robot.py
@Time : 2025/11/27 15:53:57
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Base Robot Class
'''
import torch
import numpy as np
from robogauge.tasks.robots.base_robot_config import RobotConfig
class BaseRobot:
def __init__(self, cfg: RobotConfig):
self.num_act = cfg.mdp.num_actions
self.num_obs = cfg.mdp.num_observations
self.model = None
def load_model(self):
...
def build_observation(self, sim_info: dict, goal_info: dict) -> np.ndarray:
obs = np.zeros(self.num_obs)
return obs
def get_action(self, obs) -> np.ndarray:
action = np.zeros_like(self.num_act)
return action

View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
'''
@File : base_robot_config.py
@Time : 2025/11/27 15:53:47
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Base Robot Configuration
'''
from robogauge.utils.config import Config
class RobotConfig(Config):
robot_class = 'BaseRobot'
class assets:
robot_xml = "{ROBOGAUGE_ROOT_DIR}/resources/robots/go2/go2.xml"
robot_spawn_height = 0.1 # z [m]
class control:
torch_script_model_path = "{ROBOGAUGE_ROOT_DIR}/resources/models/go2/go2_cts_61500.pt"
control_dt = 0.02 # 50 Hz
action_scale = 0.25 # target pos = action_scale * action * default_pos
stiffness = 20.0 # [N*m/rad]
damping = 0.5 # [N*m*s/rad]
class mdp:
num_observations = 46
num_actions = 12
class commands:
lin_vel_x = [-1, 1] # min max [m/s]
lin_vel_y = [-1, 1] # min max [m/s]
ang_vel_yaw = [-1, 1] # min max [rad/s]

View File

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
'''
@File : go2_config.py
@Time : 2025/11/27 16:03:27
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Go2 Robot Configuration
'''
from robogauge.tasks.robots import RobotConfig
class Go2Config(RobotConfig):
class assets:
robot_xml = "{ROBOGAUGE_ROOT_DIR}/resources/robots/go2/go2.xml"
robot_spawn_height = 0.1 # z [m]
class control:
control_dt = 0.02 # 50 Hz
action_scale = 0.25 # scale for normalized actions

View File

@@ -0,0 +1,2 @@
from .mujoco_simulator import MujocoSimulator
from .mujoco_config import MujocoConfig

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
'''
@File : mujoco_config.py
@Time : 2025/11/27 15:55:34
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Mujoco Simulator Configuration
'''
from robogauge.utils.config import Config
class MujocoConfig(Config):
simulator_class = 'MujocoSimulator'
class physics:
simulation_dt = 0.005 # 200 Hz
class viewer:
headless = False
block_rendering = True # Whether to block rendering in the viewer loop.
class render:
save_video = False
height = 480
width = 640

View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
'''
@File : mujoco_simulator.py
@Time : 2025/11/27 15:54:20
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : None
'''
import mujoco
import mujoco.viewer
from dm_control import mjcf
import time
import imageio
import numpy as np
from robogauge.utils.logger import logger
from robogauge.utils.helpers import pares_path
from robogauge.tasks.simulator.mujoco_config import MujocoConfig
class MujocoSimulator:
def __init__(self, sim_cfg: MujocoConfig):
self.cfg = sim_cfg
self.terrain_xml = None
self.robot_xml = None
self.terrain_spawn_xy = None
self.robot_spawn_height = None
self.viewer = None
self.renderer = None
self.vid_writer = None
self.vid_count = 0
self._pause = True
def load(
self,
terrain_xml: str = None,
robot_xml: str = None,
terrain_spawn_xy: list = None,
robot_spawn_height: float = None,
):
""" Load terrain and robot into the simulator, support re-loading. """
if terrain_xml is not None:
self.terrain_xml = pares_path(terrain_xml)
if robot_xml is not None:
self.robot_xml = pares_path(robot_xml)
if terrain_spawn_xy is not None:
self.terrain_spawn_xy = terrain_spawn_xy
if robot_spawn_height is not None:
self.robot_spawn_height = robot_spawn_height
terrain_xml = self.terrain_xml
robot_xml = self.robot_xml
terrain_spawn_xy = self.terrain_spawn_xy
robot_spawn_height = self.robot_spawn_height
if terrain_xml is None or robot_xml is None:
raise ValueError("Terrain and robot XML paths must be provided.")
robot_mjcf = mjcf.from_path(robot_xml)
terrain_mjcf = mjcf.from_path(terrain_xml)
for j in robot_mjcf.find_all('joint'):
if j.tag == 'freejoint':
j.remove()
attachment_frame = terrain_mjcf.attach(robot_mjcf)
attachment_frame.add('freejoint')
attachment_frame.pos = [*terrain_spawn_xy, robot_spawn_height]
if self.viewer is not None:
self.close_viewer()
self.mj_physics = mjcf.Physics.from_mjcf_model(terrain_mjcf)
self.mj_model = self.mj_physics.model.ptr
self.mj_data = self.mj_physics.data.ptr
self.mj_model.opt.timestep = self.cfg.physics.simulation_dt
self.headless = self.cfg.viewer.headless
if self.cfg.render.save_video and self.headless:
logger.warning("Cannot save video in headless mode, disabling video saving.")
self.cfg.render.save_video = False
if not self.headless:
self.viewer = mujoco.viewer.launch_passive(self.mj_model, self.mj_data, key_callback=self.key_callback)
self.last_render_time = time.time()
if self.cfg.render.save_video:
self.renderer = mujoco.Renderer(self.mj_model, height=self.cfg.render.height, width=self.cfg.render.width)
vid_dir = logger.log_dir / "videos"
vid_dir.mkdir(parents=True, exist_ok=True)
vid_path = str(vid_dir / f"sim_video_{self.vid_count:03d}.mp4")
self.vid_writer = imageio.get_writer(
vid_path,
fps=int(1 / self.cfg.physics.simulation_dt),
)
logger.info(f"Saving simulation video to: {vid_path}")
self.vid_count += 1
self._pause = False
def key_callback(self, keycode):
if keycode == 32:
self._pause = not self._pause
logger.info(f"Pause toggled: {self._pause}")
def step(self) -> dict:
""" Simulation step, pause will block thread. """
while self._pause:
time.sleep(0.1)
self.mj_physics.step()
if self.viewer is not None:
if self.viewer.is_running():
time_untile_next_render = self.cfg.physics.simulation_dt - (time.time() - self.last_render_time)
if time_untile_next_render > 0:
time.sleep(time_untile_next_render)
self.viewer.sync()
if self.vid_writer is not None:
self.renderer.update_scene(self.mj_data, camera=self.viewer.cam)
frame = self.renderer.render()
self.vid_writer.append_data(frame)
self.last_render_time = time.time()
else:
logger.warning("Viewer closed by user, stop video recording.")
self.close_viewer()
info = {}
return info
def reset(self):
""" Reset the simulator to initial state. """
self.mj_physics.reset()
if self.viewer is not None:
self.viewer.sync()
def apply_action(self, action: np.ndarray):
""" Apply action to the simulator. """
self.mj_data.ctrl[:] = action
def close_viewer(self):
""" Close the viewer and video writer. """
if self.viewer is not None:
self.viewer.close()
self.viewer = None
logger.info("Closing viewer.")
if self.vid_writer is not None:
self.vid_writer.close()
self.vid_writer = None
self.renderer = None
logger.info("Closing video writer.")

18
robogauge/utils/config.py Normal file
View File

@@ -0,0 +1,18 @@
import inspect
class Config:
# From https://github.com/leggedrobotics/legged_gym/blob/master/legged_gym/envs/base/base_config.py
def __init__(self) -> None:
""" Initializes all member classes recursively. Ignores all namse starting with '__' (buit-in methods)."""
self.init_member_classes(self)
@staticmethod
def init_member_classes(obj):
for key in dir(obj):
if key == "__class__":
continue
var = getattr(obj, key)
if inspect.isclass(var):
i_var = var()
setattr(obj, key, i_var)
Config.init_member_classes(i_var)

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
'''
@File : helpers.py
@Time : 2025/11/27 15:26:37
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Helpers for robogauge, include:
- Argument parsing
- Class to dict conversion
- Path parsing
'''
from argparse import ArgumentParser
from pathlib import Path
from robogauge import ROBOGAUGE_ROOT_DIR
def pares_path(path):
if "{ROBOGAUGE_ROOT_DIR}" in str(path):
path = str(path).replace("{ROBOGAUGE_ROOT_DIR}", ROBOGAUGE_ROOT_DIR)
return path
def class_to_dict(obj) -> dict:
if not hasattr(obj, "__dict__"):
return obj
result = {}
for key in dir(obj):
if key.startswith("_"):
continue
element = []
val = getattr(obj, key)
if isinstance(val, list):
for item in val:
element.append(class_to_dict(item))
else:
element = class_to_dict(val)
result[key] = element
return result
def str2bool(v):
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
raise TypeError('Boolean value expected.')
def parse_args():
parser = ArgumentParser()
parameters = [
{"name": "--task-name", "type": str, "default": "base", "help": "Name of the task to run."},
{"name": "--experiment-name", "type": str, "default": "base", "help": "Name of the experiment to run."},
{"name": "--headless", "action": "store_true", "default": False, "help": "Run in headless mode."},
{"name": "--save-video", "action": "store_true", "default": False, "help": "Save video output."},
]
for param in parameters:
parser.add_argument(param['name'], **{k: v for k, v in param.items() if k != 'name'})
return parser.parse_args()

133
robogauge/utils/logger.py Normal file
View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
'''
@File : my_logger.py
@Time : 2025/02/26 21:43:47
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : A customed logger, support:
1. Color level name
2. Output to console and save log to file
3. Support vscode file location jump (ctrl+left key)
'''
import time
import logging
from pathlib import Path
from robogauge import ROBOGAUGE_ROOT_DIR
class LogColor:
""" ANSI color codes """
RESET = '\033[0m'
RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
BLUE = '\033[34m'
MAGENTA = '\033[35m'
CYAN = '\033[36m'
WHITE = '\033[37m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
LOG_COLORS = {
""" Match level name to color """
'DEBUG': LogColor.CYAN,
'INFO': LogColor.GREEN,
'WARNING': LogColor.YELLOW,
'ERROR': LogColor.RED,
'CRITICAL': LogColor.RED + LogColor.BOLD,
}
class ColorFormatter(logging.Formatter):
"""Color Formatter for color_level"""
def __init__(self, fmt, datefmt=None, use_color=True):
self.formatter = logging.Formatter(fmt, datefmt)
self.use_color = use_color
def format(self, record):
record.color_level = f"{LOG_COLORS.get(record.levelname, LogColor.RESET)}{record.levelname}{LogColor.RESET}" if self.use_color else record.levelname
return self.formatter.format(record)
class Logger:
def create(self,
experiment_name,
console_output=True, color_output=True,
log_level=logging.DEBUG, save_file_mode='a'
):
"""
Create customed Logger
Args:
logger_name (str): Logger name
console_output (bool, optional): Whether output to console. Defaults to True.
color_output (bool, optional): Whether use color output. Defaults to True.
log_level (int, optional): Defaults to logging.DEBUG.
save_file_mode (str, optional): The mode of saving to path_log_file
Returns:
logging.Logger: logger
"""
self.logger = logging.getLogger(experiment_name + "_logger")
self.logger.setLevel(log_level)
console_formatter = ColorFormatter( # console output format
fmt="%(asctime)s - %(color_level)s - %(filename)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
use_color=color_output
)
file_formatter = logging.Formatter( # file output format
fmt="%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
if console_output:
sh = logging.StreamHandler()
sh.setFormatter(console_formatter)
self.logger.addHandler(sh)
self.log_dir = Path(ROBOGAUGE_ROOT_DIR) / "logs" / experiment_name / time.strftime("%Y%m%d-%H-%M-%S")
self.log_dir.mkdir(parents=True, exist_ok=True)
path_log_file = self.log_dir / "stdout.log"
if path_log_file:
fh = logging.FileHandler(path_log_file, mode=save_file_mode, encoding='utf-8')
fh.setFormatter(file_formatter)
self.logger.addHandler(fh)
def debug(self, msg, *args, **kwargs):
self.logger.debug(msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
self.logger.info(msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
self.logger.warning(msg, *args, **kwargs)
def error(self, msg, *args, **kwargs):
self.logger.error(msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
self.logger.critical(msg, *args, **kwargs)
logger = Logger()
if __name__ == '__main__':
from pathlib import Path
path_parent = Path(__file__).parents[0]
path_log = path_parent / "app.log"
logger = Logger()
logger.create("my_logger")
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")
# logger_no_color = Logger()
# logger_no_color.create("no_color_logger", console_output=True, color_output=False)
# logger_no_color.info("This is a info message without color")
# logger_file_only = Logger()
# logger_file_only.create("file_only_logger", console_output=False) # save to file only
# logger_file_only.error("This is an error message only in file")

View File

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
'''
@File : task_register.py
@Time : 2025/11/27 15:59:03
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : Task Registration Utility
'''
class TaskRegister():
def __init__(self):
self.pipeline_classes = {}
self.sim_cfgs = {}
self.gauger_cfgs = {}
self.robot_cfgs = {}
def register(self, name: str, pipeline_class, sim_cfg, gauger_cfg, robot_cfg):
self.pipeline_classes[name] = pipeline_class
self.sim_cfgs[name] = sim_cfg
self.gauger_cfgs[name] = gauger_cfg
self.robot_cfgs[name] = robot_cfg
def get_pipeline_class(self, name: str):
if name not in self.pipeline_classes:
raise ValueError(f"Task '{name}' is not registered.")
return self.pipeline_classes[name]
def get_cfgs(self, name):
if name not in self.sim_cfgs:
raise ValueError(f"Task '{name}' is not registered.")
sim_cfg = self.sim_cfgs[name]
gauger_cfg = self.gauger_cfgs[name]
robot_cfg = self.robot_cfgs[name]
return sim_cfg, gauger_cfg, robot_cfg
def make_pipeline(self, name, args=None, sim_cfg=None, gauger_cfg=None, robot_cfg=None):
default_cfgs = self.get_cfgs(name)
if sim_cfg is None:
sim_cfg = default_cfgs[0]
if gauger_cfg is None:
gauger_cfg = default_cfgs[1]
if robot_cfg is None:
robot_cfg = default_cfgs[2]
if args is not None:
self.update_args_to_cfg(sim_cfg, gauger_cfg, robot_cfg, args)
pipeline_class = self.get_pipeline_class(name)
return pipeline_class(sim_cfg, robot_cfg, gauger_cfg)
def update_args_to_cfg(self, sim_cfg, gauger_cfg, robot_cfg, args):
if args.headless is not None:
sim_cfg.viewer.headless = args.headless
if args.save_video is not None:
sim_cfg.render.save_video = args.save_video
task_register = TaskRegister()