mujoco-toolbox 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {mujoco_toolbox-0.2.0 → mujoco_toolbox-0.3.0}/PKG-INFO +17 -21
- {mujoco_toolbox-0.2.0 → mujoco_toolbox-0.3.0}/README.md +13 -14
- mujoco_toolbox-0.3.0/mujoco_toolbox/__init__.py +74 -0
- {mujoco_toolbox-0.2.0 → mujoco_toolbox-0.3.0}/mujoco_toolbox/assets.py +1 -1
- {mujoco_toolbox-0.2.0 → mujoco_toolbox-0.3.0}/mujoco_toolbox/builder.py +60 -33
- mujoco_toolbox-0.2.0/mujoco_toolbox/Controller.py → mujoco_toolbox-0.3.0/mujoco_toolbox/controller.py +31 -20
- mujoco_toolbox-0.2.0/mujoco_toolbox/Utils.py → mujoco_toolbox-0.3.0/mujoco_toolbox/utils.py +11 -15
- mujoco_toolbox-0.2.0/mujoco_toolbox/Wrapper.py → mujoco_toolbox-0.3.0/mujoco_toolbox/wrapper.py +345 -272
- mujoco_toolbox-0.3.0/pyproject.toml +135 -0
- mujoco_toolbox-0.2.0/mujoco_toolbox/__init__.py +0 -75
- mujoco_toolbox-0.2.0/pyproject.toml +0 -95
- {mujoco_toolbox-0.2.0 → mujoco_toolbox-0.3.0}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mujoco-toolbox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A modern package to handle MuJoCo environments
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: MuJoCo,robotics,simulation,toolbox,wrapper
|
|
@@ -10,11 +10,7 @@ Requires-Python: >=3.10
|
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
18
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
15
|
Requires-Dist: colorama
|
|
20
16
|
Requires-Dist: ffmpeg
|
|
@@ -29,15 +25,16 @@ Requires-Dist: screeninfo
|
|
|
29
25
|
Requires-Dist: tqdm
|
|
30
26
|
Requires-Dist: trimesh
|
|
31
27
|
Project-URL: Bug Tracker, https://github.com/MGross21/mujoco-toolbox/issues
|
|
32
|
-
Project-URL:
|
|
33
|
-
Project-URL:
|
|
34
|
-
Project-URL:
|
|
28
|
+
Project-URL: Documentation, https://github.com/MGross21/mujoco-toolbox
|
|
29
|
+
Project-URL: Homepage, https://github.com/MGross21/mujoco-toolbox
|
|
30
|
+
Project-URL: Repository, https://github.com/MGross21/mujoco-toolbox
|
|
35
31
|
Description-Content-Type: text/markdown
|
|
36
32
|
|
|
37
33
|

|
|
38
34
|

|
|
39
35
|

