#!/usr/bin/env python3 """测试 MotrixSim hfield 的 z_scale 上限——自己动手测,不信文档。""" import os, sys, numpy as np, time os.environ.setdefault("XLA_PYTHON_CLIENT_PREALLOCATE", "false") os.environ.setdefault("JAX_PLATFORMS", "cpu") sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import motrix_envs.locomotion.go1.dreamwaq # noqa from motrix_envs import registry as env_registry NUM_ENVS = 256 TEST_STEPS = 100 os.environ["DREAMWAQ_TERRAIN"] = "flat" # 用 flat 场景,手动覆盖 hfield 参数 def _build_custom_hfield(x_radius, y_radius, z_scale, z_base=0.001): """构建自定义 hfield 描述字符串,用于覆盖 XML 中的 hfield 参数。""" import tempfile, cv2 # 生成一个纯斜坡的 hfield PNG 用于测试 nx, ny = 200, 200 canvas = np.zeros((ny, nx), dtype=np.uint16) # 从左上到右下的斜坡:高度从 0 到 z_scale for i in range(ny): for j in range(nx): # 对角线斜坡,最高点在右下角 h = int((i + j) / (nx + ny) * 65535) canvas[i, j] = h # 中间 1/3 区域做平台(平坦) cx, cy = nx // 2, ny // 2 p = nx // 6 canvas[cy - p:cy + p, cx - p:cx + p] = 0 out_d = os.path.join(os.path.dirname(__file__), "..", "motrix_envs", "src", "motrix_envs", "locomotion", "go1", "xmls", "assets") os.makedirs(out_d, exist_ok=True) png_path = os.path.join(out_d, "zscale_test.png") cv2.imwrite(png_path, canvas) return png_path, (x_radius, y_radius, z_scale, z_base) def test_z_scale(z_scale, n_envs=NUM_ENVS, n_steps=TEST_STEPS): """测试给定 z_scale 下的物理稳定性。""" import tempfile, cv2 # 生成测试 hfield out_d = os.path.join(os.path.dirname(__file__), "..", "motrix_envs", "src", "motrix_envs", "locomotion", "go1", "xmls", "assets") os.makedirs(out_d, exist_ok=True) # 简单斜坡地形 nx, ny = 40, 40 # 小尺寸快速生成 canvas = np.zeros((ny, nx), dtype=np.uint16) for i in range(ny): for j in range(nx): h = int((i / ny) * 65535) # y 方向斜坡 canvas[i, j] = h # 中央平台 p = nx // 6 canvas[ny // 2 - p:ny // 2 + p, nx // 2 - p:nx // 2 + p] = 0 png_path = os.path.join(out_d, "zscale_test.png") cv2.imwrite(png_path, canvas) total_x = nx * 0.1 # HS=0.1, 粗略 total_y = ny * 0.1 # 构建 scene XML xml_path = os.path.join(os.path.dirname(__file__), "..", "motrix_envs", "src", "motrix_envs", "locomotion", "go1", "xmls", "scene_zscale_test.xml") xml = f""" """ with open(xml_path, "w") as f: f.write(xml) import motrixsim as mtx t0 = time.time() try: model = mtx.load_model(xml_path) data = mtx.SceneData(model, batch=[n_envs]) data.reset(model) body = model.get_body(0) # 随机初始位置(在平台上) init_pos = model.compute_init_dof_pos() init_pos = np.tile(init_pos, (n_envs, 1)) init_pos[:, 0] += np.random.uniform(-0.5, 0.5, n_envs) init_pos[:, 1] += np.random.uniform(-0.5, 0.5, n_envs) init_pos[:, 2] = 0.5 # 从 0.5m 掉落 data.set_dof_pos(init_pos.astype(np.float32), model) model.forward_kinematic(data) heights = np.zeros((n_steps, n_envs), dtype=np.float32) fall_count = 0 for step in range(n_steps): model.step(data) h = body.get_pose(data)[:, 2] heights[step] = h # 摔倒检测:base_z < 0.15 (趴了) fall_count += np.sum(h < 0.15) mean_h = float(np.mean(heights[-20:])) # 最后 20 步平均 total_falls = fall_count dt = time.time() - t0 return mean_h, total_falls, dt except Exception as e: return None, str(e), 0 if __name__ == "__main__": print(f"{'z_scale':>8s} {'mean_base_z':>12s} {'falls':>8s} {'time':>8s} verdict") print("-" * 65) for zs in [0.3, 0.5, 0.54, 0.8, 1.0, 1.5, 2.0, 3.0]: mean_h, falls, dt = test_z_scale(zs, n_envs=64, n_steps=50) if mean_h is None: print(f"{zs:8.3f} {'ERROR':>12s} {str(falls)[:20]:>8s}") continue ok = "✅ 稳定" if mean_h > 0.25 and falls < 10 else "⚠ 不稳" if mean_h > 0.15 else "❌ 崩溃" print(f"{zs:8.3f} {mean_h:12.4f} {falls:8d} {dt:7.1f}s {ok}")