From 9023fd407329e12441288565a8e508fd08287416 Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 16:26:02 +0800 Subject: [PATCH 01/20] Add local docs web app and Codex skills --- .gitignore | 15 +- README.md | 209 +++++++++- directory.md | 6 +- extend/deep_camera/readme.md | 11 +- mujoco_learning_doc/__init__.py | 1 + mujoco_learning_doc/main.py | 325 ++++++++++++++++ mujoco_learning_doc/static/app.js | 99 +++++ mujoco_learning_doc/static/style.css | 363 ++++++++++++++++++ mujoco_learning_doc/templates/doc.html | 88 +++++ pyproject.toml | 34 ++ setup.py | 26 ++ skills/mujoco-engineering/SKILL.md | 52 +++ skills/mujoco-engineering/agents/openai.yaml | 7 + .../references/tutorial-map.md | 82 ++++ .../mujoco-engineering/scripts/find_repo.py | 109 ++++++ skills/mujoco-teaching/SKILL.md | 50 +++ skills/mujoco-teaching/agents/openai.yaml | 7 + .../references/tutorial-map.md | 63 +++ skills/mujoco-teaching/scripts/find_repo.py | 109 ++++++ 19 files changed, 1649 insertions(+), 7 deletions(-) create mode 100644 mujoco_learning_doc/__init__.py create mode 100644 mujoco_learning_doc/main.py create mode 100644 mujoco_learning_doc/static/app.js create mode 100644 mujoco_learning_doc/static/style.css create mode 100644 mujoco_learning_doc/templates/doc.html create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 skills/mujoco-engineering/SKILL.md create mode 100644 skills/mujoco-engineering/agents/openai.yaml create mode 100644 skills/mujoco-engineering/references/tutorial-map.md create mode 100644 skills/mujoco-engineering/scripts/find_repo.py create mode 100644 skills/mujoco-teaching/SKILL.md create mode 100644 skills/mujoco-teaching/agents/openai.yaml create mode 100644 skills/mujoco-teaching/references/tutorial-map.md create mode 100644 skills/mujoco-teaching/scripts/find_repo.py diff --git a/.gitignore b/.gitignore index 3ec7f79..c22a762 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,21 @@ extend/equality/.cache/ extend/plugin/.cache/ +__pycache__/ +*.py[cod] +.venv/ +*.egg-info/ +build/ +dist/ +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.uv/ +uv.lock +skills/*/.local/ + extend/soft_contact/C++/build/ extend/soft_contact/C++/.cache/ extend/touch/C++/build/ -extend/touch/C++/.cache/ \ No newline at end of file +extend/touch/C++/.cache/ diff --git a/README.md b/README.md index 213d922..634fc72 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,214 @@ MJCF为建模部分 CPP和python均为开发接口 extend为拓展和进阶 + +## 本地 Web 文档查看环境安装(可选) +如果只想在线浏览 GitHub 上的 Markdown,可以跳过本节。若希望在本机启动 Web 文档站查看教程,推荐使用 Python 3.10 或更新版本。项目已经提供 `pyproject.toml`,执行 `pip install -e .` 会以可编辑模式安装当前仓库,并自动安装 Python 版 MuJoCo 和本地文档站依赖。 + +
+pip / venv 安装 + +```bash +python3 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +pip install -e . +``` + +
+ +
+uv 安装 + +```bash +uv venv +source .venv/bin/activate +uv pip install -e . +``` + +
+ +
+conda 安装 + +```bash +conda create -n mujoco-learning python=3.10 -y +conda activate mujoco-learning +python -m pip install --upgrade pip +pip install -e . +``` + +如果遇到 `GLIBCXX_x.x.xx not found` 之类的问题,可以在 conda 环境中尝试: + +```bash +conda install -c conda-forge libstdcxx-ng +``` + +
+ +安装完成后可以验证 MuJoCo viewer: + +```bash +python -m mujoco.viewer +``` + +也可以指定一个 MJCF 文件启动: + +```bash +python -m mujoco.viewer --mjcf=API-MJCF/mecanum.xml +``` + +更多 MuJoCo 安装说明见 [mujoco安装](MJCF/Chapter0-install/tutorial.md)。 + +## 本地文档站 +安装依赖后,在仓库根目录启动 FastAPI 文档站: + +```bash +mujoco_learning_doc +``` + +默认会从 `127.0.0.1:8000` 启动;如果 8000 已被占用,会自动尝试下一个可用端口。也可以手动指定端口: + +```bash +mujoco_learning_doc --port 8001 +``` + +或者使用环境变量: + +```bash +MUJOCO_DOCS_PORT=8001 mujoco_learning_doc +``` + +也可以直接使用 uvicorn: + +```bash +uvicorn mujoco_learning_doc.main:app --reload --host 127.0.0.1 --port 8000 +``` + +浏览器打开 即可查看本仓库的 Markdown 教程。文档站会自动读取 `README.md`、`directory.md` 以及各章节的 `tutorial.md` / `readme.md`,并支持目录导航、搜索、图片显示和代码高亮。 + +## Codex Skills +本仓库在 `skills/` 下提供两个 Codex skill: + +- `skills/mujoco-teaching`:教学型。用于向 agent 提问 MuJoCo 概念、教程内容、学习路线和 API 原理。 +- `skills/mujoco-engineering`:工程型。用于让 agent 复现示例、开发 MJCF/Python/C++ MuJoCo 功能、查找对应教程代码路径。 + +发布到 GitHub 后,可用 Codex 的 `skill-installer` 从仓库路径安装: + +```bash +python scripts/install-skill-from-github.py --repo Albusgive/mujoco_learning --path skills/mujoco-teaching skills/mujoco-engineering +``` + +安装完成后重启 Codex。skill 内只使用仓库相对路径,不包含开发电脑的绝对路径。 + +如果 Codex 不在本仓库工作区内运行,skill 会先尝试自动寻找本仓库。首次找不到时,agent 会询问本机仓库路径,并保存到已安装 skill 目录下的本地配置文件: + +```text +/.local/repo_path.txt +``` + +`.local/repo_path.txt` 是普通文件路径,Python 脚本会用当前系统的路径规则读写,Windows、macOS、Linux 都可用。不同系统可按下面方式手动设置: + +
+macOS / Linux + +```bash +python ~/.codex/skills/mujoco-engineering/scripts/find_repo.py --set /path/to/mujoco_learning +python ~/.codex/skills/mujoco-teaching/scripts/find_repo.py --set /path/to/mujoco_learning +``` + +清除保存路径: + +```bash +python ~/.codex/skills/mujoco-engineering/scripts/find_repo.py --clear +python ~/.codex/skills/mujoco-teaching/scripts/find_repo.py --clear +``` + +
+ +
+Windows PowerShell + +```powershell +python $HOME\.codex\skills\mujoco-engineering\scripts\find_repo.py --set C:\path\to\mujoco_learning +python $HOME\.codex\skills\mujoco-teaching\scripts\find_repo.py --set C:\path\to\mujoco_learning +``` + +清除保存路径: + +```powershell +python $HOME\.codex\skills\mujoco-engineering\scripts\find_repo.py --clear +python $HOME\.codex\skills\mujoco-teaching\scripts\find_repo.py --clear +``` + +
+ +
+Windows CMD + +```bat +python %USERPROFILE%\.codex\skills\mujoco-engineering\scripts\find_repo.py --set C:\path\to\mujoco_learning +python %USERPROFILE%\.codex\skills\mujoco-teaching\scripts\find_repo.py --set C:\path\to\mujoco_learning +``` + +清除保存路径: + +```bat +python %USERPROFILE%\.codex\skills\mujoco-engineering\scripts\find_repo.py --clear +python %USERPROFILE%\.codex\skills\mujoco-teaching\scripts\find_repo.py --clear +``` + +
+ +如果保存的路径失效,agent 会重新询问并覆盖保存。 + +环境变量 `MUJOCO_LEARNING_ROOT` 仍可作为可选备用方案。不同系统设置方式如下: + +
+Windows PowerShell + +当前会话临时设置: + +```powershell +$env:MUJOCO_LEARNING_ROOT = "C:\path\to\mujoco_learning" +``` + +永久设置: + +```powershell +[Environment]::SetEnvironmentVariable("MUJOCO_LEARNING_ROOT", "C:\path\to\mujoco_learning", "User") +``` + +
+ +
+Windows CMD + +当前会话临时设置: + +```bat +set MUJOCO_LEARNING_ROOT=C:\path\to\mujoco_learning +``` + +永久设置: + +```bat +setx MUJOCO_LEARNING_ROOT "C:\path\to\mujoco_learning" +``` + +
+ +
+macOS / Linux + +```bash +export MUJOCO_LEARNING_ROOT=/path/to/mujoco_learning +``` + +
+ +两个 skill 也会自动尝试从当前工作区和常见克隆目录中寻找本仓库。 + ## 教程目录 🚀[教程目录(更新中)](directory.md) ## 视频教程 @@ -15,4 +223,3 @@ extend为拓展和进阶 ![cpp](MJCF/asset/封面-cpp.png) ## 技术交流 ![](MJCF/asset/mujoco交流群.jpg) - diff --git a/directory.md b/directory.md index c1cba7f..bfd3172 100644 --- a/directory.md +++ b/directory.md @@ -93,5 +93,7 @@ > - mujoco中碰撞模型,如何通过调整动态“弹簧-阻尼”模型让模型展现不同材料的碰撞效果,对于碰撞和穿模问题如何调整 > > [soft contact(mjcf&C++&Python)](extend/soft_contact/tutorial.md) > -> 雷达/深度相机(基于ray) -> - 通过mujoco中的ray实现雷达和深度相机传感器,后续推广到mjx \ No newline at end of file +> Ray Caster(基于ray) +> - 通过mujoco中的ray实现雷达、深度相机和自定义测距传感器 +> > [ray caster](extend/deep_camera/readme.md) +> > [独立仓库:Albusgive/mujoco_ray_caster](https://github.com/Albusgive/mujoco_ray_caster) diff --git a/extend/deep_camera/readme.md b/extend/deep_camera/readme.md index 7192359..339b6bc 100644 --- a/extend/deep_camera/readme.md +++ b/extend/deep_camera/readme.md @@ -1,4 +1,9 @@ -# Deep Camera -基于射线检测实现的深度测量,和mujoco自带测距传感器方式相同。 -## C++ +# Ray Caster + +基于 MuJoCo ray 实现的射线投射测距模块,可用于雷达、深度相机和自定义距离传感器。该方向已经独立整理到新仓库维护: + +[Albusgive/mujoco_ray_caster](https://github.com/Albusgive/mujoco_ray_caster) +本目录保留早期 C++ 示例和 XML 场景,后续 ray caster 相关更新请优先查看上面的独立仓库。 + +## C++ diff --git a/mujoco_learning_doc/__init__.py b/mujoco_learning_doc/__init__.py new file mode 100644 index 0000000..01ba5de --- /dev/null +++ b/mujoco_learning_doc/__init__.py @@ -0,0 +1 @@ +"""Local documentation web app for the MuJoCo tutorial repository.""" diff --git a/mujoco_learning_doc/main.py b/mujoco_learning_doc/main.py new file mode 100644 index 0000000..8e543c7 --- /dev/null +++ b/mujoco_learning_doc/main.py @@ -0,0 +1,325 @@ +from __future__ import annotations + +import argparse +import os +import re +import socket +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable +from urllib.parse import quote, unquote, urlsplit, urlunsplit + +import markdown +from fastapi import FastAPI, HTTPException, Request +from fastapi.responses import FileResponse, HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates + + +APP_DIR = Path(__file__).resolve().parent +REPO_ROOT = APP_DIR.parent +STATIC_DIR = APP_DIR / "static" +TEMPLATES_DIR = APP_DIR / "templates" + +DOC_FILENAMES = {"README.md", "readme.md", "tutorial.md", "directory.md"} +ROOT_DOCS = ("README.md", "directory.md") +NATURAL_SORT_RE = re.compile(r"(\d+)") +MARKDOWN_EXTENSIONS = [ + "extra", + "toc", + "tables", + "fenced_code", + "codehilite", + "pymdownx.arithmatex", + "sane_lists", +] +LINK_RE = re.compile(r"(!?\[[^\]]*?\]\()([^)]+)(\))") +HTML_LINK_RE = re.compile(r"""((?:src|href)=["'])([^"']+)(["'])""", re.IGNORECASE) + + +@dataclass(frozen=True) +class DocItem: + title: str + path: str + section: str + url: str + + +@dataclass(frozen=True) +class NavGroup: + key: str + title: str + items: list[DocItem] + is_open: bool + + +app = FastAPI(title="MuJoCo 教程文档站") +app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") +templates = Jinja2Templates(directory=TEMPLATES_DIR) + + +def port_is_available(host: str, port: int) -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind((host, port)) + except OSError: + return False + return True + + +def find_available_port(host: str, start_port: int, attempts: int = 20) -> int: + for port in range(start_port, start_port + attempts): + try: + if port_is_available(host, port): + return port + except PermissionError: + return start_port + raise RuntimeError(f"No available port found from {start_port} to {start_port + attempts - 1}.") + + +def run() -> None: + import uvicorn + + parser = argparse.ArgumentParser(description="Start the local MuJoCo tutorial docs site.") + parser.add_argument("--host", default=os.environ.get("MUJOCO_DOCS_HOST", "127.0.0.1")) + parser.add_argument( + "--port", + type=int, + default=int(os.environ.get("MUJOCO_DOCS_PORT", "8000")), + help="Preferred port. If it is busy, the next free port will be used.", + ) + parser.add_argument("--no-reload", action="store_true", help="Disable uvicorn reload mode.") + args = parser.parse_args() + + port = find_available_port(args.host, args.port) + if port != args.port: + print(f"Port {args.port} is in use, using {port} instead.") + print(f"Docs site: http://{args.host}:{port}") + uvicorn.run("mujoco_learning_doc.main:app", host=args.host, port=port, reload=not args.no_reload) + + +def safe_repo_path(relative_path: str) -> Path: + decoded = unquote(relative_path).strip("/") + candidate = (REPO_ROOT / decoded).resolve() + try: + candidate.relative_to(REPO_ROOT) + except ValueError as exc: + raise HTTPException(status_code=404, detail="File not found") from exc + return candidate + + +def web_path(path: str) -> str: + return quote(path.replace("\\", "/"), safe="/") + + +def title_from_markdown(path: Path) -> str: + try: + for line in path.read_text(encoding="utf-8").splitlines(): + stripped = line.strip() + if stripped.startswith("#"): + return stripped.lstrip("#").strip() or path.parent.name + except UnicodeDecodeError: + return path.stem + return path.parent.name if path.name.lower() in {"tutorial.md", "readme.md"} else path.stem + + +def section_for(path: Path) -> str: + relative = path.relative_to(REPO_ROOT) + if len(relative.parts) == 1: + return "首页" + if relative.parts[:2] == ("extend", "mujoco_red_stone"): + return "fun" + return relative.parts[0] + + +def section_title(section: str) -> str: + titles = { + "MJCF": "MJCF建模", + "Python": "Python", + "CPP": "CPP", + "extend": "拓展和进阶", + "fun": "娱乐", + "API-MJCF": "API-MJCF", + "utils": "工具", + "首页": "首页", + } + return titles.get(section, section) + + +def section_order(section: str) -> tuple[int, str]: + order = { + "MJCF": 0, + "Python": 1, + "CPP": 2, + "extend": 3, + "fun": 4, + "API-MJCF": 5, + "utils": 6, + "首页": 98, + } + return order.get(section, 50), section.lower() + + +def iter_markdown_docs() -> Iterable[Path]: + for root_doc in ROOT_DOCS: + path = REPO_ROOT / root_doc + if path.exists(): + yield path + + for path in sorted(REPO_ROOT.rglob("*.md"), key=natural_sort_key): + if any(part.startswith(".") for part in path.relative_to(REPO_ROOT).parts): + continue + if path.relative_to(REPO_ROOT).as_posix() in ROOT_DOCS: + continue + if path.name in DOC_FILENAMES or path.name.lower() in DOC_FILENAMES: + yield path + + +def natural_sort_key(path: Path) -> list[str | int]: + relative = path.relative_to(REPO_ROOT).as_posix().lower() + return [int(part) if part.isdigit() else part for part in NATURAL_SORT_RE.split(relative)] + + +def build_docs() -> list[DocItem]: + docs: list[DocItem] = [] + seen: set[str] = set() + for path in iter_markdown_docs(): + relative = path.relative_to(REPO_ROOT).as_posix() + if relative in seen: + continue + seen.add(relative) + docs.append( + DocItem( + title=title_from_markdown(path), + path=relative, + section=section_for(path), + url=f"/docs/{web_path(relative)}", + ) + ) + return docs + + +def grouped_docs(docs: list[DocItem], active_path: str) -> list[NavGroup]: + groups: dict[str, list[DocItem]] = {} + for doc in docs: + groups.setdefault(doc.section, []).append(doc) + + nav_groups: list[NavGroup] = [] + for section in sorted(groups, key=section_order): + if section == "首页": + continue + items = groups[section] + nav_groups.append( + NavGroup( + key=section, + title=section_title(section), + items=items, + is_open=any(item.path == active_path for item in items), + ) + ) + return nav_groups + + +def home_docs(docs: list[DocItem]) -> list[DocItem]: + return [doc for doc in docs if doc.section == "首页"] + + +def is_external_url(url: str) -> bool: + parsed = urlsplit(url) + return bool(parsed.scheme or parsed.netloc) or url.startswith(("#", "mailto:", "tel:", "data:")) + + +def resolve_target(base_doc: Path, raw_target: str) -> str: + target = raw_target.strip() + if is_external_url(target): + return target + + parsed = urlsplit(target) + if not parsed.path: + return target + + resolved = (base_doc.parent / unquote(parsed.path)).resolve() + try: + relative = resolved.relative_to(REPO_ROOT).as_posix() + except ValueError: + return target + + suffix = resolved.suffix.lower() + if suffix == ".md": + path = f"/docs/{web_path(relative)}" + else: + path = f"/files/{web_path(relative)}" + + return urlunsplit(("", "", path, parsed.query, parsed.fragment)) + + +def rewrite_markdown_links(markdown_text: str, base_doc: Path) -> str: + def replace_markdown_link(match: re.Match[str]) -> str: + prefix, target, suffix = match.groups() + return f"{prefix}{resolve_target(base_doc, target)}{suffix}" + + return LINK_RE.sub(replace_markdown_link, markdown_text) + + +def rewrite_html_links(html: str, base_doc: Path) -> str: + def replace_html_link(match: re.Match[str]) -> str: + prefix, target, suffix = match.groups() + return f"{prefix}{resolve_target(base_doc, target)}{suffix}" + + return HTML_LINK_RE.sub(replace_html_link, html) + + +def render_markdown(path: Path) -> str: + source = path.read_text(encoding="utf-8") + source = rewrite_markdown_links(source, path) + html = markdown.markdown( + source, + extensions=MARKDOWN_EXTENSIONS, + extension_configs={ + "codehilite": {"guess_lang": False}, + "pymdownx.arithmatex": {"generic": True}, + }, + output_format="html5", + ) + return rewrite_html_links(html, path) + + +def render_doc(request: Request, relative_path: str) -> HTMLResponse: + path = safe_repo_path(relative_path) + if not path.exists() or not path.is_file() or path.suffix.lower() != ".md": + raise HTTPException(status_code=404, detail="Document not found") + + docs = build_docs() + active_path = path.relative_to(REPO_ROOT).as_posix() + return templates.TemplateResponse( + request, + "doc.html", + context={ + "request": request, + "title": title_from_markdown(path), + "content": render_markdown(path), + "docs": docs, + "home_docs": home_docs(docs), + "groups": grouped_docs(docs, active_path), + "active_path": active_path, + }, + ) + + +@app.get("/", response_class=HTMLResponse) +async def index(request: Request) -> HTMLResponse: + return render_doc(request, "README.md") + + +@app.get("/docs/{relative_path:path}", response_class=HTMLResponse) +async def docs(request: Request, relative_path: str) -> HTMLResponse: + return render_doc(request, relative_path) + + +@app.get("/files/{relative_path:path}") +async def files(relative_path: str) -> FileResponse: + path = safe_repo_path(relative_path) + if not path.exists() or not path.is_file(): + raise HTTPException(status_code=404, detail="File not found") + return FileResponse(path) diff --git a/mujoco_learning_doc/static/app.js b/mujoco_learning_doc/static/app.js new file mode 100644 index 0000000..32b8aff --- /dev/null +++ b/mujoco_learning_doc/static/app.js @@ -0,0 +1,99 @@ +const searchInput = document.querySelector("#doc-search"); +const sidebar = document.querySelector(".sidebar"); +const links = Array.from(document.querySelectorAll(".doc-link")); +const groups = Array.from(document.querySelectorAll(".doc-group")); +const initialOpenGroups = new WeakMap(); +const sidebarScrollKey = "mujoco-learning-doc-sidebar-scroll"; +const groupStateKey = "mujoco-learning-doc-group-state"; + +function loadGroupState() { + try { + return JSON.parse(sessionStorage.getItem(groupStateKey) || "{}"); + } catch { + return {}; + } +} + +function saveGroupState() { + const state = {}; + groups.forEach((group) => { + const title = group.querySelector("summary span")?.textContent?.trim(); + if (title) { + state[title] = group.open; + } + }); + sessionStorage.setItem(groupStateKey, JSON.stringify(state)); +} + +function saveSidebarScroll() { + if (sidebar) { + sessionStorage.setItem(sidebarScrollKey, String(sidebar.scrollTop)); + } +} + +function restoreSidebarScroll() { + if (!sidebar) { + return; + } + const saved = Number(sessionStorage.getItem(sidebarScrollKey)); + if (Number.isFinite(saved)) { + sidebar.scrollTop = saved; + requestAnimationFrame(() => { + sidebar.scrollTop = saved; + }); + } +} + +const savedGroupState = loadGroupState(); +groups.forEach((group) => { + const title = group.querySelector("summary span")?.textContent?.trim(); + if (title && Object.prototype.hasOwnProperty.call(savedGroupState, title)) { + group.open = savedGroupState[title]; + } + initialOpenGroups.set(group, group.open); + group.addEventListener("toggle", () => { + saveGroupState(); + saveSidebarScroll(); + }); +}); + +if (sidebar) { + sidebar.addEventListener("scroll", saveSidebarScroll, { passive: true }); + restoreSidebarScroll(); +} + +links.forEach((link) => { + link.addEventListener("click", () => { + saveGroupState(); + saveSidebarScroll(); + }); +}); + +if (searchInput) { + searchInput.addEventListener("input", () => { + const query = searchInput.value.trim().toLowerCase(); + + links.forEach((link) => { + const haystack = `${link.dataset.title || ""} ${link.dataset.path || ""}`; + link.hidden = query.length > 0 && !haystack.includes(query); + }); + + groups.forEach((group) => { + const visibleLinks = Array.from(group.querySelectorAll(".doc-link")).some( + (link) => !link.hidden, + ); + group.hidden = !visibleLinks; + if (query.length > 0 && visibleLinks) { + group.open = true; + } + if (query.length === 0) { + group.open = initialOpenGroups.get(group); + } + }); + }); +} + +window.addEventListener("beforeunload", () => { + saveGroupState(); + saveSidebarScroll(); +}); diff --git a/mujoco_learning_doc/static/style.css b/mujoco_learning_doc/static/style.css new file mode 100644 index 0000000..89f9b96 --- /dev/null +++ b/mujoco_learning_doc/static/style.css @@ -0,0 +1,363 @@ +:root { + color-scheme: light; + --bg: #f6f7f9; + --panel: #ffffff; + --text: #1d2430; + --muted: #697386; + --line: #d8dee8; + --accent: #0f766e; + --accent-soft: #e4f4f1; + --code-bg: #101820; + --code-text: #edf5f7; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + "Microsoft YaHei", sans-serif; +} + +a { + color: var(--accent); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.app-shell { + display: grid; + grid-template-columns: 320px minmax(0, 1fr); + min-height: 100vh; +} + +.sidebar { + position: sticky; + top: 0; + height: 100vh; + overflow-y: auto; + border-right: 1px solid var(--line); + background: var(--panel); + padding: 20px 16px; +} + +.brand { + display: flex; + align-items: center; + gap: 12px; + color: var(--text); + margin-bottom: 20px; +} + +.brand:hover { + text-decoration: none; +} + +.brand-mark { + display: grid; + width: 42px; + height: 42px; + place-items: center; + border-radius: 8px; + background: var(--accent); + color: #fff; + font-weight: 750; +} + +.brand strong, +.brand small { + display: block; +} + +.brand small { + margin-top: 2px; + color: var(--muted); +} + +.search { + display: block; + margin-bottom: 18px; +} + +.search span { + display: block; + margin-bottom: 7px; + color: var(--muted); + font-size: 13px; +} + +.search input { + width: 100%; + min-height: 38px; + border: 1px solid var(--line); + border-radius: 8px; + padding: 8px 10px; + color: var(--text); + background: #fff; + font: inherit; +} + +.doc-home { + display: grid; + gap: 4px; + border-bottom: 1px solid var(--line); + margin-bottom: 14px; + padding-bottom: 14px; +} + +.doc-link-home span { + font-size: 15px; +} + +.doc-group { + margin: 0 0 10px; + border: 1px solid transparent; + border-radius: 8px; +} + +.doc-group[open] { + border-color: #edf1f5; + background: #fbfcfd; +} + +.doc-group summary { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 42px; + border-radius: 8px; + padding: 9px 10px; + color: var(--text); + cursor: pointer; + list-style: none; + user-select: none; +} + +.doc-group summary::-webkit-details-marker { + display: none; +} + +.doc-group summary::before { + content: "›"; + display: inline-grid; + width: 18px; + height: 18px; + margin-right: 7px; + place-items: center; + color: var(--muted); + font-size: 20px; + line-height: 1; + transform: rotate(0deg); + transition: transform 0.16s ease; +} + +.doc-group[open] summary::before { + transform: rotate(90deg); +} + +.doc-group summary:hover { + background: #eef2f6; +} + +.doc-group summary span { + flex: 1; + font-size: 17px; + font-weight: 760; + overflow-wrap: anywhere; +} + +.doc-group summary small { + min-width: 26px; + border-radius: 999px; + background: #edf1f5; + color: var(--muted); + padding: 2px 7px; + text-align: center; + font-size: 12px; +} + +.doc-group-items { + display: grid; + gap: 2px; + padding: 2px 6px 8px 20px; +} + +.doc-link { + display: block; + padding: 9px 10px; + border-radius: 8px; + color: var(--text); +} + +.doc-link:hover { + background: #eef2f6; + text-decoration: none; +} + +.doc-link.is-active { + background: var(--accent-soft); + color: #075e56; +} + +.doc-link span, +.doc-link small { + display: block; + overflow-wrap: anywhere; +} + +.doc-link span { + font-size: 14px; + font-weight: 650; +} + +.doc-link small { + margin-top: 3px; + color: var(--muted); + font-size: 11px; + line-height: 1.35; +} + +.content { + min-width: 0; + padding: 34px clamp(20px, 4vw, 56px); +} + +.markdown-body { + max-width: 980px; + margin: 0 auto; + line-height: 1.75; + font-size: 16px; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4 { + line-height: 1.3; + scroll-margin-top: 18px; +} + +.markdown-body h1 { + margin: 0 0 24px; + font-size: 36px; +} + +.markdown-body h2 { + margin-top: 34px; + padding-bottom: 6px; + border-bottom: 1px solid var(--line); + font-size: 25px; +} + +.markdown-body h3 { + margin-top: 26px; + font-size: 20px; +} + +.markdown-body img { + max-width: 100%; + height: auto; + border-radius: 8px; + border: 1px solid var(--line); + background: #fff; +} + +.markdown-body table { + display: block; + width: 100%; + overflow-x: auto; + border-collapse: collapse; +} + +.markdown-body th, +.markdown-body td { + border: 1px solid var(--line); + padding: 8px 10px; + text-align: left; + vertical-align: top; +} + +.markdown-body th { + background: #edf1f5; +} + +.markdown-body code { + border-radius: 5px; + background: #e8edf2; + padding: 2px 5px; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; + font-size: 0.92em; +} + +.markdown-body pre { + overflow-x: auto; + border-radius: 8px; + background: var(--code-bg); + padding: 16px; +} + +.markdown-body pre code, +.markdown-body .codehilite code { + background: transparent; + color: var(--code-text); + padding: 0; +} + +.codehilite { + overflow-x: auto; + border-radius: 8px; + background: var(--code-bg); +} + +.codehilite pre { + margin: 0; +} + +.markdown-body blockquote { + margin: 18px 0; + border-left: 4px solid var(--accent); + padding: 6px 0 6px 16px; + color: #344052; + background: #eef7f5; +} + +.markdown-body mjx-container[jax="CHTML"][display="true"] { + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; + padding: 8px 0; +} + +.markdown-body mjx-container { + line-height: 1.35; +} + +@media (max-width: 860px) { + .app-shell { + display: block; + } + + .sidebar { + position: static; + height: auto; + max-height: 52vh; + border-right: 0; + border-bottom: 1px solid var(--line); + } + + .content { + padding: 24px 16px; + } + + .markdown-body h1 { + font-size: 30px; + } +} diff --git a/mujoco_learning_doc/templates/doc.html b/mujoco_learning_doc/templates/doc.html new file mode 100644 index 0000000..b1b9cba --- /dev/null +++ b/mujoco_learning_doc/templates/doc.html @@ -0,0 +1,88 @@ + + + + + + {{ title }} - MuJoCo 教程 + + + +
+ + +
+
+ {{ content | safe }} +
+
+
+ + + + + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f0a17d9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "mujoco-learning-docs" +version = "0.1.0" +description = "Local FastAPI documentation site and Python MuJoCo tutorial environment." +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "fastapi>=0.111", + "uvicorn[standard]>=0.30", + "jinja2>=3.1", + "markdown>=3.6", + "pymdown-extensions>=10.8", + "pygments>=2.18", + "mujoco>=3.2", +] + +[project.optional-dependencies] +dev = [ + "httpx>=0.27", +] + +[project.scripts] +mujoco_learning_doc = "mujoco_learning_doc.main:run" + +[tool.setuptools] +packages = ["mujoco_learning_doc"] +include-package-data = true + +[tool.setuptools.package-data] +mujoco_learning_doc = ["templates/*.html", "static/*.css", "static/*.js"] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0a71b35 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup + + +DEPENDENCIES = [ + "fastapi>=0.111", + "uvicorn[standard]>=0.30", + "jinja2>=3.1", + "markdown>=3.6", + "pymdown-extensions>=10.8", + "pygments>=2.18", + "mujoco>=3.2", +] + + +setup( + name="mujoco-learning-docs", + version="0.1.0", + description="Local FastAPI documentation site and Python MuJoCo tutorial environment.", + packages=["mujoco_learning_doc"], + include_package_data=True, + package_data={"mujoco_learning_doc": ["templates/*.html", "static/*.css", "static/*.js"]}, + python_requires=">=3.10", + install_requires=DEPENDENCIES, + extras_require={"dev": ["httpx>=0.27"]}, + entry_points={"console_scripts": ["mujoco_learning_doc=mujoco_learning_doc.main:run"]}, +) diff --git a/skills/mujoco-engineering/SKILL.md b/skills/mujoco-engineering/SKILL.md new file mode 100644 index 0000000..1b4be43 --- /dev/null +++ b/skills/mujoco-engineering/SKILL.md @@ -0,0 +1,52 @@ +--- +name: mujoco-engineering +description: Use when an agent needs to implement, reproduce, debug, or extend MuJoCo projects using this repository's MJCF, Python, C++, ray casting, soft contact, sensors, rendering, viewer, force, or build examples. Use it to locate relevant tutorial code paths before writing or modifying MuJoCo code. +--- + +# MuJoCo Engineering + +Use this skill for engineering work: reproduce examples, build MuJoCo scenes, write Python/C++ API code, debug MJCF, add sensors, implement ray casting, tune contact, or adapt this tutorial repository into working code. + +## Repository Path Policy + +Never hard-code a developer's local absolute path. Resolve the repository dynamically: + +- Prefer the current workspace if it contains `directory.md`, `MJCF/`, `Python/`, and `CPP/`. +- If this skill lives under `skills/mujoco-engineering` inside a clone, use the parent repository root. +- If installed globally, run `scripts/find_repo.py` from this skill. It checks the skill-local saved path, current workspace, `MUJOCO_LEARNING_ROOT`, and common clone directory names. +- On first use, if `scripts/find_repo.py` cannot find the repository, ask the user for the local clone path and save it with `scripts/find_repo.py --set PATH`. +- If the saved path becomes invalid or unavailable, ask again and rerun `scripts/find_repo.py --set PATH`. +- If no local clone is available, use the clone URL before referencing local files. + +The saved path lives in `.local/repo_path.txt` under the installed skill directory. This is a cross-platform local config file; do not edit `SKILL.md` to store user paths. + +All reference paths below are repository-relative and safe to show in public docs. + +## Engineering Workflow + +1. Classify the task: MJCF authoring, Python API, C++ API, build/config, sensor/rendering, ray casting, contact/solver tuning, or extension. +2. Read `references/tutorial-map.md` and select the closest tutorial and code example. +3. Inspect the referenced source files before implementing. +4. Reuse repository patterns and asset paths rather than inventing new structure. +5. If engineering work repeatedly fails because of parameter uncertainty, unstable behavior, or unclear validation results, selectively build a minimal test demo from the repository's test models, XML parameters, and chapter examples to isolate the issue. +6. Choose validation based on the agent/model capability: + - If the agent can understand images or video, it may render screenshots or record a short demo from the simulation, then compare the demo result with the expected behavior and with the real development output. It can also read numeric state directly from simulation when that is more precise. + - If the agent is text-only, it must validate through custom simulation outputs: print, log, or assert positions, velocities, contacts, sensor values, forces, ray distances, rendered buffer metadata, or other task-specific data. +7. Validate with the smallest relevant command first: + - Python examples: run the chapter script in the target Python/conda environment. + - C++ examples: inspect `CMakeLists.txt`, then build in that chapter's build directory. + - Docs site changes: run `python -m compileall mujoco_learning_doc` and verify affected routes. +8. If a MuJoCo API detail is uncertain, consult the official docs after checking the tutorial. + +## Implementation Guidance + +- Keep MJCF paths relative to the scene XML or repository root, matching local examples. +- Separate model-side configuration from runtime code: MJCF defines bodies/geoms/joints/sensors/actuators; Python/C++ loads the model, creates data, steps, reads/writes arrays, and renders. +- For ray or ray caster work, start from `Python/Chapter7-ray/`, `CPP/Chapter8-ray/`, and `extend/deep_camera/`; for maintained ray caster code, use `https://github.com/Albusgive/mujoco_ray_caster`. +- For soft contact, start from `extend/soft_contact/tutorial.md` and its XML/Python/C++ examples before tuning `solimp`, `solref`, stiffness, damping, or contact dimensions. +- For build problems, prefer the local chapter `CMakeLists.txt` patterns and then official MuJoCo build documentation. +- Prefer measurable validation over visual inspection when exact behavior matters; use visual screenshots/video as an optional diagnostic aid, not the only proof, unless the task is specifically visual. + +## Reference Map + +Read `references/tutorial-map.md` for topic-to-path routing and example files. diff --git a/skills/mujoco-engineering/agents/openai.yaml b/skills/mujoco-engineering/agents/openai.yaml new file mode 100644 index 0000000..7d17d65 --- /dev/null +++ b/skills/mujoco-engineering/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "MuJoCo Engineering" + short_description: "Build MuJoCo scenes and API code from examples" + brand_color: "#0F766E" + default_prompt: "Use $mujoco-engineering to implement a MuJoCo sensor example from the repository patterns." +policy: + allow_implicit_invocation: true diff --git a/skills/mujoco-engineering/references/tutorial-map.md b/skills/mujoco-engineering/references/tutorial-map.md new file mode 100644 index 0000000..ed4f554 --- /dev/null +++ b/skills/mujoco-engineering/references/tutorial-map.md @@ -0,0 +1,82 @@ +# MuJoCo Engineering Tutorial Map + +Use repository-relative paths only. Inspect referenced files before coding. + +## Project Setup And Installation + +- Editable Python package and docs site: `README.md`, `pyproject.toml`, `setup.py`, `mujoco_learning_doc/` +- MuJoCo install/source build: `MJCF/Chapter0-install/tutorial.md` +- C++ build patterns: `CPP/Chapter1-make/CMakeLists.txt`, `CPP/Chapter1-make/tutorial.md` + +## MJCF Authoring Targets + +- Base world/options/assets/materials: `MJCF/Chapter2-virtual_world/tutorial.md` +- Body tree, geoms, sites, meshes, coordinate frames: `MJCF/Chapter3-worldbody/tutorial.md` +- Joints: `MJCF/Chapter4-joint/tutorial.md` +- Friction/contact parameters: `MJCF/Chapter5-friction/tutorial.md` +- Actuators: `MJCF/Chapter6-actuator/tutorial.md` +- Light/camera/replicate: `MJCF/Chapter7-light&replicate/tutorial.md` +- Tendons: `MJCF/Chapter8-tendon/tutorial.md` +- Sensors: `MJCF/Chapter9-sensor/tutorial.md` +- CAD import scripts/data: `MJCF/Chapter10-from_CAD_software/` +- Equality constraints: `MJCF/Chapter11-equality/tutorial.md`, `extend/equality/` +- Defaults/classes: `MJCF/Chapter12-default/tutorial.md` +- Composite/flex deformables: `MJCF/Chapter13-composite/tutorial.md`, `MJCF/Chapter14-flex/tutorial.md` +- Keyframes: `MJCF/Chapter15-keyframe/tutorial.md` + +## Python API Examples + +- Viewer/stepping: `Python/Chapter1-view&step/view.py` +- Object IDs and entity lookup: `Python/Chapter2-get_obj/get_obj.py` +- Sensor reads: `Python/Chapter3-sensor_data/sensor_data.py` +- Drawing/debug visualization: `Python/Chapter4-draw/draw.py` +- Force terms: `Python/Chapter5-force/force.py` +- Rendering configuration/segmentation/stereo: `Python/Chapter6-vis_cfg/vis_cfg.py` +- Ray queries: `Python/Chapter7-ray/ray.py` + +## C++ API Examples + +- Viewer/stepping: `CPP/Chapter2-view&step/basic.cc` +- Object IDs and entity lookup: `CPP/Chapter3-get_obj/get_obj.cc` +- Sensor reads: `CPP/Chapter4-sensor_data/sensor_data.cc` +- Drawing/debug visualization: `CPP/Chapter5-draw/draw.cpp` +- Force terms: `CPP/Chapter6-force/force.cpp` +- Rendering configuration: `CPP/Chapter7-vis_cfg/vis_cfg.cpp` +- Ray queries: `CPP/Chapter8-ray/ray.cpp` +- Shared helper: `utils/mujoco_thread/` + +## Extension Examples + +- Touch sensing: `extend/touch/readme.md`, `extend/touch/C++/` +- Soft contact: `extend/soft_contact/tutorial.md`, `extend/soft_contact/soft_contact.py`, `extend/soft_contact/C++/soft_contact.cpp`, related XML files in `extend/soft_contact/` +- Ray caster / depth/range sensing: `extend/deep_camera/readme.md`, `extend/deep_camera/C++/RayCasterCamera.hpp`, external repo `https://github.com/Albusgive/mujoco_ray_caster` +- MJX/JAX experiments: `extend/jax/` +- Entertainment/redstone demo: `extend/mujoco_red_stone/` + +## Validation Commands + +- Python syntax for docs web: `conda run -n mj python -m compileall mujoco_learning_doc` +- Install package in target environment: `python -m pip install -e .` +- Start docs site: `mujoco_learning_doc --port 8000` +- For C++ chapters, follow each chapter's `CMakeLists.txt`; avoid reusing generated `build/`, `b/`, or cache artifacts as source. + +## Minimal Demo And Validation Assets + +Use these repository-relative assets selectively when parameter tuning, reproduction, or validation keeps failing and a smaller isolated demo would clarify the issue: + +- General XML examples: `API-MJCF/`, especially `mecanum.xml`, `force.xml`, `vis_cfg.xml`, `deep_ray.xml` +- MJCF chapter scene files: `MJCF/Chapter*/` +- Python chapter scripts: `Python/Chapter*/` +- C++ chapter examples and `CMakeLists.txt`: `CPP/Chapter*/` +- Soft contact XML/Python/C++ examples: `extend/soft_contact/` +- Ray caster examples: `extend/deep_camera/`, `Python/Chapter7-ray/`, `CPP/Chapter8-ray/` +- Touch/equality/JAX extension examples: `extend/touch/`, `extend/equality/`, `extend/jax/` + +For multimodal agents, a diagnostic demo may include screenshots or video captured from the viewer/render pipeline and compared with the expected visual effect. For text-only agents, produce deterministic textual outputs from simulation state, such as sensor arrays, contact counts, `qpos`, `qvel`, forces, ray hit distances, or rendered image shape/statistics. + +## Official Docs Fallback + +Use official MuJoCo docs when local examples do not answer an API or build detail: + +- Programming/source build: `https://mujoco.readthedocs.io/en/latest/programming/#building-mujoco-from-source` +- Use the XML/API/reference sections on the same site for exact attribute and function semantics. diff --git a/skills/mujoco-engineering/scripts/find_repo.py b/skills/mujoco-engineering/scripts/find_repo.py new file mode 100644 index 0000000..b33f200 --- /dev/null +++ b/skills/mujoco-engineering/scripts/find_repo.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import os +from pathlib import Path + + +REQUIRED = ("directory.md", "MJCF", "Python", "CPP") +SKILL_ROOT = Path(__file__).resolve().parents[1] +LOCAL_CONFIG = SKILL_ROOT / ".local" / "repo_path.txt" + + +def is_repo_root(path: Path) -> bool: + return all((path / item).exists() for item in REQUIRED) + + +def ancestors(path: Path): + current = path.resolve() + yield current + yield from current.parents + + +def saved_repo_path() -> Path | None: + if not LOCAL_CONFIG.exists(): + return None + value = LOCAL_CONFIG.read_text(encoding="utf-8").strip() + if not value: + return None + return Path(value).expanduser() + + +def candidates() -> list[Path]: + items: list[Path] = [] + saved_root = saved_repo_path() + if saved_root: + items.append(saved_root) + + env_root = os.environ.get("MUJOCO_LEARNING_ROOT") + if env_root: + items.append(Path(env_root).expanduser()) + + for base in ancestors(Path.cwd()): + items.append(base) + + script_path = Path(__file__).resolve() + items.extend(script_path.parents) + + home = Path.home() + items.extend( + [ + home / "mujoco_learning", + home / "mujoco-learning", + home / "code" / "mujoco_learning", + home / "projects" / "mujoco_learning", + home / "workspace" / "mujoco_learning", + ] + ) + return items + + +def save_repo_path(path: Path) -> int: + root = path.expanduser().resolve() + if not is_repo_root(root): + print(f"Invalid MuJoCo tutorial repository path: {root}") + print("Expected directory.md plus MJCF/, Python/, and CPP/ under that path.") + return 1 + LOCAL_CONFIG.parent.mkdir(parents=True, exist_ok=True) + LOCAL_CONFIG.write_text(str(root), encoding="utf-8") + print(root) + return 0 + + +def clear_repo_path() -> int: + if LOCAL_CONFIG.exists(): + LOCAL_CONFIG.unlink() + print("Cleared saved MuJoCo tutorial repository path.") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Find or configure the local MuJoCo tutorial repository.") + parser.add_argument("--set", dest="set_path", help="Save the local MuJoCo tutorial repository path.") + parser.add_argument("--clear", action="store_true", help="Clear the saved repository path.") + args = parser.parse_args() + + if args.clear: + return clear_repo_path() + if args.set_path: + return save_repo_path(Path(args.set_path)) + + for item in candidates(): + if is_repo_root(item): + print(item.resolve()) + return 0 + + if LOCAL_CONFIG.exists(): + print("Saved MuJoCo tutorial repository path is invalid or unavailable. Ask the user for the new clone path and run this script with --set PATH.") + return 1 + + print( + "MuJoCo tutorial repository not found. Ask the user for the local clone path and run this script with --set PATH.", + end="", + ) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/mujoco-teaching/SKILL.md b/skills/mujoco-teaching/SKILL.md new file mode 100644 index 0000000..7ba9b63 --- /dev/null +++ b/skills/mujoco-teaching/SKILL.md @@ -0,0 +1,50 @@ +--- +name: mujoco-teaching +description: Use when a user wants to learn MuJoCo concepts, MJCF modeling, Python/C++ MuJoCo APIs, sensors, rendering, contact, soft contact, ray casting, or asks conceptual/tutorial questions about this repository's MuJoCo lessons. Prefer the repository tutorials first, then consult official MuJoCo documentation if the tutorial is incomplete. +--- + +# MuJoCo Teaching + +Use this skill for human learning: explain MuJoCo concepts, answer tutorial questions, compare API choices, or guide a learner through MJCF/Python/C++ examples. + +## Source Priority + +1. Use this repository's tutorial material first. +2. If the repository does not fully answer the question, consult official MuJoCo docs, especially: + `https://mujoco.readthedocs.io/en/latest/programming/#building-mujoco-from-source` +3. Clearly say when an explanation comes from the local tutorial, the official docs, or your own inference. + +## Locate The Tutorial Repository + +Do not assume an absolute path. Find the repository root dynamically: + +- If the current workspace contains `directory.md` and folders `MJCF/`, `Python/`, `CPP/`, use the current workspace root. +- If this skill is installed inside the repository under `skills/mujoco-teaching`, the repository root is two directories above the skill folder. +- If installed globally, run `scripts/find_repo.py` from this skill. It checks the skill-local saved path, current workspace, `MUJOCO_LEARNING_ROOT`, and common clone directory names. +- On first use, if `scripts/find_repo.py` cannot find the repository, ask the user for the local clone path and save it with `scripts/find_repo.py --set PATH`. +- If the saved path becomes invalid or unavailable, ask again and rerun `scripts/find_repo.py --set PATH`. +- If no local clone is available, use the public repository URL the user provides. + +The saved path lives in `.local/repo_path.txt` under the installed skill directory. This is a cross-platform local config file; do not edit `SKILL.md` to store user paths. + +All paths in this skill are repository-relative. + +## Workflow + +1. Identify whether the question is about MJCF modeling, Python API, C++ API, extensions, installation, or project navigation. +2. Read `references/tutorial-map.md` to choose the most relevant tutorial path. +3. Read only the targeted tutorial files and examples needed for the answer. +4. Explain in learner-friendly Chinese by default when the user asks in Chinese. +5. Include small code/XML snippets only when they clarify the concept. +6. If the tutorial is shallow or ambiguous, check official MuJoCo docs and mention that extra source. + +## Teaching Style + +- Start with the concept, then map it to the repository's example path. +- Prefer "why this works" over just listing API calls. +- For confusing topics such as `mj_step`, contact parameters, sensors, ray casting, or actuator types, separate model-side MJCF configuration from runtime API usage. +- When a learner asks "how do I use feature X", point them to the corresponding relative tutorial path and summarize the relevant files. + +## Reference Map + +Read `references/tutorial-map.md` for topic-to-path routing. diff --git a/skills/mujoco-teaching/agents/openai.yaml b/skills/mujoco-teaching/agents/openai.yaml new file mode 100644 index 0000000..f7ecbfb --- /dev/null +++ b/skills/mujoco-teaching/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "MuJoCo Teaching" + short_description: "Explain MuJoCo using this tutorial repo" + brand_color: "#0F766E" + default_prompt: "Use $mujoco-teaching to explain MuJoCo sensors from the local tutorial and official docs." +policy: + allow_implicit_invocation: true diff --git a/skills/mujoco-teaching/references/tutorial-map.md b/skills/mujoco-teaching/references/tutorial-map.md new file mode 100644 index 0000000..445dce2 --- /dev/null +++ b/skills/mujoco-teaching/references/tutorial-map.md @@ -0,0 +1,63 @@ +# MuJoCo Tutorial Map + +Use repository-relative paths only. Do not invent absolute paths. + +## Core Entry Points + +- `README.md`: project overview, local docs site, installation commands. +- `directory.md`: human-readable tutorial table of contents. +- `MJCF/Chapter0-install/tutorial.md`: installing MuJoCo from source/release/Python. + +## MJCF Modeling Lessons + +- World and visualization setup: `MJCF/Chapter2-virtual_world/tutorial.md` +- Bodies, geoms, sites, coordinate tree: `MJCF/Chapter3-worldbody/tutorial.md` +- Joints and joint parameters: `MJCF/Chapter4-joint/tutorial.md` +- Friction and contact dimensions: `MJCF/Chapter5-friction/tutorial.md` +- Actuators and control types: `MJCF/Chapter6-actuator/tutorial.md` +- Lights, cameras, replicate, ray demos: `MJCF/Chapter7-light&replicate/tutorial.md` +- Tendons and muscle-like actuation: `MJCF/Chapter8-tendon/tutorial.md` +- Sensors, camera, IMU, velocity, angles: `MJCF/Chapter9-sensor/tutorial.md` +- CAD model import workflow: `MJCF/Chapter10-from_CAD_software/tutorial.md` +- Equality constraints and parallel mechanisms: `MJCF/Chapter11-equality/tutorial.md` +- Defaults/classes/inheritance: `MJCF/Chapter12-default/tutorial.md` +- Old composite deformables: `MJCF/Chapter13-composite/tutorial.md` +- New flex deformables: `MJCF/Chapter14-flex/tutorial.md` +- Keyframes: `MJCF/Chapter15-keyframe/tutorial.md` + +## Python API Lessons + +- Viewer, model/data, stepping: `Python/Chapter1-view&step/tutorial.md`; code: `Python/Chapter1-view&step/view.py` +- Object/entity lookup: `Python/Chapter2-get_obj/tutorial.md`; code: `Python/Chapter2-get_obj/get_obj.py` +- Sensor data access: `Python/Chapter3-sensor_data/tutorial.md`; code: `Python/Chapter3-sensor_data/sensor_data.py` +- 2D/3D drawing: `Python/Chapter4-draw/tutorial.md`; code: `Python/Chapter4-draw/draw.py` +- Force terms and validation: `Python/Chapter5-force/tutorial.md`; code: `Python/Chapter5-force/force.py` +- Rendering configuration, segmentation, stereo: `Python/Chapter6-vis_cfg/tutorial.md`; code: `Python/Chapter6-vis_cfg/vis_cfg.py` +- Ray distance queries: `Python/Chapter7-ray/tutorial.md`; code: `Python/Chapter7-ray/ray.py` + +## C++ API Lessons + +- Build and CMake: `CPP/Chapter1-make/tutorial.md`; code: `CPP/Chapter1-make/` +- Viewer and stepping: `CPP/Chapter2-view&step/tutorial.md`; code: `CPP/Chapter2-view&step/basic.cc` +- Object/entity lookup: `CPP/Chapter3-get_obj/tutorial.md`; code: `CPP/Chapter3-get_obj/get_obj.cc` +- Sensor data access: `CPP/Chapter4-sensor_data/tutorial.md`; code: `CPP/Chapter4-sensor_data/sensor_data.cc` +- 2D/3D drawing: `CPP/Chapter5-draw/tutorial.md`; code: `CPP/Chapter5-draw/draw.cpp` +- Force terms and validation: `CPP/Chapter6-force/tutorial.md`; code: `CPP/Chapter6-force/force.cpp` +- Rendering configuration: `CPP/Chapter7-vis_cfg/tutorial.md`; code: `CPP/Chapter7-vis_cfg/vis_cfg.cpp` +- Ray distance queries: `CPP/Chapter8-ray/tutorial.md`; code: `CPP/Chapter8-ray/ray.cpp` + +## Extensions And Special Topics + +- Touch sensing: `extend/touch/readme.md` +- Soft contact and solver parameter intuition: `extend/soft_contact/tutorial.md` +- Ray caster / depth/range sensing: `extend/deep_camera/readme.md`; external repo: `https://github.com/Albusgive/mujoco_ray_caster` +- Equality experiments: `extend/equality/` +- JAX/MJX examples: `extend/jax/` +- Entertainment/redstone demo: `extend/mujoco_red_stone/README.md` + +## Official Docs Fallback + +Use official MuJoCo docs when local material is incomplete: + +- Programming guide and source build: `https://mujoco.readthedocs.io/en/latest/programming/#building-mujoco-from-source` +- XML reference, API reference, and computation chapters from the same documentation site as needed. diff --git a/skills/mujoco-teaching/scripts/find_repo.py b/skills/mujoco-teaching/scripts/find_repo.py new file mode 100644 index 0000000..b33f200 --- /dev/null +++ b/skills/mujoco-teaching/scripts/find_repo.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import os +from pathlib import Path + + +REQUIRED = ("directory.md", "MJCF", "Python", "CPP") +SKILL_ROOT = Path(__file__).resolve().parents[1] +LOCAL_CONFIG = SKILL_ROOT / ".local" / "repo_path.txt" + + +def is_repo_root(path: Path) -> bool: + return all((path / item).exists() for item in REQUIRED) + + +def ancestors(path: Path): + current = path.resolve() + yield current + yield from current.parents + + +def saved_repo_path() -> Path | None: + if not LOCAL_CONFIG.exists(): + return None + value = LOCAL_CONFIG.read_text(encoding="utf-8").strip() + if not value: + return None + return Path(value).expanduser() + + +def candidates() -> list[Path]: + items: list[Path] = [] + saved_root = saved_repo_path() + if saved_root: + items.append(saved_root) + + env_root = os.environ.get("MUJOCO_LEARNING_ROOT") + if env_root: + items.append(Path(env_root).expanduser()) + + for base in ancestors(Path.cwd()): + items.append(base) + + script_path = Path(__file__).resolve() + items.extend(script_path.parents) + + home = Path.home() + items.extend( + [ + home / "mujoco_learning", + home / "mujoco-learning", + home / "code" / "mujoco_learning", + home / "projects" / "mujoco_learning", + home / "workspace" / "mujoco_learning", + ] + ) + return items + + +def save_repo_path(path: Path) -> int: + root = path.expanduser().resolve() + if not is_repo_root(root): + print(f"Invalid MuJoCo tutorial repository path: {root}") + print("Expected directory.md plus MJCF/, Python/, and CPP/ under that path.") + return 1 + LOCAL_CONFIG.parent.mkdir(parents=True, exist_ok=True) + LOCAL_CONFIG.write_text(str(root), encoding="utf-8") + print(root) + return 0 + + +def clear_repo_path() -> int: + if LOCAL_CONFIG.exists(): + LOCAL_CONFIG.unlink() + print("Cleared saved MuJoCo tutorial repository path.") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Find or configure the local MuJoCo tutorial repository.") + parser.add_argument("--set", dest="set_path", help="Save the local MuJoCo tutorial repository path.") + parser.add_argument("--clear", action="store_true", help="Clear the saved repository path.") + args = parser.parse_args() + + if args.clear: + return clear_repo_path() + if args.set_path: + return save_repo_path(Path(args.set_path)) + + for item in candidates(): + if is_repo_root(item): + print(item.resolve()) + return 0 + + if LOCAL_CONFIG.exists(): + print("Saved MuJoCo tutorial repository path is invalid or unavailable. Ask the user for the new clone path and run this script with --set PATH.") + return 1 + + print( + "MuJoCo tutorial repository not found. Ask the user for the local clone path and run this script with --set PATH.", + end="", + ) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) From 64601bb0815d22fcabf26001518607bb17add9a6 Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 16:27:13 +0800 Subject: [PATCH 02/20] new file: extend/equality/mjwarp_equality.py --- .vscode/c_cpp_properties.json | 24 +++ .vscode/settings.json | 18 +- extend/equality/mjwarp_equality.py | 253 +++++++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 extend/equality/mjwarp_equality.py diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..8982557 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,24 @@ +{ + "configurations": [ + { + "browse": { + "databaseFilename": "${workspaceFolder}/.vscode/browse.vc.db", + "limitSymbolsToIncludedHeaders": false + }, + "includePath": [ + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_hg/include/**", + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_go/include/**", + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_api/include/**", + "/home/albusgive2/rlfw/rlfw_interface/install/rlfw_msgs/include/**", + "/opt/ros/humble/include/**", + "/usr/include/**" + ], + "name": "ros2", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index abc5eaa..28470a2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,21 @@ "cmake.sourceDirectory": "/home/albusgive2/mujoco_learning/CPP/Chapter1-make", "python-envs.defaultEnvManager": "ms-python.python:conda", "python-envs.defaultPackageManager": "ms-python.python:conda", - "python-envs.pythonProjects": [] + "ROS2.distro": "humble", + "python.autoComplete.extraPaths": [ + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_hg/local/lib/python3.10/dist-packages", + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_go/local/lib/python3.10/dist-packages", + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_api/local/lib/python3.10/dist-packages", + "/home/albusgive2/rlfw/rlfw_interface/install/rlfw_msgs/local/lib/python3.10/dist-packages", + "/opt/ros/humble/lib/python3.10/site-packages", + "/opt/ros/humble/local/lib/python3.10/dist-packages" + ], + "python.analysis.extraPaths": [ + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_hg/local/lib/python3.10/dist-packages", + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_go/local/lib/python3.10/dist-packages", + "/home/albusgive2/unitree_ros2/cyclonedds_ws/install/unitree_api/local/lib/python3.10/dist-packages", + "/home/albusgive2/rlfw/rlfw_interface/install/rlfw_msgs/local/lib/python3.10/dist-packages", + "/opt/ros/humble/lib/python3.10/site-packages", + "/opt/ros/humble/local/lib/python3.10/dist-packages" + ] } \ No newline at end of file diff --git a/extend/equality/mjwarp_equality.py b/extend/equality/mjwarp_equality.py new file mode 100644 index 0000000..7842320 --- /dev/null +++ b/extend/equality/mjwarp_equality.py @@ -0,0 +1,253 @@ +# Copyright 2025 The Newton Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""mjwarp-viewer: load and simulate an MJCF with MuJoCo Warp. + +Usage: mjwarp-viewer [flags] + +Example: + mjwarp-viewer benchmark/humanoid/humanoid.xml -o "opt.solver=cg" +""" + +import copy +import enum +import logging +import shutil +import sys +import time +from typing import Sequence + +import mujoco +import mujoco.viewer +import numpy as np +import warp as wp +from absl import app +from absl import flags +from etils import epath + +import mujoco_warp as mjw + +# mjwarp-viewer has priviledged access to a few internal methods +from mujoco_warp._src.io import find_keys +from mujoco_warp._src.io import make_trajectory +from mujoco_warp._src.io import override_model + + +class EngineOptions(enum.IntEnum): + """Engine option.""" + + WARP = 0 + C = 1 + + +_CLEAR_WARP_CACHE = flags.DEFINE_bool("clear_warp_cache", False, "Clear warp caches (kernel, LTO, CUDA compute)") +_ENGINE = flags.DEFINE_enum_class("engine", EngineOptions.WARP, EngineOptions, "Simulation engine") +_NCONMAX = flags.DEFINE_integer("nconmax", None, "Maximum number of contacts.") +_NJMAX = flags.DEFINE_integer("njmax", None, "Maximum number of constraints per world.") +_NCCDMAX = flags.DEFINE_integer("nccdmax", None, "Maximum number of CCD contacts per world.") +_OVERRIDE = flags.DEFINE_multi_string("override", [], "Model overrides (notation: foo.bar = baz)", short_name="o") +_KEYFRAME = flags.DEFINE_integer("keyframe", 0, "keyframe to initialize simulation.") +_DEVICE = flags.DEFINE_string("device", None, "override the default Warp device") +_REPLAY = flags.DEFINE_string("replay", None, "keyframe sequence to replay, keyframe name must prefix match") + +_VIEWER_GLOBAL_STATE = {"running": True, "step_once": False, "eq_active": True, "reset_active": False} + + +def key_callback(key: int) -> None: + if key == 32: # Space bar + _VIEWER_GLOBAL_STATE["eq_active"] = not _VIEWER_GLOBAL_STATE["eq_active"] + logging.info("EQ_ACTIVE = %s", _VIEWER_GLOBAL_STATE["eq_active"]) + elif key == 259: # Backspace + _VIEWER_GLOBAL_STATE["reset_active"] = True + elif key == 82: # 'r' + _VIEWER_GLOBAL_STATE["running"] = not _VIEWER_GLOBAL_STATE["running"] + logging.info("RUNNING = %s", _VIEWER_GLOBAL_STATE["running"]) + elif key == 46: # period + _VIEWER_GLOBAL_STATE["step_once"] = True + + +def _load_model(path: epath.Path) -> mujoco.MjModel: + if not path.exists(): + resource_path = epath.resource_path("mujoco_warp") / path + if not resource_path.exists(): + raise FileNotFoundError(f"file not found: {path}\nalso tried: {resource_path}") + path = resource_path + + print(f"Loading model from: {path}...") + if path.suffix == ".mjb": + return mujoco.MjModel.from_binary_path(path.as_posix()) + + spec = mujoco.MjSpec.from_file(path.as_posix()) + # check if the file has any mujoco.sdf test plugins + if any(p.plugin_name.startswith("mujoco.sdf") for p in spec.plugins): + from mujoco_warp.test_data.collision_sdf.utils import register_sdf_plugins as register_sdf_plugins + + register_sdf_plugins(mjw) + return spec.compile() + + +def _compile_step(m, d): + print("Compiling physics step...", end="", flush=True) + start = time.time() + # capture the whole step function as a CUDA graph + with wp.ScopedCapture() as capture: + mjw.step(m, d) + elapsed = time.time() - start + print(f"done ({elapsed:0.2g}s).") + return capture.graph + + +def _main(argv: Sequence[str]) -> None: + """Runs viewer app.""" + if len(argv) < 2: + argv.append("equality.xml") + elif len(argv) > 2: + raise app.UsageError("Too many command-line arguments.") + + mjm = _load_model(epath.Path(argv[1])) + mjd = mujoco.MjData(mjm) + + # Set initial velocity to shoot the box complex towards the wall and rotate it + body_id = mujoco.mj_name2id(mjm, mujoco.mjtObj.mjOBJ_BODY, 'box000') + joint_id = mjm.body_jntadr[body_id] + qvel_adr = mjm.jnt_dofadr[joint_id] + mjd.qvel[qvel_adr : qvel_adr + 6] = [80.0, 0.0, 0.0, 0.0, 10.0, 0.0] + + # Get sensor id for wall force + sensor_id = mujoco.mj_name2id(mjm, mujoco.mjtObj.mjOBJ_SENSOR, 'wall_force') + + ctrls = None + ctrlid = 0 + if _REPLAY.value: + keys = find_keys(mjm, _REPLAY.value) + if not keys: + raise app.UsageError(f"Key prefix not find: {_REPLAY.value}") + ctrls = make_trajectory(mjm, keys) + mujoco.mj_resetDataKeyframe(mjm, mjd, keys[0]) + elif mjm.nkey > 0 and _KEYFRAME.value > -1: + mujoco.mj_resetDataKeyframe(mjm, mjd, _KEYFRAME.value) + + if _ENGINE.value == EngineOptions.C: + override_model(mjm, _OVERRIDE.value) + print( + f" nbody: {mjm.nbody} nv: {mjm.nv} ngeom: {mjm.ngeom} nu: {mjm.nu}\n" + f" solver: {mujoco.mjtSolver(mjm.opt.solver).name} cone: {mujoco.mjtCone(mjm.opt.cone).name}" + f" iterations: {mjm.opt.iterations} ls_iterations: {mjm.opt.ls_iterations}\n" + f" integrator: {mujoco.mjtIntegrator(mjm.opt.integrator).name}\n" + ) + print(f"MuJoCo C simulating with dt = {mjm.opt.timestep:.3f}...") + else: + wp.config.quiet = flags.FLAGS["verbosity"].value < 1 + wp.init() + wp.set_device(_DEVICE.value) + if _CLEAR_WARP_CACHE.value: + wp.clear_kernel_cache() + wp.clear_lto_cache() + # Clear CUDA compute cache for truly cold start JIT + compute_cache = epath.Path("~/.nv/ComputeCache").expanduser() + if compute_cache.exists(): + shutil.rmtree(compute_cache) + compute_cache.mkdir() + + override_model(mjm, _OVERRIDE.value) + m = mjw.put_model(mjm) + override_model(m, _OVERRIDE.value) + d = mjw.put_data(mjm, mjd, nconmax=_NCONMAX.value, njmax=_NJMAX.value, nccdmax=_NCCDMAX.value) + wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)])) + wp.copy(d.eq_active, wp.array([mjd.eq_active[:len(d.eq_active)].astype(np.int32)])) + graph = _compile_step(m, d) if wp.get_device().is_cuda else None + if graph is None: + mjw.step(m, d) # warmup step + print("Running Warp unoptimized on CPU.") + broadphase, filter = mjw.BroadphaseType(m.opt.broadphase).name, mjw.BroadphaseFilter(m.opt.broadphase_filter).name + solver, cone = mjw.SolverType(m.opt.solver).name, mjw.ConeType(m.opt.cone).name + integrator = mjw.IntegratorType(m.opt.integrator).name + iterations, ls_iterations = m.opt.iterations, m.opt.ls_iterations + ls_str = f"{'parallel' if m.opt.ls_parallel else 'iterative'} linesearch iterations: {ls_iterations}" + print( + f" nbody: {m.nbody} nv: {m.nv} ngeom: {m.ngeom} nu: {m.nu} is_sparse: {m.is_sparse}\n" + f" broadphase: {broadphase} broadphase_filter: {filter}\n" + f" solver: {solver} cone: {cone} iterations: {iterations} {ls_str}\n" + f" integrator: {integrator} graph_conditional: {m.opt.graph_conditional}" + ) + print(f"Data\n nworld: {d.nworld} nconmax: {int(d.naconmax / d.nworld)} njmax: {d.njmax}\n") + print(f"MuJoCo Warp simulating with dt = {m.opt.timestep.numpy()[0]:.3f}...") + + with mujoco.viewer.launch_passive(mjm, mjd, key_callback=key_callback) as viewer: + opt = copy.copy(mjm.opt) + + while True: + start = time.time() + + if ctrls is not None and ctrlid < len(ctrls): + mjd.ctrl[:] = ctrls[ctrlid] + ctrlid += 1 + + if _ENGINE.value == EngineOptions.C: + mujoco.mj_step(mjm, mjd) + # Add equality logic + wall_force = mjd.sensordata[sensor_id : sensor_id + 3] + if not _VIEWER_GLOBAL_STATE["eq_active"] and np.linalg.norm(wall_force) > 0.01: + mjd.eq_active[:] = 0 + else: # mjwarp + wp.copy(d.ctrl, wp.array([mjd.ctrl.astype(np.float32)])) + wp.copy(d.act, wp.array([mjd.act.astype(np.float32)])) + wp.copy(d.xfrc_applied, wp.array([mjd.xfrc_applied.astype(np.float32)])) + wp.copy(d.qpos, wp.array([mjd.qpos.astype(np.float32)])) + wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)])) + wp.copy(d.time, wp.array([mjd.time], dtype=wp.float32)) + # if the user changed an option in the MuJoCo Simulate UI, go ahead and recompile the step + # TODO: update memory tied to option max iterations + if mjm.opt != opt: + opt = copy.copy(mjm.opt) + m = mjw.put_model(mjm) + graph = _compile_step(m, d) if wp.get_device().is_cuda else None + if _VIEWER_GLOBAL_STATE["running"] or _VIEWER_GLOBAL_STATE["step_once"]: + _VIEWER_GLOBAL_STATE["step_once"] = False + if graph is None: + mjw.step(m, d) + else: + wp.capture_launch(graph) + wp.synchronize() + mjw.get_data_into(mjd, mjm, d) + # Add equality logic + wall_force = mjd.sensordata[sensor_id : sensor_id + 3] + if not _VIEWER_GLOBAL_STATE["eq_active"] and np.linalg.norm(wall_force) > 0.01: + mjd.eq_active[:] = 0 + wp.copy(d.eq_active, wp.array([mjd.eq_active.astype(np.int32)])) + + viewer.sync() + + if _VIEWER_GLOBAL_STATE["reset_active"]: + mjd.qvel[qvel_adr : qvel_adr + 6] = [80.0, 0.0, 0.0, 0.0, 10.0, 0.0] + if _ENGINE.value != EngineOptions.C: + wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)])) + _VIEWER_GLOBAL_STATE["reset_active"] = False + + elapsed = time.time() - start + if elapsed < mjm.opt.timestep: + time.sleep(mjm.opt.timestep - elapsed) + + +def main(): + # absl flags assumes __main__ is the main running module for printing usage documentation + # pyproject bin scripts break this assumption, so manually set argv and docstring + sys.argv[0] = "mujoco_warp.viewer" + sys.modules["__main__"].__doc__ = __doc__ + app.run(_main) + + +if __name__ == "__main__": + main() From 7712492cf18b03ea33b212f01647338bd78d00a5 Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 16:42:47 +0800 Subject: [PATCH 03/20] =?UTF-8?q?skills=E9=80=82=E9=85=8Dopencode=E5=92=8C?= =?UTF-8?q?claude=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/skills/mujoco-engineering/SKILL.md | 17 +++++++++++++++++ .claude/skills/mujoco-teaching/SKILL.md | 17 +++++++++++++++++ .opencode/skills/mujoco-engineering/SKILL.md | 19 +++++++++++++++++++ .opencode/skills/mujoco-teaching/SKILL.md | 19 +++++++++++++++++++ README.md | 7 +++++++ 5 files changed, 79 insertions(+) create mode 100644 .claude/skills/mujoco-engineering/SKILL.md create mode 100644 .claude/skills/mujoco-teaching/SKILL.md create mode 100644 .opencode/skills/mujoco-engineering/SKILL.md create mode 100644 .opencode/skills/mujoco-teaching/SKILL.md diff --git a/.claude/skills/mujoco-engineering/SKILL.md b/.claude/skills/mujoco-engineering/SKILL.md new file mode 100644 index 0000000..ee848f4 --- /dev/null +++ b/.claude/skills/mujoco-engineering/SKILL.md @@ -0,0 +1,17 @@ +--- +name: mujoco-engineering +description: Use when an agent needs to implement, reproduce, debug, or extend MuJoCo projects using this repository's MJCF, Python, C++, ray casting, soft contact, sensors, rendering, viewer, force, or build examples. +--- + +# MuJoCo Engineering + +This is the Claude Code project-local adapter for the canonical skill at: + +`../../../skills/mujoco-engineering/SKILL.md` + +When this skill is loaded, read and follow the canonical skill instructions and its reference map: + +- `../../../skills/mujoco-engineering/SKILL.md` +- `../../../skills/mujoco-engineering/references/tutorial-map.md` + +Use repository-relative tutorial paths only. Do not store user-specific paths in this adapter. diff --git a/.claude/skills/mujoco-teaching/SKILL.md b/.claude/skills/mujoco-teaching/SKILL.md new file mode 100644 index 0000000..1ae3b1e --- /dev/null +++ b/.claude/skills/mujoco-teaching/SKILL.md @@ -0,0 +1,17 @@ +--- +name: mujoco-teaching +description: Use when a user wants to learn MuJoCo concepts, MJCF modeling, Python/C++ MuJoCo APIs, sensors, rendering, contact, soft contact, ray casting, or asks conceptual/tutorial questions about this repository's MuJoCo lessons. +--- + +# MuJoCo Teaching + +This is the Claude Code project-local adapter for the canonical skill at: + +`../../../skills/mujoco-teaching/SKILL.md` + +When this skill is loaded, read and follow the canonical skill instructions and its reference map: + +- `../../../skills/mujoco-teaching/SKILL.md` +- `../../../skills/mujoco-teaching/references/tutorial-map.md` + +Use repository-relative tutorial paths only. Do not store user-specific paths in this adapter. diff --git a/.opencode/skills/mujoco-engineering/SKILL.md b/.opencode/skills/mujoco-engineering/SKILL.md new file mode 100644 index 0000000..d5326c6 --- /dev/null +++ b/.opencode/skills/mujoco-engineering/SKILL.md @@ -0,0 +1,19 @@ +--- +name: mujoco-engineering +description: Use when an agent needs to implement, reproduce, debug, or extend MuJoCo projects using this repository's MJCF, Python, C++, ray casting, soft contact, sensors, rendering, viewer, force, or build examples. +metadata: + canonical-source: skills/mujoco-engineering +--- + +# MuJoCo Engineering + +This is the OpenCode project-local adapter for the canonical skill at: + +`../../../skills/mujoco-engineering/SKILL.md` + +When this skill is loaded, read and follow the canonical skill instructions and its reference map: + +- `../../../skills/mujoco-engineering/SKILL.md` +- `../../../skills/mujoco-engineering/references/tutorial-map.md` + +Use repository-relative tutorial paths only. Do not store user-specific paths in this adapter. diff --git a/.opencode/skills/mujoco-teaching/SKILL.md b/.opencode/skills/mujoco-teaching/SKILL.md new file mode 100644 index 0000000..bad336b --- /dev/null +++ b/.opencode/skills/mujoco-teaching/SKILL.md @@ -0,0 +1,19 @@ +--- +name: mujoco-teaching +description: Use when a user wants to learn MuJoCo concepts, MJCF modeling, Python/C++ MuJoCo APIs, sensors, rendering, contact, soft contact, ray casting, or asks conceptual/tutorial questions about this repository's MuJoCo lessons. +metadata: + canonical-source: skills/mujoco-teaching +--- + +# MuJoCo Teaching + +This is the OpenCode project-local adapter for the canonical skill at: + +`../../../skills/mujoco-teaching/SKILL.md` + +When this skill is loaded, read and follow the canonical skill instructions and its reference map: + +- `../../../skills/mujoco-teaching/SKILL.md` +- `../../../skills/mujoco-teaching/references/tutorial-map.md` + +Use repository-relative tutorial paths only. Do not store user-specific paths in this adapter. diff --git a/README.md b/README.md index 634fc72..6c9570e 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,13 @@ uvicorn mujoco_learning_doc.main:app --reload --host 127.0.0.1 --port 8000 - `skills/mujoco-teaching`:教学型。用于向 agent 提问 MuJoCo 概念、教程内容、学习路线和 API 原理。 - `skills/mujoco-engineering`:工程型。用于让 agent 复现示例、开发 MJCF/Python/C++ MuJoCo 功能、查找对应教程代码路径。 +同时提供 OpenCode 和 Claude Code 的项目级适配入口: + +- OpenCode:`.opencode/skills/mujoco-teaching`、`.opencode/skills/mujoco-engineering` +- Claude Code:`.claude/skills/mujoco-teaching`、`.claude/skills/mujoco-engineering` + +这些适配入口会引用 `skills/` 下的 canonical skill 内容,避免维护多份重复说明。 + 发布到 GitHub 后,可用 Codex 的 `skill-installer` 从仓库路径安装: ```bash From 3e40a112fc2728a90bd0a0a59bd755195fd79761 Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 17:16:15 +0800 Subject: [PATCH 04/20] feat: refine skills with quick-reference, official doc links, and interactive C++ build skill --- .claude/skills/mujoco-cpp-build/SKILL.md | 16 + .opencode/skills/mujoco-cpp-build/SKILL.md | 18 + README.md | 80 ++-- directory.md | 68 ++-- skills/mujoco-cpp-build/SKILL.md | 55 +++ skills/mujoco-cpp-build/scripts/find_repo.py | 109 +++++ skills/mujoco-engineering/SKILL.md | 21 +- .../references/quick-reference.md | 374 ++++++++++++++++++ skills/mujoco-teaching/SKILL.md | 19 +- .../references/quick-reference.md | 374 ++++++++++++++++++ 10 files changed, 1061 insertions(+), 73 deletions(-) create mode 100644 .claude/skills/mujoco-cpp-build/SKILL.md create mode 100644 .opencode/skills/mujoco-cpp-build/SKILL.md create mode 100644 skills/mujoco-cpp-build/SKILL.md create mode 100644 skills/mujoco-cpp-build/scripts/find_repo.py create mode 100644 skills/mujoco-engineering/references/quick-reference.md create mode 100644 skills/mujoco-teaching/references/quick-reference.md diff --git a/.claude/skills/mujoco-cpp-build/SKILL.md b/.claude/skills/mujoco-cpp-build/SKILL.md new file mode 100644 index 0000000..8f499ed --- /dev/null +++ b/.claude/skills/mujoco-cpp-build/SKILL.md @@ -0,0 +1,16 @@ +--- +name: mujoco-cpp-build +description: Use when building C++ examples, compiling simulation executables, configuring CMake, or resolving C++ building errors in the MuJoCo learning repository. It requires the agent to actively prompt the user for build requirements (source vs release path, target directories). +--- + +# MuJoCo C++ Build + +This is the Claude Code project-local adapter for the canonical skill at: + +`../../../skills/mujoco-cpp-build/SKILL.md` + +When this skill is loaded, read and follow the canonical skill instructions: + +- `../../../skills/mujoco-cpp-build/SKILL.md` + +Use repository-relative tutorial paths only. Do not store user-specific paths in this adapter. diff --git a/.opencode/skills/mujoco-cpp-build/SKILL.md b/.opencode/skills/mujoco-cpp-build/SKILL.md new file mode 100644 index 0000000..700aa06 --- /dev/null +++ b/.opencode/skills/mujoco-cpp-build/SKILL.md @@ -0,0 +1,18 @@ +--- +name: mujoco-cpp-build +description: Use when building C++ examples, compiling simulation executables, configuring CMake, or resolving C++ building errors in the MuJoCo learning repository. It requires the agent to actively prompt the user for build requirements (source vs release path, target directories). +metadata: + canonical-source: skills/mujoco-cpp-build +--- + +# MuJoCo C++ Build + +This is the OpenCode project-local adapter for the canonical skill at: + +`../../../skills/mujoco-cpp-build/SKILL.md` + +When this skill is loaded, read and follow the canonical skill instructions: + +- `../../../skills/mujoco-cpp-build/SKILL.md` + +Use repository-relative tutorial paths only. Do not store user-specific paths in this adapter. diff --git a/README.md b/README.md index 6c9570e..ce1da69 100644 --- a/README.md +++ b/README.md @@ -88,34 +88,62 @@ uvicorn mujoco_learning_doc.main:app --reload --host 127.0.0.1 --port 8000 浏览器打开 即可查看本仓库的 Markdown 教程。文档站会自动读取 `README.md`、`directory.md` 以及各章节的 `tutorial.md` / `readme.md`,并支持目录导航、搜索、图片显示和代码高亮。 -## Codex Skills -本仓库在 `skills/` 下提供两个 Codex skill: - -- `skills/mujoco-teaching`:教学型。用于向 agent 提问 MuJoCo 概念、教程内容、学习路线和 API 原理。 -- `skills/mujoco-engineering`:工程型。用于让 agent 复现示例、开发 MJCF/Python/C++ MuJoCo 功能、查找对应教程代码路径。 - -同时提供 OpenCode 和 Claude Code 的项目级适配入口: - -- OpenCode:`.opencode/skills/mujoco-teaching`、`.opencode/skills/mujoco-engineering` -- Claude Code:`.claude/skills/mujoco-teaching`、`.claude/skills/mujoco-engineering` - -这些适配入口会引用 `skills/` 下的 canonical skill 内容,避免维护多份重复说明。 - -发布到 GitHub 后,可用 Codex 的 `skill-installer` 从仓库路径安装: - -```bash -python scripts/install-skill-from-github.py --repo Albusgive/mujoco_learning --path skills/mujoco-teaching skills/mujoco-engineering -``` - -安装完成后重启 Codex。skill 内只使用仓库相对路径,不包含开发电脑的绝对路径。 - -如果 Codex 不在本仓库工作区内运行,skill 会先尝试自动寻找本仓库。首次找不到时,agent 会询问本机仓库路径,并保存到已安装 skill 目录下的本地配置文件: +## Agent Skills (智能体技能安装) +本仓库在 `skills/` 下提供三个智能体技能(Skills): + +- `skills/mujoco-teaching`:教学型。用于向 Agent 提问 MuJoCo 概念、教程内容、学习路线和 API 原理。 +- `skills/mujoco-engineering`:工程型。用于让 Agent 复现示例、开发 MJCF/Python/C++ MuJoCo 功能、查找对应教程代码路径。 +- `skills/mujoco-cpp-build`:构建型。一键编译构建本仓库 C++ 实例与工具,支持源码编译版与 Release 依赖版,并在编译前主动向用户提问构建需求。 + +为了让不同平台的智能体(Agent)能识别和使用这些技能,我们提供了以下几种安装配置方式: + +### 1. Antigravity (Gemini Advanced Agentic Coding) +* **工作区自动加载**:当您在当前仓库根目录下运行 Antigravity 时,CLI 将自动扫描并加载 `skills/` 目录下的所有技能,无需额外安装。 +* **全局安装**:如果您在其他工作区也想让 Antigravity 调用这些技能,可将其复制到系统全局插件目录: + ```bash + mkdir -p ~/.gemini/config/skills + cp -r skills/mujoco-teaching skills/mujoco-engineering skills/mujoco-cpp-build ~/.gemini/config/skills/ + ``` + +### 2. Claude Code +* **工作区级适配**:Claude Code 会自动加载项目根目录下的 `.claude/skills/` 适配器。这些适配器已经配置好并指向了 `skills/` 下的 canonical 技能文件。 +* **全局安装**:如果要在其他目录下也能够使用,可将 canonical 目录直接安装至 Claude 的全局路径下: + ```bash + mkdir -p ~/.claude/skills + cp -r skills/mujoco-teaching skills/mujoco-engineering skills/mujoco-cpp-build ~/.claude/skills/ + ``` + +### 3. OpenCode +* **工作区级适配**:OpenCode 会自动加载项目根目录下的 `.opencode/skills/` 适配器。 +* **全局安装**:如果需要全局使用,复制技能文件夹至 OpenCode 全局技能目录: + ```bash + mkdir -p ~/.opencode/skills + cp -r skills/mujoco-teaching skills/mujoco-engineering skills/mujoco-cpp-build ~/.opencode/skills/ + ``` + +### 4. Codex +* **通过 skill-installer 从 GitHub 远程安装**: + ```bash + python -m skill_installer --repo Albusgive/mujoco_learning --path skills/mujoco-teaching skills/mujoco-engineering skills/mujoco-cpp-build + ``` +* **本地手动安装**:直接将技能文件夹拷贝至 Codex 本地技能存储路径: + ```bash + mkdir -p ~/.codex/skills + cp -r skills/mujoco-teaching skills/mujoco-engineering skills/mujoco-cpp-build ~/.codex/skills/ + ``` + +安装/复制完成后,请重启对应的命令行工具或 Agent 客户端。 + +--- + +## 跨工作区运行时的仓库路径设置 +如果智能体(如 Antigravity / Codex / Claude Code 等)未在当前仓库的工作区中运行,技能会在加载时尝试动态搜寻本仓库。首次找不到时,智能体主动询问您本仓库的克隆路径,并自动将其保存到已安装技能目录下的本地配置文件中: ```text /.local/repo_path.txt ``` -`.local/repo_path.txt` 是普通文件路径,Python 脚本会用当前系统的路径规则读写,Windows、macOS、Linux 都可用。不同系统可按下面方式手动设置: +`.local/repo_path.txt` 是普通路径文本文件。如果需要,您也可以通过运行技能中的 `find_repo.py` 脚本来手动设置或清除该缓存路径。不同系统设置方式如下:
macOS / Linux @@ -123,6 +151,7 @@ python scripts/install-skill-from-github.py --repo Albusgive/mujoco_learning --p ```bash python ~/.codex/skills/mujoco-engineering/scripts/find_repo.py --set /path/to/mujoco_learning python ~/.codex/skills/mujoco-teaching/scripts/find_repo.py --set /path/to/mujoco_learning +python ~/.codex/skills/mujoco-cpp-build/scripts/find_repo.py --set /path/to/mujoco_learning ``` 清除保存路径: @@ -130,6 +159,7 @@ python ~/.codex/skills/mujoco-teaching/scripts/find_repo.py --set /path/to/mujoc ```bash python ~/.codex/skills/mujoco-engineering/scripts/find_repo.py --clear python ~/.codex/skills/mujoco-teaching/scripts/find_repo.py --clear +python ~/.codex/skills/mujoco-cpp-build/scripts/find_repo.py --clear ```
@@ -140,6 +170,7 @@ python ~/.codex/skills/mujoco-teaching/scripts/find_repo.py --clear ```powershell python $HOME\.codex\skills\mujoco-engineering\scripts\find_repo.py --set C:\path\to\mujoco_learning python $HOME\.codex\skills\mujoco-teaching\scripts\find_repo.py --set C:\path\to\mujoco_learning +python $HOME\.codex\skills\mujoco-cpp-build\scripts\find_repo.py --set C:\path\to\mujoco_learning ``` 清除保存路径: @@ -147,6 +178,7 @@ python $HOME\.codex\skills\mujoco-teaching\scripts\find_repo.py --set C:\path\to ```powershell python $HOME\.codex\skills\mujoco-engineering\scripts\find_repo.py --clear python $HOME\.codex\skills\mujoco-teaching\scripts\find_repo.py --clear +python $HOME\.codex\skills\mujoco-cpp-build\scripts\find_repo.py --clear ``` @@ -157,6 +189,7 @@ python $HOME\.codex\skills\mujoco-teaching\scripts\find_repo.py --clear ```bat python %USERPROFILE%\.codex\skills\mujoco-engineering\scripts\find_repo.py --set C:\path\to\mujoco_learning python %USERPROFILE%\.codex\skills\mujoco-teaching\scripts\find_repo.py --set C:\path\to\mujoco_learning +python %USERPROFILE%\.codex\skills\mujoco-cpp-build\scripts\find_repo.py --set C:\path\to\mujoco_learning ``` 清除保存路径: @@ -164,6 +197,7 @@ python %USERPROFILE%\.codex\skills\mujoco-teaching\scripts\find_repo.py --set C: ```bat python %USERPROFILE%\.codex\skills\mujoco-engineering\scripts\find_repo.py --clear python %USERPROFILE%\.codex\skills\mujoco-teaching\scripts\find_repo.py --clear +python %USERPROFILE%\.codex\skills\mujoco-cpp-build\scripts\find_repo.py --clear ``` diff --git a/directory.md b/directory.md index bfd3172..c522c31 100644 --- a/directory.md +++ b/directory.md @@ -1,99 +1,99 @@ ### MJCF模型文件 -> 第一节 mujoco安装 [install](MJCF/Chapter0-install/tutorial.md) -> 第二节 仿真世界调整 [virtual_world](MJCF/Chapter2-virtual_world/tutorial.md) +> 第一节 mujoco安装 [install](MJCF/Chapter0-install/tutorial.md) | [官方文档: 安装与构建](https://mujoco.readthedocs.io/en/latest/programming.html#building-mujoco-from-source) +> 第二节 仿真世界调整 [virtual_world](MJCF/Chapter2-virtual_world/tutorial.md) | [官方文档: option/visual/asset](https://mujoco.readthedocs.io/en/latest/XMLreference.html#option) > - 物理世界参数,可视化配置,材质加载等 > -> 第三节 仿真世界 [worldbody](MJCF/Chapter3-worldbody/tutorial.md) +> 第三节 仿真世界 [worldbody](MJCF/Chapter3-worldbody/tutorial.md) | [官方文档: worldbody/body/geom](https://mujoco.readthedocs.io/en/latest/XMLreference.html#worldbody) > - 世界body,几何体,地形等 > -> 第四节 关节 [joint](MJCF/Chapter4-joint/tutorial.md) +> 第四节 关节 [joint](MJCF/Chapter4-joint/tutorial.md) | [官方文档: joint](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint) > - 关节类型,关节动力,关节参数等 > -> 第五节 摩擦力设置及计算方式 [friction](MJCF/Chapter5-friction/tutorial.md) +> 第五节 摩擦力设置及计算方式 [friction](MJCF/Chapter5-friction/tutorial.md) | [官方文档: friction/contact](https://mujoco.readthedocs.io/en/latest/XMLreference.html#pair) > - mujoco中多维度的摩擦力调整 > -> 第六节 驱动器 [actuator](MJCF/Chapter6-actuator/tutorial.md) +> 第六节 驱动器 [actuator](MJCF/Chapter6-actuator/tutorial.md) | [官方文档: actuator](https://mujoco.readthedocs.io/en/latest/XMLreference.html#actuator) > - 添加速度控制,位置控制,力矩控制等 > -> 第七节 灯光和复制 [light&replicate](MJCF/Chapter7-light&replicate/tutorial.md) +> 第七节 灯光和复制 [light&replicate](MJCF/Chapter7-light&replicate/tutorial.md) | [官方文档: light/camera](https://mujoco.readthedocs.io/en/latest/XMLreference.html#light) > - 灯光类型(和相机传感器关联性强) > - 复制实体,阵列排布,激光雷达演示 > -> 第八节 肌腱 [tendon](MJCF/Chapter8-tendon/tutorial.md) +> 第八节 肌腱 [tendon](MJCF/Chapter8-tendon/tutorial.md) | [官方文档: tendon](https://mujoco.readthedocs.io/en/latest/XMLreference.html#tendon) > - mujoco特有的驱动器和关节联系方式 > - 肌肉控制 > -> 第九节 传感器 [sensor](MJCF/Chapter9-sensor/tutorial.md) +> 第九节 传感器 [sensor](MJCF/Chapter9-sensor/tutorial.md) | [官方文档: sensor](https://mujoco.readthedocs.io/en/latest/XMLreference.html#sensor) > - 相机传感器,imu,速度,角度等 > -> 第十节 从CAD软件制作mjcf模型 [from_CAD_software](MJCF/Chapter10-from_CAD_software/tutorial.md) +> 第十节 从CAD软件制作mjcf模型 [from_CAD_software](MJCF/Chapter10-from_CAD_software/tutorial.md) | [官方文档: overview](https://mujoco.readthedocs.io/en/latest/overview.html#model-description) > - 以solidworks为例 > -> 第十一节 约束条件 [equality](MJCF/Chapter11-equality/tutorial.md) +> 第十一节 约束条件 [equality](MJCF/Chapter11-equality/tutorial.md) | [官方文档: equality](https://mujoco.readthedocs.io/en/latest/XMLreference.html#equality) > - 并联机构建模,驱动跟踪等 > -> 第十二节 默认属性设置 [default](MJCF/Chapter12-default/tutorial.md) +> 第十二节 默认属性设置 [default](MJCF/Chapter12-default/tutorial.md) | [官方文档: default](https://mujoco.readthedocs.io/en/latest/XMLreference.html#default) > - 几何体,body,关节等默认参数 > -> 第十三节 可变形体(老版3.2.7及以前) [composite](MJCF/Chapter13-composite/tutorial.md) +> 第十三节 可变形体(老版3.2.7及以前) [composite](MJCF/Chapter13-composite/tutorial.md) | [官方文档: composite](https://mujoco.readthedocs.io/en/latest/XMLreference.html#composite) > - 绳子,布料,软体等 > -> 第十四节 可变形体(新版3.3.0及以后) [flex](MJCF/Chapter14-flex/tutorial.md) +> 第十四节 可变形体(新版3.3.0及以后) [flex](MJCF/Chapter14-flex/tutorial.md) | [官方文档: flex](https://mujoco.readthedocs.io/en/latest/XMLreference.html#flex) > - 柔性材料,布料,软体,绳索,从网格构建可变形模型等 > -> 第十五节 关节帧 [keyframe](MJCF/Chapter15-keyframe/tutorial.md) +> 第十五节 关节帧 [keyframe](MJCF/Chapter15-keyframe/tutorial.md) | [官方文档: keyframe](https://mujoco.readthedocs.io/en/latest/XMLreference.html#keyframe) > - 加载和储存特定的姿态,关节信息等 ### API > 第一节 编译 > - 编译环境,编译命令,编译开发演示 -> > [make(C++)](CPP/Chapter1-make/tutorial.md) +> > [make(C++)](CPP/Chapter1-make/tutorial.md) | [官方文档: 编译与链接](https://mujoco.readthedocs.io/en/latest/programming.html#building-mujoco-from-source) > > 第二节 可视化和仿真进行 > - 仿真环境,可视化,仿真步进,仿真步进控制等 -> > [view&step(C++)](CPP/Chapter2-view&step/tutorial.md) -> > [view&step(Python)](Python/Chapter1-view&step/tutorial.md) +> > [view&step(C++)](CPP/Chapter2-view&step/tutorial.md) | [官方文档: 引擎初始化](https://mujoco.readthedocs.io/en/latest/programming.html#initialization) +> > [view&step(Python)](Python/Chapter1-view&step/tutorial.md) | [官方文档: 引擎初始化](https://mujoco.readthedocs.io/en/latest/programming.html#initialization) > > 第三节 获取仿真世界中的实体信息 > - 获取仿真世界中的实体信息,如名字,数量,参数信息等 -> > [get_obj(C++)](CPP/Chapter3-get_obj/tutorial.md) -> > [get_obj(Python)](Python/Chapter2-get_obj/tutorial.md) +> > [get_obj(C++)](CPP/Chapter3-get_obj/tutorial.md) | [官方文档: 实体索引与名称](https://mujoco.readthedocs.io/en/latest/programming.html#indices-and-names) +> > [get_obj(Python)](Python/Chapter2-get_obj/tutorial.md) | [官方文档: 实体索引与名称](https://mujoco.readthedocs.io/en/latest/programming.html#indices-and-names) > > 第四节 传感器数据获取 > - 获取仿真世界中的传感器数据,如相机,imu,速度,角度等 -> > [sensor_data(C++)](CPP/Chapter4-sensor_data/tutorial.md) -> > [sensor_data(Python)](Python/Chapter3-sensor_data/tutorial.md) +> > [sensor_data(C++)](CPP/Chapter4-sensor_data/tutorial.md) | [官方文档: 传感器读取](https://mujoco.readthedocs.io/en/latest/programming.html#sensors) +> > [sensor_data(Python)](Python/Chapter3-sensor_data/tutorial.md) | [官方文档: 传感器读取](https://mujoco.readthedocs.io/en/latest/programming.html#sensors) > > 第五节 2D和3D绘制 > - 2D绘制:文字,方形,表格等 > - 3D绘制:基础几何体,箭头等 -> > [draw(C++)](CPP/Chapter5-draw/tutorial.md) -> > [draw(Python)](Python/Chapter4-draw/tutorial.md) +> > [draw(C++)](CPP/Chapter5-draw/tutorial.md) | [官方文档: 自定义绘制](https://mujoco.readthedocs.io/en/latest/programming.html#visualization) +> > [draw(Python)](Python/Chapter4-draw/tutorial.md) | [官方文档: 自定义绘制](https://mujoco.readthedocs.io/en/latest/programming.html#visualization) > > 第六节 力的计算和API验证 > - mujoco中力是如何作用的及验证 -> > [force(C++)](CPP/Chapter6-force/tutorial.md) -> > [force(Python)](Python/Chapter5-force/tutorial.md) +> > [force(C++)](CPP/Chapter6-force/tutorial.md) | [官方文档: 动力学状态](https://mujoco.readthedocs.io/en/latest/programming.html#physics-state) +> > [force(Python)](Python/Chapter5-force/tutorial.md) | [官方文档: 动力学状态](https://mujoco.readthedocs.io/en/latest/programming.html#physics-state) > > 第七节 渲染配置 > - 双目相机,图像分割,渲染配置等 -> > [vis_cfg(C++)](CPP/Chapter7-vis_cfg/tutorial.md) -> > [vis_cfg(Python)](Python/Chapter6-vis_cfg/tutorial.md) +> > [vis_cfg(C++)](CPP/Chapter7-vis_cfg/tutorial.md) | [官方文档: 渲染与配置](https://mujoco.readthedocs.io/en/latest/programming.html#visualization) +> > [vis_cfg(Python)](Python/Chapter6-vis_cfg/tutorial.md) | [官方文档: 渲染与配置](https://mujoco.readthedocs.io/en/latest/programming.html#visualization) > > 第八节 射线测距 > - 测距传感器实现原理,自定义测距 -> > [ray(C++)](CPP/Chapter8-ray/tutorial.md) -> > [ray(Python)](Python/Chapter7-ray/tutorial.md) - +> > [ray(C++)](CPP/Chapter8-ray/tutorial.md) | [官方文档: 射线检测](https://mujoco.readthedocs.io/en/latest/programming.html#ray-collisions) +> > [ray(Python)](Python/Chapter7-ray/tutorial.md) | [官方文档: 射线检测](https://mujoco.readthedocs.io/en/latest/programming.html#ray-collisions) +> ### 拓展和进阶 > 触觉检测 > - 刚性触摸板和柔性材料触觉检测,通过api实现,并非插件方式 -> > [touch(C++&Python)](extend/touch/readme.md) +> > [touch(C++&Python)](extend/touch/readme.md) | [官方文档: 触觉传感器](https://mujoco.readthedocs.io/en/latest/XMLreference.html#sensor-touch) > > 软接触 > - mujoco中碰撞模型,如何通过调整动态“弹簧-阻尼”模型让模型展现不同材料的碰撞效果,对于碰撞和穿模问题如何调整 -> > [soft contact(mjcf&C++&Python)](extend/soft_contact/tutorial.md) +> > [soft contact(mjcf&C++&Python)](extend/soft_contact/tutorial.md) | [官方文档: 接触解算原理](https://mujoco.readthedocs.io/en/latest/computation/index.html#contacts) > > Ray Caster(基于ray) > - 通过mujoco中的ray实现雷达、深度相机和自定义测距传感器 -> > [ray caster](extend/deep_camera/readme.md) +> > [ray caster](extend/deep_camera/readme.md) | [官方文档: 射线求交](https://mujoco.readthedocs.io/en/latest/programming.html#ray-collisions) > > [独立仓库:Albusgive/mujoco_ray_caster](https://github.com/Albusgive/mujoco_ray_caster) diff --git a/skills/mujoco-cpp-build/SKILL.md b/skills/mujoco-cpp-build/SKILL.md new file mode 100644 index 0000000..ad5c5be --- /dev/null +++ b/skills/mujoco-cpp-build/SKILL.md @@ -0,0 +1,55 @@ +--- +name: mujoco-cpp-build +description: Use when building C++ examples, compiling simulation executables, configuring CMake, or resolving C++ building errors in the MuJoCo learning repository. It requires the agent to actively prompt the user for build requirements (source vs release path, target directories). +--- + +# MuJoCo C++ Build Skill + +Use this skill to automate the building of C++ chapters and simulate tools in the repository. This supports both **Source-built Install** (e.g., `/opt/mujoco`) and **Pre-compiled Release** (custom directory) options. + +## CRITICAL: Active Prompting Requirement +Before starting any compilation or editing of `CMakeLists.txt`, **you must actively ask the user** for their compilation requirements. Use a clear, formatted message (or ask_question tool if appropriate) to align on: +1. **MuJoCo Library Choice**: + - Option A: Source-built and installed to standard path `/opt/mujoco` (using `find_package(mujoco REQUIRED PATHS /opt/mujoco/lib/cmake NO_DEFAULT_PATH)`). + - Option B: Pre-compiled Release or Source-built in a custom directory (e.g. `/home/user/mujoco-3.3.1`). +2. **Custom Directory Path** (Only if Option B is chosen): Ask the user to provide the absolute path to their MuJoCo directory. +3. **Target Chapter**: Which chapter or executable to compile (e.g., `CPP/Chapter1-make/basic`, `CPP/Chapter2-view&step`, or "All"). + +## Workflow + +1. **Prompt the User**: Present the build options clearly and wait for their input. +2. **Locate the Workspace**: Resolve the repository path dynamically (using `scripts/find_repo.py`). +3. **Configure CMakeLists.txt**: + - Modify the target chapter's `CMakeLists.txt` based on the user's choices. + - For **Option A** (Installed under `/opt`): + ```cmake + set(MUJOCO_FOLDER /opt/mujoco/lib/cmake) + find_package(mujoco REQUIRED PATHS ${MUJOCO_FOLDER} NO_DEFAULT_PATH) + target_link_libraries(your_target mujoco::mujoco glut GL GLU glfw) + ``` + - For **Option B** (Custom path, e.g., `/path/to/mujoco`): + ```cmake + set(MUJOCO_PATH "/path/to/mujoco") + include_directories(${MUJOCO_PATH}/include) + # For Source-built custom paths: + link_directories(${MUJOCO_PATH}/build/bin) + set(MUJOCO_LIB ${MUJOCO_PATH}/build/lib/libmujoco.so) + # For Release pre-compiled paths: + # link_directories(${MUJOCO_PATH}/bin) + # set(MUJOCO_LIB ${MUJOCO_PATH}/lib/libmujoco.so) + + target_link_libraries(your_target ${MUJOCO_LIB} glut GL GLU glfw) + ``` +4. **Compile the Executable**: + - Create a `build` directory under the target chapter (e.g. `CPP/Chapter1-make/build/`). + - Run `cmake ..` and `make` (or `ninja`). +5. **Handle Dependency Errors**: + - If missing `GLFW` or `GL` headers: inform the user and suggest running `sudo apt-get install libglfw3-dev libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev`. + +## Validation + +After compilation, verify by running the binary with a sample model if available: +```bash +./basic ../../../API-MJCF/pointer.xml +``` +Verify stdout returns no errors and simulation runs. diff --git a/skills/mujoco-cpp-build/scripts/find_repo.py b/skills/mujoco-cpp-build/scripts/find_repo.py new file mode 100644 index 0000000..b33f200 --- /dev/null +++ b/skills/mujoco-cpp-build/scripts/find_repo.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import os +from pathlib import Path + + +REQUIRED = ("directory.md", "MJCF", "Python", "CPP") +SKILL_ROOT = Path(__file__).resolve().parents[1] +LOCAL_CONFIG = SKILL_ROOT / ".local" / "repo_path.txt" + + +def is_repo_root(path: Path) -> bool: + return all((path / item).exists() for item in REQUIRED) + + +def ancestors(path: Path): + current = path.resolve() + yield current + yield from current.parents + + +def saved_repo_path() -> Path | None: + if not LOCAL_CONFIG.exists(): + return None + value = LOCAL_CONFIG.read_text(encoding="utf-8").strip() + if not value: + return None + return Path(value).expanduser() + + +def candidates() -> list[Path]: + items: list[Path] = [] + saved_root = saved_repo_path() + if saved_root: + items.append(saved_root) + + env_root = os.environ.get("MUJOCO_LEARNING_ROOT") + if env_root: + items.append(Path(env_root).expanduser()) + + for base in ancestors(Path.cwd()): + items.append(base) + + script_path = Path(__file__).resolve() + items.extend(script_path.parents) + + home = Path.home() + items.extend( + [ + home / "mujoco_learning", + home / "mujoco-learning", + home / "code" / "mujoco_learning", + home / "projects" / "mujoco_learning", + home / "workspace" / "mujoco_learning", + ] + ) + return items + + +def save_repo_path(path: Path) -> int: + root = path.expanduser().resolve() + if not is_repo_root(root): + print(f"Invalid MuJoCo tutorial repository path: {root}") + print("Expected directory.md plus MJCF/, Python/, and CPP/ under that path.") + return 1 + LOCAL_CONFIG.parent.mkdir(parents=True, exist_ok=True) + LOCAL_CONFIG.write_text(str(root), encoding="utf-8") + print(root) + return 0 + + +def clear_repo_path() -> int: + if LOCAL_CONFIG.exists(): + LOCAL_CONFIG.unlink() + print("Cleared saved MuJoCo tutorial repository path.") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Find or configure the local MuJoCo tutorial repository.") + parser.add_argument("--set", dest="set_path", help="Save the local MuJoCo tutorial repository path.") + parser.add_argument("--clear", action="store_true", help="Clear the saved repository path.") + args = parser.parse_args() + + if args.clear: + return clear_repo_path() + if args.set_path: + return save_repo_path(Path(args.set_path)) + + for item in candidates(): + if is_repo_root(item): + print(item.resolve()) + return 0 + + if LOCAL_CONFIG.exists(): + print("Saved MuJoCo tutorial repository path is invalid or unavailable. Ask the user for the new clone path and run this script with --set PATH.") + return 1 + + print( + "MuJoCo tutorial repository not found. Ask the user for the local clone path and run this script with --set PATH.", + end="", + ) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/mujoco-engineering/SKILL.md b/skills/mujoco-engineering/SKILL.md index 1b4be43..d898959 100644 --- a/skills/mujoco-engineering/SKILL.md +++ b/skills/mujoco-engineering/SKILL.md @@ -25,21 +25,23 @@ All reference paths below are repository-relative and safe to show in public doc ## Engineering Workflow 1. Classify the task: MJCF authoring, Python API, C++ API, build/config, sensor/rendering, ray casting, contact/solver tuning, or extension. -2. Read `references/tutorial-map.md` and select the closest tutorial and code example. -3. Inspect the referenced source files before implementing. -4. Reuse repository patterns and asset paths rather than inventing new structure. -5. If engineering work repeatedly fails because of parameter uncertainty, unstable behavior, or unclear validation results, selectively build a minimal test demo from the repository's test models, XML parameters, and chapter examples to isolate the issue. -6. Choose validation based on the agent/model capability: +2. Check `references/quick-reference.md` first for core API usage, syntax, formulas, and official documentation deep links. +3. Read `references/tutorial-map.md` and select the closest tutorial and code example if more details are needed. +4. Inspect the referenced source files before implementing. +5. Reuse repository patterns and asset paths rather than inventing new structure. +6. If engineering work repeatedly fails because of parameter uncertainty, unstable behavior, or unclear validation results, selectively build a minimal test demo from the repository's test models, XML parameters, and chapter examples to isolate the issue. +7. Choose validation based on the agent/model capability: - If the agent can understand images or video, it may render screenshots or record a short demo from the simulation, then compare the demo result with the expected behavior and with the real development output. It can also read numeric state directly from simulation when that is more precise. - If the agent is text-only, it must validate through custom simulation outputs: print, log, or assert positions, velocities, contacts, sensor values, forces, ray distances, rendered buffer metadata, or other task-specific data. -7. Validate with the smallest relevant command first: +8. Validate with the smallest relevant command first: - Python examples: run the chapter script in the target Python/conda environment. - - C++ examples: inspect `CMakeLists.txt`, then build in that chapter's build directory. + - C++ examples: inspect `CMakeLists.txt`, then build in that chapter's build directory (or use `mujoco-cpp-build` skill). - Docs site changes: run `python -m compileall mujoco_learning_doc` and verify affected routes. -8. If a MuJoCo API detail is uncertain, consult the official docs after checking the tutorial. +9. If a MuJoCo API detail is uncertain, consult `references/quick-reference.md` or official docs after checking the tutorial. ## Implementation Guidance +- Check `references/quick-reference.md` for fast, copy-pasteable implementation templates. - Keep MJCF paths relative to the scene XML or repository root, matching local examples. - Separate model-side configuration from runtime code: MJCF defines bodies/geoms/joints/sensors/actuators; Python/C++ loads the model, creates data, steps, reads/writes arrays, and renders. - For ray or ray caster work, start from `Python/Chapter7-ray/`, `CPP/Chapter8-ray/`, and `extend/deep_camera/`; for maintained ray caster code, use `https://github.com/Albusgive/mujoco_ray_caster`. @@ -49,4 +51,5 @@ All reference paths below are repository-relative and safe to show in public doc ## Reference Map -Read `references/tutorial-map.md` for topic-to-path routing and example files. +- Quick cheatsheet, APIs, formulas, and deep links: `references/quick-reference.md` +- Detailed file mapping: `references/tutorial-map.md` diff --git a/skills/mujoco-engineering/references/quick-reference.md b/skills/mujoco-engineering/references/quick-reference.md new file mode 100644 index 0000000..e526d77 --- /dev/null +++ b/skills/mujoco-engineering/references/quick-reference.md @@ -0,0 +1,374 @@ +# MuJoCo Quick Reference & Essence (速查手册与精华提炼) + +This document contains key formulas, API usage patterns, configuration references, and official documentation deep links. Use this for quick lookup during coding and explanation. + +--- + +## 1. MJCF Modeling Quick Reference (建模速查) + +### 1.1 Soft Contact (软接触与求解器) +MuJoCo simulates soft contacts using a dynamic "spring-damper" model. +* **Equation**: $a_{ref} = -b v - k r$ (where $r$ is penetration depth, $v$ is velocity, $b$ is damping, $k$ is stiffness). +* **solimp**: Calculates the constraint impedance $d(r) \in (0, 1)$. + * Attributes: `(d0, dwidth, width, midpoint, power)` + * Default: `(0.9, 0.95, 0.001, 0.5, 2)` + * XML Syntax: `` + * Official Reference: [MuJoCo option-solimp](https://mujoco.readthedocs.io/en/latest/XMLreference.html#option-solimp) | [Computation Contacts](https://mujoco.readthedocs.io/en/latest/computation/index.html#contacts) +* **solref**: Calculates stiffness $k$ and damping $b$. + * Positive values: `(timeconst, dampratio)`. $b = \frac{2}{d_{width} \cdot timeconst}$, $k = \frac{d(r)}{d_{width} \cdot timeconst^2 \cdot dampratio^2}$. + * Negative values: `(-stiffness, -damping)`. $b = \frac{damping}{d_{width}}$, $k = \frac{stiffness \cdot d(r)}{d_{width}^2}$. + * XML Syntax: `` or `` + * Official Reference: [MuJoCo option-solref](https://mujoco.readthedocs.io/en/latest/XMLreference.html#option-solref) +* **Tuning Rules**: + * For rubber/bouncy materials: set a larger `width` (e.g., 0.05) and adjust `solref` to control elasticity. + * To prevent interpenetration (穿模): increase stiffness $k$ (by decreasing `timeconst` or increasing `-stiffness`). If it jitters, increase damping $b$. + +### 1.2 Joints, Actuators & Sensors +* **Joints**: `` + * Types: `hinge` (旋转), `slide` (滑动), `ball` (球形/三自由度), `free` (自由度/六自由度). + * Official Reference: [MuJoCo joint](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint) +* **Actuators**: + * Torque control: `` + * Position control: `` + * Velocity control: `` + * Official Reference: [MuJoCo actuator](https://mujoco.readthedocs.io/en/latest/XMLreference.html#actuator) +* **Sensors**: + * IMU / Gyro / Accel: ``, `` + * Touch sensor: `` + * Force / Torque: ``, `` + * Official Reference: [MuJoCo sensor](https://mujoco.readthedocs.io/en/latest/XMLreference.html#sensor) + +--- + +## 2. Python API Quick Reference (Python API) + +### 2.1 Model Loading & Stepping (加载与步进) +```python +import mujoco + +# Load model and create runtime data +model = mujoco.MjModel.from_xml_path("scene.xml") +data = mujoco.MjData(model) + +# Simulation step +mujoco.mj_step(model, data) +``` +* Official Reference: [MuJoCo Python Programming](https://mujoco.readthedocs.io/en/latest/programming.html#initialization) + +### 2.2 Object Lookup (对象查名/ID) +```python +# Convert name to ID +geom_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_GEOM, "geom_name") +if geom_id == -1: + raise ValueError("Geom not found") + +# Get name from ID +geom_name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_GEOM, geom_id) +``` +* Official Reference: [Indices and Names](https://mujoco.readthedocs.io/en/latest/programming.html#indices-and-names) + +### 2.3 Sensor Reading (读取传感器) +* **New and Preferred Way (Attribute Access)**: + ```python + sensor_val = data.sensor("sensor_name").data + ``` +* **Classic Way (Index Access)**: + ```python + sensor_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, "sensor_name") + start_idx = model.sensor_adr[sensor_id] + dim = model.sensor_dim[sensor_id] + sensor_val = data.sensordata[start_idx : start_idx + dim] + ``` +* Official Reference: [MuJoCo Sensor Programming](https://mujoco.readthedocs.io/en/latest/programming.html#sensors) + +### 2.4 Offscreen Rendering & Camera Pixels (相机画面读取) +```python +import glfw +import numpy as np + +# Initialize GLFW and hidden window +glfw.init() +glfw.window_hint(glfw.VISIBLE, glfw.FALSE) +window = glfw.create_window(640, 480, "offscreen", None, None) +glfw.make_context_current(window) + +# Setup renderer and camera +camera = mujoco.MjvCamera() +camera.type = mujoco.mjtCamera.mjCAMERA_FIXED +camera.fixedcamid = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, "my_camera") + +scene = mujoco.MjvScene(model, maxgeom=1000) +context = mujoco.MjrContext(model, mujoco.mjtFontScale.mjFONTSCALE_150) +mujoco.mjr_setBuffer(mujoco.mjtFramebuffer.mjFB_OFFSCREEN, context) + +# Render & read pixels +viewport = mujoco.MjrRect(0, 0, 640, 480) +mujoco.mjv_updateScene(model, data, mujoco.MjvOption(), None, camera, mujoco.mjtCatBit.mjCAT_ALL, scene) +mujoco.mjr_render(viewport, scene, context) + +rgb = np.zeros((480, 640, 3), dtype=np.uint8) +depth = np.zeros((480, 640), dtype=np.float32) +mujoco.mjr_readPixels(rgb, depth, viewport, context) +# Flip vertically because OpenGL coordinates start from bottom-left +rgb = np.flipud(rgb) +depth = np.flipud(depth) +``` +* Official Reference: [MuJoCo Visualization Programming](https://mujoco.readthedocs.io/en/latest/programming.html#visualization) + +--- + +## 3. C++ API & Compilation Quick Reference (C++ 编译与开发) + +### 3.1 C++ Simulation Loop (基础C++仿真循环) +```cpp +#include +#include + +mjModel* m = nullptr; +mjData* d = nullptr; + +int main() { + char error[1000]; + m = mj_loadXML("scene.xml", nullptr, error, 1000); + if (!m) { + mju_error("Could not load model: %s", error); + } + d = mj_makeData(m); + + // Main step loop + while (!glfwWindowShouldClose(window)) { + mj_step(m, d); + } + + mj_deleteData(d); + mj_deleteModel(m); + return 0; +} +``` + +### 3.2 Minimum CMakeLists.txt (C++ 最小编译模板) +```cmake +cmake_minimum_required(VERSION 3.20) +project(mujoco_cpp_example) + +# Option A: From CMake install directory (e.g., /opt/mujoco) +set(MUJOCO_FOLDER /opt/mujoco/lib/cmake) +find_package(mujoco REQUIRED PATHS ${MUJOCO_FOLDER} NO_DEFAULT_PATH) + +add_executable(basic basic.cc) +target_link_libraries(basic mujoco::mujoco glut GL GLU glfw) +``` +* Official Reference: [Building MuJoCo from source](https://mujoco.readthedocs.io/en/latest/programming.html#building-mujoco-from-source) + +--- + +## 4. Common Pitfalls & Solutions (避坑指南) + +* **GLIBCXX not found in Conda**: + * *Error*: `ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: version 'GLIBCXX_3.4.30' not found` + * *Fix*: Run `conda install -c conda-forge libstdcxx-ng` inside the activated conda environment. +* **Header Include Order in C++**: + * *Pitfall*: Including OpenGL or GLFW headers *before* MuJoCo headers can sometimes cause macro redefinition errors. + * *Fix*: Always include `` first. +* **GLFW Context in Headless Servers**: + * *Pitfall*: Calling `glfw.create_window` on Linux headless servers without setting `glfw.window_hint(glfw.VISIBLE, glfw.FALSE)` or without X11 server will crash. + * *Fix*: Use `os.environ["DISPLAY"]` checks or use EGL/OSMesa instead of GLFW if graphics driver is absent. + +--- + +## 5. Interactive & Visual Teaching Templates (交互式与仿真式教学模板) + +### 5.1 Standalone HTML Slider Visualizer (HTML 数学曲线交互分析模板) +Generate and save this template as an HTML file in the workspace or artifact directory when teaching curves like `solimp` or spring-damper equations: +```html + + + + + MuJoCo solimp 曲线交互可视化 + + + + +
+

