This commit is contained in:
wty-yy
2025-11-28 22:08:32 +08:00
parent 4cc0687114
commit c58ac0ab89
39 changed files with 406 additions and 114 deletions

View File

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

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

@@ -1,291 +0,0 @@
<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

@@ -1,22 +0,0 @@
<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>

View File

@@ -7,6 +7,9 @@
@Blog : https://wty-yy.github.io/
@Desc : Run Robogauge Pipeline
'''
import os
os.environ['MUJOCO_GL'] = 'glfw' # avoid mujoco.Renderer EGL context error
from robogauge.tasks import *
from robogauge.utils.task_register import task_register
from robogauge.utils.helpers import parse_args

View File

@@ -1,7 +1,8 @@
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.robots import RobotConfig, Go2Config
from robogauge.tasks.pipeline import BasePipeline
from robogauge.tasks.gauge import BaseGaugeConfig
task_register.register('base', BasePipeline, MujocoConfig, BaseGaugeConfig, RobotConfig)
task_register.register('go2', BasePipeline, MujocoConfig, BaseGaugeConfig, Go2Config)

View File

@@ -9,6 +9,9 @@
'''
from robogauge.tasks.robots.base_robot_config import RobotConfig
from robogauge.tasks.gauge.base_gauge_config import BaseGaugeConfig
from robogauge.tasks.gauge.goal_data import GoalData, VelocityGoal, PositionGoal
from robogauge.tasks.simulator.sim_data import SimData
from robogauge.utils.logger import logger
class BaseGauge:
def __init__(self, cfg: BaseGaugeConfig):
@@ -20,10 +23,19 @@ class BaseGauge:
def is_done(self) -> bool:
return False
def get_goal(self) -> dict:
goal = {}
def get_goal(self) -> GoalData:
goal = GoalData(
goal_type='velocity',
velocity_goal=VelocityGoal(
lin_vel=[5.0, 0.0, 0.0],
ang_vel=[0.0, 0.0, 0.0]
)
)
return goal
def update_metrics(self, sim_info: dict):
...
def update_metrics(self, sim_data: SimData):
if sim_data.n_step % int(0.1 / sim_data.sim_dt) != 0:
return
for i in range(len(sim_data.proprio.joint.force)):
logger.log(sim_data.proprio.joint.force[i], f'dof/force_{i}', step=sim_data.n_step)

View File

@@ -0,0 +1,22 @@
from enum import Enum
from dataclasses import dataclass
from typing import List, Optional, Literal
@dataclass
class VelocityGoal:
lin_vel: List[float] # x, y, z [m/s], z is ignored for ground robots
ang_vel: List[float] # roll, pitch, yaw [rad/s], roll and pitch are ignored for ground robots
@dataclass
class PositionGoal:
# relative to robot's current position
target_pos: List[float] # x, y, z [m], z is ignored for ground robots
# reach target orientation
target_quat: List[float] # x, y, z, w quaternion
tolerance: float # [m] position tolerance to consider goal reached
@dataclass
class GoalData:
goal_type: Literal['velocity', 'position']
velocity_goal: Optional[VelocityGoal] = None
position_goal: Optional[PositionGoal] = None

View File

