Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
191cc61
add: orca core dependancy
fracapuano Mar 26, 2026
862d049
expose new sim hands
fracapuano Mar 26, 2026
90bcd4c
fix: offload dynamics to OrcaHand objects
fracapuano Mar 26, 2026
347d0e6
fix: offload dynamics to hands
fracapuano Mar 26, 2026
8379438
add: SimHand based on the orca_core hands
fracapuano Mar 26, 2026
577431d
add: SimHand and replicable joint configurations
fracapuano Mar 26, 2026
d23afa4
add: test contract
fracapuano Apr 7, 2026
340f3b0
fix: versioning and canonical representations
fracapuano Apr 7, 2026
2e99418
add: orca core main
fracapuano Apr 7, 2026
8abd535
complete orca_core porting
fracapuano Apr 8, 2026
cce0a1e
add: demoing code is now based on orca_core
fracapuano Apr 8, 2026
87ccaa2
pin orca_arm
fracapuano Apr 9, 2026
acf6298
add: files for proper release
fracapuano Apr 14, 2026
d4f2fa7
fix: minor
fracapuano Apr 28, 2026
312ea1d
add: packaging test for CI
fracapuano Apr 28, 2026
604259f
packaging deps
fracapuano Apr 28, 2026
5a46d2c
add: orca_arm robot's definition
fracapuano Apr 28, 2026
ceeccb5
add: dummy env for bimanual manipulation
fracapuano Apr 28, 2026
7096b4b
add: random action visualizer within custom env
fracapuano Apr 28, 2026
efed212
fix: minor
fracapuano Apr 28, 2026
38e270a
fix: minor
fracapuano Apr 28, 2026
5fd627e
add: visuomotor stacking orcaarm (mujoco)
fracapuano May 17, 2026
5fd5adb
add: single arm panda arm with orca hand
fracapuano May 17, 2026
8cbb4c8
add: base targeted stacking scene
fracapuano May 17, 2026
5d5e2df
add: cameras, stacking, robots
fracapuano May 17, 2026
0da19ea
remove: camera icon
fracapuano May 17, 2026
66a48dd
fix: remove camera icons
fracapuano May 17, 2026
d2ef421
add: scripts for good env creation
fracapuano May 17, 2026
a661b0c
add: gym registry + task driven envs
fracapuano May 17, 2026
edb6856
fix: minor
fracapuano May 17, 2026
254d0af
fix: minor
fracapuano May 17, 2026
35fb1d1
add: new robots, new envs
fracapuano May 17, 2026
e7a3896
add: new robots tests
fracapuano May 17, 2026
89c1d2a
add: environment test
fracapuano May 17, 2026
00f3f5b
add: tests for paths for the new scenes
fracapuano May 17, 2026
38e94c6
add: cube stacking for readme
fracapuano May 17, 2026
fda093c
add: wrist camera to panda arm
fracapuano May 18, 2026
53f3e4c
fix: pin portable core dependency
fracapuano May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,27 @@ from orca_sim import OrcaHandCombinedExtended
env = OrcaHandCombinedExtended(version="v1") # loads the v1 hand
```

See our [`random_policy.py`](random_policy.py) example to see how to instantiate and interface an ORCA hand.
See our [`random_policy.py`](random_policy.py) example to see how to instantiate and interface an ORCA hand through the Gymnasium API.
If you want a lower-level demo built directly on the shared `orca_core` hand helpers, see [`hand_demo.py`](hand_demo.py).

## Sample task: cube stacking

Two cubes on a table, available with no robot, OrcaArm, or OrcaPanda:

```python
from orca_sim import CubeStackingTabletop, OrcaArmCubeStacking, OrcaPandaCubeStacking

