diff --git a/assets/readme/manipulation.mp4 b/assets/readme/manipulation.mp4 new file mode 100644 index 0000000000..28fed555e3 --- /dev/null +++ b/assets/readme/manipulation.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb60f244f7738bc2bab1ed46018cad54120f23995b8d3ba2e2fdcfc4454476a7 +size 748333 diff --git a/assets/readme/teleop.mp4 b/assets/readme/teleop.mp4 new file mode 100644 index 0000000000..bd3c72254b --- /dev/null +++ b/assets/readme/teleop.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b022a44cdb1ba1a4e4f613e03fd23b81253a5563f690b8aebefa139f2aef8aba +size 2385159 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..d3a4446ae1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,85 @@ +# Welcome to dimOS + +**dimOS** is an open source operating system for generalist robotics, built by Dimensional. Write one Python program and run it on a quadruped, a humanoid, a drone, or an arm. + +dimOS is also agent native. Every skill a robot exposes is also an MCP tool. An LLM coding agent can discover those tools and call them directly. + +## The problem + +Every robot ships its own SDK and the code written for one rarely survives the move to another. ROS is the usual way out, but you take on its build system and its runtime whole, before writing any behaviour of your own. Most of it also needs the hardware, so your work waits on a robot that is shared, remote, or still in a box. + +## How dimOS solves it + +- **Behaviour lives in modules, not in an SDK.** A module does one job. It declares the streams it needs as typed fields, so a module written for a quadruped runs on an arm once something publishes what it asked for. + +- **No workspace to build.** dimOS installs with pip on your laptop and reaches the robot over the network. Streams travel over LCM, shared memory, DDS, Zenoh, or ROS 2. You pick the transport per stream, and ROS is one option rather than the foundation. + +- **Nothing waits on hardware.** The same blueprint runs against a recorded session or in MuJoCo before it runs on a robot. The behaviour you debug is the behaviour that ships. + +- **Agents are modules too.** An LLM subscribes to the same streams your control loops use. It calls the same skills, and it takes its instructions in plain language. + +
+ +- :material-rocket-launch: **[Quickstart](/docs/quickstart.md)** + + Install dimOS and replay a real robot session on your laptop in a few minutes. No robot needed. + +
+ +## Capabilities + +Real robots, running dimOS. + +
+ +- [![Navigation and mapping](/assets/readme/navigation.gif)](https://x.com/stash_pomichter/status/2010471593806545367) + + **[Navigation and mapping](/docs/capabilities/navigation/index.md)** + + SLAM, dynamic obstacle avoidance, route planning, and autonomous exploration. + +- [![Agentic control](/assets/readme/agentic_control.gif)](https://x.com/stash_pomichter/status/2015912688854200322) + + **[Agentic control and MCP](/docs/capabilities/agents/index.md)** + + Talk to the robot. Every skill is also exposed as an MCP tool. + +- [![Spatial memory](/assets/readme/spatial_memory.gif)](https://x.com/stash_pomichter/status/1980741077205414328) + + **[Spatial memory](/docs/capabilities/memory/index.md)** + + Spatio-temporal RAG, object localization, and permanence. Navigate back to what the robot saw. + +- + + **[Remote teleoperation](/docs/capabilities/teleoperation/hosted.md)** + + **dimTELE** gives you remote control of any robot, from anywhere in the world, at ultra-low latency. + +- [![Perception](/assets/readme/perception.png)](/docs/capabilities/perception/index.md) + + **[Perception](/docs/capabilities/perception/index.md)** + + Detect objects in camera, place them in 3D, with VLMs and audio built in. + +- + + **[Manipulation](/docs/capabilities/manipulation/index.md)** + + Scan the scene, generate grasp candidates, plan, and execute from a spoken instruction. + +
+ +## Start here + +
+ +- :material-rocket-launch: **[Quickstart](/docs/quickstart.md)** + + Replay a real robot session on your laptop. No robot needed. + +- :material-chip: **[Platforms](/docs/platforms/quadruped/go2/index.md)** + + Unitree Go2 and G1 setup, in simulation and on real hardware. + +
diff --git a/misc/mkdocs/hooks.py b/misc/mkdocs/hooks.py index 67ac3e6a0f..4a5e95145a 100644 --- a/misc/mkdocs/hooks.py +++ b/misc/mkdocs/hooks.py @@ -33,7 +33,7 @@ THEME_CSS = Path(__file__).with_name("theme.css") REPO_ROOT = Path(__file__).resolve().parents[2] -README = REPO_ROOT / "README.md" +# The home page's demo gifs, shared with the readme rather than copied. README_ASSETS = REPO_ROOT / "assets" / "readme" GITHUB_BLOB = "https://github.com/dimensionalOS/dimos/blob/main" @@ -143,75 +143,14 @@ def _rewrite_link(match: re.Match[str], src_uri: str) -> str: return match.group(0) -def _page_url(path: str) -> str: - """docs/usage/modules.md -> usage/modules/, the url mkdocs actually builds. - - Only needed for raw html attributes: mkdocs rewrites markdown links itself, - but never looks inside an or an . - """ - page = re.sub(r"^docs/", "", path).removesuffix(".md") - page = re.sub(r"(^|/)index$", r"\1", page) - return page.rstrip("/") + "/" if page else "." - - -def _readme_as_home() -> str: - """The repo README, with its links pointed at the site instead of github.""" - text = README.read_text(encoding="utf-8") - - # Badges, star counts and the trendshift ribbon are furniture for a repo - # landing page. Here they are a wall of images above the first sentence. - text = re.sub(r"^\[!\[.*img\.shields\.io.*$", "", text, flags=re.M) - text = re.sub(r"^!\[.*img\.shields\.io.*$", "", text, flags=re.M) - text = re.sub(r"^]*spacer\.png[^>]*>", "", text) - - # is deprecated, and being an inline tag it re-blocks markdown even - # inside a div that asked for it. Material sizes the banner text anyway. - text = text.replace("", "").replace("", "") - - # Github renders markdown inside a raw html block; python-markdown only - # does so when the tag asks for it, which is what md_in_html reads. - # Only on
: md_in_html does not treat a as a markdown block, so - # the attribute would survive into the output as a stray one. - text = re.sub(r"]*\bmarkdown=)([^>]*)>", r'', text) - - # Raw html links into the docs tree, which mkdocs leaves alone. - text = re.sub( - r'(href=")docs/([^"]+\.md)"', lambda m: f'{m.group(1)}{_page_url(m.group(2))}"', text - ) - # Raw html links to source, which have no page on the site at all. - text = re.sub( - r'(href=")((?:' + "|".join(SOURCE_ROOTS) + r')/[^"]*)"', - lambda m: f'{m.group(1)}{GITHUB_BLOB}/{m.group(2)}"', - text, - ) - - # docs/usage/modules.md -> usage/modules.md, since the site root is docs/. - text = re.sub(r"\]\(docs/", "](", text) - # Anything else in the repo is source, and lives on github. - text = re.sub( - r"\]\((AGENTS\.md|CONTRIBUTING\.md|LICENSE|(?:dimos|examples|bin|native|misc|scripts)/[^)#]*)", - lambda m: f"]({GITHUB_BLOB}/{m.group(1)}", - text, - ) - # The readme opens with a centred banner rather than a heading, so give the - # page a title for the nav and the browser tab. - return f'---\ntitle: "Welcome to dimOS"\n---\n\n{text}' - - def on_files(files, config): - """Ship the theme and the readme-as-home without adding files to docs/.""" + """Ship the theme and the shared demo assets without adding files to docs/.""" from mkdocs.structure.files import File files.append(File.generated(config, "assets/mkdocs-theme.css", content=THEME_CSS.read_text())) - files.append(File.generated(config, "index.md", content=_readme_as_home())) - # The readme's screenshots live outside docs/, so pull them in by path - # rather than copying 43MB into the docs tree. + # The home page's screenshots live outside docs/, beside the readme's, so + # pull them in by path rather than copying 43MB into the docs tree. for asset in sorted(README_ASSETS.glob("*")): if asset.is_file(): files.append( diff --git a/misc/mkdocs/theme.css b/misc/mkdocs/theme.css index 451ad855b0..1b4f850bd5 100644 --- a/misc/mkdocs/theme.css +++ b/misc/mkdocs/theme.css @@ -21,7 +21,6 @@ --dim-radius-sm: 0.375rem; --dim-radius-lg: 1rem; --dim-radius-pill: 9999px; - --dim-hairline: color-mix(in srgb, var(--md-default-fg-color) 12%, transparent); --dim-nav-title: #ffffff; } @@ -36,6 +35,13 @@ --md-code-bg-color: #131923; --md-footer-bg-color: #0a0e14; --md-footer-bg-color--dark: #080b10; + + /* Hairlines, applied consistently to every raised surface. Declared here + * rather than at :root: material puts the scheme on , so a custom + * property that reads --md-default-fg-color from :root resolves against + * material's light-mode default and freezes to black — invisible on this + * background, and inherited that way by every border on the site. */ + --dim-hairline: color-mix(in srgb, var(--md-default-fg-color) 12%, transparent); } /* ---------------------------------------------------------------- layout */ @@ -44,7 +50,7 @@ * nobody can track back from. Sidebars are fixed, so the middle column takes * every pixel this cap frees up. */ .md-grid { - max-width: min(84rem, 100%); + max-width: min(72rem, 100%); } /* Reading measure only applies to prose. Code, tables, and figures keep the @@ -53,7 +59,21 @@ .md-typeset > ul, .md-typeset > ol, .md-typeset > blockquote { - max-width: 52rem; + max-width: 44rem; +} + +/* Once the table of contents is showing there is room to inset the article + * from both sidebars, so the text sits in the middle rather than running up + * against the nav. The rule runs down the article rather than the sidebar, + * because the sidebar is sticky and only as tall as its own scrollwrap. */ +@media screen and (min-width: 76.25em) { + .md-content { + border-left: 1px solid var(--dim-hairline); + } + + .md-content__inner { + padding-inline: 2.4rem; + } } /* A little more room for the nav tree; deep paths like @@ -324,6 +344,120 @@ transform: translateY(-2px); } +/* Six capability cards, three to a row so the grid stays two tidy rows. The + * columns belong on the wrapper: material gives `.grid` the tracks and sets + * the inner ul to display:contents, so a rule on the ul would do nothing. */ +.md-typeset .dim-media { + grid-template-columns: repeat(2, 1fr); +} + +@media screen and (min-width: 60em) { + .md-typeset .dim-media { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Narrower cards want less air and smaller supporting text than material's + * default, or three across turns into three columns of wrapped fragments. */ +.md-typeset .grid.cards.dim-media > ul > li { + --dim-card-pad: 0.6rem; + padding: var(--dim-card-pad); +} + +.md-typeset .dim-media > ul > li > p + p { + font-size: 0.72rem; + color: var(--md-default-fg-color--light); +} + +/* A card that leads with a demo clip: the clip is the card's top edge, so it + * cancels material's card padding and takes the card's own corners. */ +.md-typeset .grid.cards > ul > li { + --dim-card-pad: 0.8rem; +} + +.md-typeset .grid.cards > ul > li > p:first-child:has(> a > :is(img, video)) { + margin: calc(-1 * var(--dim-card-pad)) calc(-1 * var(--dim-card-pad)) 0.7rem; +} + +/* The clips are captured at whatever the source window was — 1:1, 16:10, 16:9 — + * so crop to one ratio rather than let each card set its own height. */ +.md-typeset .grid.cards > ul > li > p:first-child > a > :is(img, video) { + display: block; + width: 100%; + aspect-ratio: 16 / 9; + object-fit: cover; + border-radius: var(--dim-radius) var(--dim-radius) 0 0; +} + +/* ----------------------------------------------------------------- home */ + +/* One wide signpost rather than a row of them: a single full-width card with + * the icon floated beside the link and the line under it. */ +.md-typeset .dim-cta { + grid-template-columns: 1fr; +} + +.md-typeset .grid.cards.dim-cta > ul > li { + padding: 1.1rem 1.3rem; +} + +/* Floated so the summary wraps beside the icon too, not just the title. */ +.md-typeset .dim-cta .twemoji { + float: left; + width: 1.7em; + height: 1.7em; + margin: 0.1rem 0.9rem 0 0; + color: var(--md-accent-fg-color); +} + +.md-typeset .dim-cta .twemoji svg { + width: 100%; + height: 100%; +} + +.md-typeset .dim-cta > ul > li > p + p { + margin-top: 0.1rem; + color: var(--md-default-fg-color--light); +} + +/* The capability grid above carries a clip per card, so the two calls to + * action need to read as a different kind of card rather than a third row of + * the same one: filled surface, no outline, icon promoted above the link. */ +/* These two are the end of the page, so they get room rather than being the + * tightest cards on it: a floor on the height, and the content centred in + * whatever height wins. */ +.md-typeset .grid.cards.dim-tiles > ul > li { + display: flex; + flex-direction: column; + justify-content: center; + min-height: 9rem; + background: var(--md-default-bg-color--light); + border-color: transparent; + padding: 1.6rem; +} + +.md-typeset .grid.cards.dim-tiles > ul > li:hover { + border-color: transparent; + background: color-mix(in srgb, var(--md-accent-fg-color) 8%, var(--md-default-bg-color--light)); +} + +.md-typeset .dim-tiles .twemoji { + display: block; + margin-bottom: 0.5rem; + color: var(--md-accent-fg-color); +} + +.md-typeset .dim-tiles .twemoji svg { + width: 1.4em; + height: 1.4em; +} + +/* The summary is supporting text here, not body copy. */ +.md-typeset .dim-tiles > ul > li > p + p { + font-size: 0.72rem; + color: var(--md-default-fg-color--light); +} + /* --------------------------------------------------------------- footer */ .md-footer {