Skip to content

Commit 0ee31cd

Browse files
JalajShuklaSSJalaj Shukla
andauthored
GraspGen generator with docker dimos module integration (#1119)
* working graspgen with footer docker conversion * Add GraspGen model checkpoints to LFS * refactor: reorganize grasping module to dimos/manipulation/grasping - Move grasping module from dimos/grasping/ to dimos/manipulation/grasping/ - Rename docker_module.py to docker_runner.py with simplified implementation- Isolated the docker container not to have any langchain dependency - Add proper docstrings with Args for OpenAI function calling - Simplify visualize_grasps.py to minimal debug tool - Add get_object_pointcloud_by_object_id RPC for object ID lookups * fix: resolve mypy type errors and cleanup unused files Type fixes: - Add type annotations for ndarray, list, dict parameters - Add type: ignore comments for Docker-only imports (torch, grasp_gen) - Fix RPC call args from tuple to list in docker_runner.py - Add Iterator return type to PoseArray.__iter__ - Fix import order for ruff compliance Cleanup: - Delete unused objscene_registreation_myversion.py - Update .dockerignore to only allow data/.lfs/ * fix: CI failures - test fixes, blueprints update, and formatting Fixes: - Update test_classmethods to expect 10 RPCs (matches current Module base class) - Regenerate all_blueprints.py to include new grasping_module Formatting (ruff/pre-commit auto-fixes): - Apply import ordering and TYPE_CHECKING patterns across grasping modules - Fix whitespace and blank line formatting - Update JSON indentation in calibration.json - Regenerate uv.lock * fix: move type ignore comment to correct line for multi-line import * ci: trigger CI run --------- Co-authored-by: Jalaj Shukla <shuklajalaj98@gmail.com>
1 parent 168aef4 commit 0ee31cd

File tree

23 files changed

+1753
-13
lines changed

23 files changed

+1753
-13
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ build/
4343
.env.local
4444
.env.*.local
4545

46-
# Large data files
46+
# Large data files (LFS archives only)
4747
data/*
4848
!data/.lfs/
4949

data/.lfs/models_graspgen.tar.gz

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:058ff764c043dccc516c1519a1e23207500c20a10c432c15eb5e30104477c0a4
3+
size 2117602984

dimos/core/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ def deploy( # type: ignore[no-untyped-def]
100100
*args,
101101
**kwargs,
102102
) -> ModuleProxy:
103+
from dimos.core.docker_runner import DockerModule, is_docker_module
104+
105+
# Check if this module should run in Docker (based on its default_config)
106+
if is_docker_module(actor_class):
107+
logger.info("Deploying module in Docker.", module=actor_class.__name__)
108+
dm = DockerModule(actor_class, *args, **kwargs)
109+
dm.start() # Explicit start - follows create -> configure -> start lifecycle
110+
dask_client._docker_modules.append(dm) # type: ignore[attr-defined]
111+
return dm # type: ignore[return-value]
112+
103113
logger.info("Deploying module.", module=actor_class.__name__)
104114
actor = dask_client.submit( # type: ignore[no-untyped-call]
105115
actor_class,
@@ -175,6 +185,14 @@ def close_all() -> None:
175185
return
176186
dask_client._closed = True # type: ignore[attr-defined]
177187

188+
# Stop all Docker modules (in reverse order of deployment)
189+
for dm in reversed(dask_client._docker_modules): # type: ignore[attr-defined]
190+
try:
191+
dm.stop()
192+
except Exception:
193+
pass
194+
dask_client._docker_modules.clear() # type: ignore[attr-defined]
195+
178196
# Stop all SharedMemory transports before closing Dask
179197
# This prevents the "leaked shared_memory objects" warning and hangs
180198
try:
@@ -226,6 +244,7 @@ def close_all() -> None:
226244
# This is needed, solves race condition in CI thread check
227245
time.sleep(0.1)
228246

247+
dask_client._docker_modules = [] # type: ignore[attr-defined]
229248
dask_client.deploy = deploy # type: ignore[attr-defined]
230249
dask_client.check_worker_memory = check_worker_memory # type: ignore[attr-defined]
231250
dask_client.stop = lambda: dask_client.close() # type: ignore[attr-defined, no-untyped-call]

dimos/core/docker_build.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Copyright 2025-2026 Dimensional Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Docker image building and Dockerfile conversion utilities.
17+
Converts any Dockerfile into a DimOS module container by appending a footer
18+
that installs DimOS and creates the module entrypoint.
19+
"""
20+
21+
from __future__ import annotations
22+
23+
import subprocess
24+
from typing import TYPE_CHECKING
25+
26+
from dimos.utils.logging_config import setup_logger
27+
28+
if TYPE_CHECKING:
29+
from pathlib import Path
30+
31+
from dimos.core.docker_runner import DockerModuleConfig
32+
33+
logger = setup_logger()
34+
35+
# Timeout for quick Docker commands
36+
DOCKER_CMD_TIMEOUT = 20
37+
38+
# Sentinel value to detect already-converted Dockerfiles (UUID ensures uniqueness)
39+
DIMOS_SENTINEL = "DIMOS-MODULE-CONVERSION-427593ae-c6e8-4cf1-9b2d-ee81a420a5dc"
40+
41+
# Footer appended to Dockerfiles for DimOS module conversion
42+
DIMOS_FOOTER = f"""
43+
# ==== {DIMOS_SENTINEL} ====
44+
# Copy DimOS source from build context
45+
COPY dimos /dimos/source/dimos/
46+
COPY pyproject.toml /dimos/source/
47+
COPY docker/python/module-install.sh /tmp/module-install.sh
48+
49+
# Install DimOS and create entrypoint
50+
RUN bash /tmp/module-install.sh /dimos/source && rm /tmp/module-install.sh
51+
52+
ENTRYPOINT ["/dimos/entrypoint.sh"]
53+
"""
54+
55+
56+
def _run(cmd: list[str], *, timeout: float | None = None) -> subprocess.CompletedProcess[str]:
57+
"""Run a command and return the result."""
58+
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=False)
59+
60+
61+
def _run_streaming(cmd: list[str]) -> int:
62+
"""Run command and stream output to terminal. Returns exit code."""
63+
result = subprocess.run(cmd, text=True)
64+
return result.returncode
65+
66+
67+
def _docker_bin(cfg: DockerModuleConfig) -> str:
68+
"""Get docker binary path."""
69+
return cfg.docker_bin or "docker"
70+
71+
72+
def _image_exists(docker_bin: str, image_name: str) -> bool:
73+
"""Check if a Docker image exists locally."""
74+
r = _run([docker_bin, "image", "inspect", image_name], timeout=DOCKER_CMD_TIMEOUT)
75+
return r.returncode == 0
76+
77+
78+
def _convert_dockerfile(dockerfile: Path) -> Path:
79+
"""Append DimOS footer to Dockerfile. Returns path to converted file."""
80+
content = dockerfile.read_text()
81+
82+
# Already converted?
83+
if DIMOS_SENTINEL in content:
84+
return dockerfile
85+
86+
logger.info(f"Converting {dockerfile.name} to DimOS format")
87+
88+
converted = dockerfile.parent / f".{dockerfile.name}.dimos"
89+
converted.write_text(content.rstrip() + "\n" + DIMOS_FOOTER.lstrip("\n"))
90+
return converted
91+
92+
93+
def build_image(cfg: DockerModuleConfig) -> None:
94+
"""Build Docker image using footer mode conversion."""
95+
if cfg.docker_file is None:
96+
raise ValueError("docker_file is required for building Docker images")
97+
dockerfile = _convert_dockerfile(cfg.docker_file)
98+
99+
context = cfg.docker_build_context or cfg.docker_file.parent
100+
cmd = [_docker_bin(cfg), "build", "-t", cfg.docker_image, "-f", str(dockerfile)]
101+
for k, v in cfg.docker_build_args.items():
102+
cmd.extend(["--build-arg", f"{k}={v}"])
103+
cmd.append(str(context))
104+
105+
logger.info(f"Building Docker image: {cfg.docker_image}")
106+
exit_code = _run_streaming(cmd)
107+
if exit_code != 0:
108+
raise RuntimeError(f"Docker build failed with exit code {exit_code}")
109+
110+
111+
def image_exists(cfg: DockerModuleConfig) -> bool:
112+
"""Check if the configured Docker image exists locally."""
113+
return _image_exists(_docker_bin(cfg), cfg.docker_image)
114+
115+
116+
__all__ = [
117+
"DIMOS_FOOTER",
118+
"build_image",
119+
"image_exists",
120+
]

0 commit comments

Comments
 (0)