env = OrcaArmCubeStacking()
obs, info = env.reset(seed=0)
obs, reward, terminated, truncated, info = env.step(env.action_space.sample())
```
Or use the OrcaPanda robot in the same way:

```python
env = OrcaPandaCubeStacking()
obs, info = env.reset(seed=0)
# ...
```

## Sample task: in-hand cube orientation

Expand Down Expand Up @@ -112,4 +132,3 @@ The implementation is intentionally split so it doubles as a porting template:
- The task scene lives in [`src/orca_sim/scenes/v2/scene_right_cube_orientation.xml`](src/orca_sim/scenes/v2/scene_right_cube_orientation.xml) and composes the existing hand MJCF with a single task cube.
- The nominal palm-up hand pose and in-palm cube spawn are now authored into the task-specific scene/model files, so opening the XML directly in MuJoCo shows the intended setup.
- The task logic lives in [`src/orca_sim/task_envs.py`](src/orca_sim/task_envs.py), including reset-time cube randomization and optional hand-pose overrides for custom MJCF layouts.

143 changes: 143 additions & 0 deletions hand_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import argparse
import logging

from orca_core.demo_presets import DEMO_POSE_FRACTIONS, DEMO_SEQUENCES

from orca_sim import SimOrcaHand


SCENE_CHOICES = (
"scene_left.xml",
"scene_right.xml",
"scene_combined.xml",
"scene_left_extended.xml",
"scene_right_extended.xml",
"scene_combined_extended.xml",
"scene_right_cube_orientation.xml",
)


def _expand_fractions_bimanual(fractions: dict[str, float]) -> dict[str, float]:
"""Prefix bare canonical joint fractions with both ``left_`` and ``right_``."""
return {
f"{side}_{joint}": value
for side in ("left", "right")
for joint, value in fractions.items()
}


def run_demo(
hand: SimOrcaHand,
demo_name: str = "main",
cycles: int = 1,
num_steps: int = 25,
step_size: float = 0.05,
return_to_neutral: bool = True,
) -> None:
"""Run a named demo sequence on any scene, including combined (bimanual) ones.

For single-hand scenes this delegates directly to ``hand.run_demo``. For
combined scenes the bare canonical joint names in the demo presets are
automatically expanded to ``left_*`` / ``right_*`` so both hands move in
sync.
"""
is_bimanual = hand.config.type is None # bimanual hand has no "left" or "right"type
if not is_bimanual:
return hand.run_demo(
demo_name=demo_name,
cycles=cycles,
num_steps=num_steps,
step_size=step_size,
return_to_neutral=return_to_neutral,
)

if demo_name not in DEMO_POSE_FRACTIONS:
available = ", ".join(sorted(DEMO_SEQUENCES))
raise ValueError(f"Unknown demo '{demo_name}'. Available demos: {available}.")

for name, fractions in DEMO_POSE_FRACTIONS[demo_name].items():
hand.register_position(
name,
hand.pose_from_fractions(_expand_fractions_bimanual(fractions)),
)

hand.play_named_positions(
DEMO_SEQUENCES[demo_name],
cycles=cycles,
num_steps=num_steps,
step_size=step_size,
return_to_neutral=return_to_neutral,
)


def main() -> None:
parser = argparse.ArgumentParser(
description="Play back orca_core demo presets on SimOrcaHand."
)
parser.add_argument(
"--scene-file",
choices=SCENE_CHOICES,
default="scene_right.xml",
help="Scene XML to load.",
)
parser.add_argument(
"--version",
default=None,
help="Embodiment version to load, e.g. 'v1' or 'v2'.",
)
parser.add_argument(
"--render-mode",
choices=["human", "rgb_array"],
default="human",
help="Use 'human' for the MuJoCo viewer or 'rgb_array' for offscreen rendering.",
)
parser.add_argument(
"--demo-name",
choices=list(DEMO_SEQUENCES),
default="main",
help="Which demo sequence to play.",
)
parser.add_argument(
"--cycles",
type=int,
default=10,
help="Number of times to repeat the demo sequence.",
)
parser.add_argument(
"--num-steps",
type=int,
default=25,
help="Interpolation steps between poses. More steps = smoother motion.",
)
parser.add_argument(
"--step-size",
type=float,
default=0.05,
help="Sleep in seconds between interpolation steps. Increase to slow down playback.",
)
args = parser.parse_args()

hand = SimOrcaHand(
scene_file=args.scene_file,
version=args.version,
render_mode=args.render_mode,
)
logging.info(f"scene={args.scene_file} version={hand.version} num_joints={len(hand.config.joint_ids)}")

try:
hand.reset()
run_demo(
hand,
demo_name=args.demo_name,
cycles=args.cycles,
num_steps=args.num_steps,
step_size=args.step_size,
)
except KeyboardInterrupt:
logging.info("Demo stopped by user.")
finally:
hand.close()


if __name__ == "__main__":
main()
24 changes: 24 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,35 @@ dependencies = [
"gymnasium>=0.29",
"mujoco>=3.1",
"numpy>=1.26",
"orca_core @ git+https://github.com/orcahand/orca_core.git@91ef976cdf8206d2b937f765b1d1bce091b9e2c5",
]

[project.optional-dependencies]
dev = [
"pytest>=8",
"pytest-mock>=3.14",
]
arm = [
"mani_skill @ git+https://github.com/fracapuano/ManiSkill.git@3dc8c0c52c71c2f41932eb9f0371ca340cf1b038",
"torch",
"sapien",
"transforms3d",
"imageio",
"imageio-ffmpeg",
"orca_arm",
"pin"
]

lerobot = [
"lerobot"
]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
markers = [
"slow: long-running test intended primarily for CI",
]

[tool.setuptools]
package-dir = {"" = "src"}
Expand All @@ -36,6 +55,7 @@ where = ["src"]
[tool.setuptools.package-data]
orca_sim = [
"*.xml",
"scenes/*.xml",
"scenes/*/*.xml",
"assets/mjcf/*.xml",
"assets/mjcf/*/*.xml",
Expand All @@ -45,5 +65,9 @@ orca_sim = [
"assets/mjcf/right/collision/*.stl",
"models/mjcf/*.mjcf",
"models/*/*.mjcf",
"models/*/assets/*.xml",
"models/*/assets/*/*.stl",
"models/*/mjcf/*.xml",
"models/*/mjcf/*.mjcf",
"models/*/assets/mjcf/*/*.stl",
]
47 changes: 38 additions & 9 deletions src/orca_sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,54 @@
latest_version,
list_versions,
)
from orca_sim.envs import (
OrcaHandCombined,
OrcaHandCombinedExtended,
OrcaHandLeft,
OrcaHandLeftExtended,
OrcaHandRight,
OrcaHandRightExtended,
)
from orca_sim.registry import register_envs
from orca_sim.task_envs import OrcaHandRightCubeOrientation

try:
from orca_sim.envs import (
OrcaHandCombined,
OrcaHandCombinedExtended,
OrcaHandLeft,
OrcaHandLeftExtended,
OrcaHandRight,
OrcaHandRightExtended,
)
from orca_sim.hand import SimOrcaHand, SimOrcaHandConfig
from orca_sim.task_envs import (
CubeStackingTabletop,
OrcaArmCubeStacking,
OrcaHandRightCubeOrientation,
OrcaPandaCubeStacking,
)
except ModuleNotFoundError as exc:
if exc.name != "mujoco":
raise

CubeStackingTabletop = None
OrcaArmCubeStacking = None
OrcaHandCombined = None
OrcaHandCombinedExtended = None
OrcaHandLeft = None
OrcaHandLeftExtended = None
OrcaHandRight = None
OrcaHandRightCubeOrientation = None
OrcaHandRightExtended = None
OrcaPandaCubeStacking = None
SimOrcaHand = None
SimOrcaHandConfig = None

__all__ = [
"CubeStackingTabletop",
"OrcaArmCubeStacking",
"OrcaHandCombined",
"OrcaHandCombinedExtended",
"OrcaHandLeft",
"OrcaHandLeftExtended",
"OrcaHandRight",
"OrcaHandRightCubeOrientation",
"OrcaHandRightExtended",
"OrcaPandaCubeStacking",
"SimOrcaHand",
"SimOrcaHandConfig",
"latest_version",
"list_versions",
"register_envs",
Expand Down
11 changes: 11 additions & 0 deletions src/orca_sim/builders/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from orca_sim.builders.orcaarm_camera_mjcf import (
build_orcaarm_camera_mjcf,
camera_names,
)
from orca_sim.builders.orcapanda_mjcf import build_orcapanda_mjcf

__all__ = [
"build_orcaarm_camera_mjcf",
"build_orcapanda_mjcf",
"camera_names",
]
37 changes: 37 additions & 0 deletions src/orca_sim/builders/build_orcaarm_camera_mjcf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

import argparse
from pathlib import Path

from orca_sim.builders.orcaarm_camera_mjcf import build_orcaarm_camera_mjcf


def default_output_path() -> Path:
return (
Path(__file__).resolve().parents[1]
/ "scenes"
/ "includes"
/ "orcabot_with_cameras.xml"
)


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--debug-sites",
action="store_true",
help="Add visible marker sites for camera mounts and aim targets.",
)
parser.add_argument(
"--output",
type=Path,
default=default_output_path(),
help="Path to write the built MJCF include.",
)
args = parser.parse_args()

print(build_orcaarm_camera_mjcf(args.output, debug_sites=args.debug_sites))


if __name__ == "__main__":
main()
22 changes: 22 additions & 0 deletions src/orca_sim/builders/build_orcapanda_mjcf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

from pathlib import Path

from orca_sim.builders.orcapanda_mjcf import build_orcapanda_mjcf


def default_output_path() -> Path:
return (
Path(__file__).resolve().parents[1]
/ "scenes"
/ "includes"
/ "orcapanda_namespaced.xml"
)


def main() -> None:
print(build_orcapanda_mjcf(default_output_path()))


if __name__ == "__main__":
main()
Loading