tinysim 0.0.4__py3-none-any.whl → 0.0.5__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.
- tinysim/__init__.py +7 -2
- tinysim/_tk_base.py +20 -2
- tinysim/_widget_base.py +41 -0
- tinysim/flappy/__init__.py +5 -3
- tinysim/flappy/tk.py +10 -34
- tinysim/flappy/widget.py +9 -38
- tinysim/frogger/__init__.py +21 -4
- tinysim/frogger/tk.py +12 -28
- tinysim/frogger/widget.py +8 -37
- tinysim/mountain_car/__init__.py +17 -12
- tinysim/mountain_car/tk.py +9 -27
- tinysim/mountain_car/widget.py +8 -37
- tinysim/tinyspace.py +68 -0
- tinysim/topdown_driving/__init__.py +16 -18
- tinysim/topdown_driving/tk.py +13 -38
- tinysim/topdown_driving/track_0.json +1 -753
- tinysim/topdown_driving/widget.py +8 -42
- {tinysim-0.0.4.dist-info → tinysim-0.0.5.dist-info}/METADATA +5 -6
- {tinysim-0.0.4.dist-info → tinysim-0.0.5.dist-info}/RECORD +35 -35
- {tinysim-0.0.4.dist-info → tinysim-0.0.5.dist-info}/WHEEL +1 -1
- tinysim_mujoco/gl_viewer.py +36 -11
- tinysim_mujoco/manipulation/__init__.py +19 -5
- tinysim_mujoco/manipulation/push_env.py +268 -0
- tinysim_mujoco/manipulation/push_env_cam.py +274 -0
- tinysim_mujoco/manipulation/xmls/panda.xml +59 -16
- tinysim_mujoco/manipulation/xmls/scene.xml +0 -3
- tinysim_mujoco/manipulation/xmls/table.xml +17 -13
- tinysim_mujoco/notebook_viewer.py +3 -6
- tinysim_mujoco/unitree_a1/__init__.py +7 -10
- tinysim_warp/__init__.py +108 -0
- tinysim_warp/cart_pole/__init__.py +44 -200
- tinysim_warp/quadruped/__init__.py +23 -160
- tinysim_warp/simple_quadruped/__init__.py +20 -91
- tinysim_warp/simple_quadruped/simple_quadruped.urdf +0 -9
- tinysim/simple_amr/__init__.py +0 -0
- tinysim/simple_amr/example_maps.py +0 -121
- tinysim/simple_amr/sim.js +0 -430
- tinysim/simple_amr/styles.css +0 -54
- tinysim/simple_amr/widget.py +0 -73
- {tinysim-0.0.4.dist-info → tinysim-0.0.5.dist-info}/top_level.txt +0 -0
|
@@ -1,59 +1,25 @@
|
|
|
1
1
|
import pathlib
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import traitlets
|
|
4
|
-
import asyncio
|
|
5
|
-
import numpy as np
|
|
6
|
-
from IPython.display import display
|
|
7
|
-
from jupyter_ui_poll import ui_events
|
|
8
4
|
|
|
9
|
-
from
|
|
5
|
+
from .._widget_base import BaseWidget
|
|
6
|
+
from . import LOCAL_WALLS, TopDownDrivingEnv
|
|
10
7
|
|
|
11
8
|
|
|
12
|
-
class TopDownDrivingWidget(
|
|
9
|
+
class TopDownDrivingWidget(BaseWidget):
|
|
13
10
|
_esm = pathlib.Path(__file__).parent / "sim.js"
|
|
14
11
|
|
|
15
12
|
sim_state = traitlets.Dict(default_value={}).tag(sync=True)
|
|
16
13
|
wall_positions = traitlets.List(default_value=LOCAL_WALLS).tag(sync=True)
|
|
14
|
+
_viewport_size = traitlets.Tuple(default_value=(800, 600)).tag(sync=True)
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
).tag(sync=True)
|
|
21
|
-
_view_ready = traitlets.Bool(default_value=False).tag(sync=True)
|
|
22
|
-
|
|
23
|
-
def __init__(self, viewport_size=(800, 600), sim_env=None):
|
|
16
|
+
def __init__(self, viewport_size=(800, 600), sim_env=TopDownDrivingEnv()):
|
|
17
|
+
super().__init__(sim_env)
|
|
24
18
|
self._viewport_size = viewport_size
|
|
25
|
-
super().__init__()
|
|
26
|
-
if sim_env is None:
|
|
27
|
-
sim_env = TopDownDrivingEnv()
|
|
28
|
-
|
|
29
|
-
self.sim_env = sim_env
|
|
30
|
-
self.copy_py_state(self.sim_env.reset())
|
|
31
|
-
|
|
32
|
-
def render(self):
|
|
33
|
-
display(self)
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
with ui_events() as ui_poll:
|
|
37
|
-
while not self._view_ready:
|
|
38
|
-
ui_poll(100)
|
|
39
|
-
except Exception:
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
def copy_py_state(self, sim_state: dict):
|
|
20
|
+
def _update_props(self, sim_state):
|
|
43
21
|
self.sim_state = {
|
|
44
22
|
"x": sim_state["x"].tolist(),
|
|
45
23
|
"y": sim_state["y"].tolist(),
|
|
46
24
|
"angle": sim_state["angle"].tolist(),
|
|
47
25
|
}
|
|
48
|
-
|
|
49
|
-
async def step(self, action: int, dt: float = 0.01) -> dict:
|
|
50
|
-
sim_state = self.sim_env.step(action)
|
|
51
|
-
self.copy_py_state(sim_state)
|
|
52
|
-
|
|
53
|
-
await asyncio.sleep(dt)
|
|
54
|
-
return sim_state
|
|
55
|
-
|
|
56
|
-
async def reset(self) -> dict:
|
|
57
|
-
sim_state = self.sim_env.reset()
|
|
58
|
-
self.copy_py_state(sim_state)
|
|
59
|
-
return sim_state
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinysim
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: small modular simulation environments
|
|
5
5
|
Author-email: Matthew Taylor <matthew.taylor.andre@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/MatthewAndreTaylor/TinySim
|
|
@@ -30,7 +30,7 @@ Requires-Dist: mujoco; extra == "mujoco"
|
|
|
30
30
|
# TinySim
|
|
31
31
|
|
|
32
32
|
[](https://pypi.org/project/tinysim)
|
|
33
|
-
[](https://matthewandretaylor.github.io/TinySim
|
|
33
|
+
[](https://matthewandretaylor.github.io/TinySim)
|
|
34
34
|
|
|
35
35
|
The goal of this project is to create more minimal simulation environments for robotics and machine learning.
|
|
36
36
|
|
|
@@ -73,8 +73,7 @@ To use the [mujoco](https://github.com/google-deepmind/mujoco) simulation enviro
|
|
|
73
73
|
```bash
|
|
74
74
|
pip install tinysim[mujoco]
|
|
75
75
|
```
|
|
76
|
-
| [UnitreeA1WalkEnv](https://github.com/MatthewAndreTaylor/TinySim/tree/main/tinysim_mujoco/unitree_a1) |
|
|
77
|
-
|
|
78
|
-
| <img width="312" alt="mujoco_sim" src="https://github.com/user-attachments/assets/1109488e-0563-4694-8926-682edd0c8278" /> |
|
|
79
|
-
|
|
76
|
+
| [UnitreeA1WalkEnv](https://github.com/MatthewAndreTaylor/TinySim/tree/main/tinysim_mujoco/unitree_a1) | [ManipulationEnvV0](https://github.com/MatthewAndreTaylor/TinySim/tree/main/tinysim_mujoco/manipulation) |
|
|
77
|
+
|:-------------------------:|:-------------------------:|
|
|
78
|
+
| <img width="312" alt="mujoco_sim" src="https://github.com/user-attachments/assets/1109488e-0563-4694-8926-682edd0c8278" /> | <img width="312" alt="mujoco_sim" src="https://github.com/user-attachments/assets/07b406b5-15fc-4cfd-83c4-cf6b855d773a" /> |
|
|
80
79
|
|
|
@@ -1,35 +1,34 @@
|
|
|
1
|
-
tinysim/__init__.py,sha256=
|
|
2
|
-
tinysim/_tk_base.py,sha256=
|
|
3
|
-
tinysim/
|
|
1
|
+
tinysim/__init__.py,sha256=971PtvBBFPJ9zBfRRLdfuGRE2oEX739127n_Y6MNJIg,263
|
|
2
|
+
tinysim/_tk_base.py,sha256=vUJEx0NXfo8TLZycQNOhZgbx_2ybrBjvaJC_KQ6jvBo,1790
|
|
3
|
+
tinysim/_widget_base.py,sha256=_L6xSOH4LOpXIvCFCczDkcorS8xH2E6UjdTH3L6B6tM,1084
|
|
4
|
+
tinysim/tinyspace.py,sha256=x4MKrou4-9aKmNQ0V7RSxYhHk3akzruF56Sn14wnO7s,1950
|
|
5
|
+
tinysim/flappy/__init__.py,sha256=_0OBPUyymQCVkwCRiG7Km_oIwlrhWP808r4e1COSZ3A,3551
|
|
4
6
|
tinysim/flappy/sim.js,sha256=g8ycKdJGopu0Z5CeBgtF8Rr2Pe64dTFcSiYh7hzYPzU,3190
|
|
5
|
-
tinysim/flappy/tk.py,sha256=
|
|
6
|
-
tinysim/flappy/widget.py,sha256=
|
|
7
|
-
tinysim/frogger/__init__.py,sha256=
|
|
7
|
+
tinysim/flappy/tk.py,sha256=HR3AlHzGLxLWw2qdtY8pbyFxEELb3sItGWNeOqqjHDA,2111
|
|
8
|
+
tinysim/flappy/widget.py,sha256=BSrEvAmUKbaqF9zZq7rZ4WxgIDjt1NTmUIb0gg0gt4M,748
|
|
9
|
+
tinysim/frogger/__init__.py,sha256=YHumyU2tR8oRZoJACLPQ3Ncyw7b3MbR_W2LJSmtdv9g,5712
|
|
8
10
|
tinysim/frogger/sim.js,sha256=b1HfEtFZFbbsrZNtltuWO4h-QM4Fw3XjMRydwm5l2NU,3081
|
|
9
|
-
tinysim/frogger/tk.py,sha256=
|
|
10
|
-
tinysim/frogger/widget.py,sha256=
|
|
11
|
-
tinysim/mountain_car/__init__.py,sha256=
|
|
11
|
+
tinysim/frogger/tk.py,sha256=gXxp_9sbcFM__-JdX6Jhymf9ZzoIt7N1GmK9DPTW724,2518
|
|
12
|
+
tinysim/frogger/widget.py,sha256=mYlc7zToV7C1Gy_OdvFjCk2xygAdO2ggGupNxXruckk,952
|
|
13
|
+
tinysim/mountain_car/__init__.py,sha256=zR-WaZGF3PPz4ajBlhOqWDxaKSa68bYuKJNaR_PwKjI,2207
|
|
12
14
|
tinysim/mountain_car/sim.js,sha256=z7kxl7LbkRglfzXxmrBbwz2vMYdQV84QP3WhE0hoPVQ,4581
|
|
13
15
|
tinysim/mountain_car/styles.css,sha256=a8Bc64RdRNB1uWsBdpMEQA9STZO8mVAB2vvIPhhYuik,63
|
|
14
|
-
tinysim/mountain_car/tk.py,sha256=
|
|
15
|
-
tinysim/mountain_car/widget.py,sha256=
|
|
16
|
-
tinysim/
|
|
17
|
-
tinysim/simple_amr/example_maps.py,sha256=38duuHbd6HvhQBIIcg9PWokNMjkasPSsKPjHnBhv9qQ,3307
|
|
18
|
-
tinysim/simple_amr/sim.js,sha256=2YW2YWEEe-QWgJGLAdpt1aZi__sLL5ML84fv1s90poY,11575
|
|
19
|
-
tinysim/simple_amr/styles.css,sha256=t85kXf0xyrq5ldL-j_pV0FEekWoorK63T_ZorWEgBOw,1087
|
|
20
|
-
tinysim/simple_amr/widget.py,sha256=dmNcftB7-Z9Kf3hQ0MBaJvo97eyjCA6R74oEkHASMas,2557
|
|
21
|
-
tinysim/topdown_driving/__init__.py,sha256=HiRpxQNeu4IIOPFraGHLTO2F0VNISAk6bUiK1rsbZTE,6228
|
|
16
|
+
tinysim/mountain_car/tk.py,sha256=WMlktnnSEWXJ7ppWb-WKg2wm_jtWN-XwIrFVGk64GIo,3753
|
|
17
|
+
tinysim/mountain_car/widget.py,sha256=3EasBKyA0_MsjqlG97OLBNRlGyK8qzT2zQijWnVf9SM,860
|
|
18
|
+
tinysim/topdown_driving/__init__.py,sha256=ZA4DcQHRs9zeraTYE7Id8ityGZksYtGPN8GiSq2RsJI,5965
|
|
22
19
|
tinysim/topdown_driving/sim.js,sha256=5jmyQk4zp4KFhaG-uTsW_Cq1gHoIkR9d2Cv_ezDKarM,3076
|
|
23
|
-
tinysim/topdown_driving/tk.py,sha256=
|
|
24
|
-
tinysim/topdown_driving/track_0.json,sha256=
|
|
25
|
-
tinysim/topdown_driving/widget.py,sha256=
|
|
20
|
+
tinysim/topdown_driving/tk.py,sha256=WfiFq1zub7x9MGi5aQ4JVQB8qVGKwBT47e3n-_Mju48,4272
|
|
21
|
+
tinysim/topdown_driving/track_0.json,sha256=tZVZtftQCdsB11Gwqj6OECOyOa52RBFTE3vk_e4n8_k,9515
|
|
22
|
+
tinysim/topdown_driving/widget.py,sha256=KVn8BCHicVql07BqdcO8x3i1H67woDiTbu60ypJPZc4,802
|
|
26
23
|
tinysim_mujoco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
tinysim_mujoco/gl_viewer.py,sha256=
|
|
28
|
-
tinysim_mujoco/notebook_viewer.py,sha256=
|
|
29
|
-
tinysim_mujoco/manipulation/__init__.py,sha256=
|
|
30
|
-
tinysim_mujoco/manipulation/
|
|
31
|
-
tinysim_mujoco/manipulation/
|
|
32
|
-
tinysim_mujoco/manipulation/xmls/
|
|
24
|
+
tinysim_mujoco/gl_viewer.py,sha256=0cPmjk3GDhtca9cMiV3px_w8McjXF577NNNGJIdAWxQ,3401
|
|
25
|
+
tinysim_mujoco/notebook_viewer.py,sha256=TwLa4VCKovE3y8NkcdSOkUhoQ3JHvq7Pqni5VpANhQ4,996
|
|
26
|
+
tinysim_mujoco/manipulation/__init__.py,sha256=8sEszaf47LjRaVlSO6k81cwK-UT9mRRHB2mXZyfCl-s,1675
|
|
27
|
+
tinysim_mujoco/manipulation/push_env.py,sha256=IEQi4dR8DVwWxwvmzZhzYLBzVTkZ_62tKsy2yL2PDYw,10456
|
|
28
|
+
tinysim_mujoco/manipulation/push_env_cam.py,sha256=B-GSB9WHew2iq4iUDWc-X78OqwHBl1jtpTGvSgVaOyY,10707
|
|
29
|
+
tinysim_mujoco/manipulation/xmls/panda.xml,sha256=5uTQ473BIGDIO1aASEeLIxw2aaMBjistu_PYMjaN6BU,17868
|
|
30
|
+
tinysim_mujoco/manipulation/xmls/scene.xml,sha256=_6nYdaFPi_Of-GTJPBUg2nCLKab_uz5SM-eFWfvz6Gc,918
|
|
31
|
+
tinysim_mujoco/manipulation/xmls/table.xml,sha256=Gm0-ejM7mmrJQXGkZjEI2JpyOElQsJNbh3srGSWTuUI,1637
|
|
33
32
|
tinysim_mujoco/manipulation/xmls/assets/d405.stl,sha256=Zhfz-yK2BKZsJ-FtI5fzBfzcqKeBJIq9urv2nJ_mo8U,50084
|
|
34
33
|
tinysim_mujoco/manipulation/xmls/assets/finger_0.obj,sha256=8UwcM_XWXKrdyKWg0-Hwg-GjPsqqe-HpHLgdTyHE4vM,23013
|
|
35
34
|
tinysim_mujoco/manipulation/xmls/assets/finger_1.obj,sha256=tLZPSqpntdM74TQDqHT-r0-ov3swyLJr15JCgpx-QIg,16772
|
|
@@ -98,7 +97,7 @@ tinysim_mujoco/manipulation/xmls/assets/link7_4.obj,sha256=SZSny1BhpxKpgq5AYX1Hy
|
|
|
98
97
|
tinysim_mujoco/manipulation/xmls/assets/link7_5.obj,sha256=MsT-PFfY9bpGdtVULacqRtZKBSKmZ15Lf6J_tChFlkE,61442
|
|
99
98
|
tinysim_mujoco/manipulation/xmls/assets/link7_6.obj,sha256=1avsmFwLFqymiEQN7-usXrwQcYDJAhoiZBgs1RXnqNw,27403
|
|
100
99
|
tinysim_mujoco/manipulation/xmls/assets/link7_7.obj,sha256=s56R21mHTno3skIqRb9lnEoS5Qw8G6N74E6lulVmdgo,221627
|
|
101
|
-
tinysim_mujoco/unitree_a1/__init__.py,sha256=
|
|
100
|
+
tinysim_mujoco/unitree_a1/__init__.py,sha256=tcWaG1yL8tnF-e6EdQcKNVhQYUsiFsoqmkUD8qZ7D1I,10542
|
|
102
101
|
tinysim_mujoco/unitree_a1/unitree_a1/CHANGELOG.md,sha256=FIdhX4_Q21xamWMS3-2677HhlETlCnfaAzGemqs4Lpo,143
|
|
103
102
|
tinysim_mujoco/unitree_a1/unitree_a1/LICENSE,sha256=K3lKzx4lD1VFrlpH2B5WhepUiM4HfRCH7Yvl-B6bpcA,1559
|
|
104
103
|
tinysim_mujoco/unitree_a1/unitree_a1/README.md,sha256=tHCjy_A1jcGD7bkFy9Qw9z249Kb6HTDQQBlCk8buqlM,1606
|
|
@@ -109,11 +108,12 @@ tinysim_mujoco/unitree_a1/unitree_a1/assets/hip.obj,sha256=fVW_Gc8Csumvrb-z-4Ztd
|
|
|
109
108
|
tinysim_mujoco/unitree_a1/unitree_a1/assets/thigh.obj,sha256=CdXaRgx4N_02RSH8R6h5k3jyvbd8df04YM8qlP-En1c,979501
|
|
110
109
|
tinysim_mujoco/unitree_a1/unitree_a1/assets/thigh_mirror.obj,sha256=2LL2hKx8de-Ivj7lM5jkqCEOHTm_AMj78bVyqh99orw,1010010
|
|
111
110
|
tinysim_mujoco/unitree_a1/unitree_a1/assets/trunk.obj,sha256=8DX8cdkBSlOq4ay-KVhXghmTTjqJwR0qqj9O_5DAt3c,2014440
|
|
112
|
-
tinysim_warp/
|
|
113
|
-
tinysim_warp/
|
|
114
|
-
tinysim_warp/
|
|
115
|
-
tinysim_warp/simple_quadruped/
|
|
116
|
-
|
|
117
|
-
tinysim-0.0.
|
|
118
|
-
tinysim-0.0.
|
|
119
|
-
tinysim-0.0.
|
|
111
|
+
tinysim_warp/__init__.py,sha256=i8lHo6ZtR-XGD-isd9zifnp3F1DEs24HOcOKeF9mCjg,3525
|
|
112
|
+
tinysim_warp/cart_pole/__init__.py,sha256=ED-xfKpTlrRftjfBHDyIQ8eDuE3HxuniMAeRWNVAJXg,3636
|
|
113
|
+
tinysim_warp/quadruped/__init__.py,sha256=3fJBrG-mBdim2lT_ON5LlFEYPCfFCs608zNvbB7Q6gs,2247
|
|
114
|
+
tinysim_warp/simple_quadruped/__init__.py,sha256=QoIXplgKUoARiZn4N9Xqu6o2OZn0XLOSk61Ki-5xiz8,3085
|
|
115
|
+
tinysim_warp/simple_quadruped/simple_quadruped.urdf,sha256=60eXZG8nH-ABbtJzYuzdGqdLq-gFKSaDM7brNYt5PN4,8177
|
|
116
|
+
tinysim-0.0.5.dist-info/METADATA,sha256=-EqR_V8RIjVImxDFldhYHM3legWynSl2cM9XIMTv4ko,4177
|
|
117
|
+
tinysim-0.0.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
118
|
+
tinysim-0.0.5.dist-info/top_level.txt,sha256=v-9NmTLfZukVjPwJwEdtCU7yY4TdLsBf1H8s25tR3V4,36
|
|
119
|
+
tinysim-0.0.5.dist-info/RECORD,,
|
tinysim_mujoco/gl_viewer.py
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import mujoco
|
|
3
1
|
import glfw
|
|
2
|
+
import mujoco
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OffScreenRenderer:
|
|
7
|
+
def __init__(self, model, data, cam_id):
|
|
8
|
+
self.model = model
|
|
9
|
+
self.data = data
|
|
10
|
+
self.cam_id = cam_id
|
|
11
|
+
self.renderer = mujoco.Renderer(self.model)
|
|
12
|
+
self.renderer.update_scene(self.data, camera=self.cam_id)
|
|
13
|
+
|
|
14
|
+
def render(self):
|
|
15
|
+
self.renderer.update_scene(self.data, camera=self.cam_id)
|
|
16
|
+
|
|
17
|
+
def capture_frame(self):
|
|
18
|
+
return self.renderer.render()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def default_camera():
|
|
22
|
+
cam = mujoco.MjvCamera()
|
|
23
|
+
cam.azimuth = 90.0
|
|
24
|
+
cam.elevation = -25.0
|
|
25
|
+
cam.distance = 4.0
|
|
26
|
+
cam.lookat[:] = np.array([0.0, 0.0, 0.0])
|
|
27
|
+
cam.type = mujoco.mjtCamera.mjCAMERA_FREE
|
|
28
|
+
return cam
|
|
4
29
|
|
|
5
30
|
|
|
6
31
|
class GLViewer:
|
|
7
|
-
def __init__(self, model, data, width=1200, height=900):
|
|
32
|
+
def __init__(self, model, data, cam_id=-1, width=1200, height=900):
|
|
8
33
|
self.width = width
|
|
9
34
|
self.height = height
|
|
10
35
|
self.model = model
|
|
@@ -15,6 +40,13 @@ class GLViewer:
|
|
|
15
40
|
if not glfw.init():
|
|
16
41
|
raise RuntimeError("Failed to initialize GLFW")
|
|
17
42
|
|
|
43
|
+
if cam_id != -1:
|
|
44
|
+
self.cam = mujoco.MjvCamera()
|
|
45
|
+
self.cam.fixedcamid = cam_id
|
|
46
|
+
self.cam.type = mujoco.mjtCamera.mjCAMERA_FIXED
|
|
47
|
+
else:
|
|
48
|
+
self.cam = default_camera()
|
|
49
|
+
|
|
18
50
|
self._create_window()
|
|
19
51
|
|
|
20
52
|
def _create_window(self):
|
|
@@ -27,19 +59,12 @@ class GLViewer:
|
|
|
27
59
|
glfw.swap_interval(1)
|
|
28
60
|
glfw.set_window_close_callback(self.window, self._on_close)
|
|
29
61
|
|
|
30
|
-
self.cam = mujoco.MjvCamera()
|
|
31
62
|
self.opt = mujoco.MjvOption()
|
|
32
63
|
self.scene = mujoco.MjvScene(self.model, maxgeom=500)
|
|
33
64
|
self.context = mujoco.MjrContext(
|
|
34
65
|
self.model, mujoco.mjtFontScale.mjFONTSCALE_150
|
|
35
66
|
)
|
|
36
67
|
|
|
37
|
-
self.cam.azimuth = 90.0
|
|
38
|
-
self.cam.elevation = -25.0
|
|
39
|
-
self.cam.distance = 4.0
|
|
40
|
-
self.cam.lookat[:] = np.array([0.0, 0.0, 0.0])
|
|
41
|
-
self.cam.type = mujoco.mjtCamera.mjCAMERA_FREE
|
|
42
|
-
|
|
43
68
|
def render(self):
|
|
44
69
|
if self.window is None:
|
|
45
70
|
return
|
|
@@ -85,4 +110,4 @@ class GLViewer:
|
|
|
85
110
|
|
|
86
111
|
# OpenGL framebuffer origin is bottom-left, flip vertically
|
|
87
112
|
rgb_buffer = np.flip(rgb_buffer, axis=0)
|
|
88
|
-
return rgb_buffer
|
|
113
|
+
return rgb_buffer
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import pathlib
|
|
2
2
|
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
3
|
try:
|
|
6
4
|
import mujoco
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
from ..gl_viewer import GLViewer, OffScreenRenderer
|
|
8
7
|
from ..notebook_viewer import NotebookViewer
|
|
9
8
|
except ImportError:
|
|
10
9
|
raise ImportError(
|
|
@@ -13,8 +12,9 @@ except ImportError:
|
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class ManipulationBaseEnv:
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
def __init__(
|
|
16
|
+
self, headless=False, use_d405_camera=False, show_cam_renders=False, **kwargs
|
|
17
|
+
):
|
|
18
18
|
model_path = str(pathlib.Path(__file__).parent / "xmls/scene.xml")
|
|
19
19
|
self.model = mujoco.MjModel.from_xml_path(model_path)
|
|
20
20
|
self.data = mujoco.MjData(self.model)
|
|
@@ -28,11 +28,25 @@ class ManipulationBaseEnv:
|
|
|
28
28
|
else:
|
|
29
29
|
self.viewer = None
|
|
30
30
|
|
|
31
|
+
self.d405_viewer = None
|
|
32
|
+
|
|
33
|
+
if use_d405_camera:
|
|
34
|
+
self.cam_id = mujoco.mj_name2id(
|
|
35
|
+
self.model, mujoco.mjtObj.mjOBJ_CAMERA, "camera_d405"
|
|
36
|
+
)
|
|
37
|
+
if show_cam_renders:
|
|
38
|
+
self.d405_viewer = GLViewer(self.model, self.data, cam_id=self.cam_id)
|
|
39
|
+
else:
|
|
40
|
+
self.d405_viewer = OffScreenRenderer(self.model, self.data, self.cam_id)
|
|
41
|
+
|
|
31
42
|
def step(self, action=None, n_frames=20):
|
|
32
43
|
self.render()
|
|
33
44
|
mujoco.mj_step(self.model, self.data, nstep=n_frames)
|
|
34
45
|
mujoco.mj_rnePostConstraint(self.model, self.data)
|
|
35
46
|
|
|
47
|
+
if self.d405_viewer:
|
|
48
|
+
self.d405_viewer.render()
|
|
49
|
+
|
|
36
50
|
def render(self):
|
|
37
51
|
if self.viewer:
|
|
38
52
|
self.viewer.render()
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import mujoco
|
|
5
|
+
|
|
6
|
+
from . import ManipulationBaseEnv
|
|
7
|
+
except ImportError:
|
|
8
|
+
raise ImportError(
|
|
9
|
+
"Mujoco is not properly installed. Install using `pip install tinysim[mujoco]`"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
_FLOAT_EPS = np.finfo(np.float64).eps
|
|
13
|
+
_EPS4 = _FLOAT_EPS * 4.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ManipulationEnvV0:
|
|
17
|
+
|
|
18
|
+
def __init__(self, **kwargs):
|
|
19
|
+
self.env = ManipulationBaseEnv(**kwargs)
|
|
20
|
+
self._model = self.env.model
|
|
21
|
+
self._data = self.env.data
|
|
22
|
+
self.frame_skip = 25
|
|
23
|
+
self.dt = self._model.opt.timestep * self.frame_skip
|
|
24
|
+
|
|
25
|
+
self._body_name2id = {}
|
|
26
|
+
for body_id in range(self._model.nbody):
|
|
27
|
+
name = mujoco.mj_id2name(self._model, mujoco.mjtObj.mjOBJ_BODY, body_id)
|
|
28
|
+
if name is not None:
|
|
29
|
+
self._body_name2id[name] = body_id
|
|
30
|
+
|
|
31
|
+
self.joint_names = []
|
|
32
|
+
for j in range(self._model.njnt):
|
|
33
|
+
name = mujoco.mj_id2name(self._model, mujoco.mjtObj.mjOBJ_JOINT, j)
|
|
34
|
+
if name is not None:
|
|
35
|
+
self.joint_names.append(name)
|
|
36
|
+
|
|
37
|
+
distance_threshold = 0.05
|
|
38
|
+
self.neutral_joint_values = np.array(
|
|
39
|
+
[0.00, 0.41, 0.00, -1.85, 0.00, 2.26, 0.79]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.current_step = 0
|
|
43
|
+
self.maximum_episode_steps = 128
|
|
44
|
+
self.distance_threshold = distance_threshold
|
|
45
|
+
|
|
46
|
+
self.goal = self.get_site_xpos(self._model, self._data, "goal_site").copy()
|
|
47
|
+
self.initial_object_pos = self.get_site_xpos(
|
|
48
|
+
self._model, self._data, "obj_site"
|
|
49
|
+
).copy()
|
|
50
|
+
free_joint_index = self.joint_names.index("obj_joint")
|
|
51
|
+
self.arm_joint_names = self.joint_names[:free_joint_index][0:7]
|
|
52
|
+
# self.gripper_joint_names = self.joint_names[:free_joint_index][7:9]
|
|
53
|
+
self.set_joint_neutral()
|
|
54
|
+
self.reset_mocap_welds(self._model, self._data)
|
|
55
|
+
mujoco.mj_forward(self._model, self._data)
|
|
56
|
+
|
|
57
|
+
def reset_mocap_welds(self, model, data):
|
|
58
|
+
if model.nmocap > 0 and model.eq_data is not None:
|
|
59
|
+
for i in range(model.eq_data.shape[0]):
|
|
60
|
+
if model.eq_type[i] == mujoco.mjtEq.mjEQ_WELD:
|
|
61
|
+
model.eq_data[i, :7] = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0])
|
|
62
|
+
mujoco.mj_forward(model, data)
|
|
63
|
+
|
|
64
|
+
def reset_mocap2body_xpos(self, model, data):
|
|
65
|
+
if model.eq_type is None or model.eq_obj1id is None or model.eq_obj2id is None:
|
|
66
|
+
return
|
|
67
|
+
for eq_type, obj1_id, obj2_id in zip(
|
|
68
|
+
model.eq_type, model.eq_obj1id, model.eq_obj2id
|
|
69
|
+
):
|
|
70
|
+
if eq_type != mujoco.mjtEq.mjEQ_WELD:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
mocap_id = model.body_mocapid[obj1_id]
|
|
74
|
+
if mocap_id != -1:
|
|
75
|
+
# obj1 is the mocap, obj2 is the welded body
|
|
76
|
+
body_idx = obj2_id
|
|
77
|
+
else:
|
|
78
|
+
# obj2 is the mocap, obj1 is the welded body
|
|
79
|
+
mocap_id = model.body_mocapid[obj2_id]
|
|
80
|
+
body_idx = obj1_id
|
|
81
|
+
|
|
82
|
+
assert mocap_id != -1
|
|
83
|
+
data.mocap_pos[mocap_id][:] = data.xpos[body_idx]
|
|
84
|
+
data.mocap_quat[mocap_id][:] = data.xquat[body_idx]
|
|
85
|
+
|
|
86
|
+
def mocap_set_action(self, model, data, action):
|
|
87
|
+
if model.nmocap > 0:
|
|
88
|
+
action, _ = np.split(action, (model.nmocap * 7,))
|
|
89
|
+
action = action.reshape(model.nmocap, 7)
|
|
90
|
+
pos_delta = action[:, :3]
|
|
91
|
+
quat_delta = action[:, 3:]
|
|
92
|
+
self.reset_mocap2body_xpos(model, data)
|
|
93
|
+
data.mocap_pos[:] = data.mocap_pos + pos_delta
|
|
94
|
+
data.mocap_quat[:] = data.mocap_quat + quat_delta
|
|
95
|
+
|
|
96
|
+
def ctrl_set_action(self, model, data, action):
|
|
97
|
+
if model.nmocap > 0:
|
|
98
|
+
_, action = np.split(action, (model.nmocap * 7,))
|
|
99
|
+
|
|
100
|
+
if len(data.ctrl) > 0:
|
|
101
|
+
for i in range(action.shape[0]):
|
|
102
|
+
if model.actuator_biastype[i] == 0:
|
|
103
|
+
data.ctrl[i] = action[i]
|
|
104
|
+
else:
|
|
105
|
+
idx = model.jnt_qposadr[model.actuator_trnid[i, 0]]
|
|
106
|
+
data.ctrl[i] = data.qpos[idx] + action[i]
|
|
107
|
+
|
|
108
|
+
def step(self, action):
|
|
109
|
+
self.current_step += 1
|
|
110
|
+
# just move the end effector in x, y, z
|
|
111
|
+
pos_ctrl = action.copy() * 0.05 # scale action
|
|
112
|
+
rot_ctrl = [
|
|
113
|
+
1.0,
|
|
114
|
+
0.0,
|
|
115
|
+
0.0,
|
|
116
|
+
0.0,
|
|
117
|
+
]
|
|
118
|
+
action = np.concatenate([pos_ctrl, rot_ctrl])
|
|
119
|
+
|
|
120
|
+
self.ctrl_set_action(self._model, self._data, action)
|
|
121
|
+
self.mocap_set_action(self._model, self._data, action)
|
|
122
|
+
|
|
123
|
+
self.env.step(n_frames=self.frame_skip)
|
|
124
|
+
obs = self._get_obs()
|
|
125
|
+
terminated = self._is_success(obs["achieved_goal"], self.goal)
|
|
126
|
+
truncated = self.current_step >= self.maximum_episode_steps
|
|
127
|
+
reward = self.compute_reward(obs["achieved_goal"], self.goal)
|
|
128
|
+
return obs, reward, terminated, truncated, {}
|
|
129
|
+
|
|
130
|
+
def mat2euler(self, mat):
|
|
131
|
+
mat = np.asarray(mat, dtype=np.float64)
|
|
132
|
+
cy = np.sqrt(mat[..., 2, 2] * mat[..., 2, 2] + mat[..., 1, 2] * mat[..., 1, 2])
|
|
133
|
+
condition = cy > _EPS4
|
|
134
|
+
euler = np.empty(mat.shape[:-1], dtype=np.float64)
|
|
135
|
+
euler[..., 2] = np.where(
|
|
136
|
+
condition,
|
|
137
|
+
-np.arctan2(mat[..., 0, 1], mat[..., 0, 0]),
|
|
138
|
+
-np.arctan2(-mat[..., 1, 0], mat[..., 1, 1]),
|
|
139
|
+
)
|
|
140
|
+
euler[..., 1] = np.where(
|
|
141
|
+
condition,
|
|
142
|
+
-np.arctan2(-mat[..., 0, 2], cy),
|
|
143
|
+
-np.arctan2(-mat[..., 0, 2], cy),
|
|
144
|
+
)
|
|
145
|
+
euler[..., 0] = np.where(
|
|
146
|
+
condition, -np.arctan2(mat[..., 1, 2], mat[..., 2, 2]), 0.0
|
|
147
|
+
)
|
|
148
|
+
return euler
|
|
149
|
+
|
|
150
|
+
def get_site_xpos(self, model, data, name):
|
|
151
|
+
site_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, name)
|
|
152
|
+
return data.site_xpos[site_id]
|
|
153
|
+
|
|
154
|
+
def get_site_xvelp(self, model, data, name):
|
|
155
|
+
site_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, name)
|
|
156
|
+
jacp = self.get_site_jacp(model, data, site_id)
|
|
157
|
+
xvelp = jacp @ data.qvel
|
|
158
|
+
return xvelp
|
|
159
|
+
|
|
160
|
+
def get_site_jacp(self, model, data, site_id):
|
|
161
|
+
jacp = np.zeros((3, model.nv))
|
|
162
|
+
mujoco.mj_jacSite(model, data, jacp, None, site_id)
|
|
163
|
+
return jacp
|
|
164
|
+
|
|
165
|
+
def _get_obs(self) -> dict:
|
|
166
|
+
ee_position = self.get_site_xpos(self._model, self._data, "tip").copy()
|
|
167
|
+
ee_velocity = (
|
|
168
|
+
self.get_site_xvelp(self._model, self._data, "tip").copy() * self.dt
|
|
169
|
+
)
|
|
170
|
+
object_position = self.get_site_xpos(self._model, self._data, "obj_site").copy()
|
|
171
|
+
object_rotation = self.mat2euler(
|
|
172
|
+
self.get_site_xmat(self._model, self._data, "obj_site")
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
"observation": np.concatenate(
|
|
177
|
+
[
|
|
178
|
+
ee_position,
|
|
179
|
+
ee_velocity,
|
|
180
|
+
object_position,
|
|
181
|
+
object_rotation,
|
|
182
|
+
]
|
|
183
|
+
).copy(),
|
|
184
|
+
"achieved_goal": object_position.copy(),
|
|
185
|
+
"desired_goal": self.goal.copy(),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
def set_joint_qpos(self, model, data, name, value):
|
|
189
|
+
"""Set the joint positions (qpos) of the model."""
|
|
190
|
+
joint_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, name)
|
|
191
|
+
joint_addr = model.jnt_qposadr[joint_id]
|
|
192
|
+
# All joints are assumed to be of type mjJNT_HINGE in this environment
|
|
193
|
+
data.qpos[joint_addr] = value
|
|
194
|
+
|
|
195
|
+
def set_object_qpos(self, model, data):
|
|
196
|
+
joint_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, "obj_joint")
|
|
197
|
+
joint_addr = model.jnt_qposadr[joint_id]
|
|
198
|
+
data.qpos[joint_addr : joint_addr + 3] = np.array(self.initial_object_pos)
|
|
199
|
+
# reset orientation
|
|
200
|
+
data.qpos[joint_addr + 3 : joint_addr + 7] = np.array([1.0, 0.0, 0.0, 0.0])
|
|
201
|
+
|
|
202
|
+
def _sample_goal(self):
|
|
203
|
+
goal = np.array([0.3, 0.0, 0.0])
|
|
204
|
+
return goal
|
|
205
|
+
|
|
206
|
+
def goal_distance(self, goal_a, goal_b):
|
|
207
|
+
assert goal_a.shape == goal_b.shape
|
|
208
|
+
return np.linalg.norm(goal_a - goal_b, axis=-1)
|
|
209
|
+
|
|
210
|
+
def compute_reward(self, achieved_goal, desired_goal, info=None):
|
|
211
|
+
d = self.goal_distance(achieved_goal, desired_goal)
|
|
212
|
+
# give reward for the end effector being close to the object
|
|
213
|
+
ee_position = self.get_site_xpos(self._model, self._data, "tip")
|
|
214
|
+
object_position = self.get_site_xpos(self._model, self._data, "obj_site")
|
|
215
|
+
ee_object_distance = np.linalg.norm(ee_position - object_position, axis=-1)
|
|
216
|
+
|
|
217
|
+
# the arm tries to cheat by moving the object with the side of the gripper
|
|
218
|
+
# negative ward if the arm contacts the ground with
|
|
219
|
+
effector_distance_reward = -0.1 * (ee_object_distance > 0.05).astype(np.float32)
|
|
220
|
+
|
|
221
|
+
# todo: check correctness
|
|
222
|
+
# give some reward if the cube is moving towards the goal
|
|
223
|
+
object_velocity = self.get_site_xvelp(self._model, self._data, "obj_site")
|
|
224
|
+
goal_direction = desired_goal[0] - object_position
|
|
225
|
+
goal_direction /= np.linalg.norm(goal_direction) + 1e-8
|
|
226
|
+
velocity_towards_goal = np.dot(object_velocity, goal_direction)
|
|
227
|
+
velocity_reward = max(0.0, velocity_towards_goal)
|
|
228
|
+
|
|
229
|
+
reward = -d + effector_distance_reward + velocity_reward
|
|
230
|
+
return reward
|
|
231
|
+
|
|
232
|
+
# sparse
|
|
233
|
+
# return -(d > self.distance_threshold).astype(np.float32)
|
|
234
|
+
|
|
235
|
+
def _is_success(self, achieved_goal, desired_goal):
|
|
236
|
+
d = self.goal_distance(achieved_goal, desired_goal)
|
|
237
|
+
return (d < self.distance_threshold).astype(np.float32)
|
|
238
|
+
|
|
239
|
+
def set_joint_neutral(self):
|
|
240
|
+
for name, value in zip(self.arm_joint_names, self.neutral_joint_values):
|
|
241
|
+
self.set_joint_qpos(self._model, self._data, name, value)
|
|
242
|
+
|
|
243
|
+
def _reset_sim(self) -> bool:
|
|
244
|
+
self.set_joint_neutral()
|
|
245
|
+
self.set_object_qpos(self._model, self._data)
|
|
246
|
+
mujoco.mj_forward(self._model, self._data)
|
|
247
|
+
|
|
248
|
+
def reset(self, **kwargs):
|
|
249
|
+
self.current_step = 0
|
|
250
|
+
self._reset_sim()
|
|
251
|
+
obs = self._get_obs()
|
|
252
|
+
|
|
253
|
+
# self.env.step(n_frames=self.frame_skip)
|
|
254
|
+
# frame = self.env.viewer.capture_frame()
|
|
255
|
+
# import matplotlib.pyplot as plt
|
|
256
|
+
# plt.imsave("debug_frame.png", frame)
|
|
257
|
+
# raise Exception("Debug")
|
|
258
|
+
return obs, {}
|
|
259
|
+
|
|
260
|
+
def get_site_xmat(self, model, data, name: str):
|
|
261
|
+
site_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, name)
|
|
262
|
+
return data.site_xmat[site_id].reshape(3, 3)
|
|
263
|
+
|
|
264
|
+
def get_body_state(self, name):
|
|
265
|
+
body_id = self._body_name2id[name]
|
|
266
|
+
body_xpos = self._data.xpos[body_id]
|
|
267
|
+
body_xquat = self._data.xquat[body_id]
|
|
268
|
+
return np.concatenate([body_xpos, body_xquat])
|