@@ -10,15 +10,17 @@
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.robots import BaseRobot, RobotConfig, Go2Config, Go2
from robogauge.tasks.gauge import BaseGauge, BaseGaugeConfig
class BasePipeline:
def __init__(self,
run_name: str,
simulator_cfg: MujocoConfig,
robot_cfg: RobotConfig,
gauge_cfg: BaseGaugeConfig
):
self.run_name = run_name
self.simulator_cfg = simulator_cfg
self.robot_cfg = robot_cfg
self.gauge_cfg = gauge_cfg
@@ -28,31 +30,34 @@ class BasePipeline:
self.gauge: BaseGauge = eval(gauge_cfg.gauge_class)(gauge_cfg)
def load(self):
logger.create_tensorboard(self.run_name)
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
self.robot_cfg.assets.robot_spawn_height,
self.robot_cfg.control.default_dof_pos
)
def run(self):
try:
self.load()
info = self.sim.step()
sim_data = 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...")
logger.info("Running 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)
obs = self.robot.build_observation(sim_data, goal)
action, p_gains, d_gains, control_type = self.robot.get_action(obs)
self.sim.setup_action(action, p_gains, d_gains, control_type)
for _ in range(frame_skip):
self.sim.apply_action(action)
info = self.sim.step()
self.gauge.update_metrics(info)
sim_data = self.sim.step()
self.gauge.update_metrics(sim_data)
if self.gauge.is_reset():
self.sim.reset()
info = self.sim.step()
sim_data = self.sim.step()
finally:
self.sim.close_viewer()
logger.info("Pipeline execution finished.")
logger.info(f"Logging saved at: {logger.log_dir}")

View File

@@ -1,4 +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
from .go2.go2_config import Go2Config
from .go2.go2 import Go2

View File

@@ -9,21 +9,55 @@
'''
import torch
import numpy as np
from robogauge.utils.helpers import parse_path
from robogauge.utils.logger import logger
from robogauge.tasks.robots.base_robot_config import RobotConfig
from robogauge.tasks.simulator.sim_data import SimData
from robogauge.tasks.gauge.goal_data import GoalData
class BaseRobot:
def __init__(self, cfg: RobotConfig):
self.num_act = cfg.mdp.num_actions
self.num_obs = cfg.mdp.num_observations
self.model = None
self.cfg = cfg
self.device = self.cfg.control.device
self.num_obs = cfg.control.num_observations
self.num_action = cfg.control.num_actions
self.control_type = cfg.control.control_type
self.p_gains = np.array(cfg.control.p_gains)
self.d_gains = np.array(cfg.control.d_gains)
script_model_path = parse_path(cfg.control.torch_script_model_path)
logger.info(f"Loading robot model from '{script_model_path}'")
self.model = torch.jit.load(script_model_path).to(self.device)
self.model.eval()
def load_model(self):
...
def build_observation(self, sim_info: dict, goal_info: dict) -> np.ndarray:
obs = np.zeros(self.num_obs)
def build_observation(self, sim_data: SimData, goal_data: GoalData) -> np.ndarray:
obs = np.zeros(self.num_obs, dtype=np.float32)
return obs
def get_action(self, obs) -> np.ndarray:
action = np.zeros_like(self.num_act)
return action
def get_action(self, obs: np.ndarray):
"""
Returns:
action: (num_action,) target joint positions/velocities/torques
p_gains: (num_action,) proportional gains for Mujoco PD controller
d_gains: (num_action,) derivative gains for Mujoco PD controller
control_type: 'P', 'V', or 'T' for position/velocity/torque control
"""
action = np.zeros(self.num_action, dtype=np.float32)
return action, self.p_gains, self.d_gains, self.control_type
def get_projected_gravity(quat):
""" Compute world frame gravity (0, 0, -1) projected into robot base frame.
Args:
quat: (4,) quaternion (w, x, y, z) from robot base to world frame
Returns:
projected_gravity: (3,) projected gravity vector in robot base frame
"""
qw, qx, qy, qz = quat
gravity_orientation = np.zeros(3)
gravity_orientation[0] = 2 * (-qz * qx + qw * qy)
gravity_orientation[1] = -2 * (qz * qy + qw * qx)
gravity_orientation[2] = 1 - 2 * (qw * qw + qz * qz)
return gravity_orientation

View File

@@ -7,6 +7,7 @@
@Blog : https://wty-yy.github.io/
@Desc : Base Robot Configuration
'''
from typing_extensions import Literal
from robogauge.utils.config import Config
class RobotConfig(Config):
@@ -17,16 +18,32 @@ class RobotConfig(Config):
robot_spawn_height = 0.1 # z [m]
class control:
torch_script_model_path = "{ROBOGAUGE_ROOT_DIR}/resources/models/go2/go2_cts_61500.pt"
device = 'cpu'
torch_script_model_path = "{ROBOGAUGE_ROOT_DIR}/resources/models/go2/go2_cts_83501.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]
control_type = 'P' # Position control
# Mujoco joint PD gains
p_gains = [20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0] # [N*m/rad]
d_gains = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5] # [N*m*s/rad]
class mdp:
num_observations = 46
num_observations = 45
num_actions = 12
max_velocity_cmd = [1.5, 1.0, 2.0]
default_dof_pos = [0.1, 0.8, -1.5, -0.1, 0.8, -1.5,
0.1, 1.0, -1.5, -0.1, 1.0, -1.5]
mj2model_dof_indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
class scales:
lin_vel = 2.0
ang_vel = 0.25
dof_pos = 1.0
dof_vel = 0.05
action = 0.25 # target pos = action_scale * action * default_pos
cmd = [2.0, 2.0, 0.25]
class commands:
lin_vel_x = [-1, 1] # min max [m/s]
lin_vel_y = [-1, 1] # min max [m/s]

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
'''
@File : go2.py
@Time : 2025/11/28 15:27:07
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : None
'''
import torch
import numpy as np
from robogauge.tasks.robots.base_robot import BaseRobot, get_projected_gravity
from robogauge.tasks.robots.go2.go2_config import Go2Config
from robogauge.tasks.simulator.sim_data import SimData
from robogauge.tasks.gauge.goal_data import GoalData
from robogauge.utils.logger import logger
class Go2(BaseRobot):
def __init__(self, cfg: Go2Config):
super().__init__(cfg)
self.max_velocity_cmd = np.array(cfg.control.max_velocity_cmd, dtype=np.float32)
self.default_dof_pos = np.array(cfg.control.default_dof_pos, dtype=np.float32)
self.last_action = np.zeros(self.num_action, dtype=np.float32)
self.action_scale = cfg.control.scales.action
self.mj2model_idx = self.cfg.control.mj2model_dof_indices
self.model2mj_idx = [self.mj2model_idx.index(i) for i in range(len(self.mj2model_idx))]
def build_observation(self, sim_data: SimData, goal_data: GoalData) -> np.ndarray:
sim_proprio = sim_data.proprio
obs = np.zeros(self.num_obs)
if goal_data.goal_type == 'velocity':
ang_vel = sim_proprio.imu.ang_vel * self.cfg.control.scales.ang_vel
projected_gravity = get_projected_gravity(sim_proprio.imu.quat)
dof_pos = (sim_proprio.joint.pos - self.default_dof_pos) * self.cfg.control.scales.dof_pos
dof_vel = sim_proprio.joint.vel * self.cfg.control.scales.dof_vel
cmd = np.array(goal_data.velocity_goal.lin_vel[:2] + goal_data.velocity_goal.ang_vel[2:3], np.float32)
cmd = np.minimum(np.maximum(cmd, -self.max_velocity_cmd), self.max_velocity_cmd)
cmd *= self.cfg.control.scales.cmd
obs[:3] = ang_vel
obs[3:6] = projected_gravity
obs[6:9] = cmd
obs[9:9+self.num_action] = dof_pos[self.mj2model_idx]
obs[9+self.num_action:9+2*self.num_action] = dof_vel[self.mj2model_idx]
obs[9+2*self.num_action:9+3*self.num_action] = self.last_action[self.mj2model_idx]
else:
raise NotImplementedError(f"Goal type '{goal_data.goal_type}' not implemented in Go2 robot.")
return obs
def get_action(self, obs: np.ndarray):
obs_tensor = torch.tensor(obs, dtype=torch.float32).unsqueeze(0).to(self.device)
action = self.model(obs_tensor).detach().cpu().numpy().squeeze(0)[self.model2mj_idx]
self.last_action = action
target_dof_pos = action * self.action_scale + self.default_dof_pos
return target_dof_pos, self.p_gains, self.d_gains, self.control_type