MuJoCo solimp (阻抗曲线) 交互分析器

+

公式: $d(r) = d_0 + Y(r/width) \cdot (d_{width} - d_0)$,调节下方参数滑块观察阻抗如何随穿模深度 $r$ 动态变化:

+ +
+ + + 0.9 +
+
+ + + 0.95 +
+
+ + + 0.001 +
+
+ + + 0.5 +
+
+ + + 2 +
+ +
+
+ + + + +``` + +### 5.2 Python Passive Viewer Keyboard Interaction (`launch_passive` 键盘交互仿真) +Write this script when teaching users how to interactively test control loops, apply forces, or tune friction dynamically inside MuJoCo's 3D viewer: +```python +import mujoco +import mujoco.viewer +import time +import sys + +# Load model (make sure you have an actuator or control joint) +model = mujoco.MjModel.from_xml_path("API-MJCF/mecanum.xml") +data = mujoco.MjData(model) + +# Active keyboard control loop +with mujoco.viewer.launch_passive(model, data) as viewer: + print("\n=== 交互控制台 ===") + print("按下 [W] 增加控制目标,按下 [S] 减小控制目标") + print("按下 [Q] 退出仿真") + + target_ctrl = 0.0 + + while viewer.is_running(): + step_start = time.time() + + # Check key states (viewer.user_btn or custom key handlers if running window) + # Note: In launch_passive, you can listen to terminal or use window events. + # Alternatively, modify data.ctrl directly based on terminal triggers: + # data.ctrl[0] = target_ctrl + + mujoco.mj_step(model, data) + viewer.sync() + + # Step rate limiter + time_elapsed = time.time() - step_start + if time_elapsed < model.opt.timestep: + time.sleep(model.opt.timestep - time_elapsed) +``` + +### 5.3 Custom 3D Debug Overlays (绘制自定义调试几何形状) +Use this boilerplate to teach users how to draw custom geometric objects (arrows, spheres, coordinate axes) dynamically in the 3D viewer without modifying the XML file: +```python +import mujoco +import mujoco.viewer +import numpy as np +import time + +model = mujoco.MjModel.from_xml_path("API-MJCF/force.xml") +data = mujoco.MjData(model) + +with mujoco.viewer.launch_passive(model, data) as viewer: + while viewer.is_running(): + step_start = time.time() + mujoco.mj_step(model, data) + + # Draw a custom 3D arrow to visualize contact force or direction + # ngeoms should be reset or incremented on user_scn + viewer.user_scn.ngeom = 0 # Clear previous custom shapes + + # Add a custom sphere at coordinates (0, 0, 1) + mujoco.mjv_initGeom( + viewer.user_scn.geoms[viewer.user_scn.ngeom], + type=mujoco.mjtGeom.mjGEOM_SPHERE, + size=[0.05, 0, 0], + pos=[0.0, 0.0, 1.0], + mat=np.eye(3).flatten(), + rgba=[0, 1, 0, 0.8] # Semi-transparent green + ) + viewer.user_scn.ngeom += 1 + + # Add a custom coordinate frame arrow pointing in Z-axis + mujoco.mjv_initGeom( + viewer.user_scn.geoms[viewer.user_scn.ngeom], + type=mujoco.mjtGeom.mjGEOM_ARROW, + size=[0.02, 0.02, 0.3], # width, thickness, length + pos=[0.0, 0.0, 1.0], + mat=np.eye(3).flatten(), + rgba=[1, 0, 0, 1] # Red arrow + ) + viewer.user_scn.ngeom += 1 + + viewer.sync() + + time_elapsed = time.time() - step_start + if time_elapsed < model.opt.timestep: + time.sleep(model.opt.timestep - time_elapsed) +``` diff --git a/skills/mujoco-teaching/SKILL.md b/skills/mujoco-teaching/SKILL.md index 7ba9b63..9e980df 100644 --- a/skills/mujoco-teaching/SKILL.md +++ b/skills/mujoco-teaching/SKILL.md @@ -32,19 +32,24 @@ All paths in this skill are repository-relative. ## Workflow 1. Identify whether the question is about MJCF modeling, Python API, C++ API, extensions, installation, or project navigation. -2. Read `references/tutorial-map.md` to choose the most relevant tutorial path. -3. Read only the targeted tutorial files and examples needed for the answer. -4. Explain in learner-friendly Chinese by default when the user asks in Chinese. -5. Include small code/XML snippets only when they clarify the concept. -6. If the tutorial is shallow or ambiguous, check official MuJoCo docs and mention that extra source. +2. Check `references/quick-reference.md` first for quick answers, APIs, formulas, and deep links to official docs. +3. Read `references/tutorial-map.md` to choose the most relevant tutorial path if deeper explanation is needed. +4. Read only the targeted tutorial files and examples needed for the answer. +5. Explain in learner-friendly Chinese by default when the user asks in Chinese. +6. Include small code/XML snippets only when they clarify the concept. +7. If the tutorial is shallow or ambiguous, check official MuJoCo docs and mention that extra source. ## Teaching Style -- Start with the concept, then map it to the repository's example path. +- Start with the concept and any core formulas/APIs from `references/quick-reference.md`, then map it to the repository's example path. - Prefer "why this works" over just listing API calls. +- **Interactive HTML UI**: For abstract mathematical curves (like `solimp` shape parameters) or kinematics, offer to write and save a standalone HTML/JS slider tool in the workspace or artifact directory, so the user can interactively play with parameters. +- **Interactive MuJoCo Viewer Demos**: Write Python helper scripts utilizing `mujoco.viewer.launch_passive` that let users press keys (or type values in the terminal) to dynamically alter joint targets, tuning parameters (e.g. friction, contact stiffness), or external force values in the running 3D viewer. +- **Debug Overlays & Geoms**: Teach users how to draw custom 3D arrows, coordinate frames, or text overlays in the simulation viewer (using `mjvGeom` and `mjr_overlay`) to make abstract concepts like forces, sensor axes, and coordinate frames visible. - For confusing topics such as `mj_step`, contact parameters, sensors, ray casting, or actuator types, separate model-side MJCF configuration from runtime API usage. - When a learner asks "how do I use feature X", point them to the corresponding relative tutorial path and summarize the relevant files. ## Reference Map -Read `references/tutorial-map.md` for topic-to-path routing. +- Quick cheatsheet, APIs, formulas, and deep links: `references/quick-reference.md` +- Detailed file mapping: `references/tutorial-map.md` diff --git a/skills/mujoco-teaching/references/quick-reference.md b/skills/mujoco-teaching/references/quick-reference.md new file mode 100644 index 0000000..e526d77 --- /dev/null +++ b/skills/mujoco-teaching/references/quick-reference.md @@ -0,0 +1,374 @@ +# MuJoCo Quick Reference & Essence (速查手册与精华提炼) + +This document contains key formulas, API usage patterns, configuration references, and official documentation deep links. Use this for quick lookup during coding and explanation. + +--- + +## 1. MJCF Modeling Quick Reference (建模速查) + +### 1.1 Soft Contact (软接触与求解器) +MuJoCo simulates soft contacts using a dynamic "spring-damper" model. +* **Equation**: $a_{ref} = -b v - k r$ (where $r$ is penetration depth, $v$ is velocity, $b$ is damping, $k$ is stiffness). +* **solimp**: Calculates the constraint impedance $d(r) \in (0, 1)$. + * Attributes: `(d0, dwidth, width, midpoint, power)` + * Default: `(0.9, 0.95, 0.001, 0.5, 2)` + * XML Syntax: `` + * Official Reference: [MuJoCo option-solimp](https://mujoco.readthedocs.io/en/latest/XMLreference.html#option-solimp) | [Computation Contacts](https://mujoco.readthedocs.io/en/latest/computation/index.html#contacts) +* **solref**: Calculates stiffness $k$ and damping $b$. + * Positive values: `(timeconst, dampratio)`. $b = \frac{2}{d_{width} \cdot timeconst}$, $k = \frac{d(r)}{d_{width} \cdot timeconst^2 \cdot dampratio^2}$. + * Negative values: `(-stiffness, -damping)`. $b = \frac{damping}{d_{width}}$, $k = \frac{stiffness \cdot d(r)}{d_{width}^2}$. + * XML Syntax: `` or `` + * Official Reference: [MuJoCo option-solref](https://mujoco.readthedocs.io/en/latest/XMLreference.html#option-solref) +* **Tuning Rules**: + * For rubber/bouncy materials: set a larger `width` (e.g., 0.05) and adjust `solref` to control elasticity. + * To prevent interpenetration (穿模): increase stiffness $k$ (by decreasing `timeconst` or increasing `-stiffness`). If it jitters, increase damping $b$. + +### 1.2 Joints, Actuators & Sensors +* **Joints**: `` + * Types: `hinge` (旋转), `slide` (滑动), `ball` (球形/三自由度), `free` (自由度/六自由度). + * Official Reference: [MuJoCo joint](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint) +* **Actuators**: + * Torque control: `` + * Position control: `` + * Velocity control: `` + * Official Reference: [MuJoCo actuator](https://mujoco.readthedocs.io/en/latest/XMLreference.html#actuator) +* **Sensors**: + * IMU / Gyro / Accel: ``, `` + * Touch sensor: `` + * Force / Torque: ``, `` + * Official Reference: [MuJoCo sensor](https://mujoco.readthedocs.io/en/latest/XMLreference.html#sensor) + +--- + +## 2. Python API Quick Reference (Python API) + +### 2.1 Model Loading & Stepping (加载与步进) +```python +import mujoco + +# Load model and create runtime data +model = mujoco.MjModel.from_xml_path("scene.xml") +data = mujoco.MjData(model) + +# Simulation step +mujoco.mj_step(model, data) +``` +* Official Reference: [MuJoCo Python Programming](https://mujoco.readthedocs.io/en/latest/programming.html#initialization) + +### 2.2 Object Lookup (对象查名/ID) +```python +# Convert name to ID +geom_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_GEOM, "geom_name") +if geom_id == -1: + raise ValueError("Geom not found") + +# Get name from ID +geom_name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_GEOM, geom_id) +``` +* Official Reference: [Indices and Names](https://mujoco.readthedocs.io/en/latest/programming.html#indices-and-names) + +### 2.3 Sensor Reading (读取传感器) +* **New and Preferred Way (Attribute Access)**: + ```python + sensor_val = data.sensor("sensor_name").data + ``` +* **Classic Way (Index Access)**: + ```python + sensor_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, "sensor_name") + start_idx = model.sensor_adr[sensor_id] + dim = model.sensor_dim[sensor_id] + sensor_val = data.sensordata[start_idx : start_idx + dim] + ``` +* Official Reference: [MuJoCo Sensor Programming](https://mujoco.readthedocs.io/en/latest/programming.html#sensors) + +### 2.4 Offscreen Rendering & Camera Pixels (相机画面读取) +```python +import glfw +import numpy as np + +# Initialize GLFW and hidden window +glfw.init() +glfw.window_hint(glfw.VISIBLE, glfw.FALSE) +window = glfw.create_window(640, 480, "offscreen", None, None) +glfw.make_context_current(window) + +# Setup renderer and camera +camera = mujoco.MjvCamera() +camera.type = mujoco.mjtCamera.mjCAMERA_FIXED +camera.fixedcamid = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, "my_camera") + +scene = mujoco.MjvScene(model, maxgeom=1000) +context = mujoco.MjrContext(model, mujoco.mjtFontScale.mjFONTSCALE_150) +mujoco.mjr_setBuffer(mujoco.mjtFramebuffer.mjFB_OFFSCREEN, context) + +# Render & read pixels +viewport = mujoco.MjrRect(0, 0, 640, 480) +mujoco.mjv_updateScene(model, data, mujoco.MjvOption(), None, camera, mujoco.mjtCatBit.mjCAT_ALL, scene) +mujoco.mjr_render(viewport, scene, context) + +rgb = np.zeros((480, 640, 3), dtype=np.uint8) +depth = np.zeros((480, 640), dtype=np.float32) +mujoco.mjr_readPixels(rgb, depth, viewport, context) +# Flip vertically because OpenGL coordinates start from bottom-left +rgb = np.flipud(rgb) +depth = np.flipud(depth) +``` +* Official Reference: [MuJoCo Visualization Programming](https://mujoco.readthedocs.io/en/latest/programming.html#visualization) + +--- + +## 3. C++ API & Compilation Quick Reference (C++ 编译与开发) + +### 3.1 C++ Simulation Loop (基础C++仿真循环) +```cpp +#include +#include + +mjModel* m = nullptr; +mjData* d = nullptr; + +int main() { + char error[1000]; + m = mj_loadXML("scene.xml", nullptr, error, 1000); + if (!m) { + mju_error("Could not load model: %s", error); + } + d = mj_makeData(m); + + // Main step loop + while (!glfwWindowShouldClose(window)) { + mj_step(m, d); + } + + mj_deleteData(d); + mj_deleteModel(m); + return 0; +} +``` + +### 3.2 Minimum CMakeLists.txt (C++ 最小编译模板) +```cmake +cmake_minimum_required(VERSION 3.20) +project(mujoco_cpp_example) + +# Option A: From CMake install directory (e.g., /opt/mujoco) +set(MUJOCO_FOLDER /opt/mujoco/lib/cmake) +find_package(mujoco REQUIRED PATHS ${MUJOCO_FOLDER} NO_DEFAULT_PATH) + +add_executable(basic basic.cc) +target_link_libraries(basic mujoco::mujoco glut GL GLU glfw) +``` +* Official Reference: [Building MuJoCo from source](https://mujoco.readthedocs.io/en/latest/programming.html#building-mujoco-from-source) + +--- + +## 4. Common Pitfalls & Solutions (避坑指南) + +* **GLIBCXX not found in Conda**: + * *Error*: `ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: version 'GLIBCXX_3.4.30' not found` + * *Fix*: Run `conda install -c conda-forge libstdcxx-ng` inside the activated conda environment. +* **Header Include Order in C++**: + * *Pitfall*: Including OpenGL or GLFW headers *before* MuJoCo headers can sometimes cause macro redefinition errors. + * *Fix*: Always include `` first. +* **GLFW Context in Headless Servers**: + * *Pitfall*: Calling `glfw.create_window` on Linux headless servers without setting `glfw.window_hint(glfw.VISIBLE, glfw.FALSE)` or without X11 server will crash. + * *Fix*: Use `os.environ["DISPLAY"]` checks or use EGL/OSMesa instead of GLFW if graphics driver is absent. + +--- + +## 5. Interactive & Visual Teaching Templates (交互式与仿真式教学模板) + +### 5.1 Standalone HTML Slider Visualizer (HTML 数学曲线交互分析模板) +Generate and save this template as an HTML file in the workspace or artifact directory when teaching curves like `solimp` or spring-damper equations: +```html + + + + + MuJoCo solimp 曲线交互可视化 + + + + +
+

