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.
Files changed (99) hide show
  1. assets/materials/realistic/common/ceramic.png +0 -0
  2. assets/materials/realistic/common/cream-plaster.png +0 -0
  3. assets/materials/realistic/common/gray-felt.png +0 -0
  4. assets/materials/realistic/common/gray-plaster.png +0 -0
  5. assets/materials/realistic/common/gray-woodgrain.png +0 -0
  6. assets/materials/realistic/common/light-gray-floor-tile.png +0 -0
  7. assets/materials/realistic/common/light-gray-plaster.png +0 -0
  8. assets/materials/realistic/common/metal.png +0 -0
  9. assets/materials/realistic/common/oak-wood.png +0 -0
  10. assets/materials/realistic/common/steel-brushed.png +0 -0
  11. assets/materials/realistic/common/steel-scratched.png +0 -0
  12. assets/materials/realistic/common/varnished-wood-panels.png +0 -0
  13. assets/materials/realistic/common/walnut-wood.png +0 -0
  14. assets/materials/realistic/common/white-bricks.png +0 -0
  15. assets/materials/realistic/common/wood-tiles.png +0 -0
  16. assets/materials/realistic/tiles074/Tiles074_2K_Color.png +0 -0
  17. assets/materials/realistic/woodfloor049/WoodFloor049_2K_Color.png +0 -0
  18. configs/box_insertion/profile_default.json +204 -0
  19. react_manip/__init__.py +12 -0
  20. react_manip/asset_cli.py +18 -0
  21. react_manip/builtin.py +168 -0
  22. react_manip/cameras.py +301 -0
  23. react_manip/category_objects.py +248 -0
  24. react_manip/core/__init__.py +60 -0
  25. react_manip/core/aggregate.py +59 -0
  26. react_manip/core/compatibility.py +64 -0
  27. react_manip/core/contracts.py +42 -0
  28. react_manip/core/geometry.py +269 -0
  29. react_manip/core/language.py +116 -0
  30. react_manip/core/metrics.py +196 -0
  31. react_manip/core/observation.py +59 -0
  32. react_manip/core/protocol.py +91 -0
  33. react_manip/core/registry.py +94 -0
  34. react_manip/core/runner.py +498 -0
  35. react_manip/core/scenarios.py +110 -0
  36. react_manip/core/specs.py +203 -0
  37. react_manip/core/timeouts.py +44 -0
  38. react_manip/core/visual_randomization.py +261 -0
  39. react_manip/evaluation/__init__.py +4 -0
  40. react_manip/evaluation/metrics.py +17 -0
  41. react_manip/evaluation/protocols.py +326 -0
  42. react_manip/evaluation/viewer.py +240 -0
  43. react_manip/instances/__init__.py +6 -0
  44. react_manip/instances/candidate_bank.py +109 -0
  45. react_manip/instances/public.py +95 -0
  46. react_manip/instances/records.py +345 -0
  47. react_manip/instances/runtime.py +306 -0
  48. react_manip/instances/suite.py +231 -0
  49. react_manip/instances/trajectory.py +59 -0
  50. react_manip/instances/variation.py +118 -0
  51. react_manip/legacy.py +318 -0
  52. react_manip/policies/__init__.py +5 -0
  53. react_manip/policies/base.py +79 -0
  54. react_manip/rendering.py +58 -0
  55. react_manip/robot_registry.py +31 -0
  56. react_manip/robots/__init__.py +33 -0
  57. react_manip/robots/adapters.py +135 -0
  58. react_manip/robots/agilex_adapter.py +35 -0
  59. react_manip/robots/agilex_description.py +74 -0
  60. react_manip/robots/agilex_robosuite.py +246 -0
  61. react_manip/robots/arm_actions.py +17 -0
  62. react_manip/robots/base.py +119 -0
  63. react_manip/robots/catalog.py +18 -0
  64. react_manip/robots/legacy_agilex_robosuite.py +169 -0
  65. react_manip/robots/mobile_base.py +33 -0
  66. react_manip/robots/mounts.py +80 -0
  67. react_manip/robots/resolution.py +103 -0
  68. react_manip/robots/sawyer_robosuite.py +147 -0
  69. react_manip/robots/sensors.py +137 -0
  70. react_manip/utils/__init__.py +1 -0
  71. react_manip/visual_task.py +50 -0
  72. react_manip-0.1.0.dist-info/METADATA +446 -0
  73. react_manip-0.1.0.dist-info/RECORD +99 -0
  74. react_manip-0.1.0.dist-info/WHEEL +5 -0
  75. react_manip-0.1.0.dist-info/entry_points.txt +2 -0
  76. react_manip-0.1.0.dist-info/licenses/LICENSE +131 -0
  77. react_manip-0.1.0.dist-info/top_level.txt +5 -0
  78. scripts/add_phone_lean_to_bank.py +242 -0
  79. scripts/agilex_wheeled_builder.py +133 -0
  80. scripts/audit_task_solvability.py +150 -0
  81. scripts/build_agilex_assets.py +317 -0
  82. scripts/build_legacy_agilex_assets.py +244 -0
  83. scripts/evaluate_all_tasks.py +309 -0
  84. scripts/evaluate_policy.py +281 -0
  85. scripts/piperx_assets.lock.json +181 -0
  86. scripts/pygame_demo.py +360 -0
  87. scripts/pygame_task_debug.py +242 -0
  88. scripts/regenerate_instance_bank.py +192 -0
  89. scripts/remove_task_from_bank.py +103 -0
  90. scripts/select_task_instances.py +64 -0
  91. scripts/verify_curling_reachability.py +206 -0
  92. scripts/verify_piperx_tasks.py +81 -0
  93. tasks/box_insertion/__init__.py +3 -0
  94. tasks/box_insertion/assets/scene.xml +50 -0
  95. tasks/box_insertion/env.py +642 -0
  96. tasks/box_insertion/evaluation.json +16 -0
  97. tasks/box_insertion/instance_contract.json +321 -0
  98. tasks/box_insertion/scoring.py +23 -0
  99. 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,3 @@
1
+ """Imported box-insertion asset task."""
2
+ from .env import BoxInsertion
3
+ __all__ = ["BoxInsertion"]
@@ -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>