yubi-mujoco 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. yubi_mujoco/__init__.py +13 -0
  2. yubi_mujoco/__main__.py +3 -0
  3. yubi_mujoco/_images.py +31 -0
  4. yubi_mujoco/adapter.py +107 -0
  5. yubi_mujoco/assets/NOTICE.md +34 -0
  6. yubi_mujoco/assets/SOURCE.md +42 -0
  7. yubi_mujoco/assets/cad_assembly.json +167 -0
  8. yubi_mujoco/assets/cad_manifest.json +801 -0
  9. yubi_mujoco/assets/colors.json +14 -0
  10. yubi_mujoco/assets/licenses/Apache-2.0.txt +202 -0
  11. yubi_mujoco/assets/licenses/CERN-OHL-W-2.0.txt +289 -0
  12. yubi_mujoco/assets/licenses/MIT.txt +26 -0
  13. yubi_mujoco/assets/mass_properties.json +108 -0
  14. yubi_mujoco/assets/meshes/cad_left_jaw_attachment.stl +0 -0
  15. yubi_mujoco/assets/meshes/cad_left_jaw_flap.stl +0 -0
  16. yubi_mujoco/assets/meshes/cad_left_jaw_hardware.stl +0 -0
  17. yubi_mujoco/assets/meshes/cad_left_jaw_pad.stl +0 -0
  18. yubi_mujoco/assets/meshes/cad_left_jaw_rubber.stl +0 -0
  19. yubi_mujoco/assets/meshes/cad_palm_camera.stl +0 -0
  20. yubi_mujoco/assets/meshes/cad_palm_servo.stl +0 -0
  21. yubi_mujoco/assets/meshes/cad_palm_structure.stl +0 -0
  22. yubi_mujoco/assets/meshes/cad_right_jaw_attachment.stl +0 -0
  23. yubi_mujoco/assets/meshes/cad_right_jaw_flap.stl +0 -0
  24. yubi_mujoco/assets/meshes/cad_right_jaw_hardware.stl +0 -0
  25. yubi_mujoco/assets/meshes/cad_right_jaw_pad.stl +0 -0
  26. yubi_mujoco/assets/meshes/cad_right_jaw_rubber.stl +0 -0
  27. yubi_mujoco/cli.py +268 -0
  28. yubi_mujoco/env.py +360 -0
  29. yubi_mujoco/geometry.py +57 -0
  30. yubi_mujoco/model.py +310 -0
  31. yubi_mujoco/scripted.py +77 -0
  32. yubi_mujoco-0.1.0.dist-info/METADATA +169 -0
  33. yubi_mujoco-0.1.0.dist-info/RECORD +41 -0
  34. yubi_mujoco-0.1.0.dist-info/WHEEL +5 -0
  35. yubi_mujoco-0.1.0.dist-info/entry_points.txt +2 -0
  36. yubi_mujoco-0.1.0.dist-info/licenses/LICENSE +26 -0
  37. yubi_mujoco-0.1.0.dist-info/licenses/src/yubi_mujoco/assets/NOTICE.md +34 -0
  38. yubi_mujoco-0.1.0.dist-info/licenses/src/yubi_mujoco/assets/licenses/Apache-2.0.txt +202 -0
  39. yubi_mujoco-0.1.0.dist-info/licenses/src/yubi_mujoco/assets/licenses/CERN-OHL-W-2.0.txt +289 -0
  40. yubi_mujoco-0.1.0.dist-info/licenses/src/yubi_mujoco/assets/licenses/MIT.txt +26 -0
  41. yubi_mujoco-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,13 @@