View File

@@ -7,14 +7,44 @@
@Blog : https://wty-yy.github.io/
@Desc : Go2 Robot Configuration
'''
from typing_extensions import Literal
from robogauge.tasks.robots import RobotConfig
class Go2Config(RobotConfig):
robot_class = 'Go2'
class assets:
robot_xml = "{ROBOGAUGE_ROOT_DIR}/resources/robots/go2/go2.xml"
robot_spawn_height = 0.1 # z [m]
class control:
class control(RobotConfig.control):
device = 'cpu'
torch_script_model_path = "{ROBOGAUGE_ROOT_DIR}/resources/models/go2/go2_cts_83501.pt"
control_dt = 0.02 # 50 Hz
action_scale = 0.25 # scale for normalized actions
control_type = 'P' # Position control
# Mujoco joint PD gains
p_gains = [20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0] # [N*m/rad]
d_gains = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5] # [N*m*s/rad]
num_observations = 45
num_actions = 12
max_velocity_cmd = [1.5, 1.0, 2.0]
default_dof_pos = [0.1, 0.8, -1.5, -0.1, 0.8, -1.5,
0.1, 1.0, -1.5, -0.1, 1.0, -1.5]
mj2model_dof_indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
class scales(RobotConfig.control.scales):
lin_vel = 2.0
ang_vel = 0.25
dof_pos = 1.0
dof_vel = 0.05
action = 0.25 # target pos = action_scale * action * default_pos
cmd = [2.0, 2.0, 0.25]
class commands(RobotConfig.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

@@ -13,7 +13,7 @@ class MujocoConfig(Config):
simulator_class = 'MujocoSimulator'
class physics:
simulation_dt = 0.005 # 200 Hz
simulation_dt = 0.002 # 500 Hz
class viewer:
headless = False
@@ -21,5 +21,6 @@ class MujocoConfig(Config):
class render:
save_video = False
video_fps = 30
height = 480
width = 640

View File

@@ -5,7 +5,7 @@
@Author : wty-yy
@Version : 1.0
@Blog : https://wty-yy.github.io/
@Desc : None
@Desc : Mujoco Simulator for Robogauge
'''
import mujoco
import mujoco.viewer
@@ -15,11 +15,15 @@ import re
import time
import imageio
import numpy as np
from typing import Literal
from robogauge.utils.logger import logger
from robogauge.utils.helpers import pares_path
from robogauge.utils.helpers import parse_path
from robogauge.tasks.simulator.mujoco_config import MujocoConfig
from robogauge.tasks.simulator.sim_data import RobotProprioception, JointState, BaseState, IMUState
from robogauge.tasks.simulator.sim_data import (
SimData,
RobotProprioception, JointState, BaseState, IMUState
)
class MujocoSimulator:
def __init__(self, sim_cfg: MujocoConfig):
@@ -28,12 +32,14 @@ class MujocoSimulator:
self.robot_xml = None
self.terrain_spawn_xy = None
self.robot_spawn_height = None
self.default_dof_pos = None
self.viewer = None
self.renderer = None
self.vid_writer = None
self.vid_count = 0
self._pause = True
self.n_step = 0
self.sim_time = 0.0
def load(
self,
@@ -41,16 +47,19 @@ class MujocoSimulator:
robot_xml: str = None,
terrain_spawn_xy: list = None,
robot_spawn_height: float = None,
default_dof_pos: list = None,
):
""" Load terrain and robot into the simulator, support re-loading. """
if terrain_xml is not None:
self.terrain_xml = pares_path(terrain_xml)
self.terrain_xml = parse_path(terrain_xml)
if robot_xml is not None:
self.robot_xml = pares_path(robot_xml)
self.robot_xml = parse_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
if default_dof_pos is not None:
self.default_dof_pos = default_dof_pos
terrain_xml = self.terrain_xml
robot_xml = self.robot_xml
@@ -58,6 +67,8 @@ class MujocoSimulator:
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.")
if default_dof_pos is None:
raise ValueError("Default DOF positions must be provided.")
robot_mjcf = mjcf.from_path(robot_xml)
terrain_mjcf = mjcf.from_path(terrain_xml)
@@ -74,6 +85,10 @@ class MujocoSimulator:
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.sim_dt = self.cfg.physics.simulation_dt
self.mj_data.qpos[7:] = default_dof_pos
mujoco.mj_forward(self.mj_model, self.mj_data)
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.")
@@ -94,23 +109,33 @@ class MujocoSimulator:
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),
fps=self.cfg.render.video_fps,
)
self.vid_frame_skip = int(1 / (self.cfg.render.video_fps * self.sim_dt * 2))
logger.info(f"Saving simulation video to: {vid_path}")
self.vid_count += 1
self._pause = False
self.n_step = 0
self.sim_time = 0.0
self.preload_sensors()
# Robot controller placeholders
self.action = None
self.p_gains = None
self.d_gains = None
self.control_type = None
def key_callback(self, keycode):
if keycode == 32:
self._pause = not self._pause
logger.info(f"Pause toggled: {self._pause}")
def step(self) -> dict:
def step(self) -> SimData:
""" Simulation step, pause will block thread. """
while self._pause:
time.sleep(0.1)
self.update_torque()
self.mj_physics.step()
if self.viewer is not None:
if self.viewer.is_running():
@@ -120,7 +145,7 @@ class MujocoSimulator:
if time_untile_next_render > 0:
time.sleep(time_untile_next_render)
self.viewer.sync()
if self.vid_writer is not None:
if self.vid_writer is not None and self.n_step % self.vid_frame_skip == 0:
self.renderer.update_scene(self.mj_data, camera=self.viewer.cam)
frame = self.renderer.render()
self.vid_writer.append_data(frame)
@@ -129,43 +154,75 @@ class MujocoSimulator:
logger.warning("Viewer closed by user, stop video recording.")
self.close_viewer()
n_sensor = self.mj_model.nsensor
proprio = RobotProprioception(
self.proprio = proprio = RobotProprioception(
joint=JointState(
pos=self.get_sensor_data('joint_pos'),
vel=self.get_sensor_data('joint_vel'),
force=self.get_sensor_data('joint_eff'),
),
imu=IMUState(
quat=self.get_sensor_data('imu_quat'),
ang_vel=self.get_sensor_data('imu_ang_vel'),
acc=self.get_sensor_data('imu_acc'),
pos=self.get_sensor_data('imu_pos'),
quat=self.get_sensor_data('imu_quat'),
acc=self.get_sensor_data('imu_acc'),
lin_vel=self.get_sensor_data('imu_lin_vel'),
ang_vel=self.get_sensor_data('imu_ang_vel'),
),
base=BaseState(
pos=self.mj_data.qpos[:3], # world frame
quat=self.mj_data.qpos[3:7], # world frame
vel=self.mj_data.qvel[:3], # body frame
lin_vel=self.mj_data.qvel[:3], # body frame
ang_vel=self.mj_data.qvel[3:6], # body frame
)
)
if self.n_step % int(0.1 / self.sim_dt) == 0:
logger.log(value=np.mean(proprio.imu.quat - proprio.base.quat), tag="sim/delta_quat", step=self.n_step)
logger.log(value=np.mean(proprio.imu.ang_vel - proprio.base.ang_vel), tag="sim/delta_ang_vel", step=self.n_step)
logger.log(value=np.mean(proprio.imu.lin_vel - proprio.base.lin_vel), tag="sim/delta_lin_vel", step=self.n_step)
if self.n_step == 0:
self.debug_print_proprio_shapes(proprio)
self.n_step += 1
self.debug_print_proprio_shapes()
return proprio
sim_data = SimData(
n_step=self.n_step,
sim_dt=self.sim_dt,
proprio=proprio
)
self.n_step += 1
self.sim_time = self.n_step * self.sim_dt
return sim_data
def reset(self):
""" Reset the simulator to initial state. """
self.mj_physics.reset()
self.mj_data.qpos[7:] = self.default_dof_pos
mujoco.mj_forward(self.mj_model, self.mj_data)
self.action = None
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 setup_action(self,
action: np.ndarray,
p_gains: np.ndarray = None,
d_gains: np.ndarray = None,
control_type: Literal['P'] = 'P'
):
""" Setup action to the simulator. """
self.action = action
self.p_gains = p_gains
self.d_gains = d_gains
self.control_type = control_type
def update_torque(self):
if self.action is None:
return
dof_pos = self.proprio.joint.pos
dof_vel = self.proprio.joint.vel
if self.control_type == 'P':
torques = self.p_gains * (self.action - dof_pos) - self.d_gains * dof_vel
else:
raise NotImplementedError(f"Control type '{self.control_type}' not implemented.")
self.mj_data.ctrl[:] = torques
def close_viewer(self):
""" Close the viewer and video writer. """
@@ -189,12 +246,16 @@ class MujocoSimulator:
self.imu_acc = self.find_sensors(tag_name="accelerometer")
self.imu_pos = self.find_sensors(tag_name="framepos")
self.imu_lin_vel = self.find_sensors(tag_name="framelinvel")
actuator_names = [mujoco.mj_id2name(self.mj_model, mujoco.mjtObj.mjOBJ_ACTUATOR, i) for i in range(self.mj_model.nu)]
logger.info(
f"\n{'='*20} XML SENSOR NAMES {'='*20}\n"
f"""Joint Position Sensors [{len(self.joint_pos_sensor_names)}]: {self.joint_pos_sensor_names}\n"""
f"""Joint Velocity Sensors [{len(self.joint_vel_sensor_names)}]: {self.joint_vel_sensor_names}\n"""
f"""Joint Effort Sensors [{len(self.joint_eff_sensor_names)}]: {self.joint_eff_sensor_names}\n"""
f"""IMU Sensors: Quat{self.imu_quat}, AngVel{self.imu_ang_vel}, Acc{self.imu_acc}, Pos{self.imu_pos}, LinVel{self.imu_lin_vel}"""
f"""Joint Position Sensors [{len(self.joint_pos_sensor_names)}]: {[x.rsplit('/')[-1] for x in self.joint_pos_sensor_names]}\n"""
f"""Joint Velocity Sensors [{len(self.joint_vel_sensor_names)}]: {[x.rsplit('/')[-1] for x in self.joint_vel_sensor_names]}\n"""
f"""Joint Effort Sensors [{len(self.joint_eff_sensor_names)}]: {[x.rsplit('/')[-1] for x in self.joint_eff_sensor_names]}\n"""
f"""Actuators [{len(actuator_names)}]: {[x.rsplit('/')[-1] for x in actuator_names]}\n"""
f"""IMU Sensors: Quat{self.imu_quat}, AngVel{self.imu_ang_vel}, Acc{self.imu_acc}, Pos{self.imu_pos}, LinVel{self.imu_lin_vel}\n"""
f"""!!!Checkout actuators order is consistent with joint sensors!!!\n"""
f"{'='*58}"
)
# Cache sensor indices
@@ -259,7 +320,7 @@ class MujocoSimulator:
data_list.append(self.mj_data.sensordata[adr:adr+dim])
return np.concatenate(data_list)
def debug_print_proprio_shapes(self, proprio: RobotProprioception):
def debug_print_proprio_shapes(self):
"""Log shapes (or lengths) of each numpy vector inside a RobotProprioception.
This helps debug mismatched sensor sizes between robots.
@@ -271,9 +332,9 @@ class MujocoSimulator:
except Exception:
return None
jp = proprio.joint
bs = proprio.base
imu = proprio.imu
jp = self.proprio.joint
bs = self.proprio.base
imu = self.proprio.imu
logger.info("Proprioception shapes:")
logger.info(f" joint.pos: { _shape(jp.pos) }")
@@ -282,7 +343,7 @@ class MujocoSimulator:
logger.info(f" base.pos: { _shape(bs.pos) }")
logger.info(f" base.quat: { _shape(bs.quat) }")
logger.info(f" base.vel: { _shape(bs.vel) }")
logger.info(f" base.vel: { _shape(bs.lin_vel) }")
logger.info(f" base.ang_vel: { _shape(bs.ang_vel) }")
logger.info(f" imu.quat: { _shape(imu.quat) }")

View File

@@ -11,19 +11,25 @@ class JointState:
class BaseState:
pos: np.ndarray
quat: np.ndarray
vel: np.ndarray
lin_vel: np.ndarray
ang_vel: np.ndarray
@dataclass
class IMUState:
quat: np.ndarray
ang_vel: np.ndarray
acc: np.ndarray
pos: np.ndarray
quat: np.ndarray
acc: np.ndarray
lin_vel: np.ndarray
ang_vel: np.ndarray
@dataclass
class RobotProprioception:
joint: JointState
base: BaseState
imu: IMUState
@dataclass
class SimData:
n_step: int
sim_dt: float
proprio: RobotProprioception

View File

@@ -14,7 +14,7 @@ from argparse import ArgumentParser
from pathlib import Path
from robogauge import ROBOGAUGE_ROOT_DIR
def pares_path(path):
def parse_path(path):
if "{ROBOGAUGE_ROOT_DIR}" in str(path):
path = str(path).replace("{ROBOGAUGE_ROOT_DIR}", ROBOGAUGE_ROOT_DIR)
return path
@@ -47,10 +47,13 @@ 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": "--experiment-name", "type": str, "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()
args = parser.parse_args()
if args.experiment_name is None:
args.experiment_name = f"{args.task_name}_exp"
return args

View File

@@ -14,6 +14,7 @@ import time
import logging
from pathlib import Path
from robogauge import ROBOGAUGE_ROOT_DIR
from torch.utils.tensorboard import SummaryWriter
class LogColor:
""" ANSI color codes """
@@ -49,6 +50,10 @@ class ColorFormatter(logging.Formatter):
class Logger:
logger: logging.Logger = None
log_dir: Path = None
writer: SummaryWriter = None
def create(self,
experiment_name,
console_output=True, color_output=True,
@@ -93,6 +98,12 @@ class Logger:
fh = logging.FileHandler(path_log_file, mode=save_file_mode, encoding='utf-8')
fh.setFormatter(file_formatter)
self.logger.addHandler(fh)
def create_tensorboard(self, run_name: str):
if self.writer is not None:
self.writer.close()
self.writer = SummaryWriter(str(self.log_dir / run_name))
self.info(f"Tensorboard writer created at: {self.log_dir / run_name}")
def debug(self, msg, *args, **kwargs):
self.logger.debug(msg, *args, **kwargs, stacklevel=2)
@@ -108,6 +119,19 @@ class Logger:
def critical(self, msg, *args, **kwargs):
self.logger.critical(msg, *args, **kwargs, stacklevel=2)
def log(self, value, tag, step):
""" Log scalar value to tensorboard
Args:
value (float): scalar value
tag (str): tag name
step (int): step number
"""
if self.writer is not None:
self.writer.add_scalar(tag, value, step)
else:
self.warning("Tensorboard writer is not initialized, skipping log.")
logger = Logger()

View File

@@ -33,7 +33,7 @@ class TaskRegister():
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):
def make_pipeline(self, name, args=None, sim_cfg=None, gauger_cfg=None, robot_cfg=None, run_name='0'):
default_cfgs = self.get_cfgs(name)
if sim_cfg is None:
sim_cfg = default_cfgs[0]
@@ -44,7 +44,7 @@ class TaskRegister():
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)
return pipeline_class(run_name, 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: