Skip to content

Commit f770bdf

Browse files
authored
Merge pull request #64 from KraHsu/dev
Dev → main: core facade cleanup + Genesis 0.4.7 compat
2 parents 32b0b3a + bc84562 commit f770bdf

15 files changed

Lines changed: 130 additions & 90 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ uv run genelab list tasks
8383
- `genelab.envs`, `genelab.robots`, `genelab.tasks` — thin core namespaces for registry helpers.
8484
- `genelab.actuator`, `genelab.entity`, `genelab.scene`, `genelab.sensor`, `genelab.terrains`,
8585
and `genelab.rl` — extension namespaces for robotics research code.
86+
- `genelab.asset_zoo` — bundled example robots (`g1`, `go1`, `anymal-c`, `franka`, `cartpole`).
87+
Not part of the core facade: fetch via the `ROBOTS` registry (`ROBOTS.get("g1")()`) or import
88+
the config directly (`from genelab.asset_zoo import UnitreeG1Cfg`).
8689

8790
Downstream projects live in their own Python packages and register robots, environments, and
8891
tasks through GeneLab's registry and extension hooks. The minimal template lives at

‎docs/concepts/asset_zoo.en.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Asset zoo
22

3-
`genelab.asset_zoo` ships curated robot configurations as part of the core package, so
4-
`from genelab.lab import CartpoleCfg` works out of the box. Each entry pairs a
3+
`genelab.asset_zoo` ships curated robot configurations as a bundled extension of the
4+
core package (not part of the `genelab.lab` facade), so `from genelab.asset_zoo import
5+
CartpoleCfg` works out of the box. Each entry pairs a
56
declarative `AssetSpec` (URL + md5 + filename, optionally + `archive_member`) with a
67
lazy factory that returns a fresh `ArticulationCfg`. The factories trigger an
78
md5-verified download only when invoked, so read-only commands like
@@ -112,7 +113,7 @@ register_robot("my-robot", MyRobotCfg, description="...", cfg_type=ArticulationC
112113
```
113114

114115
Then add the module to `asset_zoo/__init__.py` so import side-effects run during
115-
`load_builtin_registries()`. Downstream projects that prefer to keep robots out-of-tree
116+
`load_bundled_asset_zoo()`. Downstream projects that prefer to keep robots out-of-tree
116117
should use the same `AssetSpec` + `register_robot` pattern from their own extension
117118
package — the helpers are public.
118119

@@ -125,7 +126,7 @@ package — the helpers are public.
125126
* **Stale cache after upstream re-upload** — the path is keyed by md5, so updating the
126127
spec naturally points at a fresh subdirectory; old digests linger on disk until the
127128
user deletes `.cache/assets/<name>/` manually.
128-
* **Factory called before `load_builtin_registries()`** — `ROBOTS.get("cartpole")`
129+
* **Factory called before `load_bundled_asset_zoo()`** — `ROBOTS.get("cartpole")`
129130
raises `KeyError`; the CLI calls the loader at startup so this only bites direct API
130131
use. Import `genelab.asset_zoo` explicitly to register without going through the CLI.
131132

‎docs/concepts/asset_zoo.zh.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 资产库
22

3-
`genelab.asset_zoo` 把精选的机器人配置随核心包一起发布,所以 `from genelab.lab import CartpoleCfg` 开箱即用。每个条目把一个声明式的 `AssetSpec`(URL + md5 + 文件名,可选 `archive_member`)和一个返回新 `ArticulationCfg` 的 lazy factory 配对。factory 仅在被调用时才触发带 md5 校验的下载,所以像 `genelab list robots` 这种只读命令不会触网。
3+
`genelab.asset_zoo` 把精选的机器人配置作为核心包的预置扩展一起发布(不属于 `genelab.lab` 公开门面),所以 `from genelab.asset_zoo import CartpoleCfg` 开箱即用。每个条目把一个声明式的 `AssetSpec`(URL + md5 + 文件名,可选 `archive_member`)和一个返回新 `ArticulationCfg` 的 lazy factory 配对。factory 仅在被调用时才触发带 md5 校验的下载,所以像 `genelab list robots` 这种只读命令不会触网。
44

55
## 为什么需要预置库
66

@@ -82,14 +82,14 @@ def MyRobotCfg() -> ArticulationCfg:
8282
register_robot("my-robot", MyRobotCfg, description="...", cfg_type=ArticulationCfg)
8383
```
8484

85-
随后把模块加进 `asset_zoo/__init__.py`,让 `load_builtin_registries()` 触发导入副作用。下游项目若想把机器人放在树外,可以在自己的扩展包里用同样的 `AssetSpec` + `register_robot` 模式 —— 这两个 helper 都是公开 API。
85+
随后把模块加进 `asset_zoo/__init__.py`,让 `load_bundled_asset_zoo()` 触发导入副作用。下游项目若想把机器人放在树外,可以在自己的扩展包里用同样的 `AssetSpec` + `register_robot` 模式 —— 这两个 helper 都是公开 API。
8686

8787
## 需要知道的失败模式
8888

8989
* **网络不可达** —— `AssetDownloadError` 包住 `URLError`;staging 文件已删除,重试从干净状态开始。
9090
* **md5 不匹配** —— 抛 `AssetDownloadError`,错误带两份 digest;staging 文件在异常抛出前已删除。
9191
* **上游重新上传后缓存陈旧** —— 路径按 md5 键控,更新 spec 自然指到新子目录;旧 digest 对应的目录留在磁盘上直到用户手动删除 `.cache/assets/<name>/`。
92-
* **factory 在 `load_builtin_registries()` 之前被调** —— `ROBOTS.get("cartpole")` 抛 `KeyError`;CLI 启动时会调用 loader,所以这只有在直接用 API 时才会出现。需要绕过 CLI 时显式 `import genelab.asset_zoo` 即可注册。
92+
* **factory 在 `load_bundled_asset_zoo()` 之前被调** —— `ROBOTS.get("cartpole")` 抛 `KeyError`;CLI 启动时会调用 loader,所以这只有在直接用 API 时才会出现。需要绕过 CLI 时显式 `import genelab.asset_zoo` 即可注册。
9393

9494
## See also
9595

‎examples/unitree/src/genelab_unitree/g1/env_cfg.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,9 +548,9 @@ def unitree_g1_velocity_env_cfg(play: bool = False) -> ManagerBasedRlEnvCfg:
548548
# ImportError swallowed so users without the 'teleop' extra still get a
549549
# working play path.
550550
try:
551-
from genelab.bridges.dearpygui import DearPyGuiCommandBridgeCfg
551+
from genelab.bridges.dearpygui import DearPyGuiTwistBridgeCfg
552552

553-
cfg.bridges_cfg["teleop"] = DearPyGuiCommandBridgeCfg(
553+
cfg.bridges_cfg["teleop"] = DearPyGuiTwistBridgeCfg(
554554
command_name="twist",
555555
vx_range=(-2.0, 2.0),
556556
vy_range=(-1.0, 1.0),

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = "Isaac Lab API for RL and robotics research powered by Genesis."
55
readme = "README.md"
66
requires-python = ">=3.12"
77
dependencies = [
8-
"genesis-world",
8+
"genesis-world>=0.4.7",
99
"typer>=0.12",
1010
"rich>=13",
1111
"questionary>=2",

‎src/genelab/bridges/__init__.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
"""Play-time bridges: ``Bridge`` protocol + reference implementations.
22
3-
``DearPyGuiCommandBridge`` is not re-exported here because its module performs a
3+
``DearPyGuiTwistBridge`` is not re-exported here because its module performs a
44
lazy ``import dearpygui`` at first use; importing the symbol unconditionally
55
would still go through the module body (no ``dearpygui`` import there), but we
66
keep the surface minimal and ask GUI users to take the longer path:
77
8-
from genelab.bridges.dearpygui import DearPyGuiCommandBridgeCfg
8+
from genelab.bridges.dearpygui import DearPyGuiTwistBridgeCfg
99
"""
1010

1111
from genelab.bridges.base import Bridge, BridgeCfg
12-
from genelab.bridges.keyboard import KeyboardCommandBridge, KeyboardCommandBridgeCfg
12+
from genelab.bridges.keyboard import KeyboardTwistBridge, KeyboardTwistBridgeCfg
1313

1414
__all__ = [
1515
"Bridge",
1616
"BridgeCfg",
17-
"KeyboardCommandBridge",
18-
"KeyboardCommandBridgeCfg",
17+
"KeyboardTwistBridge",
18+
"KeyboardTwistBridgeCfg",
1919
]

‎src/genelab/bridges/dearpygui.py‎

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
"""DearPyGui teleop bridge: three sliders for ``(vx, vy, ωz)`` in a side window.
1+
"""DearPyGui twist bridge: three sliders for ``(vx, vy, ωz)`` in a side window.
2+
3+
Drives a 3-component velocity-twist command term (``vx``, ``vy``, ``ωz``). Not
4+
a generic command bridge — the layout is hardcoded to that shape.
25
36
Optional extra (``uv sync --extra teleop`` installs ``dearpygui``). Lazy import
47
inside :py:meth:`on_build` so importing this module never crashes core when the
@@ -34,26 +37,34 @@
3437
_logger = logging.getLogger(__name__)
3538

3639

37-
@dataclass
38-
class DearPyGuiCommandBridgeCfg(BridgeCfg):
39-
"""Configuration for :class:`DearPyGuiCommandBridge`."""
40+
@dataclass(kw_only=True)
41+
class DearPyGuiTwistBridgeCfg(BridgeCfg):
42+
"""Configuration for :class:`DearPyGuiTwistBridge`.
43+
44+
Range fields are required (no defaults): a teleop GUI cannot guess sensible
45+
slider limits for an unknown robot, so the caller must set them to the
46+
twist command term's actual range.
47+
"""
48+
49+
vx_range: tuple[float, float]
50+
"""Slider min/max for ``vx`` (m/s). Set to your robot's twist command limits."""
51+
52+
vy_range: tuple[float, float]
53+
"""Slider min/max for ``vy`` (m/s). Set to your robot's twist command limits."""
54+
55+
wz_range: tuple[float, float]
56+
"""Slider min/max for ``ωz`` (rad/s). Set to your robot's twist command limits."""
4057

4158
command_name: str = "twist"
4259
"""Name of the command term to drive."""
4360

44-
vx_range: tuple[float, float] = (-2.0, 2.0)
45-
vy_range: tuple[float, float] = (-1.0, 1.0)
46-
wz_range: tuple[float, float] = (-1.5, 1.5)
47-
"""Slider min/max — defaults match the Unitree G1 velocity ranges."""
48-
4961
default_vx: float = 0.0
5062
default_vy: float = 0.0
5163
default_wz: float = 0.0
5264
"""Initial slider values, also written into the command buffer at attach so
53-
the very first obs the policy sees is in-distribution. Useful when the
54-
trained policy is weak at sustained ``[0, 0, 0]`` — set ``default_vx=0.3``
55-
to start the robot in a known-good forward-walk mode (e.g. Unitree G1's
56-
forward-env training slice), then drag sliders down to test standing.
65+
the very first obs the policy sees is in-distribution. Defaults are zero
66+
(robot-agnostic safe state); override when the policy is weak at sustained
67+
``[0, 0, 0]`` and you want to start in a known-good locomotion regime.
5768
"""
5869

5970
title: str = "GeneLab Teleop"
@@ -62,15 +73,15 @@ class DearPyGuiCommandBridgeCfg(BridgeCfg):
6273

6374
def __post_init__(self) -> None:
6475
if self.class_type is None:
65-
self.class_type = DearPyGuiCommandBridge
76+
self.class_type = DearPyGuiTwistBridge
6677

6778

68-
class DearPyGuiCommandBridge:
79+
class DearPyGuiTwistBridge:
6980
"""``Bridge`` impl that drives ``(vx, vy, ωz)`` from three DPG sliders."""
7081

71-
cfg: DearPyGuiCommandBridgeCfg
82+
cfg: DearPyGuiTwistBridgeCfg
7283

73-
def __init__(self, cfg: DearPyGuiCommandBridgeCfg) -> None:
84+
def __init__(self, cfg: DearPyGuiTwistBridgeCfg) -> None:
7485
self.cfg = cfg
7586
self._lock = threading.Lock()
7687
self._vx: float = cfg.default_vx

‎src/genelab/bridges/keyboard.py‎

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
"""Keyboard teleop bridge: drive a velocity command term from WASD / QE / Space.
1+
"""Keyboard twist bridge: drive a 3-component (vx, vy, ωz) command term from WASD / QE / Space.
22
3-
This bridge is the simplest possible reference implementation of the :class:`Bridge`
4-
protocol. It uses Genesis's built-in keybind mechanism (``Viewer.register_keybinds``)
5-
so no extra dependency is needed.
3+
Drives a 3-component velocity-twist command term — the layout is hardcoded to
4+
that shape (not a generic command bridge). It is the simplest possible reference
5+
implementation of the :class:`Bridge` protocol, using Genesis's built-in keybind
6+
mechanism (``Viewer.register_keybinds``) so no extra dependency is needed.
67
78
Default keymap (rebind via cfg fields):
89
@@ -37,8 +38,8 @@
3738

3839

3940
@dataclass
40-
class KeyboardCommandBridgeCfg(BridgeCfg):
41-
"""Configuration for :class:`KeyboardCommandBridge`."""
41+
class KeyboardTwistBridgeCfg(BridgeCfg):
42+
"""Configuration for :class:`KeyboardTwistBridge`."""
4243

4344
command_name: str = "twist"
4445
"""Name of the command term to drive (e.g. the velocity ``twist`` term)."""
@@ -54,15 +55,15 @@ class KeyboardCommandBridgeCfg(BridgeCfg):
5455

5556
def __post_init__(self) -> None:
5657
if self.class_type is None:
57-
self.class_type = KeyboardCommandBridge
58+
self.class_type = KeyboardTwistBridge
5859

5960

60-
class KeyboardCommandBridge:
61+
class KeyboardTwistBridge:
6162
"""``Bridge`` impl that maps keyboard input → velocity command buffer."""
6263

63-
cfg: KeyboardCommandBridgeCfg
64+
cfg: KeyboardTwistBridgeCfg
6465

65-
def __init__(self, cfg: KeyboardCommandBridgeCfg) -> None:
66+
def __init__(self, cfg: KeyboardTwistBridgeCfg) -> None:
6667
self.cfg = cfg
6768
# Per-axis desired command; written by key callbacks, read in pre_step.
6869
# Plain Python floats — single-threaded mutation from pyglet's event
@@ -77,21 +78,21 @@ def __init__(self, cfg: KeyboardCommandBridgeCfg) -> None:
7778
def on_build(self, env: "ManagerBasedRlEnv") -> None:
7879
if env.num_envs != 1:
7980
_logger.info(
80-
"KeyboardCommandBridge: skipping (num_envs=%d, need 1). "
81+
"KeyboardTwistBridge: skipping (num_envs=%d, need 1). "
8182
"Pass --num_envs 1 to enable teleop.",
8283
env.num_envs,
8384
)
8485
return
8586
sim_cfg = getattr(env.cfg, "simulation", None)
8687
if sim_cfg is None or not getattr(sim_cfg, "vis", False):
8788
_logger.warning(
88-
"KeyboardCommandBridge: simulation.vis is False — no viewer to attach "
89+
"KeyboardTwistBridge: simulation.vis is False — no viewer to attach "
8990
"keybinds to. Bridge will be a no-op."
9091
)
9192
return
9293
viewer = env.scene.gs_scene.viewer
9394
if viewer is None:
94-
_logger.warning("KeyboardCommandBridge: scene has no viewer; bridge disabled.")
95+
_logger.warning("KeyboardTwistBridge: scene has no viewer; bridge disabled.")
9596
return
9697

9798
term = env.command_manager.get_term(self.cfg.command_name)

‎src/genelab/cli/__init__.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@
3838
)
3939
from genelab.cli._scaffold import create_project_skeleton
4040
from genelab.configs import apply_overrides
41-
from genelab.registry import TASKS, load_entrypoint_extensions, load_extension_module
41+
from genelab.registry import (
42+
TASKS,
43+
load_bundled_asset_zoo,
44+
load_entrypoint_extensions,
45+
load_extension_module,
46+
)
4247

4348
__all__ = [
4449
"PROF_KEYS",
@@ -355,6 +360,11 @@ def _state(ctx: typer.Context) -> _RootState:
355360

356361

357362
def _load_extensions(state: _RootState) -> None:
363+
# Bundled example robots (g1, go1, anymal-c, franka, cartpole) live in
364+
# genelab.asset_zoo. They are an opinionated bundle of examples, not core
365+
# API — load explicitly so the registration is intentional rather than a
366+
# side-effect of any other import.
367+
load_bundled_asset_zoo()
358368
if not state.no_entry_points:
359369
load_entrypoint_extensions()
360370
for module_name in state.extension_modules:

‎src/genelab/lab.py‎

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,11 @@
1313
ImplicitPDActuator,
1414
ImplicitPDActuatorCfg,
1515
)
16-
from genelab.asset_zoo import (
17-
AnymalCCfg,
18-
CartpoleCfg,
19-
FrankaPandaCfg,
20-
UnitreeG1Cfg,
21-
UnitreeGo1Cfg,
22-
)
2316
from genelab.bridges import (
2417
Bridge,
2518
BridgeCfg,
26-
KeyboardCommandBridge,
27-
KeyboardCommandBridgeCfg,
19+
KeyboardTwistBridge,
20+
KeyboardTwistBridgeCfg,
2821
)
2922
from genelab.configs import (
3023
InteractiveSceneCfg,
@@ -48,7 +41,7 @@
4841
TASKS,
4942
Registry,
5043
RegistryEntry,
51-
load_builtin_registries,
44+
load_bundled_asset_zoo,
5245
load_entrypoint_extensions,
5346
load_extension_module,
5447
)
@@ -112,15 +105,13 @@ class GenesisBackendCfg:
112105
"TASKS",
113106
"ActuatorBase",
114107
"ActuatorBaseCfg",
115-
"AnymalCCfg",
116108
"Articulation",
117109
"ArticulationCfg",
118110
"Bridge",
119111
"BridgeCfg",
120112
"CameraData",
121113
"CameraSensor",
122114
"CameraSensorCfg",
123-
"CartpoleCfg",
124115
"ContactData",
125116
"ContactSensor",
126117
"ContactSensorCfg",
@@ -130,7 +121,6 @@ class GenesisBackendCfg:
130121
"FrameTransformerData",
131122
"FrameTransformerSensor",
132123
"FrameTransformerSensorCfg",
133-
"FrankaPandaCfg",
134124
"GenesisBackendCfg",
135125
"Gnoise",
136126
"GridPattern",
@@ -144,8 +134,8 @@ class GenesisBackendCfg:
144134
"ImplicitPDActuatorCfg",
145135
"InteractiveScene",
146136
"InteractiveSceneCfg",
147-
"KeyboardCommandBridge",
148-
"KeyboardCommandBridgeCfg",
137+
"KeyboardTwistBridge",
138+
"KeyboardTwistBridgeCfg",
149139
"ManagerBasedEnv",
150140
"ManagerBasedEnvCfg",
151141
"NoiseCfg",
@@ -171,11 +161,9 @@ class GenesisBackendCfg:
171161
"TerrainHeightSensor",
172162
"TerrainHeightSensorCfg",
173163
"TerrainImporter",
174-
"UnitreeG1Cfg",
175-
"UnitreeGo1Cfg",
176164
"Unoise",
177165
"apply_overrides",
178-
"load_builtin_registries",
166+
"load_bundled_asset_zoo",
179167
"load_entrypoint_extensions",
180168
"load_extension_module",
181169
]

0 commit comments

Comments
 (0)