1
+ """Arm-free YUBI simulation. Set MUJOCO_GL before first environment import."""
2
+
3
+ __all__ = ["YubiEnv", "SimConfig"]
4
+ __version__ = "0.1.0"
5
+
6
+
7
+ def __getattr__(name):
8
+ # Keep CLI import lightweight so headless GL selection precedes MuJoCo import.
9
+ if name in __all__:
10
+ from .env import YubiEnv, SimConfig
11
+
12
+ return {"YubiEnv": YubiEnv, "SimConfig": SimConfig}[name]
13
+ raise AttributeError(name)
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
yubi_mujoco/_images.py ADDED
@@ -0,0 +1,31 @@
1
+ """Write RGB PNG images without optional video dependencies."""
2
+
3
+ from pathlib import Path
4
+ import struct
5
+ import zlib
6
+
7
+ import numpy as np
8
+
9
+
10
+ def write_png(path, pixels):
11
+ """Write an HWC uint8 RGB image as a lossless PNG."""
12
+ pixels = np.asarray(pixels)
13
+ if pixels.ndim != 3 or pixels.shape[2] != 3 or pixels.dtype != np.uint8:
14
+ raise ValueError("pixels must have shape (height, width, 3) and dtype uint8")
15
+ height, width, _ = pixels.shape
16
+ if not height or not width:
17
+ raise ValueError("image dimensions must be positive")
18
+
19
+ def chunk(kind, data):
20
+ return (
21
+ struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data))
22
+ )
23
+
24
+ header = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
25
+ rows = b"".join(b"\0" + row.tobytes() for row in pixels)
26
+ Path(path).write_bytes(
27
+ b"\x89PNG\r\n\x1a\n"
28
+ + chunk(b"IHDR", header)
29
+ + chunk(b"IDAT", zlib.compress(rows))
30
+ + chunk(b"IEND", b"")
31
+ )
yubi_mujoco/adapter.py ADDED
@@ -0,0 +1,107 @@
1
+ """Local Policy(checkpoint_dir).infer(obs) evaluation; no model/network side effects."""
2
+
3
+ from pathlib import Path
4
+ import importlib.util
5
+ import time
6
+ import numpy as np
7
+ from .geometry import pose
8
+
9
+
10
+ def validate_chunk(actions, min_rows=16, *, executed_rows=None):
11
+ if not isinstance(min_rows, int) or min_rows < 1:
12
+ raise ValueError("min_rows must be a positive integer")
13
+ a = np.asarray(actions)
14
+ if a.ndim != 2 or a.shape[1] != 16 or a.shape[0] < min_rows:
15
+ raise ValueError(f"policy must return (N,16) with N>={min_rows}; received {a.shape}")
16
+ if not np.issubdtype(a.dtype, np.number) or np.iscomplexobj(a) or not np.isfinite(a).all():
17
+ raise ValueError("policy actions must be finite numeric values")
18
+ if executed_rows is None:
19
+ executed_rows = len(a)
20
+ if not isinstance(executed_rows, int) or not 1 <= executed_rows <= len(a):
21
+ raise ValueError("executed_rows must be an integer within the returned chunk")
22
+ for row in a[:executed_rows]:
23
+ pose(row[:7])
24
+ pose(row[7:14])
25
+ return a.astype(np.float64)
26
+
27
+
28
+ class HoldPolicy:
29
+ def __init__(self, checkpoint_dir=None):
30
+ pass
31
+
32
+ def infer(self, obs):
33
+ a = np.zeros((32, 16), dtype=np.float32)
34
+ a[:, 6] = a[:, 13] = 1
35
+ a[:, 14:16] = obs["observation.joint_states"]
36
+ return a
37
+
38
+
39
+ def load_policy(policy_path, checkpoint_dir):
40
+ path = Path(policy_path).resolve()
41
+ if not path.is_file():
42
+ raise FileNotFoundError(path)
43
+ spec = importlib.util.spec_from_file_location("yubi_user_policy", path)
44
+ if spec is None or spec.loader is None:
45
+ raise ValueError(f"cannot import {path}")
46
+ module = importlib.util.module_from_spec(spec)
47
+ spec.loader.exec_module(module)
48
+ return module.Policy(str(Path(checkpoint_dir).resolve()))
49
+
50
+
51
+ def run_policy(
52
+ env, policy, *, adopt_rows=16, max_calls=None, on_step=None, translation_frame="body"
53
+ ):
54
+ """Measure simulation task success, not Arena real-robot scores.
55
+
56
+ Simulator pauses while infer runs, as described by the public robot contract.
57
+ Translation frame and row frequency are explicit caller choices.
58
+ No dataset-only gripper time shift is applied to live actions.
59
+ """
60
+ if not isinstance(adopt_rows, int) or adopt_rows <= 0:
61
+ raise ValueError("adopt_rows must be a positive integer")
62
+ if max_calls is not None and (not isinstance(max_calls, int) or max_calls <= 0):
63
+ raise ValueError("max_calls must be a positive integer")
64
+ latencies = []
65
+ calls = 0
66
+ rate_limited_steps = 0
67
+ status = "running"
68
+ error = None
69
+ while not env._done and (max_calls is None or calls < max_calls):
70
+ obs = env.observe(images=True)
71
+ start = time.perf_counter()
72
+ try:
73
+ actions = validate_chunk(policy.infer(obs), executed_rows=adopt_rows)
74
+ latencies.append(time.perf_counter() - start)
75
+ # Fail before partially executing a chunk with illegal gripper positions.
76
+ for row in actions[:adopt_rows]:
77
+ env._motor_to_jaw(row[14:16])
78
+ calls += 1
79
+ for action in actions[:adopt_rows]:
80
+ _, _, terminated, truncated, info = env.step_delta(
81
+ action, translation_frame=translation_frame
82
+ )
83
+ rate_limited_steps += int(any(info["target_rate_limited"]))
84
+ if on_step:
85
+ on_step(env, info)
86
+ if terminated or truncated:
87
+ break
88
+ except (ValueError, RuntimeError, FloatingPointError) as exc:
89
+ status, error = "invalid_policy_output", f"{type(exc).__name__}: {exc}"
90
+ break
91
+ if status == "running":
92
+ status = "success" if env.info()["success"] else "horizon" if env._done else "call_limit"
93
+ return {
94
+ **env.info(),
95
+ "status": status,
96
+ "error": error,
97
+ "policy_calls": calls,
98
+ "latency_seconds": latencies,
99
+ "latency_p95_seconds": float(np.percentile(latencies, 95)) if latencies else None,
100
+ "rate_limited_steps": rate_limited_steps,
101
+ "action_hz": env.config.control_hz,
102
+ "translation_frame": translation_frame,
103
+ "adopt_rows": adopt_rows,
104
+ "scoring": "local_sim_task_success_only",
105
+ "camera_calibrated": False,
106
+ "motor_mapping_calibrated": False,
107
+ }
@@ -0,0 +1,34 @@
1
+ # YUBI MuJoCo notices
2
+
3
+ Original simulation software: Copyright (c) 2026 YUBI simulation contributors,
4
+ MIT license. The root LICENSE does not relicense third-party assets.
5
+
6
+ Toyota-derived CAD and transformation/grouping metadata:
7
+ Copyright 2026 Toyota Motor Corporation, CERN-OHL-W-2.0.
8
+ Full license: licenses/CERN-OHL-W-2.0.txt.
9
+
10
+ Modification notice, 2026-10-01: tessellated the pinned robot-gripper STEP
11
+ assembly; partitioned 97 solid leaves into fixed palm and moving jaws;
12
+ converted millimeters to meters and re-expressed geometry in a canonical
13
+ root frame and verified hinge-local frames. No intentional solid-geometry
14
+ design changes. Distributed meshes are 13 disjoint material partitions;
15
+ redundant whole-body aggregate meshes are omitted. Approximate black/red
16
+ rendering colors follow the official assembled gripper reference.
17
+ mass_properties.json holds body mass, center-of-mass and inertia estimates
18
+ computed from the same STEP solids with stated material assumptions.
19
+ The CAD-derived meshes and transformation/grouping metadata retain
20
+ CERN-OHL-W-2.0. See SOURCE.md for complete source and regeneration instructions.
21
+
22
+ Published software/API/coordinate conventions were consulted from:
23
+ - airoa-org/yubi-sw, commit 1eccb041efc4d8966b03e4ccf4b402aa79d78be4
24
+ Copyright 2026 AI Robot Association
25
+ - airoa-org/umi-arena-evaluation, commit 8763f10a022139dbcf4293b60f797a717d8bd1ad
26
+ These upstream references use Apache-2.0; a copy is in licenses/Apache-2.0.txt.
27
+ No pretrained weights or restricted datasets are redistributed.
28
+
29
+ OpenArm MuJoCo (https://github.com/enactic/openarm_mujoco) was consulted as a
30
+ packaging/usability reference; its code and robot assets are not included.
31
+
32
+ This is an independent project. It is not endorsed or certified by Toyota,
33
+ AIRoA, or Enactic. No trademark rights are granted. See the full licenses for
34
+ warranty disclaimers and other terms.
@@ -0,0 +1,42 @@
1
+ # Complete source and asset provenance
2
+
3
+ For a published 0.1.0 release, obtain its complete corresponding source as
4
+ `yubi_mujoco-0.1.0.tar.gz`, the same-version source distribution (sdist), at:
5
+ https://pypi.org/project/yubi-mujoco/0.1.0/#files
6
+
7
+ The sdist contains the unmodified source STEP, original hardware license and
8
+ commercial guidance, the CAD exporter, and all grouping/transformation data.
9
+ It must be published alongside the wheel. A wheel alone is not the complete
10
+ CAD source. Repository source is also intended at:
11
+ https://github.com/k1000dai/yubi_mujoco
12
+
13
+ Original hardware source: https://github.com/Toyota/yubi-hw
14
+ Pinned revision: dd8bd13d2fd8e5003057243576f88be333d95fc5
15
+ Original path: STEP/gripper/YUBI Gripper Assy_Dynamixel_ver2.STEP
16
+ SHA-256: 0e60bf62de970e5fae5e7cff104f547a954ef8d5d7b9dde9835b381b95f4fcc8
17
+ Copyright 2026 Toyota Motor Corporation. License: CERN-OHL-W-2.0.
18
+ See NOTICE.md for the dated modification notice and licenses/ for full terms.
19
+
20
+ ## Regenerate
21
+
22
+ Extract the sdist, change into its root, and use a Python interpreter with
23
+ FreeCAD 1.0 native Python modules (Import, Part, and MeshPart) available:
24
+
25
+ python cad/fetch_source.py
26
+ /usr/bin/python3 cad/export.py
27
+
28
+ The first command verifies the bundled STEP; it downloads only that pinned
29
+ public source if it is absent. The exporter itself uses no network. Set
30
+ FREECAD_LIB for an alternate FreeCAD Python library location. FreeCAD is not
31
+ a runtime dependency and is installed separately, not as a pip extra.
32
+
33
+ The exporter verifies the source hash, all 97 expected solid leaves, their
34
+ unique body assignments, 8 mm shaft bores, and parallel-finger orientation.
35
+ Default tessellation: 0.12 mm linear deflection, 0.35 rad angular deflection.
36
+ Version-specific FreeCAD/OpenCascade tessellation may change mesh hashes.
37
+
38
+ Runtime meshes and corresponding SHA-256 hashes are listed in
39
+ cad_manifest.json; exact frame/pivot transforms are in cad_assembly.json.
40
+ Body mass estimates in mass_properties.json are regenerated by
41
+ cad/mass_properties.py, which needs OpenCascade's Python bindings (cadquery-ocp).
42
+ Detailed geometry derivation is in docs/CAD_PROVENANCE.md in the sdist.
@@ -0,0 +1,167 @@
1
+ {
2
+ "schema_version": 1,
3
+ "source_step": "cad/source/YUBI Gripper Assy_Dynamixel_ver2.STEP",
4
+ "cad_unit": "millimeter",
5
+ "mesh_unit": "meter",
6
+ "cad_origin_mm": [
7
+ 854.070661629179,
8
+ 856.5046236934521,
9
+ 1286.5734593075358
10
+ ],
11
+ "cad_to_canonical_rotation": [
12
+ [
13
+ 0,
14
+ 0,
15
+ 1
16
+ ],
17
+ [
18
+ 1,
19
+ 0,
20
+ 0
21
+ ],
22
+ [
23
+ 0,
24
+ 1,
25
+ 0
26
+ ]
27
+ ],
28
+ "cad_to_canonical_scale": 0.001,
29
+ "formula": "canonical_m = 0.001 * rotation @ (cad_mm - cad_origin_mm)",
30
+ "canonical_axes": {
31
+ "x": "+CAD Z, along parallel fingers",
32
+ "y": "+CAD X, toward source left jaw",
33
+ "z": "+CAD Y, hinge axis / camera side"
34
+ },
35
+ "bodies": {
36
+ "palm": {
37
+ "origin_m": [
38
+ 0,
39
+ 0,
40
+ 0
41
+ ]
42
+ },
43
+ "left_jaw": {
44
+ "origin_m": [
45
+ -0.015999999999828787,
46
+ 0.014999999999733518,
47
+ 0.0
48
+ ],
49
+ "joint_axis": [
50
+ 0,
51
+ 0,
52
+ 1
53
+ ],
54
+ "opening_angle_sign": 1,
55
+ "cad_neutral_joint_angle_rad": 0.0
56
+ },
57
+ "right_jaw": {
58
+ "origin_m": [
59
+ -0.01600000000017144,
60
+ -0.014999999999733518,
61
+ 0.0
62
+ ],
63
+ "joint_axis": [
64
+ 0,
65
+ 0,
66
+ 1
67
+ ],
68
+ "opening_angle_sign": -1,
69
+ "cad_neutral_joint_angle_rad": 0.0
70
+ }
71
+ },
72
+ "cad_neutral": "Parallel attachments point +canonical X; proven by paired flap mounting holes. This is a geometric reference, not a measured motor encoder zero.",
73
+ "calibration": {
74
+ "motor_zero_verified": false,
75
+ "robot_joint_limits_verified": false,
76
+ "nominal_glove_opening_range_rad": [
77
+ 0.0,
78
+ 0.94
79
+ ],
80
+ "nominal_range_warning": "Glove URDF range, not measured robot travel. CAD neutral already has a nonzero pad gap. Do not call q=0 fully closed."
81
+ },
82
+ "geometric_evidence": {
83
+ "right_jaw": {
84
+ "shaft_bore_radius_mm": 4.0,
85
+ "shaft_bore_center_mm": [
86
+ 839.0706616294455,
87
+ 862.5046236934472,
88
+ 1270.5734593073644
89
+ ],
90
+ "shaft_axis_cad": [
91
+ 0,
92
+ 1,
93
+ 0
94
+ ],
95
+ "flap_screw_centers_mm": [
96
+ [
97
+ 834.0706616294581,
98
+ 866.5046236934472,
99
+ 1301.5734593073664
100
+ ],
101
+ [
102
+ 834.0706616294653,
103
+ 866.5046236934472,
104
+ 1319.5734593073664
105
+ ]
106
+ ],
107
+ "flap_screw_spacing_mm": 18.0,
108
+ "finger_direction_cad": [
109
+ 4.0421986745463475e-13,
110
+ 0.0,
111
+ 1.0
112
+ ]
113
+ },
114
+ "left_jaw": {
115
+ "shaft_bore_radius_mm": 4.0,
116
+ "shaft_bore_center_mm": [
117
+ 869.0706616289125,
118
+ 862.504623693457,
119
+ 1270.573459307707
120
+ ],
121
+ "shaft_axis_cad": [
122
+ 0,
123
+ 1,
124
+ 0
125
+ ],
126
+ "flap_screw_centers_mm": [
127
+ [
128
+ 874.0706616289125,
129
+ 866.504623693457,
130
+ 1301.573459307707
131
+ ],
132
+ [
133
+ 874.0706616289125,
134
+ 866.504623693457,
135
+ 1319.573459307707
136
+ ]
137
+ ],
138
+ "flap_screw_spacing_mm": 18.0,
139
+ "finger_direction_cad": [
140
+ 0.0,
141
+ 0.0,
142
+ 1.0
143
+ ]
144
+ }
145
+ },
146
+ "optional_material_meshes": {
147
+ "palm": [
148
+ "cad_palm_camera.stl",
149
+ "cad_palm_servo.stl",
150
+ "cad_palm_structure.stl"
151
+ ],
152
+ "left_jaw": [
153
+ "cad_left_jaw_attachment.stl",
154
+ "cad_left_jaw_flap.stl",
155
+ "cad_left_jaw_hardware.stl",
156
+ "cad_left_jaw_pad.stl",
157
+ "cad_left_jaw_rubber.stl"
158
+ ],
159
+ "right_jaw": [
160
+ "cad_right_jaw_attachment.stl",
161
+ "cad_right_jaw_flap.stl",
162
+ "cad_right_jaw_hardware.stl",
163
+ "cad_right_jaw_pad.stl",
164
+ "cad_right_jaw_rubber.stl"
165
+ ]
166
+ }
167
+ }