diff --git a/src/holosoma/holosoma/managers/command/terms/locomotion.py b/src/holosoma/holosoma/managers/command/terms/locomotion.py index 9205e47f..f17f6897 100644 --- a/src/holosoma/holosoma/managers/command/terms/locomotion.py +++ b/src/holosoma/holosoma/managers/command/terms/locomotion.py @@ -42,10 +42,6 @@ def reset(self, env_ids: torch.Tensor | None) -> None: if idx.numel() == 0: return - if self.env.is_evaluating: - commands[idx] = 0.0 - return - self._resample(idx) def step(self) -> None: diff --git a/src/holosoma/holosoma/simulator/isaacsim/isaacsim.py b/src/holosoma/holosoma/simulator/isaacsim/isaacsim.py index ecad4d83..11779622 100644 --- a/src/holosoma/holosoma/simulator/isaacsim/isaacsim.py +++ b/src/holosoma/holosoma/simulator/isaacsim/isaacsim.py @@ -125,7 +125,7 @@ def __init__(self, tyro_config: FullSimConfig, terrain_manager: TerrainManager, print("[INFO]: Scene manager: ", self.scene) if self.simulator_config.viewer.enable_tracking: - viewer_config: ViewerCfg = ViewerCfg(origin_type="asset_root", asset_name="robot") + viewer_config: ViewerCfg = ViewerCfg(origin_type="asset_root", asset_name="robot", eye=(0.0, -1.5, 1.5)) else: viewer_config: ViewerCfg = ViewerCfg() diff --git a/src/holosoma_retargeting/holosoma_retargeting/config_types/data_type.py b/src/holosoma_retargeting/holosoma_retargeting/config_types/data_type.py index 611e1c23..74764041 100644 --- a/src/holosoma_retargeting/holosoma_retargeting/config_types/data_type.py +++ b/src/holosoma_retargeting/holosoma_retargeting/config_types/data_type.py @@ -2,9 +2,15 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Any, TypedDict +from holosoma_retargeting.config_types.robot import ( + RobotDefaults, + _default_robot_defaults, + _validate_robot_type, +) + # Pre-defined constants for each data format LAFAN_DEMO_JOINTS = [ "Hips", @@ -346,13 +352,13 @@ class MotionDataConfig: data_format: str = "smplh" # Use str instead of Literal to allow dynamic robot types robot_type: str = "g1" + robot_defaults: dict[str, RobotDefaults] = field(default_factory=_default_robot_defaults) def __post_init__(self) -> None: """Validate data_format and robot_type.""" _validate_data_format(self.data_format) - from holosoma_retargeting.config_types.robot import _validate_robot_type - _validate_robot_type(self.robot_type) + _validate_robot_type(self.robot_type, self.robot_defaults) # Optional overrides - if None, will use defaults from data_format demo_joints: list[str] | None = None diff --git a/src/holosoma_retargeting/holosoma_retargeting/config_types/robot.py b/src/holosoma_retargeting/holosoma_retargeting/config_types/robot.py index ac93c0f0..e4c13939 100644 --- a/src/holosoma_retargeting/holosoma_retargeting/config_types/robot.py +++ b/src/holosoma_retargeting/holosoma_retargeting/config_types/robot.py @@ -2,8 +2,8 @@ from __future__ import annotations -from dataclasses import dataclass -from typing import TypedDict +from dataclasses import dataclass, field +from typing import Mapping, TypedDict import numpy as np @@ -21,14 +21,23 @@ class RobotDefaults(TypedDict): } -def _validate_robot_type(robot_type: str) -> None: - """Validate that robot_type exists in _ROBOT_DEFAULTS.""" - if robot_type not in _ROBOT_DEFAULTS: - available = ", ".join(sorted(_ROBOT_DEFAULTS.keys())) +def _default_robot_defaults() -> dict[str, RobotDefaults]: + """Copy robot defaults so each config instance can be customized safely.""" + return {name: defaults.copy() for name, defaults in _ROBOT_DEFAULTS.items()} + + +def _validate_robot_type(robot_type: str, robot_defaults: Mapping[str, RobotDefaults] | None = None) -> None: + """Validate that robot_type exists in robot defaults.""" + if robot_defaults is None: + robot_defaults = _ROBOT_DEFAULTS + + if robot_type not in robot_defaults: + available = ", ".join(sorted(robot_defaults.keys())) raise ValueError( f"Invalid robot_type: '{robot_type}'. " f"Available robot types: {available}. " - f"Add your robot to _ROBOT_DEFAULTS in config_types/robot.py" + f"Add your robot to RobotConfig.robot_defaults " + f"(default defined by _ROBOT_DEFAULTS in config_types/robot.py)" ) @@ -51,10 +60,11 @@ class RobotConfig: # Robot type selector - determines which defaults to use # Use str instead of Literal to allow dynamic robot types via _ROBOT_DEFAULTS robot_type: str = "g1" + robot_defaults: dict[str, RobotDefaults] = field(default_factory=_default_robot_defaults) def __post_init__(self) -> None: """Validate robot_type after initialization.""" - _validate_robot_type(self.robot_type) + _validate_robot_type(self.robot_type, self.robot_defaults) # Robot configuration (optional overrides) robot_dof: int | None = None @@ -78,7 +88,7 @@ def _robot_dof(self) -> int: """Get robot DOF - use override if provided, else use robot_type default.""" if self.robot_dof is not None: return self.robot_dof - return _ROBOT_DEFAULTS[self.robot_type]["robot_dof"] + return self.robot_defaults[self.robot_type]["robot_dof"] ROBOT_DOF = property( _robot_dof, @@ -89,7 +99,7 @@ def _robot_height(self) -> float: """Get robot height - use override if provided, else use robot_type default.""" if self.robot_height is not None: return self.robot_height - return _ROBOT_DEFAULTS[self.robot_type]["robot_height"] + return self.robot_defaults[self.robot_type]["robot_height"] ROBOT_HEIGHT = property( _robot_height,