react-manip 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.
- assets/materials/realistic/common/ceramic.png +0 -0
- assets/materials/realistic/common/cream-plaster.png +0 -0
- assets/materials/realistic/common/gray-felt.png +0 -0
- assets/materials/realistic/common/gray-plaster.png +0 -0
- assets/materials/realistic/common/gray-woodgrain.png +0 -0
- assets/materials/realistic/common/light-gray-floor-tile.png +0 -0
- assets/materials/realistic/common/light-gray-plaster.png +0 -0
- assets/materials/realistic/common/metal.png +0 -0
- assets/materials/realistic/common/oak-wood.png +0 -0
- assets/materials/realistic/common/steel-brushed.png +0 -0
- assets/materials/realistic/common/steel-scratched.png +0 -0
- assets/materials/realistic/common/varnished-wood-panels.png +0 -0
- assets/materials/realistic/common/walnut-wood.png +0 -0
- assets/materials/realistic/common/white-bricks.png +0 -0
- assets/materials/realistic/common/wood-tiles.png +0 -0
- assets/materials/realistic/tiles074/Tiles074_2K_Color.png +0 -0
- assets/materials/realistic/woodfloor049/WoodFloor049_2K_Color.png +0 -0
- configs/box_insertion/profile_default.json +204 -0
- react_manip/__init__.py +12 -0
- react_manip/asset_cli.py +18 -0
- react_manip/builtin.py +168 -0
- react_manip/cameras.py +301 -0
- react_manip/category_objects.py +248 -0
- react_manip/core/__init__.py +60 -0
- react_manip/core/aggregate.py +59 -0
- react_manip/core/compatibility.py +64 -0
- react_manip/core/contracts.py +42 -0
- react_manip/core/geometry.py +269 -0
- react_manip/core/language.py +116 -0
- react_manip/core/metrics.py +196 -0
- react_manip/core/observation.py +59 -0
- react_manip/core/protocol.py +91 -0
- react_manip/core/registry.py +94 -0
- react_manip/core/runner.py +498 -0
- react_manip/core/scenarios.py +110 -0
- react_manip/core/specs.py +203 -0
- react_manip/core/timeouts.py +44 -0
- react_manip/core/visual_randomization.py +261 -0
- react_manip/evaluation/__init__.py +4 -0
- react_manip/evaluation/metrics.py +17 -0
- react_manip/evaluation/protocols.py +326 -0
- react_manip/evaluation/viewer.py +240 -0
- react_manip/instances/__init__.py +6 -0
- react_manip/instances/candidate_bank.py +109 -0
- react_manip/instances/public.py +95 -0
- react_manip/instances/records.py +345 -0
- react_manip/instances/runtime.py +306 -0
- react_manip/instances/suite.py +231 -0
- react_manip/instances/trajectory.py +59 -0
- react_manip/instances/variation.py +118 -0
- react_manip/legacy.py +318 -0
- react_manip/policies/__init__.py +5 -0
- react_manip/policies/base.py +79 -0
- react_manip/rendering.py +58 -0
- react_manip/robot_registry.py +31 -0
- react_manip/robots/__init__.py +33 -0
- react_manip/robots/adapters.py +135 -0
- react_manip/robots/agilex_adapter.py +35 -0
- react_manip/robots/agilex_description.py +74 -0
- react_manip/robots/agilex_robosuite.py +246 -0
- react_manip/robots/arm_actions.py +17 -0
- react_manip/robots/base.py +119 -0
- react_manip/robots/catalog.py +18 -0
- react_manip/robots/legacy_agilex_robosuite.py +169 -0
- react_manip/robots/mobile_base.py +33 -0
- react_manip/robots/mounts.py +80 -0
- react_manip/robots/resolution.py +103 -0
- react_manip/robots/sawyer_robosuite.py +147 -0
- react_manip/robots/sensors.py +137 -0
- react_manip/utils/__init__.py +1 -0
- react_manip/visual_task.py +50 -0
- react_manip-0.1.0.dist-info/METADATA +446 -0
- react_manip-0.1.0.dist-info/RECORD +99 -0
- react_manip-0.1.0.dist-info/WHEEL +5 -0
- react_manip-0.1.0.dist-info/entry_points.txt +2 -0
- react_manip-0.1.0.dist-info/licenses/LICENSE +131 -0
- react_manip-0.1.0.dist-info/top_level.txt +5 -0
- scripts/add_phone_lean_to_bank.py +242 -0
- scripts/agilex_wheeled_builder.py +133 -0
- scripts/audit_task_solvability.py +150 -0
- scripts/build_agilex_assets.py +317 -0
- scripts/build_legacy_agilex_assets.py +244 -0
- scripts/evaluate_all_tasks.py +309 -0
- scripts/evaluate_policy.py +281 -0
- scripts/piperx_assets.lock.json +181 -0
- scripts/pygame_demo.py +360 -0
- scripts/pygame_task_debug.py +242 -0
- scripts/regenerate_instance_bank.py +192 -0
- scripts/remove_task_from_bank.py +103 -0
- scripts/select_task_instances.py +64 -0
- scripts/verify_curling_reachability.py +206 -0
- scripts/verify_piperx_tasks.py +81 -0
- tasks/box_insertion/__init__.py +3 -0
- tasks/box_insertion/assets/scene.xml +50 -0
- tasks/box_insertion/env.py +642 -0
- tasks/box_insertion/evaluation.json +16 -0
- tasks/box_insertion/instance_contract.json +321 -0
- tasks/box_insertion/scoring.py +23 -0
- tasks/box_insertion/task_spec.json +333 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Diagnostic physical reachability through the unmodified evaluation action API.
|
|
3
|
+
|
|
4
|
+
Calibration reads private geometry to construct witness actions. Frozen witnesses
|
|
5
|
+
are then replayed by evaluate_policy.py without observation or environment access.
|
|
6
|
+
This is NOT a learned-policy score, RGB perception test, or universal solvability
|
|
7
|
+
certificate. It does not alter the shipped bank, physics, controls, or scoring.
|
|
8
|
+
"""
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import argparse
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
20
|
+
if str(ROOT) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(ROOT))
|
|
22
|
+
from react_manip.core.scenarios import Scenario
|
|
23
|
+
from react_manip.instances.candidate_bank import load_bank, copy_group, write_json
|
|
24
|
+
from react_manip.instances.records import seal, save_record
|
|
25
|
+
from react_manip.instances.runtime import ReplayEnv, runtime_identity, _construct
|
|
26
|
+
from react_manip.robots.arm_actions import arm_action
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FrozenWitnessPolicy:
|
|
30
|
+
policy_id = 'diagnostic-privileged-calibration-frozen-actions'
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self.actions = np.asarray(json.loads(Path(os.environ['CURLING_WITNESS_ACTIONS']).read_text()))
|
|
34
|
+
if self.actions.ndim != 2 or not np.isfinite(self.actions).all() or np.any(np.abs(self.actions) > 1):
|
|
35
|
+
raise ValueError('Invalid frozen witness actions')
|
|
36
|
+
self.index = 0
|
|
37
|
+
|
|
38
|
+
def reset(self, **kwargs):
|
|
39
|
+
self.index = 0
|
|
40
|
+
|
|
41
|
+
def begin_episode(self, **kwargs):
|
|
42
|
+
self.reset()
|
|
43
|
+
|
|
44
|
+
def end_episode(self, **kwargs):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def act(self, observation, info=None):
|
|
48
|
+
# Intentionally ignores observations, feedback, and private scene data.
|
|
49
|
+
if self.index >= len(self.actions):
|
|
50
|
+
raise RuntimeError('Witness exhausted before episode termination')
|
|
51
|
+
action = self.actions[self.index].copy()
|
|
52
|
+
self.index += 1
|
|
53
|
+
return action
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def rollout(env, strength):
|
|
57
|
+
env.reset()
|
|
58
|
+
raw = env.env
|
|
59
|
+
r = raw.robots[0]
|
|
60
|
+
arm = r.arms[0]
|
|
61
|
+
layout = r.composite_controller._action_split_indexes
|
|
62
|
+
c = r.part_controllers[arm]
|
|
63
|
+
assert c._goal_update_mode == 'achieved'
|
|
64
|
+
records = []
|
|
65
|
+
peak = 0.0
|
|
66
|
+
first = None
|
|
67
|
+
release = None
|
|
68
|
+
before = runtime_identity(env._wrapped if isinstance(env, ReplayEnv) else env, 'curling_push')
|
|
69
|
+
|
|
70
|
+
def step(delta):
|
|
71
|
+
nonlocal peak, first, release
|
|
72
|
+
a = arm_action(env.action_dim, layout, arm, np.r_[np.clip(np.array(delta) / raw.position_scale, -1, 1), np.zeros(3)], {arm: 1.0})
|
|
73
|
+
records.append(a.tolist())
|
|
74
|
+
(obs, _, term, trunc, info) = env.step(a)
|
|
75
|
+
peak = max(peak, float(raw.stone_velocity()[0]))
|
|
76
|
+
if raw._state.phase == 'pushing' and first is None:
|
|
77
|
+
first = float(raw.sim.data.time)
|
|
78
|
+
if raw._state.phase == 'sliding' and release is None:
|
|
79
|
+
release = dict(time=float(raw.sim.data.time), x=float(raw.stone_position()[0]), velocity=raw.stone_velocity().tolist())
|
|
80
|
+
return term or trunc
|
|
81
|
+
for goal in ([0.32, 0, 0.98], [0.32, 0, 0.71]):
|
|
82
|
+
for _ in range(700):
|
|
83
|
+
delta = np.array(goal) - raw.eef_pos()
|
|
84
|
+
if np.linalg.norm(delta) < 0.003:
|
|
85
|
+
break
|
|
86
|
+
if step(np.clip(delta, -0.01, 0.01)):
|
|
87
|
+
return (dict(score=0, info=raw._info(), stage='approach'), records)
|
|
88
|
+
for _ in range(100):
|
|
89
|
+
pos = raw.eef_pos()
|
|
90
|
+
delta = [strength, -pos[1], 0.71 - pos[2]]
|
|
91
|
+
if step(delta):
|
|
92
|
+
break
|
|
93
|
+
if raw.stone_position()[0] > 0.78 or (raw._state.last_contact is not None and raw.sim.data.time - raw._state.last_contact >= 0.01):
|
|
94
|
+
break
|
|
95
|
+
hold = raw.eef_pos().copy()
|
|
96
|
+
for _ in range(2600):
|
|
97
|
+
if raw._finished:
|
|
98
|
+
break
|
|
99
|
+
if step(hold - raw.eef_pos()):
|
|
100
|
+
break
|
|
101
|
+
assert c._goal_update_mode == 'achieved'
|
|
102
|
+
assert before == runtime_identity(env._wrapped if isinstance(env, ReplayEnv) else env, 'curling_push')
|
|
103
|
+
return (dict(strength=strength, score=raw._info()['task_score'], info=raw._info(), peak_speed=peak, stone=raw.stone_position().tolist(), release=release, steps=len(records)), records)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def calibrate(env, out):
|
|
107
|
+
"""Bounded privileged diagnostic search; only normal env.step moves bodies."""
|
|
108
|
+
parameters = env.env._parameters
|
|
109
|
+
target = parameters['target']['distance_m']
|
|
110
|
+
estimate = np.sqrt(2 * parameters['ice']['friction'] * 9.81 * (target - .55)) / 6.4
|
|
111
|
+
trials = []
|
|
112
|
+
for offset in (0, .005, -.005, .01, -.01, .02, -.02):
|
|
113
|
+
trial, actions = rollout(env, float(np.clip(estimate + offset, .015, .15)))
|
|
114
|
+
trials.append(trial)
|
|
115
|
+
print(json.dumps(dict(case=out.name, **trial)), flush=True)
|
|
116
|
+
write_json(out / 'calibration.json', trials)
|
|
117
|
+
if trial['score'] == 100:
|
|
118
|
+
write_json(out / 'actions.json', actions)
|
|
119
|
+
return trial
|
|
120
|
+
raise RuntimeError(f'No witness found in bounded search for {out.name}; this alone does not prove impossibility')
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def replay_cli(out, arguments, expected_episodes):
|
|
124
|
+
result_path = out / 'evaluation.json'
|
|
125
|
+
command = [sys.executable, str(ROOT / 'scripts/evaluate_policy.py'), *arguments,
|
|
126
|
+
'--state-only-smoke', '--policy',
|
|
127
|
+
'scripts.verify_curling_reachability:FrozenWitnessPolicy', '--out', str(result_path)]
|
|
128
|
+
environment = dict(os.environ, CURLING_WITNESS_ACTIONS=str((out / 'actions.json').resolve()))
|
|
129
|
+
with (out / 'evaluation.log').open('w') as log:
|
|
130
|
+
subprocess.run(command, cwd=ROOT, env=environment, stdout=log, stderr=subprocess.STDOUT, check=True)
|
|
131
|
+
result = json.loads(result_path.read_text())
|
|
132
|
+
rows = result['episodes']
|
|
133
|
+
if not result.get('valid', True) or len(rows) != expected_episodes:
|
|
134
|
+
raise RuntimeError(f'Invalid evaluation for {out.name}')
|
|
135
|
+
if any(row.get('error') or row.get('episode_score') != 100 for row in rows):
|
|
136
|
+
raise RuntimeError(f'Frozen witness failed in evaluator for {out.name}')
|
|
137
|
+
print(f'{out.name}: {len(rows)} evaluator replays passed', flush=True)
|
|
138
|
+
return dict(episodes=len(rows), all_scores_100=True, command=command,
|
|
139
|
+
observation_mode='state_only_smoke', official_score=None)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main(argv=None):
|
|
143
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
144
|
+
parser.add_argument('--bank', type=Path, default=ROOT / 'react_manip/suites/piperx_candidates_v1/bank.json')
|
|
145
|
+
parser.add_argument('--out', type=Path, required=True, help='New diagnostic output directory')
|
|
146
|
+
args = parser.parse_args(argv)
|
|
147
|
+
out = args.out.resolve()
|
|
148
|
+
out.mkdir(parents=True, exist_ok=False)
|
|
149
|
+
bank, source, manifest, groups = load_bank(args.bank)
|
|
150
|
+
report = dict(bank_digest=bank['content_digest'],
|
|
151
|
+
script_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
|
152
|
+
scope='Unmodified action API and physics; privileged calibration followed by blind action replay',
|
|
153
|
+
limitations=['Not a learned-model or RGB perception evaluation',
|
|
154
|
+
'Only saved candidates and four mass/distance corners; not every point in the parameter continuum',
|
|
155
|
+
'Each candidate has 15 identical initial states; repetition checks evaluator compatibility, not 15 distinct scenarios'],
|
|
156
|
+
candidates=[], boundaries=[], completed=False)
|
|
157
|
+
write_json(out / 'report.json', report)
|
|
158
|
+
for instance, episodes in groups['curling_push']:
|
|
159
|
+
name = instance['task_instance_id']
|
|
160
|
+
directory = out / name
|
|
161
|
+
directory.mkdir()
|
|
162
|
+
group = next(g for g in manifest['groups'] if g['task_instance']['digest'] == instance['content_digest'])
|
|
163
|
+
copy_group(group, source.parent, directory)
|
|
164
|
+
subset = seal('manifest', suite_id='diagnostic_' + name, protocol=manifest['protocol'], groups=[group])
|
|
165
|
+
save_record(subset, directory / 'manifest.json')
|
|
166
|
+
env = ReplayEnv(instance, cameras=False)
|
|
167
|
+
try:
|
|
168
|
+
env.bind(episodes[0])
|
|
169
|
+
witness = calibrate(env, directory)
|
|
170
|
+
finally:
|
|
171
|
+
env.close()
|
|
172
|
+
replay = replay_cli(directory, ['--manifest', str(directory / 'manifest.json')], len(episodes))
|
|
173
|
+
p = instance['scenario']['parameters']
|
|
174
|
+
report['candidates'].append(dict(task_instance_id=name, mass_kg=p['stone']['mass_kg'],
|
|
175
|
+
target_m=p['target']['distance_m'], witness=witness, replay=replay))
|
|
176
|
+
write_json(out / 'report.json', report)
|
|
177
|
+
base = groups['curling_push'][0][0]['scenario']
|
|
178
|
+
for mass in (1., 10.):
|
|
179
|
+
for target in (1., 5.):
|
|
180
|
+
name = f'boundary_mass_{mass:g}_target_{target:g}'
|
|
181
|
+
directory = out / name
|
|
182
|
+
directory.mkdir()
|
|
183
|
+
data = deepcopy(base)
|
|
184
|
+
data.pop('digest')
|
|
185
|
+
data['scenario_id'] = name
|
|
186
|
+
data['parameters']['stone']['mass_kg'] = mass
|
|
187
|
+
data['parameters']['target']['distance_m'] = target
|
|
188
|
+
scenario = Scenario(**data)
|
|
189
|
+
scenario.save(directory / 'scenario.json')
|
|
190
|
+
env = _construct('curling_push', scenario, 'piperx')
|
|
191
|
+
try:
|
|
192
|
+
witness = calibrate(env, directory)
|
|
193
|
+
finally:
|
|
194
|
+
env.close()
|
|
195
|
+
replay = replay_cli(directory, ['--task', 'curling_push', '--scenario',
|
|
196
|
+
str(directory / 'scenario.json')], 15)
|
|
197
|
+
report['boundaries'].append(dict(mass_kg=mass, target_m=target, witness=witness, replay=replay))
|
|
198
|
+
write_json(out / 'report.json', report)
|
|
199
|
+
report['completed'] = True
|
|
200
|
+
report['verified_evaluator_episodes'] = sum(r['replay']['episodes'] for r in report['candidates'] + report['boundaries'])
|
|
201
|
+
write_json(out / 'report.json', report)
|
|
202
|
+
return 0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == '__main__':
|
|
206
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Reset/step physical task combinations and verify compiled gripper surfaces.
|
|
3
|
+
|
|
4
|
+
This is a smoke check, not a success-rate evaluation or replacement for a
|
|
5
|
+
new candidate bank. Output records include each task/embodiment separately.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
import traceback
|
|
12
|
+
import numpy as np
|
|
13
|
+
ROOT=Path(__file__).resolve().parents[1]
|
|
14
|
+
sys.path.insert(0,str(ROOT))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
p=argparse.ArgumentParser(description=__doc__)
|
|
19
|
+
p.add_argument('--robots',nargs='+',default=['piperx','dual_piperx','mobile_dual_piperx'])
|
|
20
|
+
p.add_argument('--tasks',nargs='+')
|
|
21
|
+
p.add_argument('--steps',type=int,default=5)
|
|
22
|
+
p.add_argument('--mobile-vx',type=float,default=.1)
|
|
23
|
+
p.add_argument('--mobile-wz',type=float,default=0.)
|
|
24
|
+
p.add_argument('--out',type=Path,default=ROOT/'outputs/piperx_task_smoke.json')
|
|
25
|
+
args=p.parse_args()
|
|
26
|
+
from react_manip import register_builtin_tasks
|
|
27
|
+
from react_manip.core.registry import get_task_registry
|
|
28
|
+
from react_manip.robots.agilex_robosuite import validate_compiled_grippers
|
|
29
|
+
from react_manip.robots.mobile_base import set_wheel_command
|
|
30
|
+
from react_manip.core.scenarios import sample_scenario
|
|
31
|
+
register_builtin_tasks()
|
|
32
|
+
rows=[]
|
|
33
|
+
for definition in get_task_registry().list():
|
|
34
|
+
task=definition.spec.task_id
|
|
35
|
+
if args.tasks and task not in args.tasks:continue
|
|
36
|
+
for robot in args.robots:
|
|
37
|
+
if not definition.spec.supports_robot(robot):continue
|
|
38
|
+
env=None;row=dict(task=task,robot=robot)
|
|
39
|
+
try:
|
|
40
|
+
kwargs=dict(seed=0,robot_id=robot,has_offscreen_renderer=False)
|
|
41
|
+
if task=='button_indicator_matching':
|
|
42
|
+
profile=json.loads((ROOT/'configs'/task/'profile_default.json').read_text())
|
|
43
|
+
kwargs['scenario']=sample_scenario(profile,seed=0)
|
|
44
|
+
env=definition.factory(**kwargs)
|
|
45
|
+
env.reset()
|
|
46
|
+
raw=getattr(env,'env',env)
|
|
47
|
+
validate_compiled_grippers(env)
|
|
48
|
+
model,data=raw.sim.model,raw.sim.data
|
|
49
|
+
start_root=np.asarray(data.body_xpos[model.body_name2id('robot0_base')]).copy() if robot in ('mobile_dual_piperx','aloha_mobile') else np.zeros(3)
|
|
50
|
+
max_tilt=0.
|
|
51
|
+
for _ in range(args.steps):
|
|
52
|
+
if robot in ('mobile_dual_piperx','aloha_mobile'):
|
|
53
|
+
command = getattr(raw, 'set_mobile_base_command', None)
|
|
54
|
+
if command is not None:
|
|
55
|
+
command(args.mobile_vx, args.mobile_wz)
|
|
56
|
+
else:
|
|
57
|
+
set_wheel_command(raw, args.mobile_vx, args.mobile_wz)
|
|
58
|
+
env.step(np.zeros(raw.action_dim))
|
|
59
|
+
if robot in ('mobile_dual_piperx','aloha_mobile'):
|
|
60
|
+
root=model.body_name2id('robot0_base')
|
|
61
|
+
max_tilt=max(max_tilt,float(np.arccos(np.clip(data.body_xmat[root].reshape(3,3)[2,2],-1,1))))
|
|
62
|
+
validate_compiled_grippers(env)
|
|
63
|
+
if not np.isfinite(data.qpos).all() or not np.isfinite(data.qvel).all():
|
|
64
|
+
raise ValueError('Nonfinite state')
|
|
65
|
+
if np.max(np.abs(data.qvel))>1000:raise ValueError('Unstable velocities')
|
|
66
|
+
if robot in ('mobile_dual_piperx','aloha_mobile'):
|
|
67
|
+
root=model.body_name2id('robot0_base')
|
|
68
|
+
row.update(root_displacement_m=float(np.linalg.norm(data.body_xpos[root,:2]-start_root[:2])),max_tilt_rad=max_tilt)
|
|
69
|
+
if max_tilt>.35:raise ValueError('Mobile chassis tilted more than 20 degrees')
|
|
70
|
+
row.update(passed=True,action_dim=int(raw.action_dim),ngeom=int(model.ngeom),max_speed=float(np.abs(data.qvel).max()))
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
row.update(passed=False,error=f'{type(exc).__name__}: {exc}')
|
|
73
|
+
traceback.print_exc()
|
|
74
|
+
finally:
|
|
75
|
+
if env is not None:env.close()
|
|
76
|
+
rows.append(row);print(json.dumps(row),flush=True)
|
|
77
|
+
args.out.parent.mkdir(parents=True,exist_ok=True)
|
|
78
|
+
args.out.write_text(json.dumps(rows,indent=2)+'\n')
|
|
79
|
+
return int(not rows or any(not r['passed'] for r in rows))
|
|
80
|
+
|
|
81
|
+
if __name__=='__main__':raise SystemExit(main())
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='utf-8'?>
|
|
2
|
+
<mujoco model="box_insertion">
|
|
3
|
+
<option timestep="0.002" gravity="0 0 -9.81" integrator="implicitfast" />
|
|
4
|
+
<asset>
|
|
5
|
+
<material name="table_mat" rgba="0.42 0.25 0.12 1" />
|
|
6
|
+
<material name="box_mat" rgba="0.12 0.15 0.18 1" />
|
|
7
|
+
<material name="block_mat" rgba="0.72 0.42 0.16 1" />
|
|
8
|
+
</asset>
|
|
9
|
+
<worldbody>
|
|
10
|
+
<geom name="floor" type="plane" pos="0 0 0" size="3 3 0.1" friction="0.6 0.01 0.001" />
|
|
11
|
+
<!-- Worktable spans x=[-0.20, 0.70] and y=[-0.80, 0.80]. The extra
|
|
12
|
+
lateral room supports the sampled 0.60 to 1.00 m block and its
|
|
13
|
+
width-plus-0.05 m slot opening. The arena resizes these geoms per
|
|
14
|
+
Scenario before MuJoCo compilation. -->
|
|
15
|
+
<geom name="table" type="box" pos="0.25 0 0.755" size="0.45 0.80 0.025" material="table_mat" group="1" friction="0.005 0.01 0.001" />
|
|
16
|
+
<geom name="table_leg_nx_py" type="box" pos="-0.14 0.74 0.3775" size="0.035 0.035 0.3775" material="table_mat" group="1" />
|
|
17
|
+
<geom name="table_leg_nx_ny" type="box" pos="-0.14 -0.74 0.3775" size="0.035 0.035 0.3775" material="table_mat" group="1" />
|
|
18
|
+
<geom name="table_leg_px_py" type="box" pos="0.64 0.74 0.3775" size="0.035 0.035 0.3775" material="table_mat" group="1" />
|
|
19
|
+
<geom name="table_leg_px_ny" type="box" pos="0.64 -0.74 0.3775" size="0.035 0.035 0.3775" material="table_mat" group="1" />
|
|
20
|
+
<!-- A joint-free body is welded to the world at the tabletop. There is
|
|
21
|
+
intentionally no slot floor: the same table geom supports the block
|
|
22
|
+
both outside and inside the fixture, so no threshold step exists. -->
|
|
23
|
+
<body name="slot_fixture" pos="0.26 0 0.78">
|
|
24
|
+
<!-- Slot walls override block friction. Values below are legacy
|
|
25
|
+
fallbacks; the arena applies sampled side/back wall coefficients. -->
|
|
26
|
+
<geom name="box_back" type="box" pos="0.15 0 0.035" size="0.01 0.545 0.035" material="box_mat" group="1" rgba="0.10 0.16 0.24 1" priority="1" friction="0.02 0.002 0.0001" />
|
|
27
|
+
<geom name="box_left" type="box" pos="0 -0.535 0.035" size="0.16 0.01 0.035" material="box_mat" group="1" rgba="0.10 0.16 0.24 1" priority="1" friction="3.0 0.02 0.001" />
|
|
28
|
+
<geom name="box_right" type="box" pos="0 0.535 0.035" size="0.16 0.01 0.035" material="box_mat" group="1" rgba="0.10 0.16 0.24 1" priority="1" friction="3.0 0.02 0.001" />
|
|
29
|
+
</body>
|
|
30
|
+
<!-- Invisible reference site used by geometry observations; it is not a
|
|
31
|
+
physical object and must not look like a second insertion block. -->
|
|
32
|
+
<site name="box_entry" type="box" pos="0.10 0 0.80" size="0.008 0.525 0.045" rgba="0 0 0 0" />
|
|
33
|
+
<site name="box_bottom_target" type="sphere" pos="0.334125 0 0.802107" size="0.004" rgba="0 0 0 0" />
|
|
34
|
+
<camera name="overview" pos="-1.00 -1.80 1.50" fovy="55" xyaxes="0.843 -0.538 0 0.172 0.270 0.949" />
|
|
35
|
+
<camera name="boxview" pos="-0.45 -1.50 1.05" fovy="55" xyaxes="0.917 -0.398 0 0.060 0.139 0.989" />
|
|
36
|
+
<body name="wood_block" pos="-0.02587494622388665 0.0 0.8021071570474048">
|
|
37
|
+
<!-- At reset the block spans x=[-0.092, 0.040], leaving 0.06 m before
|
|
38
|
+
the slot entrance at x=0.10. A free joint lets gravity generate the
|
|
39
|
+
table normal load and therefore genuine Coulomb friction. -->
|
|
40
|
+
<freejoint name="block_free" />
|
|
41
|
+
<!-- One rigid body with two collision regions across local Y. Their
|
|
42
|
+
shared boundary is resized from the sampled Scenario before model
|
|
43
|
+
compilation. Sampled regional densities determine the body's
|
|
44
|
+
total mass, center of mass and inertia; defaults below are legacy. -->
|
|
45
|
+
<geom name="block_contact_left" type="box" pos="0 -0.25 0" size="0.065875 0.25 0.022107" density="650" group="0" rgba="0 0 0 0" friction="0.10 0.01 0.001" />
|
|
46
|
+
<geom name="block_contact_right" type="box" pos="0 0.25 0" size="0.065875 0.25 0.022107" density="650" group="0" rgba="0 0 0 0" friction="0.10 0.01 0.001" />
|
|
47
|
+
<geom name="block_visual" type="box" size="0.065875 0.50 0.022107" material="block_mat" group="1" contype="0" conaffinity="0" mass="0" />
|
|
48
|
+
</body>
|
|
49
|
+
</worldbody>
|
|
50
|
+
</mujoco>
|