|
|
40
36
|
[](https://github.com/MGross21/mujoco-toolbox/actions/workflows/publish.yml)
|
|
37
|
+
[](https://github.com/MGross21/mujoco-toolbox/actions/workflows/docs.yml)
|
|
41
38
|
|
|
42
39
|
# Mujoco Toolbox
|
|
43
40
|
|
|
@@ -96,28 +93,26 @@ ffmpeg -version
|
|
|
96
93
|
|
|
97
94
|
</details>
|
|
98
95
|
|
|
99
|
-
##
|
|
96
|
+
## Example Script
|
|
97
|
+
|
|
98
|
+
*Bare minimum to run MuJoCo simulation and display result*
|
|
100
99
|
|
|
101
100
|
```python
|
|
102
101
|
import mujoco_toolbox as mjtb
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
mjtb.VERBOSITY = True # Additional Performance and Debugging Print Statements (Default=False)
|
|
106
|
-
mjtb.CAPTURE_PARAMETERS = "all" # MjData Simulation Parameters (Default = ['time', 'qpos', 'qvel', 'act', 'qacc', 'xpos', 'xquat', 'xmat', 'ctrl', 'sensordata'])
|
|
107
|
-
|
|
108
|
-
# Example Code
|
|
109
|
-
mjtb.Wrapper("path/to/your/xml").runSim(render=True).renderMedia()
|
|
103
|
+
mjtb.Wrapper("path/to/your/xml").run(render=True).save()
|
|
110
104
|
```
|
|
111
105
|
|
|
112
106
|
## Pre-Made Controllers
|
|
113
107
|
|
|
114
108
|
```python
|
|
115
|
-
from mujoco_toolbox import
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
from mujoco_toolbox.controller import (
|
|
110
|
+
cosine_controller,
|
|
111
|
+
random_controller,
|
|
112
|
+
live_controller,
|
|
113
|
+
sine_controller,
|
|
114
|
+
step_controller,
|
|
115
|
+
)
|
|
121
116
|
|
|
122
117
|
# Wrapper can use custom controllers as well!
|
|
123
118
|
```
|
|
@@ -131,3 +126,4 @@ realTimeController
|
|
|
131
126
|
### URDF
|
|
132
127
|
|
|
133
128
|

|
|
129
|
+
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|

|
|
3
3
|

|
|
4
4
|
[](https://github.com/MGross21/mujoco-toolbox/actions/workflows/publish.yml)
|
|
5
|
+
[](https://github.com/MGross21/mujoco-toolbox/actions/workflows/docs.yml)
|
|
5
6
|
|
|
6
7
|
# Mujoco Toolbox
|
|
7
8
|
|
|
@@ -60,28 +61,26 @@ ffmpeg -version
|
|
|
60
61
|
|
|
61
62
|
</details>
|
|
62
63
|
|
|
63
|
-
##
|
|
64
|
+
## Example Script
|
|
65
|
+
|
|
66
|
+
*Bare minimum to run MuJoCo simulation and display result*
|
|
64
67
|
|
|
65
68
|
```python
|
|
66
69
|
import mujoco_toolbox as mjtb
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
mjtb.VERBOSITY = True # Additional Performance and Debugging Print Statements (Default=False)
|
|
70
|
-
mjtb.CAPTURE_PARAMETERS = "all" # MjData Simulation Parameters (Default = ['time', 'qpos', 'qvel', 'act', 'qacc', 'xpos', 'xquat', 'xmat', 'ctrl', 'sensordata'])
|
|
71
|
-
|
|
72
|
-
# Example Code
|
|
73
|
-
mjtb.Wrapper("path/to/your/xml").runSim(render=True).renderMedia()
|
|
71
|
+
mjtb.Wrapper("path/to/your/xml").run(render=True).save()
|
|
74
72
|
```
|
|
75
73
|
|
|
76
74
|
## Pre-Made Controllers
|
|
77
75
|
|
|
78
76
|
```python
|
|
79
|
-
from mujoco_toolbox import
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
from mujoco_toolbox.controller import (
|
|
78
|
+
cosine_controller,
|
|
79
|
+
random_controller,
|
|
80
|
+
live_controller,
|
|
81
|
+
sine_controller,
|
|
82
|
+
step_controller,
|
|
83
|
+
)
|
|
85
84
|
|
|
86
85
|
# Wrapper can use custom controllers as well!
|
|
87
86
|
```
|
|
@@ -94,4 +93,4 @@ realTimeController
|
|
|
94
93
|
|
|
95
94
|
### URDF
|
|
96
95
|
|
|
97
|
-

|
|
96
|
+

|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Mujoco Toolbox.
|
|
2
|
+
==============
|
|
3
|
+
A toolbox for working with MuJoCo simulations. This package provides various utilities and controllers to facilitate
|
|
4
|
+
the simulation process.
|
|
5
|
+
|
|
6
|
+
Modules:
|
|
7
|
+
--------
|
|
8
|
+
- `Wrapper`: Contains the Wrapper class for interfacing with MuJoCo.
|
|
9
|
+
- `Controller`: Includes sineController, cosineController, and randomController for controlling simulations.
|
|
10
|
+
- `Builder`: Contains the Builder class for creating and merging MuJoCo models.
|
|
11
|
+
- `assets`: Contains pre-defined assets for building MuJoCo models.
|
|
12
|
+
|
|
13
|
+
Constants:
|
|
14
|
+
----------
|
|
15
|
+
- CAPTURE_PARAMETERS: List of MjData fields to capture during simulation.
|
|
16
|
+
|
|
17
|
+
Notes
|
|
18
|
+
-----
|
|
19
|
+
This package is still under development. Report any issues to https://github.com/MGross21/mujoco-toolbox/issues.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .assets import WORLD_ASSETS, glovebox
|
|
24
|
+
from .builder import Builder
|
|
25
|
+
from .controller import (
|
|
26
|
+
cosine_controller,
|
|
27
|
+
live_controller,
|
|
28
|
+
random_controller,
|
|
29
|
+
sine_controller,
|
|
30
|
+
step_controller,
|
|
31
|
+
)
|
|
32
|
+
from .utils import _Platform, print_warning
|
|
33
|
+
from .wrapper import Wrapper
|
|
34
|
+
|
|
35
|
+
__version__ = "0.3.0"
|
|
36
|
+
__author__ = "Michael Gross"
|
|
37
|
+
__github_repo__ = "mujoco-toolbox"
|
|
38
|
+
__license__ = "MIT"
|
|
39
|
+
__status__ = "Development"
|
|
40
|
+
__all__ = [
|
|
41
|
+
"CAPTURE_PARAMETERS",
|
|
42
|
+
"COMPUTER",
|
|
43
|
+
"WORLD_ASSETS",
|
|
44
|
+
"Builder",
|
|
45
|
+
"Wrapper",
|
|
46
|
+
"cosine_controller",
|
|
47
|
+
"glovebox",
|
|
48
|
+
"live_controller",
|
|
49
|
+
"random_controller",
|
|
50
|
+
"sine_controller",
|
|
51
|
+
"step_controller",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
COMPUTER = _Platform() # Singleton Instance
|
|
55
|
+
MAX_GEOM_SCALAR: int = 2 # Scalar value for mujoco.Renderer.max_geom
|
|
56
|
+
CAPTURE_PARAMETERS = [ # MjData default fields to capture during simulation
|
|
57
|
+
"time",
|
|
58
|
+
"qpos",
|
|
59
|
+
"qvel",
|
|
60
|
+
"act",
|
|
61
|
+
"qacc",
|
|
62
|
+
"xpos",
|
|
63
|
+
"xquat",
|
|
64
|
+
"xmat",
|
|
65
|
+
"ctrl",
|
|
66
|
+
"sensordata",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
if __version__.startswith("0"):
|
|
70
|
+
print_warning(
|
|
71
|
+
f"{__package__} (v{__version__}) is still under development.",
|
|
72
|
+
f"Report any issues to https://github.com/MGross21/{__github_repo__}/issues",
|
|
73
|
+
)
|
|
74
|
+
del _Platform, print_warning
|
|
@@ -3,14 +3,30 @@ import xml.etree.ElementTree as ET
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Builder:
|
|
6
|
-
def __init__(self,
|
|
7
|
-
"""
|
|
8
|
-
|
|
6
|
+
def __init__(self, *args: str) -> None:
|
|
7
|
+
"""Initialize with an XML string or a file path. Input is required."""
|
|
8
|
+
for arg in args:
|
|
9
|
+
if not isinstance(arg, str):
|
|
10
|
+
msg = "Input must be an XML string or a file path"
|
|
11
|
+
raise TypeError(msg)
|
|
12
|
+
if len(args) == 0:
|
|
13
|
+
msg = "Input is required to initialize the Builder"
|
|
14
|
+
raise ValueError(msg)
|
|
15
|
+
if len(args) > 1:
|
|
16
|
+
# Create a new Builder instance for each additional argument and merge them
|
|
17
|
+
self.tree, self.root = self.load_model(args[0])
|
|
18
|
+
for additional_arg in args[1:]:
|
|
19
|
+
additional_builder = Builder(additional_arg)
|
|
20
|
+
self.__add__(additional_builder)
|
|
21
|
+
else:
|
|
22
|
+
xml_input = args[0]
|
|
23
|
+
self.tree, self.root = self.load_model(xml_input)
|
|
9
24
|
|
|
10
25
|
def load_model(self, xml_input):
|
|
11
|
-
"""
|
|
26
|
+
"""Load a MuJoCo model from a file or XML string."""
|
|
12
27
|
if not isinstance(xml_input, str):
|
|
13
|
-
|
|
28
|
+
msg = "Input must be either an XML string or a file path"
|
|
29
|
+
raise ValueError(msg)
|
|
14
30
|
|
|
15
31
|
# Check if input is an XML string
|
|
16
32
|
if xml_input.strip().startswith("<"):
|
|
@@ -31,11 +47,13 @@ class Builder:
|
|
|
31
47
|
|
|
32
48
|
return tree, tree.getroot()
|
|
33
49
|
except ET.ParseError:
|
|
34
|
-
|
|
50
|
+
msg = "Invalid XML string provided"
|
|
51
|
+
raise ValueError(msg)
|
|
35
52
|
else:
|
|
36
53
|
# Treat input as file path
|
|
37
54
|
if not os.path.exists(xml_input):
|
|
38
|
-
|
|
55
|
+
msg = f"File not found: {xml_input}"
|
|
56
|
+
raise FileNotFoundError(msg)
|
|
39
57
|
|
|
40
58
|
# Handle URDF files
|
|
41
59
|
if xml_input.lower().endswith(".urdf"):
|
|
@@ -54,11 +72,11 @@ class Builder:
|
|
|
54
72
|
|
|
55
73
|
return tree, tree.getroot()
|
|
56
74
|
except ET.ParseError:
|
|
57
|
-
|
|
75
|
+
msg = f"Invalid XML file: {xml_input}"
|
|
76
|
+
raise ValueError(msg)
|
|
58
77
|
|
|
59
78
|
def wrap_urdf_in_mujoco(self, urdf_input):
|
|
60
|
-
"""
|
|
61
|
-
Wrap a URDF inside <mujoco> tags for MuJoCo compatibility.
|
|
79
|
+
"""Wrap a URDF inside <mujoco> tags for MuJoCo compatibility.
|
|
62
80
|
Input can be a file path, ElementTree, or XML string.
|
|
63
81
|
"""
|
|
64
82
|
# Handle string input (could be a file path or XML string)
|
|
@@ -69,18 +87,21 @@ class Builder:
|
|
|
69
87
|
root = ET.fromstring(urdf_input)
|
|
70
88
|
tree = ET.ElementTree(root)
|
|
71
89
|
except ET.ParseError:
|
|
72
|
-
|
|
90
|
+
msg = "Invalid URDF XML string provided"
|
|
91
|
+
raise ValueError(msg)
|
|
73
92
|
else:
|
|
74
93
|
# It's a file path
|
|
75
94
|
try:
|
|
76
95
|
tree = ET.parse(urdf_input)
|
|
77
96
|
except:
|
|
78
|
-
|
|
97
|
+
msg = f"Could not parse URDF file: {urdf_input}"
|
|
98
|
+
raise ValueError(msg)
|
|
79
99
|
# Handle ElementTree input
|
|
80
100
|
elif isinstance(urdf_input, ET.ElementTree):
|
|
81
101
|
tree = urdf_input
|
|
82
102
|
else:
|
|
83
|
-
|
|
103
|
+
msg = "URDF input must be a string or ElementTree"
|
|
104
|
+
raise TypeError(msg)
|
|
84
105
|
|
|
85
106
|
root = tree.getroot()
|
|
86
107
|
|
|
@@ -95,8 +116,8 @@ class Builder:
|
|
|
95
116
|
# Create a new tree with the <mujoco> root
|
|
96
117
|
return ET.ElementTree(mujoco_elem), mujoco_elem
|
|
97
118
|
|
|
98
|
-
def merge_tags(self, tag_name, root_1, root_2):
|
|
99
|
-
"""
|
|
119
|
+
def merge_tags(self, tag_name, root_1, root_2) -> None:
|
|
120
|
+
"""Merge a specific tag (e.g., worldbody, asset) from two models."""
|
|
100
121
|
section_1 = root_1.find(tag_name)
|
|
101
122
|
section_2 = root_2.find(tag_name)
|
|
102
123
|
|
|
@@ -111,7 +132,7 @@ class Builder:
|
|
|
111
132
|
section_1.append(element)
|
|
112
133
|
|
|
113
134
|
def __add__(self, other):
|
|
114
|
-
"""
|
|
135
|
+
"""Implement the + operator for merging two models."""
|
|
115
136
|
model_root = None
|
|
116
137
|
|
|
117
138
|
if isinstance(other, Builder):
|
|
@@ -121,7 +142,8 @@ class Builder:
|
|
|
121
142
|
# If the input is a string, load as XML
|
|
122
143
|
_, model_root = self.load_model(other)
|
|
123
144
|
else:
|
|
124
|
-
|
|
145
|
+
msg = "Addition only supported between Builder objects or with XML strings/files"
|
|
146
|
+
raise TypeError(msg)
|
|
125
147
|
|
|
126
148
|
# List of sections to merge
|
|
127
149
|
sections_to_merge = ["asset", "worldbody", "camera", "light", "contact", "equality",
|
|
@@ -135,22 +157,24 @@ class Builder:
|
|
|
135
157
|
return self
|
|
136
158
|
|
|
137
159
|
def __radd__(self, other):
|
|
138
|
-
"""
|
|
160
|
+
"""Implement the reverse + operator for merging two models."""
|
|
139
161
|
if other == 0: # Handle sum() starting value
|
|
140
162
|
return self
|
|
141
163
|
return self.__add__(other)
|
|
142
164
|
|
|
143
|
-
def save(self, file_path):
|
|
144
|
-
"""
|
|
165
|
+
def save(self, file_path) -> "os.path":
|
|
166
|
+
"""Save the merged model to a file."""
|
|
145
167
|
if self.tree is not None:
|
|
146
168
|
# Format the XML with proper indentation before saving
|
|
147
169
|
self._indent_xml(self.root)
|
|
148
170
|
self.tree.write(file_path, encoding="utf-8", xml_declaration=True)
|
|
149
171
|
else:
|
|
150
|
-
|
|
172
|
+
msg = "No model loaded. Cannot save."
|
|
173
|
+
raise ValueError(msg)
|
|
174
|
+
return os.path.abspath(file_path)
|
|
151
175
|
|
|
152
|
-
def _indent_xml(self, elem, level=0):
|
|
153
|
-
"""
|
|
176
|
+
def _indent_xml(self, elem, level=0) -> None:
|
|
177
|
+
"""Add proper indentation to make the XML file more readable."""
|
|
154
178
|
i = "\n" + level * " "
|
|
155
179
|
if len(elem):
|
|
156
180
|
if not elem.text or not elem.text.strip():
|
|
@@ -161,24 +185,27 @@ class Builder:
|
|
|
161
185
|
self._indent_xml(sub_elem, level + 1)
|
|
162
186
|
if not elem[-1].tail or not elem[-1].tail.strip():
|
|
163
187
|
elem[-1].tail = i
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
elem.tail = i
|
|
188
|
+
elif level and (not elem.tail or not elem.tail.strip()):
|
|
189
|
+
elem.tail = i
|
|
167
190
|
|
|
168
|
-
def __str__(self):
|
|
169
|
-
"""
|
|
191
|
+
def __str__(self) -> str:
|
|
192
|
+
"""Return the XML string of the model."""
|
|
170
193
|
if self.tree is not None:
|
|
171
194
|
# Format the XML with proper indentation
|
|
172
195
|
self._indent_xml(self.root)
|
|
173
196
|
return ET.tostring(self.root, encoding="unicode", method="xml")
|
|
174
|
-
|
|
175
|
-
|
|
197
|
+
msg = "No model loaded. Cannot generate string."
|
|
198
|
+
raise ValueError(msg)
|
|
176
199
|
|
|
177
|
-
def __repr__(self):
|
|
178
|
-
"""
|
|
200
|
+
def __repr__(self) -> str:
|
|
201
|
+
"""Return the XML string of the model."""
|
|
179
202
|
return self.__str__()
|
|
180
203
|
|
|
204
|
+
def __len__(self) -> int:
|
|
205
|
+
"""Return the number of elements in the model."""
|
|
206
|
+
return len(self.root) if self.root is not None else 0
|
|
207
|
+
|
|
181
208
|
@property
|
|
182
209
|
def xml(self):
|
|
183
|
-
"""
|
|
210
|
+
"""Property to quickly grab the XML string of the model."""
|
|
184
211
|
return str(self)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from numpy import cos, pi, random, sin
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def _apply_control(model, data, value, joint=None, axis=None, delay=0):
|
|
4
|
+
def _apply_control(model, data, value, joint=None, axis=None, delay=0) -> None:
|
|
5
5
|
"""Common helper function for controller logic to reduce redundancy.
|
|
6
6
|
|
|
7
7
|
Args:
|
|
@@ -11,6 +11,7 @@ def _apply_control(model, data, value, joint=None, axis=None, delay=0):
|
|
|
11
11
|
joint (list[int], optional): Joints to apply control to.
|
|
12
12
|
axis (int, optional): Axis to apply control to.
|
|
13
13
|
delay (float, optional): Delay before applying control.
|
|
14
|
+
|
|
14
15
|
"""
|
|
15
16
|
# Don't apply control until after delay
|
|
16
17
|
if data.time < delay:
|
|
@@ -28,7 +29,7 @@ def _apply_control(model, data, value, joint=None, axis=None, delay=0):
|
|
|
28
29
|
data.qpos[axis] = value
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
def
|
|
32
|
+
def step_controller(model, data, **kwargs) -> None:
|
|
32
33
|
"""A step controller for the simulation.
|
|
33
34
|
|
|
34
35
|
Args:
|
|
@@ -39,21 +40,24 @@ def stepController(model, data, **kwargs):
|
|
|
39
40
|
|
|
40
41
|
Returns:
|
|
41
42
|
None
|
|
43
|
+
|
|
42
44
|
"""
|
|
43
45
|
amplitude = kwargs.get("amplitude", 1)
|
|
44
|
-
joint = kwargs.get("joint"
|
|
45
|
-
axis = kwargs.get("axis"
|
|
46
|
+
joint = kwargs.get("joint")
|
|
47
|
+
axis = kwargs.get("axis")
|
|
46
48
|
delay = kwargs.get("delay", 0)
|
|
47
49
|
|
|
48
50
|
if delay < 0:
|
|
49
|
-
|
|
51
|
+
msg = "Delay must be non-negative."
|
|
52
|
+
raise ValueError(msg)
|
|
50
53
|
if joint is not None and axis is not None:
|
|
51
|
-
|
|
54
|
+
msg = "Cannot specify both 'joint' and 'axis'."
|
|
55
|
+
raise ValueError(msg)
|
|
52
56
|
|
|
53
57
|
_apply_control(model, data, amplitude, joint=joint, axis=axis, delay=delay)
|
|
54
58
|
|
|
55
59
|
|
|
56
|
-
def
|
|
60
|
+
def sine_controller(model, data, **kwargs) -> None:
|
|
57
61
|
"""A simple sine wave controller for the simulation.
|
|
58
62
|
|
|
59
63
|
Args:
|
|
@@ -65,21 +69,23 @@ def sineController(model, data, **kwargs):
|
|
|
65
69
|
|
|
66
70
|
Returns:
|
|
67
71
|
None
|
|
72
|
+
|
|
68
73
|
"""
|
|
69
74
|
amplitude = kwargs.get("amplitude", 1)
|
|
70
75
|
frequency = kwargs.get("frequency", 1)
|
|
71
76
|
phase = kwargs.get("phase", 0)
|
|
72
|
-
joint = kwargs.get("joint"
|
|
77
|
+
joint = kwargs.get("joint")
|
|
73
78
|
delay = kwargs.get("delay", 0)
|
|
74
79
|
|
|
75
80
|
if delay < 0:
|
|
76
|
-
|
|
81
|
+
msg = "Delay must be non-negative."
|
|
82
|
+
raise ValueError(msg)
|
|
77
83
|
|
|
78
84
|
value = amplitude * sin(2 * pi * frequency * data.time + phase)
|
|
79
85
|
_apply_control(model, data, value, joint=joint, delay=delay)
|
|
80
86
|
|
|
81
87
|
|
|
82
|
-
def
|
|
88
|
+
def cosine_controller(model, data, **kwargs) -> None:
|
|
83
89
|
"""A simple cosine wave controller for the simulation.
|
|
84
90
|
|
|
85
91
|
Args:
|
|
@@ -91,21 +97,23 @@ def cosineController(model, data, **kwargs):
|
|
|
91
97
|
|
|
92
98
|
Returns:
|
|
93
99
|
None
|
|
100
|
+
|
|
94
101
|
"""
|
|
95
102
|
amplitude = kwargs.get("amplitude", 1)
|
|
96
103
|
frequency = kwargs.get("frequency", 1)
|
|
97
104
|
phase = kwargs.get("phase", 0)
|
|
98
|
-
joint = kwargs.get("joint"
|
|
105
|
+
joint = kwargs.get("joint")
|
|
99
106
|
delay = kwargs.get("delay", 0)
|
|
100
107
|
|
|
101
108
|
if delay < 0:
|
|
102
|
-
|
|
109
|
+
msg = "Delay must be non-negative."
|
|
110
|
+
raise ValueError(msg)
|
|
103
111
|
|
|
104
112
|
value = amplitude * cos(2 * pi * frequency * data.time + phase)
|
|
105
113
|
_apply_control(model, data, value, joint=joint, delay=delay)
|
|
106
114
|
|
|
107
115
|
|
|
108
|
-
def
|
|
116
|
+
def random_controller(model, data, **kwargs) -> None:
|
|
109
117
|
"""A random controller for the simulation.
|
|
110
118
|
|
|
111
119
|
Args:
|
|
@@ -116,21 +124,24 @@ def randomController(model, data, **kwargs):
|
|
|
116
124
|
|
|
117
125
|
Returns:
|
|
118
126
|
None
|
|
127
|
+
|
|
119
128
|
"""
|
|
120
129
|
amplitude = kwargs.get("amplitude", 1)
|
|
121
|
-
joint = kwargs.get("joint"
|
|
122
|
-
axis = kwargs.get("axis"
|
|
130
|
+
joint = kwargs.get("joint")
|
|
131
|
+
axis = kwargs.get("axis")
|
|
123
132
|
delay = kwargs.get("delay", 0)
|
|
124
133
|
|
|
125
134
|
if delay < 0:
|
|
126
|
-
|
|
135
|
+
msg = "Delay must be non-negative."
|
|
136
|
+
raise ValueError(msg)
|
|
127
137
|
if joint is not None and axis is not None:
|
|
128
|
-
|
|
138
|
+
msg = "Cannot specify both 'joint' and 'axis'."
|
|
139
|
+
raise ValueError(msg)
|
|
129
140
|
|
|
130
141
|
value = amplitude * random.rand()
|
|
131
142
|
_apply_control(model, data, value, joint=joint, axis=axis, delay=delay)
|
|
132
143
|
|
|
133
|
-
def
|
|
144
|
+
def live_controller(model, data, **kwargs) -> None:
|
|
134
145
|
"""A real-time controller for the simulation.
|
|
135
146
|
|
|
136
147
|
Args:
|
|
@@ -138,8 +149,9 @@ def realTimeController(model, data, **kwargs):
|
|
|
138
149
|
|
|
139
150
|
Returns:
|
|
140
151
|
None
|
|
152
|
+
|
|
141
153
|
"""
|
|
142
|
-
from .
|
|
154
|
+
from .utils import print_warning
|
|
143
155
|
for key, value in kwargs.get("controller_params", {}).items():
|
|
144
156
|
if hasattr(data, key):
|
|
145
157
|
setattr(data, key, value)
|
|
@@ -150,4 +162,3 @@ def realTimeController(model, data, **kwargs):
|
|
|
150
162
|
# if hasattr(data, 'control'):
|
|
151
163
|
# data.control = kwargs.get("control", data.control)
|
|
152
164
|
|
|
153
|
-
return
|
|
@@ -15,21 +15,17 @@ def timer(func):
|
|
|
15
15
|
result = func(*args, **kwargs)
|
|
16
16
|
end_time = time()
|
|
17
17
|
total_time = end_time - start_time
|
|
18
|
-
|
|
19
|
-
from . import VERBOSITY
|
|
20
|
-
|
|
21
|
-
if VERBOSITY:
|
|
22
|
-
print(f"Function '{func.__name__}' took {total_time:.4f} seconds")
|
|
18
|
+
print(f"Function {func.__name__} took {total_time:.4f} seconds to execute")
|
|
23
19
|
return result
|
|
24
20
|
|
|
25
21
|
return time_wrapper
|
|
26
22
|
|
|
27
23
|
def print_warning(*args, prefix: bool = True) -> None:
|
|
28
|
-
"""
|
|
29
|
-
Prints the first argument as an orange warning message and prints the other arguments in regular text, all on the same line.
|
|
24
|
+
"""Prints the first argument as an orange warning message and prints the other arguments in regular text, all on the same line.
|
|
30
25
|
|
|
31
26
|
Args:
|
|
32
27
|
*args: First argument is printed as an orange warning, other arguments are printed normally.
|
|
28
|
+
|
|
33
29
|
"""
|
|
34
30
|
if args:
|
|
35
31
|
if prefix:
|
|
@@ -54,11 +50,11 @@ def print_warning(*args, prefix: bool = True) -> None:
|
|
|
54
50
|
print(*args[1:])
|
|
55
51
|
|
|
56
52
|
def print_success(*args, prefix: bool = True) -> None:
|
|
57
|
-
"""
|
|
58
|
-
Prints the first argument as a green success message and prints the other arguments in regular text, all on the same line.
|
|
53
|
+
"""Prints the first argument as a green success message and prints the other arguments in regular text, all on the same line.
|
|
59
54
|
|
|
60
55
|
Args:
|
|
61
56
|
*args: First argument is printed as a green success message, other arguments are printed normally.
|
|
57
|
+
|
|
62
58
|
"""
|
|
63
59
|
if args:
|
|
64
60
|
if prefix:
|
|
@@ -83,7 +79,7 @@ def print_success(*args, prefix: bool = True) -> None:
|
|
|
83
79
|
print(*args[1:])
|
|
84
80
|
|
|
85
81
|
class _Platform:
|
|
86
|
-
def __init__(self):
|
|
82
|
+
def __init__(self) -> None:
|
|
87
83
|
self.OS = platform.system()
|
|
88
84
|
self.PROCESSOR = platform.processor()
|
|
89
85
|
self.MACHINE = platform.machine()
|
|
@@ -95,25 +91,25 @@ class _Platform:
|
|
|
95
91
|
self.RESOLUTION, self.GUI_ENABLED = self.get_resolution()
|
|
96
92
|
# self.NUM_MONITORS = get_monitors().__len__()
|
|
97
93
|
|
|
98
|
-
def __repr__(self):
|
|
94
|
+
def __repr__(self) -> str:
|
|
99
95
|
return (
|
|
100
96
|
f"Operating System: {self.OS}\n"
|
|
97
|
+
f"Architecture: {self.ARCHITECTURE}\n"
|
|
101
98
|
f"Processor: {self.PROCESSOR}\n"
|
|
102
99
|
f"Machine Type: {self.MACHINE}\n"
|
|
100
|
+
f"CPU Count: {self.CPU_COUNT}\n"
|
|
103
101
|
f"Python Version: {self.PYTHON_VERSION}\n"
|
|
104
102
|
f"IDE: {self.IDE}\n"
|
|
105
|
-
f"CPU Count: {self.CPU_COUNT}\n"
|
|
106
|
-
f"Architecture: {self.ARCHITECTURE}\n"
|
|
107
103
|
f"Python Implementation: {self.PYTHON_IMPLEMENTATION}\n"
|
|
108
104
|
f"Resolution (disp0): {self.RESOLUTION}\n"
|
|
109
105
|
f"GUI Enabled: {self.GUI_ENABLED}"
|
|
110
106
|
)
|
|
111
107
|
|
|
112
|
-
def __str__(self):
|
|
108
|
+
def __str__(self) -> str:
|
|
113
109
|
return self.__repr__()
|
|
114
110
|
|
|
115
111
|
@staticmethod
|
|
116
|
-
def get_developer_mode():
|
|
112
|
+
def get_developer_mode() -> str:
|
|
117
113
|
if "ipykernel" in sys.modules and not sys.stdin.isatty():
|
|
118
114
|
return "jupyter"
|
|
119
115
|
return "terminal" if hasattr(sys, "ps1") else "script"
|