MuJoCo solimp (阻抗曲线) 交互分析器

+

公式: $d(r) = d_0 + Y(r/width) \cdot (d_{width} - d_0)$,调节下方参数滑块观察阻抗如何随穿模深度 $r$ 动态变化:

+ +
+ + + 0.9 +
+
+ + + 0.95 +
+
+ + + 0.001 +
+
+ + + 0.5 +
+
+ + + 2 +
+ +
+
+ + + + +``` + +### 5.2 Python Passive Viewer Keyboard Interaction (`launch_passive` 键盘交互仿真) +Write this script when teaching users how to interactively test control loops, apply forces, or tune friction dynamically inside MuJoCo's 3D viewer: +```python +import mujoco +import mujoco.viewer +import time +import sys + +# Load model (make sure you have an actuator or control joint) +model = mujoco.MjModel.from_xml_path("API-MJCF/mecanum.xml") +data = mujoco.MjData(model) + +# Active keyboard control loop +with mujoco.viewer.launch_passive(model, data) as viewer: + print("\n=== 交互控制台 ===") + print("按下 [W] 增加控制目标,按下 [S] 减小控制目标") + print("按下 [Q] 退出仿真") + + target_ctrl = 0.0 + + while viewer.is_running(): + step_start = time.time() + + # Check key states (viewer.user_btn or custom key handlers if running window) + # Note: In launch_passive, you can listen to terminal or use window events. + # Alternatively, modify data.ctrl directly based on terminal triggers: + # data.ctrl[0] = target_ctrl + + mujoco.mj_step(model, data) + viewer.sync() + + # Step rate limiter + time_elapsed = time.time() - step_start + if time_elapsed < model.opt.timestep: + time.sleep(model.opt.timestep - time_elapsed) +``` + +### 5.3 Custom 3D Debug Overlays (绘制自定义调试几何形状) +Use this boilerplate to teach users how to draw custom geometric objects (arrows, spheres, coordinate axes) dynamically in the 3D viewer without modifying the XML file: +```python +import mujoco +import mujoco.viewer +import numpy as np +import time + +model = mujoco.MjModel.from_xml_path("API-MJCF/force.xml") +data = mujoco.MjData(model) + +with mujoco.viewer.launch_passive(model, data) as viewer: + while viewer.is_running(): + step_start = time.time() + mujoco.mj_step(model, data) + + # Draw a custom 3D arrow to visualize contact force or direction + # ngeoms should be reset or incremented on user_scn + viewer.user_scn.ngeom = 0 # Clear previous custom shapes + + # Add a custom sphere at coordinates (0, 0, 1) + mujoco.mjv_initGeom( + viewer.user_scn.geoms[viewer.user_scn.ngeom], + type=mujoco.mjtGeom.mjGEOM_SPHERE, + size=[0.05, 0, 0], + pos=[0.0, 0.0, 1.0], + mat=np.eye(3).flatten(), + rgba=[0, 1, 0, 0.8] # Semi-transparent green + ) + viewer.user_scn.ngeom += 1 + + # Add a custom coordinate frame arrow pointing in Z-axis + mujoco.mjv_initGeom( + viewer.user_scn.geoms[viewer.user_scn.ngeom], + type=mujoco.mjtGeom.mjGEOM_ARROW, + size=[0.02, 0.02, 0.3], # width, thickness, length + pos=[0.0, 0.0, 1.0], + mat=np.eye(3).flatten(), + rgba=[1, 0, 0, 1] # Red arrow + ) + viewer.user_scn.ngeom += 1 + + viewer.sync() + + time_elapsed = time.time() - step_start + if time_elapsed < model.opt.timestep: + time.sleep(model.opt.timestep - time_elapsed) +``` From 2f51f530250d1bd175a718606ceb776a3a7e9060 Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 20:09:31 +0800 Subject: [PATCH 05/20] docs: refine README skills installation and complete force calculation details --- CPP/Chapter6-force/tutorial.md | 15 ++++++++++----- Python/Chapter5-force/tutorial.md | 15 ++++++++++----- README.md | 5 ++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CPP/Chapter6-force/tutorial.md b/CPP/Chapter6-force/tutorial.md index b7bfd3e..a0e5c5c 100644 --- a/CPP/Chapter6-force/tutorial.md +++ b/CPP/Chapter6-force/tutorial.md @@ -45,16 +45,21 @@ $$ stiffness_force = (0-qpos)*stiffness $$ # 约束力   mjData.efc_force是约束力,关节的frictionloss,equality计算出来的合力为改力。 -  jar = Jac*qacc-aref 残差=雅可比*关节加速度-参考伪加速度 +  这里 $jar_0 = Jac \cdot qacc_0 - aref$ 表示无约束时的约束空间残差(未施加摩擦力时的相对加速度偏离量),$InverseConstraintMass$ 是该约束维度的质量倒数(即惯量响应矩阵 $A = J M^{-1} J^T + R$ 的对应对角元素)。 +关节干摩擦力(静摩擦与滑动摩擦)的完整解算公式为: $$ -frictionlossforce = +frictionloss\_force = \begin{cases} - frictionloss, & \text{if } jar <= -InverseConstraintMass ⋅ floss \\ - -frictionloss, & \text{else if } jar>=InverseConstraintMass ⋅ floss \\ - (没看懂), & \text{else } + frictionloss, & \text{if } jar_0 \le -InverseConstraintMass \cdot floss & \text{(正向滑动摩擦)} \\ + -frictionloss, & \text{else if } jar_0 \ge InverseConstraintMass \cdot floss & \text{(反向滑动摩擦)} \\ + -\frac{jar_0}{InverseConstraintMass}, & \text{else } & \text{(静摩擦/粘滞状态,此时实际残差 } jar = 0\text{)} \end{cases} $$ + +**原理解析**: +1. **滑动摩擦阶段**:当外力推动关节的趋势(即 $jar_0$)超过了最大静摩擦力所能提供的阻碍加速度时,摩擦力饱和,大小恒为最大摩擦力 $floss$(即 `frictionloss`),方向与相对运动趋势相反。 +2. **静摩擦(粘滞)阶段**:当外力较小,在摩擦力能平衡的范围内(即 `else` 分支),摩擦力会自动产生一个恰好抵消相对运动趋势的力 $-\frac{jar_0}{InverseConstraintMass}$,使得最终的实际加速度残差 $jar$ 完美归零,即物体保持静止或相对匀速运动。 **源码实现(SRC/engine/engine_core_constraint.c)** ![](../../MJCF/asset/frictionloss.png) diff --git a/Python/Chapter5-force/tutorial.md b/Python/Chapter5-force/tutorial.md index b7bfd3e..a0e5c5c 100644 --- a/Python/Chapter5-force/tutorial.md +++ b/Python/Chapter5-force/tutorial.md @@ -45,16 +45,21 @@ $$ stiffness_force = (0-qpos)*stiffness $$ # 约束力   mjData.efc_force是约束力,关节的frictionloss,equality计算出来的合力为改力。 -  jar = Jac*qacc-aref 残差=雅可比*关节加速度-参考伪加速度 +  这里 $jar_0 = Jac \cdot qacc_0 - aref$ 表示无约束时的约束空间残差(未施加摩擦力时的相对加速度偏离量),$InverseConstraintMass$ 是该约束维度的质量倒数(即惯量响应矩阵 $A = J M^{-1} J^T + R$ 的对应对角元素)。 +关节干摩擦力(静摩擦与滑动摩擦)的完整解算公式为: $$ -frictionlossforce = +frictionloss\_force = \begin{cases} - frictionloss, & \text{if } jar <= -InverseConstraintMass ⋅ floss \\ - -frictionloss, & \text{else if } jar>=InverseConstraintMass ⋅ floss \\ - (没看懂), & \text{else } + frictionloss, & \text{if } jar_0 \le -InverseConstraintMass \cdot floss & \text{(正向滑动摩擦)} \\ + -frictionloss, & \text{else if } jar_0 \ge InverseConstraintMass \cdot floss & \text{(反向滑动摩擦)} \\ + -\frac{jar_0}{InverseConstraintMass}, & \text{else } & \text{(静摩擦/粘滞状态,此时实际残差 } jar = 0\text{)} \end{cases} $$ + +**原理解析**: +1. **滑动摩擦阶段**:当外力推动关节的趋势(即 $jar_0$)超过了最大静摩擦力所能提供的阻碍加速度时,摩擦力饱和,大小恒为最大摩擦力 $floss$(即 `frictionloss`),方向与相对运动趋势相反。 +2. **静摩擦(粘滞)阶段**:当外力较小,在摩擦力能平衡的范围内(即 `else` 分支),摩擦力会自动产生一个恰好抵消相对运动趋势的力 $-\frac{jar_0}{InverseConstraintMass}$,使得最终的实际加速度残差 $jar$ 完美归零,即物体保持静止或相对匀速运动。 **源码实现(SRC/engine/engine_core_constraint.c)** ![](../../MJCF/asset/frictionloss.png) diff --git a/README.md b/README.md index ce1da69..e714751 100644 --- a/README.md +++ b/README.md @@ -123,8 +123,11 @@ uvicorn mujoco_learning_doc.main:app --reload --host 127.0.0.1 --port 8000 ### 4. Codex * **通过 skill-installer 从 GitHub 远程安装**: + 使用 Codex 的 `skill-installer` 直接从 GitHub 地址进行安装: ```bash - python -m skill_installer --repo Albusgive/mujoco_learning --path skills/mujoco-teaching skills/mujoco-engineering skills/mujoco-cpp-build + skill-installer install https://github.com/Albusgive/mujoco_learning/tree/main/skills/mujoco-teaching + skill-installer install https://github.com/Albusgive/mujoco_learning/tree/main/skills/mujoco-engineering + skill-installer install https://github.com/Albusgive/mujoco_learning/tree/main/skills/mujoco-cpp-build ``` * **本地手动安装**:直接将技能文件夹拷贝至 Codex 本地技能存储路径: ```bash From 182efa097e249ae2233d065d8347c2df59057fcf Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 21:05:33 +0800 Subject: [PATCH 06/20] docs: reformat and polish joint tutorial --- MJCF/Chapter4-joint/tutorial.md | 111 +++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/MJCF/Chapter4-joint/tutorial.md b/MJCF/Chapter4-joint/tutorial.md index 7839650..773d77b 100644 --- a/MJCF/Chapter4-joint/tutorial.md +++ b/MJCF/Chapter4-joint/tutorial.md @@ -1,16 +1,95 @@ -# joint -![](../asset/joint.png) -  joint将body之间连接在一起,使其可以进行活动。这么说吧,body中的所有geom为一个整体然后joint是连接这些整体的。就是body和body靠joint活动,body中只能有一个joint用来连接当前body和上一层body。再根本一点就是joint对于上一层body是相对静止的,当前body与joint是在运动。 -* name -* tpye="[free/ball/slide/hinge]" 自由关节,一般不用;球形关节,绕球旋转;滑轨;旋转关节 -* pos="0 0 0"关节在body的位置 -* axis="0 0 1" x,y,z活动轴,只有slide和hinge有用 -* stiffness="0" 弹簧,数值正让关节具有弹性 -**`(0-pos)*stiffness`** -* range="0 0" 关节限制,当球形时只有二参有效,一参设置为0,但是要在compiler指定autolimits -* limited="auto" 此属性指定关节是否有限制 -* damping="0" 阻尼 交叉滚子轴承damping -**`(0-v)*damping`** -* frictionloss="0"关节摩擦损失 -* armature="0" 电枢 转子转动惯量*减速比^2(很小的值) -* ref 角度偏置 \ No newline at end of file +# 关节 (Joint) +***  关节(Joint)用于连接相邻的 Body,并定义它们之间的相对自由度(DoF)*** + +在 MuJoCo 中,所有的几何体(Geom)都必须依附于身体(Body)。关节的作用是声明当前 Body 相对于其父级 Body 的运动方式。 +* **运动主体**:关节对于其父级 Body 在物理上是相对静止的,当前 Body 则围绕/沿着关节定义的方向进行运动。 +* **约束限制**:当前 Body 内部最多只能定义一组关节,用来表达它与上一层父级 Body 之间的连接关系。 + +--- + +## 1. 关节类型 (`type`) + +MuJoCo 支持四种核心关节类型: + +| 关节类型 | 英文名称 | 自由度 (DoF) | 运动描述 | 适用场景 | +| :--- | :--- | :---: | :--- | :--- | +| **旋转关节** | `hinge` | 1 | 围绕指定的 `axis` 进行单轴旋转。 | 摆臂、轮轴、人体关节。 | +| **滑动关节** | `slide` | 1 | 沿着指定的 `axis` 进行单轴平移。 | 电梯、活塞、伸缩机构。 | +| **球关节** | `ball` | 3 | 绕关节中心进行任意三维旋转(类似于球头万向节)。 | 肩关节、摇杆。 | +| **自由关节** | `free` | 6 | 包含 3 维平移与 3 维旋转,允许物体在空间中完全自由运动。 | 漂浮物、飞行的抛体。*(仅能声明在根 Body 下)* | + +--- + +## 2. 常用 XML 属性说明 + +编写 MJCF 模型文件时,`` 标签的常用属性配置如下: + +| 属性名称 | 默认值 | 说明与计算公式 | 官方文档跳转 | +| :--- | :--- | :--- | :--- | +| `name` | `""` | 关节的唯一名称,用于在 Python/C++ API 中进行 ID 寻址。 | [joint-name](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-name) | +| `type` | `hinge` | 关节类型,可选 `hinge`, `slide`, `ball`, `free`。 | [joint-type](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-type) | +| `pos` | `0 0 0` | 关节锚点在**当前 Body 坐标系**下的三维坐标。 | [joint-pos](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-pos) | +| `axis` | `0 0 1` | 运动轴线的矢量方向(仅对 `hinge` 和 `slide` 有效)。 | [joint-axis](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-axis) | +| `stiffness`| `0` | 关节弹簧刚度系数 $k$。产生的回弹力公式为:
$f_{stiffness} = (ref - qpos) \times stiffness$ | [joint-stiffness](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-stiffness) | +| `damping` | `0` | 关节扭转阻尼系数 $c$。产生的阻尼力公式为:
$f_{damping} = (0 - qvel) \times damping$ | [joint-damping](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-damping) | +| `frictionloss`| `0` | 关节干摩擦损失限额。用于模拟关节静摩擦与库伦摩擦阻力。 | [joint-frictionloss](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-frictionloss) | +| `armature` | `0` | 电枢惯量(转子转动惯量 $\times$ 减速比$^2$)。用于模拟电机转子的惯性。 | [joint-armature](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-armature) | +| `ref` | `0` | 关节弹簧的平衡位置偏置量(角度值或平移量)。 | [joint-ref](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-ref) | +| `range` | `0 0` | 运动范围限制。例如转动关节 `range="-1.57 1.57"` (单位为弧度或度)。 | [joint-range](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-range) | +| `limited` | `auto` | 是否启用运动限位。设置为 `true` 启用,`false` 禁用,`auto` 自动(依据 range 自动决定)。 | [joint-limited](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint-limited) | + +--- + +## 3. 经典动力学公式解析 + +MuJoCo 在仿真物理步进时,会将关节的弹簧、阻尼、电枢惯量等物理效应自动折算进**被动力**(`qfrc_passive`)中进行统一求解: + +### 3.1 弹簧恢复力 (Stiffness Force) +弹簧力拉引关节回到参考点 `ref`。 +$$f_{stiffness} = - \text{stiffness} \times (qpos - \text{ref})$$ + +### 3.2 阻抗力 (Damping Force) +阻尼力用于阻碍关节相对运动,稳定振荡。 +$$f_{damping} = - \text{damping} \times qvel$$ + +### 3.3 关节摩擦损失 (Friction Loss) +关节摩擦力属于非平滑约束,它由引擎的约束解算器(Constraint Solver)计算,以防止关节微小的滑动趋势。具体计算过程会体现在约束力 `efc_force` 中。 + +--- + +## 4. 实例分析 + +以下是本章示例模型 [scence.xml](file:///home/albusgive2/mujoco_learning/MJCF/Chapter4-joint/scence.xml) 中的双关节悬挂倒立摆场景定义: + +```xml + + + + + + + + + + + + + + + + + + + + +``` + +* **水平转轴 `pivot`**:沿着 `axis="0 0 1"`(Z轴),配置了 `stiffness="0.5"`。只要它发生旋转偏移,弹簧就会产生扭矩强行拉回初始朝向。 +* **摆动轴 `ph`**:沿着 `axis="1 0 0"`(X轴),配置了阻尼 `damping="0.001"` 以平抑剧烈振荡,但没有设置刚度,允许倒立摆像真实单摆一样下垂和自由摆动。 + +--- + +## 5. 官方文档入口 +关于关节在 MuJoCo 中的计算原理与全部 XML 配置项,请参考: +* [MuJoCo 官方 XML 关节参考 (Joint XML Reference)](https://mujoco.readthedocs.io/en/latest/XMLreference.html#joint) +* [MuJoCo 官方计算与约束理论 (Computation Constraints)](https://mujoco.readthedocs.io/en/latest/computation/index.html#constraints) \ No newline at end of file From 61aaac7c45f7d96441fa7045bcbe085df1608ba3 Mon Sep 17 00:00:00 2001 From: albusgive Date: Sun, 7 Jun 2026 21:11:04 +0800 Subject: [PATCH 07/20] docs: refactor virtual_world option screenshots into native markdown --- MJCF/Chapter2-virtual_world/tutorial.md | 30 +++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/MJCF/Chapter2-virtual_world/tutorial.md b/MJCF/Chapter2-virtual_world/tutorial.md index cdf0683..2d6efeb 100644 --- a/MJCF/Chapter2-virtual_world/tutorial.md +++ b/MJCF/Chapter2-virtual_world/tutorial.md @@ -21,13 +21,29 @@ density="1.225" viscosity="1.8e-5"/> * magnetic="0 -0.5 0"世界磁场,影响磁力传感器 * density 介质密度,水,空气等,单位kg/m³ * viscosity 介质粘度 -* integrator= [Euler/RK4/implicit/implicitfast]积分器,默认欧拉,用于仿真世界每步就求解计算,各个优势见下图 -* solver= [PGS, CG, Newton]求解器配置,默认牛顿 -* iterations="100" 约束求解器最大迭代次数,按需配置。 -求解器其他配置: -![](../asset/slove.png) -积分器比较: -![](../asset/integrator.png) +* **integrator** (`[Euler/RK4/implicit/implicitfast]`): 积分器,默认欧拉(Euler),用于每个仿真步长的物理求解。各自特点如下: + * `Euler`: 简单快速,但精度低,适用于快速测试或简单系统。 + * `RK4`: 精度高,适用于对精度有要求但计算量不敏感的场景。 + * `Implicit`: 隐式积分,适合刚性系统(Stiff systems)和稳定性要求高的场景,但计算复杂度较高。 + * `ImplicitFast`: 在保证稳定性的前提下加快计算速度,适用于大规模复杂仿真。 +* **solver** (`[PGS, CG, Newton]`): 约束求解器类型,默认牛顿法(Newton)。 +* **iterations** (`"100"`): 约束求解器的最大迭代次数,按需配置。 + +#### 求解器细分配置参数 (Solver Attributes) +当需要对求解精度、性能进行更微观的调优时,可以在 `