From 3b4ced6437289b04d91fd9ba1fa945c4136b4158 Mon Sep 17 00:00:00 2001 From: cen617-code <1057290604@qq.com> Date: Tue, 1 Sep 2026 14:23:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(web-platform):=20release=20V0.7=20?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .prettierignore | 1 + README.md | 3 + package-lock.json | 4 +- package.json | 2 +- web_platform/README.md | 36 + web_platform/e2e/app.spec.ts | 318 +++++++ web_platform/index.html | 62 +- web_platform/src/app/App.tsx | 804 ++++++++++++++++-- .../components/FeedbackComponents.test.tsx | 25 +- .../app/components/MapAssetLibrary.test.tsx | 51 ++ .../src/app/components/MapAssetLibrary.tsx | 298 +++++++ .../app/components/MapEditorPanel.test.tsx | 106 +++ .../src/app/components/MapEditorPanel.tsx | 597 +++++++++++++ .../app/components/PhysicalMapPanel.test.tsx | 118 +++ .../src/app/components/PhysicalMapPanel.tsx | 474 +++++++++++ .../src/app/components/SidebarPanel.tsx | 137 ++- web_platform/src/app/components/StatusBar.tsx | 17 + .../app/components/WorkbenchHeader.test.tsx | 9 +- .../src/app/components/WorkbenchHeader.tsx | 28 +- .../src/app/components/WorkspaceOverlays.tsx | 195 +++-- web_platform/src/map/MapComposer.test.ts | 174 ++++ web_platform/src/map/MapComposer.ts | 263 ++++++ web_platform/src/map/MapLoader.test.ts | 118 +++ web_platform/src/map/MapLoader.ts | 103 +++ .../src/map/editor/MapDocumentCompiler.ts | 54 ++ .../map/editor/MapDocumentImporter.test.ts | 65 ++ .../src/map/editor/MapDocumentImporter.ts | 230 +++++ web_platform/src/map/editor/MapEditSession.ts | 151 ++++ .../src/map/editor/MapPackageExporter.ts | 64 ++ web_platform/src/map/editor/assetCatalog.ts | 58 ++ web_platform/src/map/editor/editor.test.ts | 162 ++++ web_platform/src/map/editor/editorSchema.ts | 188 ++++ web_platform/src/map/editor/placement.ts | 81 ++ web_platform/src/map/editor/types.ts | 88 ++ web_platform/src/map/mapPaths.test.ts | 23 + web_platform/src/map/mapPaths.ts | 44 + web_platform/src/map/mapSchema.ts | 138 +++ web_platform/src/map/physicalMap.test.ts | 156 ++++ web_platform/src/map/physicalMap.ts | 295 +++++++ web_platform/src/map/terrainGenerator.test.ts | 63 ++ web_platform/src/map/terrainGenerator.ts | 288 +++++++ web_platform/src/map/types.ts | 140 +++ web_platform/src/project/cachedFiles.test.ts | 1 + web_platform/src/project/importer.test.ts | 60 +- web_platform/src/project/importer.ts | 146 +++- web_platform/src/project/types.ts | 12 + web_platform/src/project/workspace.test.ts | 1 + web_platform/src/simulation/PhysicsAdapter.ts | 151 +++- web_platform/src/simulation/geometry.test.ts | 27 +- web_platform/src/simulation/geometry.ts | 54 ++ web_platform/src/styles.css | 102 ++- .../src/viewer/MapEditorLayer.test.ts | 110 +++ web_platform/src/viewer/MapEditorLayer.ts | 267 ++++++ web_platform/src/viewer/MuJoCoViewer.ts | 148 +++- .../src/viewer/VisualMapLayer.test.ts | 72 ++ web_platform/src/viewer/VisualMapLayer.ts | 142 ++++ web_platform/vite.config.ts | 31 +- 58 files changed, 7287 insertions(+), 269 deletions(-) create mode 100644 web_platform/src/app/components/MapAssetLibrary.test.tsx create mode 100644 web_platform/src/app/components/MapAssetLibrary.tsx create mode 100644 web_platform/src/app/components/MapEditorPanel.test.tsx create mode 100644 web_platform/src/app/components/MapEditorPanel.tsx create mode 100644 web_platform/src/app/components/PhysicalMapPanel.test.tsx create mode 100644 web_platform/src/app/components/PhysicalMapPanel.tsx create mode 100644 web_platform/src/map/MapComposer.test.ts create mode 100644 web_platform/src/map/MapComposer.ts create mode 100644 web_platform/src/map/MapLoader.test.ts create mode 100644 web_platform/src/map/MapLoader.ts create mode 100644 web_platform/src/map/editor/MapDocumentCompiler.ts create mode 100644 web_platform/src/map/editor/MapDocumentImporter.test.ts create mode 100644 web_platform/src/map/editor/MapDocumentImporter.ts create mode 100644 web_platform/src/map/editor/MapEditSession.ts create mode 100644 web_platform/src/map/editor/MapPackageExporter.ts create mode 100644 web_platform/src/map/editor/assetCatalog.ts create mode 100644 web_platform/src/map/editor/editor.test.ts create mode 100644 web_platform/src/map/editor/editorSchema.ts create mode 100644 web_platform/src/map/editor/placement.ts create mode 100644 web_platform/src/map/editor/types.ts create mode 100644 web_platform/src/map/mapPaths.test.ts create mode 100644 web_platform/src/map/mapPaths.ts create mode 100644 web_platform/src/map/mapSchema.ts create mode 100644 web_platform/src/map/physicalMap.test.ts create mode 100644 web_platform/src/map/physicalMap.ts create mode 100644 web_platform/src/map/terrainGenerator.test.ts create mode 100644 web_platform/src/map/terrainGenerator.ts create mode 100644 web_platform/src/map/types.ts create mode 100644 web_platform/src/viewer/MapEditorLayer.test.ts create mode 100644 web_platform/src/viewer/MapEditorLayer.ts create mode 100644 web_platform/src/viewer/VisualMapLayer.test.ts create mode 100644 web_platform/src/viewer/VisualMapLayer.ts diff --git a/.gitignore b/.gitignore index 7cdc71b1..dc57c6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ playwright-report/ web_platform/test-results/ web_platform/playwright-report/ web_platform/node_modules/.vite/ +.playwright-cli/ *.tsbuildinfo # Python diff --git a/.prettierignore b/.prettierignore index 23de50ee..944d5227 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,6 +9,7 @@ web-platform-dist/ coverage/ playwright-report/ test-results/ +.playwright-cli/ web_platform/fixtures/ web_platform/public/ package-lock.json diff --git a/README.md b/README.md index c92040c1..f7467dee 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ - ROS `package://`、常见 URDF 兼容转换及 DAE 降级处理 - Three.js 模型、碰撞体、坐标系、关节轴、质心和惯量可视化 - 播放、暂停、单步、重置、变速、关节拖动与外力交互 +- 内置平地、坡道、楼梯、随机障碍物及 9 类系统参数化地形(粗糙/波浪、金字塔阶梯、深坑、沟壑等) +- 工程地图包:静态 MJCF/OBJ/STL/高度场碰撞层、GLB 视觉层和机器人出生点 +- V3 地图创作层:认证资产库支持点击添加、拖到画布落位和首个资产自动创建场景,并可通过表单与视口操纵器继续编辑、事务式应用及导出地图 ZIP - 浏览器内 Python 控制器(Pyodide) - ONNX 强化学习策略推理(ONNX Runtime Web) - 可选的本机 mjlab 训练桥接服务 diff --git a/package-lock.json b/package-lock.json index 7cb572fb..644be042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mujoco-web-platform", - "version": "0.6.1", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mujoco-web-platform", - "version": "0.6.1", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "@monaco-editor/react": "^4.7.0", diff --git a/package.json b/package.json index 898d3ecf..249bf2af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mujoco-web-platform", - "version": "0.6.1", + "version": "0.7.0", "description": "基于 MuJoCo WebAssembly 的本地机器人仿真与控制平台", "private": true, "type": "module", diff --git a/web_platform/README.md b/web_platform/README.md index bdec43b2..d54d719a 100644 --- a/web_platform/README.md +++ b/web_platform/README.md @@ -12,6 +12,7 @@ - Three.js primitive、mesh、材质/贴图显示与对象选择 - 播放、暂停、单步、重置、0.25×–4× 速度 - actuator 滑杆、hinge/slide 关节拖动、动态 body 外力拖拽 +- 内置平地、坡道、楼梯、可复现随机障碍物及 9 类系统参数化地形,可配置尺寸、摩擦、难度、种子与高度场采样精度 - 导入单文件 `.py` 控制器,通过本地 Pyodide 在 `mj_step` 前按仿真时间同步执行 - 导入 mjlab 导出的 `policy.onnx`,在浏览器本地执行 Go2-W 平衡/速度策略推理 - 从图形界面向本机训练桥接服务发起 mjlab 强化学习训练、查看进度/日志、停止任务并导入训练生成的 ONNX @@ -61,6 +62,41 @@ python3 -m http.server 8080 --directory web-platform-dist - 默认限制:2000 个文件、单文件 128 MiB、总解压大小 512 MiB、ZIP 文件 128 MiB。 - 文件夹或 ZIP 中的 `.py` 会显示在“控制 → Python 控制器”;也可以在加载模型后单独导入不超过 1 MiB 的 `.py`。 +## 地图模块 + +模型加载后打开右侧“地图”标签,可以选择平地、坡道、楼梯、随机障碍物、系统参数化地形或工程内地图包。参数化地形包括离散障碍、沟壑、倒金字塔阶梯、深坑、金字塔阶梯、轨道、随机粗糙、踏石和波浪地形;相同参数与随机种子会确定性生成相同碰撞层。点击“应用并重新编译”后,平台会在当前入口同目录生成临时组合 MJCF;原始工程文件不会被修改。模型编译失败时保留上一个可用仿真会话。 + +工程地图由 `map.json`、静态 MJCF 碰撞层和可选的自包含 GLB 视觉层组成: + +```text +maps/warehouse/ +├── map.json +├── physics/world.xml +├── physics/meshes/*.obj +├── visuals/scene.glb +└── authoring/map.scene.json # V3 可选创作层 +``` + +最小描述示例: + +```json +{ + "schemaVersion": 1, + "id": "warehouse", + "name": "仓库", + "coordinateSystem": { "units": "m", "up": "Z", "forward": "+X" }, + "physics": { "source": "physics/world.xml" }, + "visual": { "source": "visuals/scene.glb" }, + "spawnPoints": [{ "id": "main", "name": "主入口", "position": [0, 0, 0.35], "yawDeg": 0 }] +} +``` + +物理地图仅允许静态 `worldbody` 以及 mesh、heightfield、texture、material 等基础 asset,不允许 joint、mocap body、actuator、sensor、include 或 default class。OBJ/STL 应使用简化碰撞模型;高精度模型只放入 GLB。GLB 必须是 2.0 自包含文件,外部 URI 会被拒绝。地图统一使用米制、Z-up、+X 前向坐标系。 + +V3 可编辑地图使用 `schemaVersion: 2`,并增加 `"authoring": { "source": "authoring/map.scene.json" }`。创作层支持方盒、圆柱、胶囊、坡道、楼梯和出生点。“场景 · 资产库”中的认证资产可点击添加或拖到画布落位;没有可编辑地图时,首个资产会立即创建 Schema V2 场景草稿,不触发 MuJoCo 重编译。新增对象支持三种放置方式:自动贴地、沿世界 `-Z` 落到最高静态承载面的自动重力落位,以及禁止位姿编辑的锁定模式。只有点击“应用并重新编译”后才提交物理层。可以通过表单或视口 TransformControls 修改位置、绕 Z 轴旋转和原语尺寸,支持移动/旋转吸附、视口拾取、复制、删除及对齐地面;`W`/`E`/`S` 切换移动、旋转和缩放工具,`Delete` 删除,`Ctrl+Z`/`Ctrl+Y` 撤销重做。编辑只更新 Three.js 草稿预览,点击编辑器内“应用并重新编译”后才生成确定性的静态 MJCF。失败时保留旧仿真和草稿。浏览器不会直接写回原目录,可使用“导出地图 ZIP”下载当前已提交地图包。没有 `authoring.source` 的 V1/V2 地图默认只读;仅由 `box`、`cylinder`、`capsule` 构成且不含 asset、材质、碰撞过滤或隐藏姿态语义的静态 MJCF,可通过“创建可编辑副本”显式升级。转换会生成 `authoring/map.scene.json`、Schema V2 描述和确定性物理层;任何不可逆语义都会导致整体拒绝,不会静默丢失内容。 + +物理地图超过 2000 个 geom 会产生性能警告,超过 10000 个会被拒绝;GLB 超过 100 万三角面会警告,超过 300 万会被拒绝。当前原生 URDF 模式不支持地图,请切换到“转换为 MJCF”。 + ## Python 控制器 Python 控制器是可信的单文件脚本,必须同步定义 `step(ctx, state)`;可选定义 `NAME`、`CONTROL_HZ`(限制为 1–500 Hz)、`init(api)`、`command(name, state)`、`reset(state)` 和 `dispose(state)`。`init` 可用 `api.joint(name)`、`api.actuator(name)`、`api.sensor(name)`、`api.body(name)` 预解析 ID;`step` 可用 `ctx.qpos(id)`、`ctx.qvel(id)`、`ctx.sensor(id)`、`ctx.body_quat(id)`、`ctx.body_position(id)` 读取状态,并用 `ctx.set_control(id, value)` 写入经过有限值检查和 actuator 限幅的控制量。定义 `command` 后,界面会显示停止、前进、后退、左转、右转和起跳按钮,并分别传入 `stop`、`forward`、`backward`、`turn_left`、`turn_right`、`jump`。所有回调都必须同步;异常会自动停止控制器或显示诊断,运行期异常还会暂停仿真并清零 `ctrl`。 diff --git a/web_platform/e2e/app.spec.ts b/web_platform/e2e/app.spec.ts index 7565d976..3c067464 100644 --- a/web_platform/e2e/app.spec.ts +++ b/web_platform/e2e/app.spec.ts @@ -22,6 +22,46 @@ const SIMPLE_MODEL = ` const SLIDE_DIRECTION_MODEL = ``; +function minimalGlb(): Buffer { + const json = Buffer.from( + JSON.stringify({ + asset: { version: '2.0' }, + scene: 0, + scenes: [{ nodes: [0] }], + nodes: [{ mesh: 0 }], + meshes: [{ primitives: [{ attributes: { POSITION: 0 } }] }], + buffers: [{ byteLength: 36 }], + bufferViews: [{ buffer: 0, byteOffset: 0, byteLength: 36, target: 34962 }], + accessors: [ + { + bufferView: 0, + componentType: 5126, + count: 3, + type: 'VEC3', + min: [0, 0, 0], + max: [1, 1, 0], + }, + ], + }), + ); + const jsonPadding = (4 - (json.length % 4)) % 4; + const jsonChunk = Buffer.concat([json, Buffer.alloc(jsonPadding, 0x20)]); + const positions = Buffer.from(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]).buffer); + const totalLength = 12 + 8 + jsonChunk.length + 8 + positions.length; + const glb = Buffer.alloc(totalLength); + glb.writeUInt32LE(0x46546c67, 0); + glb.writeUInt32LE(2, 4); + glb.writeUInt32LE(totalLength, 8); + glb.writeUInt32LE(jsonChunk.length, 12); + glb.writeUInt32LE(0x4e4f534a, 16); + jsonChunk.copy(glb, 20); + const binOffset = 20 + jsonChunk.length; + glb.writeUInt32LE(positions.length, binOffset); + glb.writeUInt32LE(0x004e4942, binOffset + 4); + positions.copy(glb, binOffset + 8); + return glb; +} + const LARGE_MODEL = `