first commit
This commit is contained in:
		
						commit
						803b1dedd3
					
				
							
								
								
									
										11
									
								
								Open_Duck_Playground/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Open_Duck_Playground/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | Open_Duck_Playground.egg-info/* | ||||||
|  | uv.lock | ||||||
|  | __pycache__/ | ||||||
|  | playground/sigmaban2019/ | ||||||
|  | playground/open_duck_mini_v2_skate/ | ||||||
|  | robot_saved_obs.pkl | ||||||
|  | mujoco_saved_obs.pkl | ||||||
|  | checkpoints/ | ||||||
|  | .tmp/ | ||||||
|  | *.onnx | ||||||
|  | *.TXT | ||||||
							
								
								
									
										98
									
								
								Open_Duck_Playground/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								Open_Duck_Playground/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | |||||||
|  | # Open Duck Playground | ||||||
|  | 
 | ||||||
|  | # Installation  | ||||||
|  | 
 | ||||||
|  | Install uv | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | curl -LsSf https://astral.sh/uv/install.sh | sh | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Training | ||||||
|  | 
 | ||||||
|  | If you want to use the [imitation reward](https://la.disneyresearch.com/wp-content/uploads/BD_X_paper.pdf), you can generate reference motion with [this repo](https://github.com/apirrone/Open_Duck_reference_motion_generator) | ||||||
|  | 
 | ||||||
|  | Then copy `polynomial_coefficients.pkl` in `playground/<robot>/data/` | ||||||
|  | 
 | ||||||
|  | You'll also have to set `USE_IMITATION_REWARD=True` in it's `joystick.py` file | ||||||
|  | 
 | ||||||
|  | Run:  | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | uv run playground/<robot>/runner.py  | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Tensorboard | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | uv run tensorboard --logdir=<yourlogdir> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Inference  | ||||||
|  | 
 | ||||||
|  | Infer mujoco | ||||||
|  | 
 | ||||||
|  | (for now this is specific to open_duck_mini_v2) | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | uv run playground/open_duck_mini_v2/mujoco_infer.py -o <path_to_.onnx> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Documentation | ||||||
|  | 
 | ||||||
|  | ## Project structure :  | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | . | ||||||
|  | ├── pyproject.toml | ||||||
|  | ├── README.md | ||||||
|  | ├── playground | ||||||
|  | │   ├── common | ||||||
|  | │   │   ├── export_onnx.py | ||||||
|  | │   │   ├── onnx_infer.py | ||||||
|  | │   │   ├── poly_reference_motion.py | ||||||
|  | │   │   ├── randomize.py | ||||||
|  | │   │   ├── rewards.py | ||||||
|  | │   │   └── runner.py | ||||||
|  | │   ├── open_duck_mini_v2 | ||||||
|  | │   │   ├── base.py | ||||||
|  | │   │   ├── data | ||||||
|  | │   │   │   └── polynomial_coefficients.pkl | ||||||
|  | │   │   ├── joystick.py | ||||||
|  | │   │   ├── mujoco_infer.py | ||||||
|  | │   │   ├── constants.py | ||||||
|  | │   │   ├── runner.py | ||||||
|  | │   │   └── xmls | ||||||
|  | │   │       ├── assets | ||||||
|  | │   │       ├── open_duck_mini_v2_no_head.xml | ||||||
|  | │   │       ├── open_duck_mini_v2.xml | ||||||
|  | │   │       ├── scene_mjx_flat_terrain.xml | ||||||
|  | │   │       ├── scene_mjx_rough_terrain.xml | ||||||
|  | │   │       └── scene.xml | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Adding a new robot | ||||||
|  | 
 | ||||||
|  | Create a new directory in `playground` named after `<your robot>`. You can copy the `open_duck_mini_v2` directory as a starting point. | ||||||
|  | 
 | ||||||
|  | You will need to: | ||||||
|  | - Edit `base.py`: Mainly renaming stuff to match you robot's name | ||||||
|  | - Edit `constants.py`: specify the names of some important geoms, sensors etc | ||||||
|  |   - In your `mjcf`, you'll probably have to add some sites, name some bodies/geoms and add the sensors. Look at how we did it for `open_duck_mini_v2` | ||||||
|  | - Add your `mjcf` assets in `xmls`.  | ||||||
|  | - Edit `joystick.py` : to choose the rewards you are interested in | ||||||
|  |   - Note: for now there is still some hard coded values etc. We'll improve things on the way | ||||||
|  | - Edit `runner.py` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Notes | ||||||
|  | 
 | ||||||
|  | Inspired from https://github.com/kscalelabs/mujoco_playground | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Current win | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | uv run playground/open_duck_mini_v2/runner.py --task flat_terrain_backlash --num_timesteps 300000000 | ||||||
|  | ``` | ||||||
							
								
								
									
										0
									
								
								Open_Duck_Playground/aze
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								Open_Duck_Playground/aze
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								Open_Duck_Playground/playground/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								Open_Duck_Playground/playground/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								Open_Duck_Playground/playground/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								Open_Duck_Playground/playground/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										189
									
								
								Open_Duck_Playground/playground/common/export_onnx.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								Open_Duck_Playground/playground/common/export_onnx.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,189 @@ | |||||||
|  | 
 | ||||||
|  | import tensorflow as tf | ||||||
|  | from tensorflow.keras import layers | ||||||
|  | import tf2onnx | ||||||
|  | import numpy as np | ||||||
|  | 
 | ||||||
|  | def export_onnx( | ||||||
|  |     params, act_size, ppo_params, obs_size, output_path="ONNX.onnx" | ||||||
|  | ): | ||||||
|  |     print(" === EXPORT ONNX === ") | ||||||
|  | 
 | ||||||
|  |     # inference_fn = make_inference_fn(params, deterministic=True) | ||||||
|  | 
 | ||||||
|  |     class MLP(tf.keras.Model): | ||||||
|  |         def __init__( | ||||||
|  |             self, | ||||||
|  |             layer_sizes, | ||||||
|  |             activation=tf.nn.relu, | ||||||
|  |             kernel_init="lecun_uniform", | ||||||
|  |             activate_final=False, | ||||||
|  |             bias=True, | ||||||
|  |             layer_norm=False, | ||||||
|  |             mean_std=None, | ||||||
|  |         ): | ||||||
|  |             super().__init__() | ||||||
|  | 
 | ||||||
|  |             self.layer_sizes = layer_sizes | ||||||
|  |             self.activation = activation | ||||||
|  |             self.kernel_init = kernel_init | ||||||
|  |             self.activate_final = activate_final | ||||||
|  |             self.bias = bias | ||||||
|  |             self.layer_norm = layer_norm | ||||||
|  | 
 | ||||||
|  |             if mean_std is not None: | ||||||
|  |                 self.mean = tf.Variable(mean_std[0], trainable=False, dtype=tf.float32) | ||||||
|  |                 self.std = tf.Variable(mean_std[1], trainable=False, dtype=tf.float32) | ||||||
|  |             else: | ||||||
|  |                 self.mean = None | ||||||
|  |                 self.std = None | ||||||
|  | 
 | ||||||
|  |             self.mlp_block = tf.keras.Sequential(name="MLP_0") | ||||||
|  |             for i, size in enumerate(self.layer_sizes): | ||||||
|  |                 dense_layer = layers.Dense( | ||||||
|  |                     size, | ||||||
|  |                     activation=self.activation, | ||||||
|  |                     kernel_initializer=self.kernel_init, | ||||||
|  |                     name=f"hidden_{i}", | ||||||
|  |                     use_bias=self.bias, | ||||||
|  |                 ) | ||||||
|  |                 self.mlp_block.add(dense_layer) | ||||||
|  |                 if self.layer_norm: | ||||||
|  |                     self.mlp_block.add( | ||||||
|  |                         layers.LayerNormalization(name=f"layer_norm_{i}") | ||||||
|  |                     ) | ||||||
|  |             if not self.activate_final and self.mlp_block.layers: | ||||||
|  |                 if ( | ||||||
|  |                     hasattr(self.mlp_block.layers[-1], "activation") | ||||||
|  |                     and self.mlp_block.layers[-1].activation is not None | ||||||
|  |                 ): | ||||||
|  |                     self.mlp_block.layers[-1].activation = None | ||||||
|  | 
 | ||||||
|  |             self.submodules = [self.mlp_block] | ||||||
|  | 
 | ||||||
|  |         def call(self, inputs): | ||||||
|  |             if isinstance(inputs, list): | ||||||
|  |                 inputs = inputs[0] | ||||||
|  |             if self.mean is not None and self.std is not None: | ||||||
|  |                 print(self.mean.shape, self.std.shape) | ||||||
|  |                 inputs = (inputs - self.mean) / self.std | ||||||
|  |             logits = self.mlp_block(inputs) | ||||||
|  |             loc, _ = tf.split(logits, 2, axis=-1) | ||||||
|  |             return tf.tanh(loc) | ||||||
|  | 
 | ||||||
|  |     def make_policy_network( | ||||||
|  |         param_size, | ||||||
|  |         mean_std, | ||||||
|  |         hidden_layer_sizes=[256, 256], | ||||||
|  |         activation=tf.nn.relu, | ||||||
|  |         kernel_init="lecun_uniform", | ||||||
|  |         layer_norm=False, | ||||||
|  |     ): | ||||||
|  |         policy_network = MLP( | ||||||
|  |             layer_sizes=list(hidden_layer_sizes) + [param_size], | ||||||
|  |             activation=activation, | ||||||
|  |             kernel_init=kernel_init, | ||||||
|  |             layer_norm=layer_norm, | ||||||
|  |             mean_std=mean_std, | ||||||
|  |         ) | ||||||
|  |         return policy_network | ||||||
|  | 
 | ||||||
|  |     mean = params[0].mean["state"] | ||||||
|  |     std = params[0].std["state"] | ||||||
|  | 
 | ||||||
|  |     # Convert mean/std jax arrays to tf tensors. | ||||||
|  |     mean_std = (tf.convert_to_tensor(mean), tf.convert_to_tensor(std)) | ||||||
|  | 
 | ||||||
|  |     tf_policy_network = make_policy_network( | ||||||
|  |         param_size=act_size * 2, | ||||||
|  |         mean_std=mean_std, | ||||||
|  |         hidden_layer_sizes=ppo_params.network_factory.policy_hidden_layer_sizes, | ||||||
|  |         activation=tf.nn.swish, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     example_input = tf.zeros((1, obs_size)) | ||||||
|  |     example_output = tf_policy_network(example_input) | ||||||
|  |     print(example_output.shape) | ||||||
|  | 
 | ||||||
|  |     def transfer_weights(jax_params, tf_model): | ||||||
|  |         """ | ||||||
|  |         Transfer weights from a JAX parameter dictionary to the TensorFlow model. | ||||||
|  | 
 | ||||||
|  |         Parameters: | ||||||
|  |         - jax_params: dict | ||||||
|  |         Nested dictionary with structure {block_name: {layer_name: {params}}}. | ||||||
|  |         For example: | ||||||
|  |         { | ||||||
|  |             'CNN_0': { | ||||||
|  |             'Conv_0': {'kernel': np.ndarray}, | ||||||
|  |             'Conv_1': {'kernel': np.ndarray}, | ||||||
|  |             'Conv_2': {'kernel': np.ndarray}, | ||||||
|  |             }, | ||||||
|  |             'MLP_0': { | ||||||
|  |             'hidden_0': {'kernel': np.ndarray, 'bias': np.ndarray}, | ||||||
|  |             'hidden_1': {'kernel': np.ndarray, 'bias': np.ndarray}, | ||||||
|  |             'hidden_2': {'kernel': np.ndarray, 'bias': np.ndarray}, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         - tf_model: tf.keras.Model | ||||||
|  |         An instance of the adapted VisionMLP model containing named submodules and layers. | ||||||
|  |         """ | ||||||
|  |         for layer_name, layer_params in jax_params.items(): | ||||||
|  |             try: | ||||||
|  |                 tf_layer = tf_model.get_layer("MLP_0").get_layer(name=layer_name) | ||||||
|  |             except ValueError: | ||||||
|  |                 print(f"Layer {layer_name} not found in TensorFlow model.") | ||||||
|  |                 continue | ||||||
|  |             if isinstance(tf_layer, tf.keras.layers.Dense): | ||||||
|  |                 kernel = np.array(layer_params["kernel"]) | ||||||
|  |                 bias = np.array(layer_params["bias"]) | ||||||
|  |                 print( | ||||||
|  |                     f"Transferring Dense layer {layer_name}, kernel shape {kernel.shape}, bias shape {bias.shape}" | ||||||
|  |                 ) | ||||||
|  |                 tf_layer.set_weights([kernel, bias]) | ||||||
|  |             else: | ||||||
|  |                 print(f"Unhandled layer type in {layer_name}: {type(tf_layer)}") | ||||||
|  | 
 | ||||||
|  |         print("Weights transferred successfully.") | ||||||
|  | 
 | ||||||
|  |     net_params = params[1] | ||||||
|  |     # try attribute‐style .policy | ||||||
|  |     if hasattr(net_params, "policy"): | ||||||
|  |         policy_tree = net_params.policy | ||||||
|  |     # try dict‐style ["policy"] | ||||||
|  |     elif isinstance(net_params, dict) and "policy" in net_params: | ||||||
|  |         policy_tree = net_params["policy"] | ||||||
|  |     # fallback to Flax FrozenDict { "params": … } | ||||||
|  |     elif isinstance(net_params, dict) and "params" in net_params: | ||||||
|  |         policy_tree = net_params["params"] | ||||||
|  |     else: | ||||||
|  |         raise KeyError(f"Cannot locate policy params in {type(net_params)}; keys = {list(net_params.keys())}") | ||||||
|  | 
 | ||||||
|  |     # policy_tree is now the dict of weight arrays | ||||||
|  |     transfer_weights(policy_tree, tf_policy_network) | ||||||
|  | 
 | ||||||
|  |     # Example inputs for the model | ||||||
|  |     test_input = [np.ones((1, obs_size), dtype=np.float32)] | ||||||
|  | 
 | ||||||
|  |     # Define the TensorFlow input signature | ||||||
|  |     spec = [ | ||||||
|  |         tf.TensorSpec(shape=(1, obs_size), dtype=tf.float32, name="obs") | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     tensorflow_pred = tf_policy_network(test_input)[0] | ||||||
|  |     # Build the model by calling it with example data | ||||||
|  |     print(f"Tensorflow prediction: {tensorflow_pred}") | ||||||
|  | 
 | ||||||
|  |     tf_policy_network.output_names = ["continuous_actions"] | ||||||
|  | 
 | ||||||
|  |     # opset 11 matches isaac lab. | ||||||
|  |     model_proto, _ = tf2onnx.convert.from_keras( | ||||||
|  |         tf_policy_network, input_signature=spec, opset=11, output_path=output_path | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     # For Antoine :) | ||||||
|  |     model_proto, _ = tf2onnx.convert.from_keras( | ||||||
|  |         tf_policy_network, input_signature=spec, opset=11, output_path="ONNX.onnx" | ||||||
|  |     ) | ||||||
|  |     return | ||||||
							
								
								
									
										46
									
								
								Open_Duck_Playground/playground/common/onnx_infer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Open_Duck_Playground/playground/common/onnx_infer.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | import onnxruntime | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OnnxInfer: | ||||||
|  |     def __init__(self, onnx_model_path, input_name="obs", awd=False): | ||||||
|  |         self.onnx_model_path = onnx_model_path | ||||||
|  |         self.ort_session = onnxruntime.InferenceSession( | ||||||
|  |             self.onnx_model_path, providers=["CPUExecutionProvider"] | ||||||
|  |         ) | ||||||
|  |         self.input_name = input_name | ||||||
|  |         self.awd = awd | ||||||
|  | 
 | ||||||
|  |     def infer(self, inputs): | ||||||
|  |         if self.awd: | ||||||
|  |             outputs = self.ort_session.run(None, {self.input_name: [inputs]}) | ||||||
|  |             return outputs[0][0] | ||||||
|  |         else: | ||||||
|  |             outputs = self.ort_session.run( | ||||||
|  |                 None, {self.input_name: inputs.astype("float32")} | ||||||
|  |             ) | ||||||
|  |             return outputs[0] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     import argparse | ||||||
|  |     import numpy as np | ||||||
|  |     import time | ||||||
|  | 
 | ||||||
|  |     parser = argparse.ArgumentParser() | ||||||
|  |     parser.add_argument("-o", "--onnx_model_path", type=str, required=True) | ||||||
|  |     args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  |     obs_size = 46 | ||||||
|  | 
 | ||||||
|  |     oi = OnnxInfer(args.onnx_model_path, awd=True) | ||||||
|  |     times = [] | ||||||
|  |     for i in range(1000): | ||||||
|  |         inputs = np.random.uniform(size=obs_size).astype(np.float32) | ||||||
|  |         # inputs = np.arange(obs_size).astype(np.float32) | ||||||
|  |         # print(inputs) | ||||||
|  |         start = time.time() | ||||||
|  |         print(oi.infer(inputs)) | ||||||
|  |         times.append(time.time() - start) | ||||||
|  | 
 | ||||||
|  |     print("Average time: ", sum(times) / len(times)) | ||||||
|  |     print("Average fps: ", 1 / (sum(times) / len(times))) | ||||||
							
								
								
									
										222
									
								
								Open_Duck_Playground/playground/common/plot_saved_obs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								Open_Duck_Playground/playground/common/plot_saved_obs.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,222 @@ | |||||||
|  | import pickle | ||||||
|  | 
 | ||||||
|  | import matplotlib.pyplot as plt | ||||||
|  | import numpy as np | ||||||
|  | from scipy.spatial.transform import Rotation as R | ||||||
|  | import argparse | ||||||
|  | 
 | ||||||
|  | parser = argparse.ArgumentParser() | ||||||
|  | parser.add_argument( | ||||||
|  |     "-d", "--data", type=str, required=False, default="mujoco_saved_obs.pkl" | ||||||
|  | ) | ||||||
|  | args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | init_pos = np.array( | ||||||
|  |     [ | ||||||
|  |         0.002, | ||||||
|  |         0.053, | ||||||
|  |         -0.63, | ||||||
|  |         1.368, | ||||||
|  |         -0.784, | ||||||
|  |         0.0, | ||||||
|  |         0, | ||||||
|  |         0, | ||||||
|  |         0, | ||||||
|  |         # 0, | ||||||
|  |         # 0, | ||||||
|  |         -0.003, | ||||||
|  |         -0.065, | ||||||
|  |         0.635, | ||||||
|  |         1.379, | ||||||
|  |         -0.796, | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | joints_order = [ | ||||||
|  |     "left_hip_yaw", | ||||||
|  |     "left_hip_roll", | ||||||
|  |     "left_hip_pitch", | ||||||
|  |     "left_knee", | ||||||
|  |     "left_ankle", | ||||||
|  |     "neck_pitch", | ||||||
|  |     "head_pitch", | ||||||
|  |     "head_yaw", | ||||||
|  |     "head_roll", | ||||||
|  |     "right_hip_yaw", | ||||||
|  |     "right_hip_roll", | ||||||
|  |     "right_hip_pitch", | ||||||
|  |     "right_knee", | ||||||
|  |     "right_ankle", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | obses = pickle.load(open(args.data, "rb")) | ||||||
|  | 
 | ||||||
|  | num_dofs = 14 | ||||||
|  | dof_poses = []  # (dof, num_obs) | ||||||
|  | actions = []  # (dof, num_obs) | ||||||
|  | 
 | ||||||
|  | for i in range(num_dofs): | ||||||
|  |     print(i) | ||||||
|  |     dof_poses.append([]) | ||||||
|  |     actions.append([]) | ||||||
|  |     for obs in obses: | ||||||
|  |         dof_poses[i].append(obs[13 : 13 + num_dofs][i]) | ||||||
|  |         actions[i].append(obs[26 : 26 + num_dofs][i]) | ||||||
|  | 
 | ||||||
|  | # plot action vs dof pos | ||||||
|  | 
 | ||||||
|  | nb_dofs = len(dof_poses) | ||||||
|  | nb_rows = int(np.sqrt(nb_dofs)) | ||||||
|  | nb_cols = int(np.ceil(nb_dofs / nb_rows)) | ||||||
|  | 
 | ||||||
|  | fig, axs = plt.subplots(nb_rows, nb_cols, sharex=True, sharey=True) | ||||||
|  | 
 | ||||||
|  | for i in range(nb_rows): | ||||||
|  |     for j in range(nb_cols): | ||||||
|  |         if i * nb_cols + j >= nb_dofs: | ||||||
|  |             break | ||||||
|  |         axs[i, j].plot(actions[i * nb_cols + j], label="action") | ||||||
|  |         axs[i, j].plot(dof_poses[i * nb_cols + j], label="dof_pos") | ||||||
|  |         axs[i, j].legend() | ||||||
|  |         axs[i, j].set_title(f"{joints_order[i * nb_cols + j]}") | ||||||
|  | 
 | ||||||
|  | fig.suptitle(f"{args.data}") | ||||||
|  | plt.show() | ||||||
|  | 
 | ||||||
|  | obses_names = [ | ||||||
|  |     "gyro x", | ||||||
|  |     "gyro y", | ||||||
|  |     "gyro z", | ||||||
|  |     "accelo x", | ||||||
|  |     "accelo y", | ||||||
|  |     "accelo z", | ||||||
|  |     # commands | ||||||
|  |     "command 0", | ||||||
|  |     "command 1", | ||||||
|  |     "command 2", | ||||||
|  |     "command 3", | ||||||
|  |     "command 4", | ||||||
|  |     "command 5", | ||||||
|  |     "command 6", | ||||||
|  |     # dof pos | ||||||
|  |     "pos_" + str(joints_order[0]), | ||||||
|  |     "pos_" + str(joints_order[1]), | ||||||
|  |     "pos_" + str(joints_order[2]), | ||||||
|  |     "pos_" + str(joints_order[3]), | ||||||
|  |     "pos_" + str(joints_order[4]), | ||||||
|  |     "pos_" + str(joints_order[5]), | ||||||
|  |     "pos_" + str(joints_order[6]), | ||||||
|  |     "pos_" + str(joints_order[7]), | ||||||
|  |     "pos_" + str(joints_order[8]), | ||||||
|  |     "pos_" + str(joints_order[9]), | ||||||
|  |     "pos_" + str(joints_order[10]), | ||||||
|  |     "pos_" + str(joints_order[11]), | ||||||
|  |     "pos_" + str(joints_order[12]), | ||||||
|  |     "pos_" + str(joints_order[13]), | ||||||
|  |     # dof vel | ||||||
|  |     "vel_" + str(joints_order[0]), | ||||||
|  |     "vel_" + str(joints_order[1]), | ||||||
|  |     "vel_" + str(joints_order[2]), | ||||||
|  |     "vel_" + str(joints_order[3]), | ||||||
|  |     "vel_" + str(joints_order[4]), | ||||||
|  |     "vel_" + str(joints_order[5]), | ||||||
|  |     "vel_" + str(joints_order[6]), | ||||||
|  |     "vel_" + str(joints_order[7]), | ||||||
|  |     "vel_" + str(joints_order[8]), | ||||||
|  |     "vel_" + str(joints_order[9]), | ||||||
|  |     "vel_" + str(joints_order[10]), | ||||||
|  |     "vel_" + str(joints_order[11]), | ||||||
|  |     "vel_" + str(joints_order[12]), | ||||||
|  |     "vel_" + str(joints_order[13]), | ||||||
|  |     # action | ||||||
|  |     "last_action_" + str(joints_order[0]), | ||||||
|  |     "last_action_" + str(joints_order[1]), | ||||||
|  |     "last_action_" + str(joints_order[2]), | ||||||
|  |     "last_action_" + str(joints_order[3]), | ||||||
|  |     "last_action_" + str(joints_order[4]), | ||||||
|  |     "last_action_" + str(joints_order[5]), | ||||||
|  |     "last_action_" + str(joints_order[6]), | ||||||
|  |     "last_action_" + str(joints_order[7]), | ||||||
|  |     "last_action_" + str(joints_order[8]), | ||||||
|  |     "last_action_" + str(joints_order[9]), | ||||||
|  |     "last_action_" + str(joints_order[10]), | ||||||
|  |     "last_action_" + str(joints_order[11]), | ||||||
|  |     "last_action_" + str(joints_order[12]), | ||||||
|  |     "last_action_" + str(joints_order[13]), | ||||||
|  |     "last_last_action_" + str(joints_order[0]), | ||||||
|  |     "last_last_action_" + str(joints_order[1]), | ||||||
|  |     "last_last_action_" + str(joints_order[2]), | ||||||
|  |     "last_last_action_" + str(joints_order[3]), | ||||||
|  |     "last_last_action_" + str(joints_order[4]), | ||||||
|  |     "last_last_action_" + str(joints_order[5]), | ||||||
|  |     "last_last_action_" + str(joints_order[6]), | ||||||
|  |     "last_last_action_" + str(joints_order[7]), | ||||||
|  |     "last_last_action_" + str(joints_order[8]), | ||||||
|  |     "last_last_action_" + str(joints_order[9]), | ||||||
|  |     "last_last_action_" + str(joints_order[10]), | ||||||
|  |     "last_last_action_" + str(joints_order[11]), | ||||||
|  |     "last_last_action_" + str(joints_order[12]), | ||||||
|  |     "last_last_action_" + str(joints_order[13]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[0]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[1]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[2]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[3]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[4]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[5]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[6]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[7]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[8]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[9]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[10]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[11]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[12]), | ||||||
|  |     "last_last_last_action_" + str(joints_order[13]), | ||||||
|  |     "motor_targets_" + str(joints_order[0]), | ||||||
|  |     "motor_targets_" + str(joints_order[1]), | ||||||
|  |     "motor_targets_" + str(joints_order[2]), | ||||||
|  |     "motor_targets_" + str(joints_order[3]), | ||||||
|  |     "motor_targets_" + str(joints_order[4]), | ||||||
|  |     "motor_targets_" + str(joints_order[5]), | ||||||
|  |     "motor_targets_" + str(joints_order[6]), | ||||||
|  |     "motor_targets_" + str(joints_order[7]), | ||||||
|  |     "motor_targets_" + str(joints_order[8]), | ||||||
|  |     "motor_targets_" + str(joints_order[9]), | ||||||
|  |     "motor_targets_" + str(joints_order[10]), | ||||||
|  |     "motor_targets_" + str(joints_order[11]), | ||||||
|  |     "motor_targets_" + str(joints_order[12]), | ||||||
|  |     "motor_targets_" + str(joints_order[13]), | ||||||
|  |     "contact left", | ||||||
|  |     "contact right", | ||||||
|  |     "imitation_phase 1", | ||||||
|  |     "imitation_phase 2" | ||||||
|  |     # ref (ignored) | ||||||
|  | ] | ||||||
|  | # print(len(obses_names)) | ||||||
|  | # exit() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # obses = [[56 obs at time 0], [56 obs at time 1], ...] | ||||||
|  | 
 | ||||||
|  | nb_obs = len(obses[0]) | ||||||
|  | print(nb_obs) | ||||||
|  | nb_rows = int(np.sqrt(nb_obs)) | ||||||
|  | nb_cols = int(np.ceil(nb_obs / nb_rows)) | ||||||
|  | 
 | ||||||
|  | fig, axs = plt.subplots(nb_rows, nb_cols, sharex=True, sharey=True) | ||||||
|  | 
 | ||||||
|  | for i in range(nb_rows): | ||||||
|  |     for j in range(nb_cols): | ||||||
|  |         if i * nb_cols + j >= nb_obs: | ||||||
|  |             break | ||||||
|  |         axs[i, j].plot([obs[i * nb_cols + j] for obs in obses]) | ||||||
|  |         axs[i, j].set_title(obses_names[i * nb_cols + j]) | ||||||
|  | 
 | ||||||
|  | # set ylim between -5 and 5 | ||||||
|  | 
 | ||||||
|  | for ax in axs.flat: | ||||||
|  |     ax.set_ylim([-5, 5]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | fig.suptitle(f"{args.data}") | ||||||
|  | plt.show() | ||||||
							
								
								
									
										187
									
								
								Open_Duck_Playground/playground/common/poly_reference_motion.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								Open_Duck_Playground/playground/common/poly_reference_motion.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | |||||||
|  | import jax.numpy as jp | ||||||
|  | from jax import vmap | ||||||
|  | import pickle | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # dimensions_names = [ | ||||||
|  | #     0  "pos left_hip_yaw", | ||||||
|  | #     1  "pos left_hip_roll", | ||||||
|  | #     2  "pos left_hip_pitch", | ||||||
|  | #     3  "pos left_knee", | ||||||
|  | #     4  "pos left_ankle", | ||||||
|  | #     5  "pos neck_pitch", | ||||||
|  | #     6  "pos head_pitch", | ||||||
|  | #     7  "pos head_yaw", | ||||||
|  | #     8  "pos head_roll", | ||||||
|  | #     9  "pos left_antenna", | ||||||
|  | #     10 "pos right_antenna", | ||||||
|  | #     11 "pos right_hip_yaw", | ||||||
|  | #     12 "pos right_hip_roll", | ||||||
|  | #     13 "pos right_hip_pitch", | ||||||
|  | #     14 "pos right_knee", | ||||||
|  | #     15 "pos right_ankle", | ||||||
|  | 
 | ||||||
|  | #     16 "vel left_hip_yaw", | ||||||
|  | #     17 "vel left_hip_roll", | ||||||
|  | #     18 "vel left_hip_pitch", | ||||||
|  | #     19 "vel left_knee", | ||||||
|  | #     20 "vel left_ankle", | ||||||
|  | #     21 "vel neck_pitch", | ||||||
|  | #     22 "vel head_pitch", | ||||||
|  | #     23 "vel head_yaw", | ||||||
|  | #     24 "vel head_roll", | ||||||
|  | #     25 "vel left_antenna", | ||||||
|  | #     26 "vel right_antenna", | ||||||
|  | #     27 "vel right_hip_yaw", | ||||||
|  | #     28 "vel right_hip_roll", | ||||||
|  | #     29 "vel right_hip_pitch", | ||||||
|  | #     30 "vel right_knee", | ||||||
|  | #     31 "vel right_ankle", | ||||||
|  | 
 | ||||||
|  | #     32 "foot_contacts left", | ||||||
|  | #     33 "foot_contacts right", | ||||||
|  | 
 | ||||||
|  | #     34 "base_linear_vel x", | ||||||
|  | #     35 "base_linear_vel y", | ||||||
|  | #     36 "base_linear_vel z", | ||||||
|  | 
 | ||||||
|  | #     37 "base_angular_vel x", | ||||||
|  | #     38 "base_angular_vel y", | ||||||
|  | #     39 "base_angular_vel z", | ||||||
|  | # ] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PolyReferenceMotion: | ||||||
|  |     def __init__(self, polynomial_coefficients: str): | ||||||
|  |         data = pickle.load(open(polynomial_coefficients, "rb")) | ||||||
|  |         # data = json.load(open(polynomial_coefficients)) | ||||||
|  |         self.dx_range = [0, 0] | ||||||
|  |         self.dy_range = [0, 0] | ||||||
|  |         self.dtheta_range = [0, 0] | ||||||
|  |         self.dxs = [] | ||||||
|  |         self.dys = [] | ||||||
|  |         self.dthetas = [] | ||||||
|  |         self.data_array = [] | ||||||
|  |         self.period = None | ||||||
|  |         self.fps = None | ||||||
|  |         self.frame_offsets = None | ||||||
|  |         self.startend_double_support_ratio = None | ||||||
|  |         self.start_offset = None | ||||||
|  |         self.nb_steps_in_period = None | ||||||
|  | 
 | ||||||
|  |         self.process(data) | ||||||
|  | 
 | ||||||
|  |     def process(self, data): | ||||||
|  |         print("[Poly ref data] Processing ...") | ||||||
|  |         _data = {} | ||||||
|  |         for name in data.keys(): | ||||||
|  |             split = name.split("_") | ||||||
|  |             dx = float(split[0]) | ||||||
|  |             dy = float(split[1]) | ||||||
|  |             dtheta = float(split[2]) | ||||||
|  | 
 | ||||||
|  |             if self.period is None: | ||||||
|  |                 self.period = data[name]["period"] | ||||||
|  |                 self.fps = data[name]["fps"] | ||||||
|  |                 self.frame_offsets = data[name]["frame_offsets"] | ||||||
|  |                 self.startend_double_support_ratio = data[name][ | ||||||
|  |                     "startend_double_support_ratio" | ||||||
|  |                 ] | ||||||
|  |                 self.start_offset = int(self.startend_double_support_ratio * self.fps) | ||||||
|  |                 self.nb_steps_in_period = int(self.period * self.fps) | ||||||
|  | 
 | ||||||
|  |             if dx not in self.dxs: | ||||||
|  |                 self.dxs.append(dx) | ||||||
|  | 
 | ||||||
|  |             if dy not in self.dys: | ||||||
|  |                 self.dys.append(dy) | ||||||
|  | 
 | ||||||
|  |             if dtheta not in self.dthetas: | ||||||
|  |                 self.dthetas.append(dtheta) | ||||||
|  | 
 | ||||||
|  |             self.dx_range = [min(dx, self.dx_range[0]), max(dx, self.dx_range[1])] | ||||||
|  |             self.dy_range = [min(dy, self.dy_range[0]), max(dy, self.dy_range[1])] | ||||||
|  |             self.dtheta_range = [ | ||||||
|  |                 min(dtheta, self.dtheta_range[0]), | ||||||
|  |                 max(dtheta, self.dtheta_range[1]), | ||||||
|  |             ] | ||||||
|  | 
 | ||||||
|  |             if dx not in _data: | ||||||
|  |                 _data[dx] = {} | ||||||
|  | 
 | ||||||
|  |             if dy not in _data[dx]: | ||||||
|  |                 _data[dx][dy] = {} | ||||||
|  | 
 | ||||||
|  |             if dtheta not in _data[dx][dy]: | ||||||
|  |                 _data[dx][dy][dtheta] = data[name] | ||||||
|  | 
 | ||||||
|  |             _coeffs = data[name]["coefficients"] | ||||||
|  | 
 | ||||||
|  |             coeffs = [] | ||||||
|  |             for k, v in _coeffs.items(): | ||||||
|  |                 coeffs.append(jp.flip(jp.array(v))) | ||||||
|  |             _data[dx][dy][dtheta] = coeffs | ||||||
|  | 
 | ||||||
|  |         # print(self.dtheta_range) | ||||||
|  |         # exit() | ||||||
|  | 
 | ||||||
|  |         self.dxs = sorted(self.dxs) | ||||||
|  |         self.dys = sorted(self.dys) | ||||||
|  |         self.dthetas = sorted(self.dthetas) | ||||||
|  | 
 | ||||||
|  |         nb_dx = len(self.dxs) | ||||||
|  |         nb_dy = len(self.dys) | ||||||
|  |         nb_dtheta = len(self.dthetas) | ||||||
|  | 
 | ||||||
|  |         self.data_array = nb_dx * [None] | ||||||
|  |         for x, dx in enumerate(self.dxs): | ||||||
|  |             self.data_array[x] = nb_dy * [None] | ||||||
|  |             for y, dy in enumerate(self.dys): | ||||||
|  |                 self.data_array[x][y] = nb_dtheta * [None] | ||||||
|  |                 for th, dtheta in enumerate(self.dthetas): | ||||||
|  |                     self.data_array[x][y][th] = jp.array(_data[dx][dy][dtheta]) | ||||||
|  | 
 | ||||||
|  |         self.data_array = jp.array(self.data_array) | ||||||
|  | 
 | ||||||
|  |         print("[Poly ref data] Done processing") | ||||||
|  | 
 | ||||||
|  |     def vel_to_index(self, dx, dy, dtheta): | ||||||
|  | 
 | ||||||
|  |         dx = jp.clip(dx, self.dx_range[0], self.dx_range[1]) | ||||||
|  |         dy = jp.clip(dy, self.dy_range[0], self.dy_range[1]) | ||||||
|  |         dtheta = jp.clip(dtheta, self.dtheta_range[0], self.dtheta_range[1]) | ||||||
|  | 
 | ||||||
|  |         ix = jp.argmin(jp.abs(jp.array(self.dxs) - dx)) | ||||||
|  |         iy = jp.argmin(jp.abs(jp.array(self.dys) - dy)) | ||||||
|  |         itheta = jp.argmin(jp.abs(jp.array(self.dthetas) - dtheta)) | ||||||
|  | 
 | ||||||
|  |         return ix, iy, itheta | ||||||
|  | 
 | ||||||
|  |     def sample_polynomial(self, t, coeffs): | ||||||
|  |         return vmap(lambda c: jp.polyval(c, t))(coeffs) | ||||||
|  | 
 | ||||||
|  |     def get_reference_motion(self, dx, dy, dtheta, i): | ||||||
|  |         ix, iy, itheta = self.vel_to_index(dx, dy, dtheta) | ||||||
|  |         t = i % self.nb_steps_in_period / self.nb_steps_in_period | ||||||
|  |         t = jp.clip(t, 0.0, 1.0)  # safeguard | ||||||
|  |         ret = self.sample_polynomial(t, self.data_array[ix][iy][itheta]) | ||||||
|  |         return ret | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  | 
 | ||||||
|  |     PRM = PolyReferenceMotion( | ||||||
|  |         "playground/open_duck_mini_v2/data/polynomial_coefficients.pkl" | ||||||
|  |     ) | ||||||
|  |     vals = [] | ||||||
|  |     select_dim = -1 | ||||||
|  |     for i in range(PRM.nb_steps_in_period): | ||||||
|  |         vals.append(PRM.get_reference_motion(0.0, -0.05, -0.1, i)[select_dim]) | ||||||
|  | 
 | ||||||
|  |     # plot | ||||||
|  |     import matplotlib.pyplot as plt | ||||||
|  |     import numpy as np | ||||||
|  | 
 | ||||||
|  |     ts = np.arange(0, PRM.nb_steps_in_period) | ||||||
|  |     plt.plot(ts, vals) | ||||||
|  |     plt.show() | ||||||
| @ -0,0 +1,138 @@ | |||||||
|  | import numpy as np | ||||||
|  | import pickle | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PolyReferenceMotion: | ||||||
|  |     def __init__(self, polynomial_coefficients: str): | ||||||
|  |         data = pickle.load(open(polynomial_coefficients, "rb")) | ||||||
|  |         self.dx_range = [0, 0] | ||||||
|  |         self.dy_range = [0, 0] | ||||||
|  |         self.dtheta_range = [0, 0] | ||||||
|  |         self.dxs = [] | ||||||
|  |         self.dys = [] | ||||||
|  |         self.dthetas = [] | ||||||
|  |         self.data_array = [] | ||||||
|  |         self.period = None | ||||||
|  |         self.fps = None | ||||||
|  |         self.frame_offsets = None | ||||||
|  |         self.startend_double_support_ratio = None | ||||||
|  |         self.start_offset = None | ||||||
|  |         self.nb_steps_in_period = None | ||||||
|  | 
 | ||||||
|  |         self.process(data) | ||||||
|  | 
 | ||||||
|  |     def process(self, data): | ||||||
|  |         print("[Poly ref data] Processing ...") | ||||||
|  |         _data = {} | ||||||
|  |         for name in data.keys(): | ||||||
|  |             split = name.split("_") | ||||||
|  |             dx = float(split[0]) | ||||||
|  |             dy = float(split[1]) | ||||||
|  |             dtheta = float(split[2]) | ||||||
|  | 
 | ||||||
|  |             if self.period is None: | ||||||
|  |                 self.period = data[name]["period"] | ||||||
|  |                 self.fps = data[name]["fps"] | ||||||
|  |                 self.frame_offsets = data[name]["frame_offsets"] | ||||||
|  |                 self.startend_double_support_ratio = data[name][ | ||||||
|  |                     "startend_double_support_ratio" | ||||||
|  |                 ] | ||||||
|  |                 self.start_offset = int(self.startend_double_support_ratio * self.fps) | ||||||
|  |                 self.nb_steps_in_period = int(self.period * self.fps) | ||||||
|  | 
 | ||||||
|  |             if dx not in self.dxs: | ||||||
|  |                 self.dxs.append(dx) | ||||||
|  | 
 | ||||||
|  |             if dy not in self.dys: | ||||||
|  |                 self.dys.append(dy) | ||||||
|  | 
 | ||||||
|  |             if dtheta not in self.dthetas: | ||||||
|  |                 self.dthetas.append(dtheta) | ||||||
|  | 
 | ||||||
|  |             self.dx_range = [min(dx, self.dx_range[0]), max(dx, self.dx_range[1])] | ||||||
|  |             self.dy_range = [min(dy, self.dy_range[0]), max(dy, self.dy_range[1])] | ||||||
|  |             self.dtheta_range = [ | ||||||
|  |                 min(dtheta, self.dtheta_range[0]), | ||||||
|  |                 max(dtheta, self.dtheta_range[1]), | ||||||
|  |             ] | ||||||
|  | 
 | ||||||
|  |             if dx not in _data: | ||||||
|  |                 _data[dx] = {} | ||||||
|  | 
 | ||||||
|  |             if dy not in _data[dx]: | ||||||
|  |                 _data[dx][dy] = {} | ||||||
|  | 
 | ||||||
|  |             if dtheta not in _data[dx][dy]: | ||||||
|  |                 _data[dx][dy][dtheta] = data[name] | ||||||
|  | 
 | ||||||
|  |             _coeffs = data[name]["coefficients"] | ||||||
|  | 
 | ||||||
|  |             coeffs = [] | ||||||
|  |             for k, v in _coeffs.items(): | ||||||
|  |                 coeffs.append(v) | ||||||
|  |             _data[dx][dy][dtheta] = coeffs | ||||||
|  | 
 | ||||||
|  |         self.dxs = sorted(self.dxs) | ||||||
|  |         self.dys = sorted(self.dys) | ||||||
|  |         self.dthetas = sorted(self.dthetas) | ||||||
|  | 
 | ||||||
|  |         nb_dx = len(self.dxs) | ||||||
|  |         nb_dy = len(self.dys) | ||||||
|  |         nb_dtheta = len(self.dthetas) | ||||||
|  | 
 | ||||||
|  |         self.data_array = nb_dx * [None] | ||||||
|  |         for x, dx in enumerate(self.dxs): | ||||||
|  |             self.data_array[x] = nb_dy * [None] | ||||||
|  |             for y, dy in enumerate(self.dys): | ||||||
|  |                 self.data_array[x][y] = nb_dtheta * [None] | ||||||
|  |                 for th, dtheta in enumerate(self.dthetas): | ||||||
|  |                     self.data_array[x][y][th] = _data[dx][dy][dtheta] | ||||||
|  | 
 | ||||||
|  |         self.data_array = self.data_array | ||||||
|  | 
 | ||||||
|  |         print("[Poly ref data] Done processing") | ||||||
|  | 
 | ||||||
|  |     def vel_to_index(self, dx, dy, dtheta): | ||||||
|  | 
 | ||||||
|  |         dx = np.clip(dx, self.dx_range[0], self.dx_range[1]) | ||||||
|  |         dy = np.clip(dy, self.dy_range[0], self.dy_range[1]) | ||||||
|  |         dtheta = np.clip(dtheta, self.dtheta_range[0], self.dtheta_range[1]) | ||||||
|  | 
 | ||||||
|  |         ix = np.argmin(np.abs(np.array(self.dxs) - dx)) | ||||||
|  |         iy = np.argmin(np.abs(np.array(self.dys) - dy)) | ||||||
|  |         itheta = np.argmin(np.abs(np.array(self.dthetas) - dtheta)) | ||||||
|  | 
 | ||||||
|  |         return int(ix), int(iy), int(itheta) | ||||||
|  | 
 | ||||||
|  |     def sample_polynomial(self, t, coeffs): | ||||||
|  |         ret = [] | ||||||
|  |         for c in coeffs: | ||||||
|  |             ret.append(np.polyval(np.flip(c), t)) | ||||||
|  | 
 | ||||||
|  |         return ret | ||||||
|  | 
 | ||||||
|  |     def get_reference_motion(self, dx, dy, dtheta, i): | ||||||
|  |         ix, iy, itheta = self.vel_to_index(dx, dy, dtheta) | ||||||
|  |         t = i % self.nb_steps_in_period / self.nb_steps_in_period | ||||||
|  |         t = np.clip(t, 0.0, 1.0)  # safeguard | ||||||
|  |         ret = self.sample_polynomial(t, self.data_array[ix][iy][itheta]) | ||||||
|  |         return ret | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  | 
 | ||||||
|  |     PRM = PolyReferenceMotion( | ||||||
|  |         "playground/open_duck_mini_v2/data/polynomial_coefficients.pkl" | ||||||
|  |     ) | ||||||
|  |     vals = [] | ||||||
|  |     select_dim = -1 | ||||||
|  |     for i in range(PRM.nb_steps_in_period): | ||||||
|  |         vals.append(PRM.get_reference_motion(0.0, -0.05, -0.1, i)[select_dim]) | ||||||
|  | 
 | ||||||
|  |     # plot | ||||||
|  |     import matplotlib.pyplot as plt | ||||||
|  |     import numpy as np | ||||||
|  | 
 | ||||||
|  |     ts = np.arange(0, PRM.nb_steps_in_period) | ||||||
|  |     plt.plot(ts, vals) | ||||||
|  |     plt.show() | ||||||
							
								
								
									
										146
									
								
								Open_Duck_Playground/playground/common/randomize.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								Open_Duck_Playground/playground/common/randomize.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | |||||||
|  | # Copyright 2025 DeepMind Technologies Limited | ||||||
|  | # Copyright 2025 Antoine Pirrone - Steve Nguyen | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | # ============================================================================== | ||||||
|  | """Domain randomization for the Open Duck Mini V2 environment. (based on Berkeley Humanoid)""" | ||||||
|  | 
 | ||||||
|  | import jax | ||||||
|  | from mujoco import mjx | ||||||
|  | import jax.numpy as jp | ||||||
|  | 
 | ||||||
|  | FLOOR_GEOM_ID = 0 | ||||||
|  | TORSO_BODY_ID = 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def domain_randomize(model: mjx.Model, rng: jax.Array): | ||||||
|  | 
 | ||||||
|  |     # _dof_addr=jp.array([6,8,10,12,14,16,18,20,22,24]) | ||||||
|  |     # _joint_addr=jp.array([7,9,11,13,15,17,19,21,23,25]) | ||||||
|  | 
 | ||||||
|  |     dof_id = jp.array( | ||||||
|  |         [idx for idx, fr in enumerate(model.dof_hasfrictionloss) if fr == True] | ||||||
|  |     )  # for backlash joint we disable frictionloss | ||||||
|  |     jnt_id = model.dof_jntid[dof_id] | ||||||
|  | 
 | ||||||
|  |     dof_addr = jp.array([jadd for jadd in model.jnt_dofadr if jadd in dof_id]) | ||||||
|  |     joint_addr = model.jnt_qposadr[jnt_id] | ||||||
|  | 
 | ||||||
|  |     @jax.vmap | ||||||
|  |     def rand_dynamics(rng): | ||||||
|  |         # Floor friction: =U(0.4, 1.0). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         geom_friction = model.geom_friction.at[FLOOR_GEOM_ID, 0].set( | ||||||
|  |             jax.random.uniform(key, minval=0.5, maxval=1.0)  # was 0.4, 1.0 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Scale static friction: *U(0.9, 1.1). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         frictionloss = model.dof_frictionloss[dof_addr] * jax.random.uniform( | ||||||
|  |             key, shape=(model.nu,), minval=0.9, maxval=1.1 | ||||||
|  |         ) | ||||||
|  |         dof_frictionloss = model.dof_frictionloss.at[dof_addr].set(frictionloss) | ||||||
|  | 
 | ||||||
|  |         # Scale armature: *U(1.0, 1.05). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         armature = model.dof_armature[dof_addr] * jax.random.uniform( | ||||||
|  |             key, shape=(model.nu,), minval=1.0, maxval=1.05 | ||||||
|  |         ) | ||||||
|  |         dof_armature = model.dof_armature.at[dof_addr].set(armature) | ||||||
|  | 
 | ||||||
|  |         # Jitter center of mass positiion: +U(-0.05, 0.05). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         dpos = jax.random.uniform(key, (3,), minval=-0.05, maxval=0.05) | ||||||
|  |         body_ipos = model.body_ipos.at[TORSO_BODY_ID].set( | ||||||
|  |             model.body_ipos[TORSO_BODY_ID] + dpos | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Scale all link masses: *U(0.9, 1.1). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         dmass = jax.random.uniform(key, shape=(model.nbody,), minval=0.9, maxval=1.1) | ||||||
|  |         body_mass = model.body_mass.at[:].set(model.body_mass * dmass) | ||||||
|  | 
 | ||||||
|  |         # Add mass to torso: +U(-0.2, 0.2). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         dmass = jax.random.uniform(key, minval=-0.1, maxval=0.1)  # was -0.2, 0.2 | ||||||
|  |         body_mass = body_mass.at[TORSO_BODY_ID].set(body_mass[TORSO_BODY_ID] + dmass) | ||||||
|  | 
 | ||||||
|  |         # Jitter qpos0: +U(-0.05, 0.05). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         qpos0 = model.qpos0 | ||||||
|  |         qpos0 = qpos0.at[joint_addr].set( | ||||||
|  |             qpos0[joint_addr] | ||||||
|  |             + jax.random.uniform( | ||||||
|  |                 key, shape=(model.nu,), minval=-0.03, maxval=0.03 | ||||||
|  |             )  # was -0.05 0.05 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # # Randomize KP | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         factor = jax.random.uniform( | ||||||
|  |             key, shape=(model.nu,), minval=0.9, maxval=1.1 | ||||||
|  |         )  # was 0.8, 1.2 | ||||||
|  |         current_kp = model.actuator_gainprm[:, 0] | ||||||
|  |         actuator_gainprm = model.actuator_gainprm.at[:, 0].set(current_kp * factor) | ||||||
|  |         actuator_biasprm = model.actuator_biasprm.at[:, 1].set(-current_kp * factor) | ||||||
|  | 
 | ||||||
|  |         return ( | ||||||
|  |             geom_friction, | ||||||
|  |             body_ipos, | ||||||
|  |             dof_frictionloss, | ||||||
|  |             dof_armature, | ||||||
|  |             body_mass, | ||||||
|  |             qpos0, | ||||||
|  |             actuator_gainprm, | ||||||
|  |             actuator_biasprm, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     ( | ||||||
|  |         friction, | ||||||
|  |         body_ipos, | ||||||
|  |         frictionloss, | ||||||
|  |         armature, | ||||||
|  |         body_mass, | ||||||
|  |         qpos0, | ||||||
|  |         actuator_gainprm, | ||||||
|  |         actuator_biasprm, | ||||||
|  |     ) = rand_dynamics(rng) | ||||||
|  | 
 | ||||||
|  |     in_axes = jax.tree_util.tree_map(lambda x: None, model) | ||||||
|  |     in_axes = in_axes.tree_replace( | ||||||
|  |         { | ||||||
|  |             "geom_friction": 0, | ||||||
|  |             "body_ipos": 0, | ||||||
|  |             "dof_frictionloss": 0, | ||||||
|  |             "dof_armature": 0, | ||||||
|  |             "body_mass": 0, | ||||||
|  |             "qpos0": 0, | ||||||
|  |             "actuator_gainprm": 0, | ||||||
|  |             "actuator_biasprm": 0, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     model = model.tree_replace( | ||||||
|  |         { | ||||||
|  |             "geom_friction": friction, | ||||||
|  |             "body_ipos": body_ipos, | ||||||
|  |             "dof_frictionloss": frictionloss, | ||||||
|  |             "dof_armature": armature, | ||||||
|  |             "body_mass": body_mass, | ||||||
|  |             "qpos0": qpos0, | ||||||
|  |             "actuator_gainprm": actuator_gainprm, | ||||||
|  |             "actuator_biasprm": actuator_biasprm, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     return model, in_axes | ||||||
							
								
								
									
										241
									
								
								Open_Duck_Playground/playground/common/rewards.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								Open_Duck_Playground/playground/common/rewards.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,241 @@ | |||||||
|  | """ | ||||||
|  | Set of commonly used rewards | ||||||
|  | For examples on how to use some rewards, look at https://github.com/google-deepmind/mujoco_playground/blob/main/mujoco_playground/_src/locomotion/berkeley_humanoid/joystick.py | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import jax | ||||||
|  | import jax.numpy as jp | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Tracking rewards. | ||||||
|  | def reward_tracking_lin_vel( | ||||||
|  |     commands: jax.Array, | ||||||
|  |     local_vel: jax.Array, | ||||||
|  |     tracking_sigma: float, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     # lin_vel_error = jp.sum(jp.square(commands[:2] - local_vel[:2])) | ||||||
|  |     # return jp.nan_to_num(jp.exp(-lin_vel_error / self._config.reward_config.tracking_sigma)) | ||||||
|  |     y_tol = 0.1 | ||||||
|  |     error_x = jp.square(commands[0] - local_vel[0]) | ||||||
|  |     error_y = jp.clip(jp.abs(local_vel[1] - commands[1]) - y_tol, 0.0, None) | ||||||
|  |     lin_vel_error = error_x + jp.square(error_y) | ||||||
|  |     return jp.nan_to_num(jp.exp(-lin_vel_error / tracking_sigma)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_tracking_ang_vel( | ||||||
|  |     commands: jax.Array, | ||||||
|  |     ang_vel: jax.Array, | ||||||
|  |     tracking_sigma: float, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     ang_vel_error = jp.square(commands[2] - ang_vel[2]) | ||||||
|  |     return jp.nan_to_num(jp.exp(-ang_vel_error / tracking_sigma)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Base-related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_lin_vel_z(global_linvel) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.square(global_linvel[2])) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_ang_vel_xy(global_angvel) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.sum(jp.square(global_angvel[:2]))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_orientation(torso_zaxis: jax.Array) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.sum(jp.square(torso_zaxis[:2]))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_base_height(base_height: jax.Array, base_height_target: float) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.square(base_height - base_height_target)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_base_y_swing( | ||||||
|  |     base_y_speed: jax.Array, | ||||||
|  |     freq: float, | ||||||
|  |     amplitude: float, | ||||||
|  |     t: float, | ||||||
|  |     tracking_sigma: float, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     target_y_speed = amplitude * jp.sin(2 * jp.pi * freq * t) | ||||||
|  |     y_speed_error = jp.square(target_y_speed - base_y_speed) | ||||||
|  |     return jp.nan_to_num(jp.exp(-y_speed_error / tracking_sigma)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Energy related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_torques(torques: jax.Array) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.sum(jp.square(torques))) | ||||||
|  |     # return jp.nan_to_num(jp.sum(jp.abs(torques))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_energy(qvel: jax.Array, qfrc_actuator: jax.Array) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.sum(jp.abs(qvel) * jp.abs(qfrc_actuator))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_action_rate(act: jax.Array, last_act: jax.Array) -> jax.Array: | ||||||
|  |     c1 = jp.nan_to_num(jp.sum(jp.square(act - last_act))) | ||||||
|  |     return c1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Other rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_joint_pos_limits( | ||||||
|  |     qpos: jax.Array, soft_lowers: float, soft_uppers: float | ||||||
|  | ) -> jax.Array: | ||||||
|  |     out_of_limits = -jp.clip(qpos - soft_lowers, None, 0.0) | ||||||
|  |     out_of_limits += jp.clip(qpos - soft_uppers, 0.0, None) | ||||||
|  |     return jp.nan_to_num(jp.sum(out_of_limits)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_stand_still( | ||||||
|  |     commands: jax.Array, | ||||||
|  |     qpos: jax.Array, | ||||||
|  |     qvel: jax.Array, | ||||||
|  |     default_pose: jax.Array, | ||||||
|  |     ignore_head: bool = False, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     # TODO no hard coded slices | ||||||
|  |     cmd_norm = jp.linalg.norm(commands[:3]) | ||||||
|  |     if not ignore_head: | ||||||
|  |         pose_cost = jp.sum(jp.abs(qpos - default_pose)) | ||||||
|  |         vel_cost = jp.sum(jp.abs(qvel)) | ||||||
|  |     else: | ||||||
|  |         left_leg_pos = qpos[:5] | ||||||
|  |         right_leg_pos = qpos[9:] | ||||||
|  |         left_leg_vel = qvel[:5] | ||||||
|  |         right_leg_vel = qvel[9:] | ||||||
|  |         left_leg_default = default_pose[:5] | ||||||
|  |         right_leg_default = default_pose[9:] | ||||||
|  |         pose_cost = jp.sum(jp.abs(left_leg_pos - left_leg_default)) + jp.sum( | ||||||
|  |             jp.abs(right_leg_pos - right_leg_default) | ||||||
|  |         ) | ||||||
|  |         vel_cost = jp.sum(jp.abs(left_leg_vel)) + jp.sum(jp.abs(right_leg_vel)) | ||||||
|  | 
 | ||||||
|  |     return jp.nan_to_num(pose_cost + vel_cost) * (cmd_norm < 0.01) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_termination(done: jax.Array) -> jax.Array: | ||||||
|  |     return done | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_alive() -> jax.Array: | ||||||
|  |     return jp.array(1.0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Pose-related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_head_pos( | ||||||
|  |     joints_qpos: jax.Array, | ||||||
|  |     joints_qvel: jax.Array, | ||||||
|  |     cmd: jax.Array, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     move_cmd_norm = jp.linalg.norm(cmd[:3]) | ||||||
|  |     head_cmd = cmd[3:] | ||||||
|  |     head_pos = joints_qpos[5:9] | ||||||
|  |     # head_vel = joints_qvel[5:9] | ||||||
|  | 
 | ||||||
|  |     # target_head_qvel = jp.zeros_like(head_cmd) | ||||||
|  | 
 | ||||||
|  |     head_pos_error = jp.sum(jp.square(head_pos - head_cmd)) | ||||||
|  | 
 | ||||||
|  |     # head_vel_error = jp.sum(jp.square(head_vel - target_head_qvel)) | ||||||
|  | 
 | ||||||
|  |     return jp.nan_to_num(head_pos_error) * (move_cmd_norm > 0.01) | ||||||
|  |     # return jp.nan_to_num(head_pos_error + head_vel_error) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_joint_deviation_hip( | ||||||
|  |     qpos: jax.Array, cmd: jax.Array, hip_indices: jax.Array, default_pose: jax.Array | ||||||
|  | ) -> jax.Array: | ||||||
|  |     cost = jp.sum(jp.abs(qpos[hip_indices] - default_pose[hip_indices])) | ||||||
|  |     cost *= jp.abs(cmd[1]) > 0.1 | ||||||
|  |     return jp.nan_to_num(cost) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_joint_deviation_knee( | ||||||
|  |     qpos: jax.Array, knee_indices: jax.Array, default_pose: jax.Array | ||||||
|  | ) -> jax.Array: | ||||||
|  |     return jp.nan_to_num( | ||||||
|  |         jp.sum(jp.abs(qpos[knee_indices] - default_pose[knee_indices])) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_pose( | ||||||
|  |     qpos: jax.Array, default_pose: jax.Array, weights: jax.Array | ||||||
|  | ) -> jax.Array: | ||||||
|  |     return jp.nan_to_num(jp.sum(jp.square(qpos - default_pose) * weights)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Feet related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_feet_slip(contact: jax.Array, global_linvel: jax.Array) -> jax.Array: | ||||||
|  |     body_vel = global_linvel[:2] | ||||||
|  |     reward = jp.sum(jp.linalg.norm(body_vel, axis=-1) * contact) | ||||||
|  |     return jp.nan_to_num(reward) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_feet_clearance( | ||||||
|  |     feet_vel: jax.Array, | ||||||
|  |     foot_pos: jax.Array, | ||||||
|  |     max_foot_height: float, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     # feet_vel = data.sensordata[self._foot_linvel_sensor_adr] | ||||||
|  |     vel_xy = feet_vel[..., :2] | ||||||
|  |     vel_norm = jp.sqrt(jp.linalg.norm(vel_xy, axis=-1)) | ||||||
|  |     # foot_pos = data.site_xpos[self._feet_site_id] | ||||||
|  |     foot_z = foot_pos[..., -1] | ||||||
|  |     delta = jp.abs(foot_z - max_foot_height) | ||||||
|  |     return jp.nan_to_num(jp.sum(delta * vel_norm)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_feet_height( | ||||||
|  |     swing_peak: jax.Array, | ||||||
|  |     first_contact: jax.Array, | ||||||
|  |     max_foot_height: float, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     error = swing_peak / max_foot_height - 1.0 | ||||||
|  |     return jp.nan_to_num(jp.sum(jp.square(error) * first_contact)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def reward_feet_air_time( | ||||||
|  |     air_time: jax.Array, | ||||||
|  |     first_contact: jax.Array, | ||||||
|  |     commands: jax.Array, | ||||||
|  |     threshold_min: float = 0.1,  # 0.2 | ||||||
|  |     threshold_max: float = 0.5, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     cmd_norm = jp.linalg.norm(commands[:3]) | ||||||
|  |     air_time = (air_time - threshold_min) * first_contact | ||||||
|  |     air_time = jp.clip(air_time, max=threshold_max - threshold_min) | ||||||
|  |     reward = jp.sum(air_time) | ||||||
|  |     reward *= cmd_norm > 0.01  # No reward for zero commands. | ||||||
|  |     return jp.nan_to_num(reward) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def reward_feet_phase( | ||||||
|  |     foot_pos: jax.Array, | ||||||
|  |     rz: jax.Array, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     # Reward for tracking the desired foot height. | ||||||
|  |     # foot_pos = data.site_xpos[self._feet_site_id] | ||||||
|  |     foot_z = foot_pos[..., -1] | ||||||
|  |     # rz = gait.get_rz(phase, swing_height=foot_height) | ||||||
|  |     error = jp.sum(jp.square(foot_z - rz)) | ||||||
|  |     reward = jp.exp(-error / 0.01) | ||||||
|  |     # TODO(kevin): Ensure no movement at 0 command. | ||||||
|  |     # cmd_norm = jp.linalg.norm(commands) | ||||||
|  |     # reward *= cmd_norm > 0.1  # No reward for zero commands. | ||||||
|  |     return jp.nan_to_num(reward) | ||||||
							
								
								
									
										196
									
								
								Open_Duck_Playground/playground/common/rewards_numpy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								Open_Duck_Playground/playground/common/rewards_numpy.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,196 @@ | |||||||
|  | """ | ||||||
|  | Set of commonly used rewards | ||||||
|  | For examples on how to use some rewards, look at https://github.com/google-deepmind/mujoco_playground/blob/main/mujoco_playground/_src/locomotion/berkeley_humanoid/joystick.py | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | # import jax | ||||||
|  | # import jax.numpy as np | ||||||
|  | 
 | ||||||
|  | import numpy as np | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Tracking rewards. | ||||||
|  | def reward_tracking_lin_vel(commands, local_vel, tracking_sigma): | ||||||
|  |     # lin_vel_error = np.sum(np.square(commands[:2] - local_vel[:2])) | ||||||
|  |     # return np.nan_to_num(np.exp(-lin_vel_error / self._config.reward_config.tracking_sigma)) | ||||||
|  |     y_tol = 0.1 | ||||||
|  |     error_x = np.square(commands[0] - local_vel[0]) | ||||||
|  |     error_y = np.clip(np.abs(local_vel[1] - commands[1]) - y_tol, 0.0, None) | ||||||
|  |     lin_vel_error = error_x + np.square(error_y) | ||||||
|  |     return np.nan_to_num(np.exp(-lin_vel_error / tracking_sigma)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_tracking_ang_vel(commands, ang_vel, tracking_sigma): | ||||||
|  |     ang_vel_error = np.square(commands[2] - ang_vel[2]) | ||||||
|  |     return np.nan_to_num(np.exp(-ang_vel_error / tracking_sigma)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Base-related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_lin_vel_z(global_linvel): | ||||||
|  |     return np.nan_to_num(np.square(global_linvel[2])) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_ang_vel_xy(global_angvel): | ||||||
|  |     return np.nan_to_num(np.sum(np.square(global_angvel[:2]))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_orientation(torso_zaxis): | ||||||
|  |     return np.nan_to_num(np.sum(np.square(torso_zaxis[:2]))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_base_height(base_height, base_height_target): | ||||||
|  |     return np.nan_to_num(np.square(base_height - base_height_target)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_base_y_swing(base_y_speed, freq, amplitude, t, tracking_sigma): | ||||||
|  |     target_y_speed = amplitude * np.sin(2 * np.pi * freq * t) | ||||||
|  |     y_speed_error = np.square(target_y_speed - base_y_speed) | ||||||
|  |     return np.nan_to_num(np.exp(-y_speed_error / tracking_sigma)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Energy related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_torques(torques): | ||||||
|  |     return np.nan_to_num(np.sum(np.square(torques))) | ||||||
|  |     # return np.nan_to_num(np.sum(np.abs(torques))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_energy(qvel, qfrc_actuator): | ||||||
|  |     return np.nan_to_num(np.sum(np.abs(qvel) * np.abs(qfrc_actuator))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_action_rate(act, last_act): | ||||||
|  |     c1 = np.nan_to_num(np.sum(np.square(act - last_act))) | ||||||
|  |     return c1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Other rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_joint_pos_limits(qpos, soft_lowers, soft_uppers): | ||||||
|  |     out_of_limits = -np.clip(qpos - soft_lowers, None, 0.0) | ||||||
|  |     out_of_limits += np.clip(qpos - soft_uppers, 0.0, None) | ||||||
|  |     return np.nan_to_num(np.sum(out_of_limits)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_stand_still(commands, qpos, qvel, default_pose, ignore_head=False): | ||||||
|  |     # TODO no hard coded slices | ||||||
|  |     cmd_norm = np.linalg.norm(commands[:3]) | ||||||
|  |     if not ignore_head: | ||||||
|  |         pose_cost = np.sum(np.abs(qpos - default_pose)) | ||||||
|  |         vel_cost = np.sum(np.abs(qvel)) | ||||||
|  |     else: | ||||||
|  |         left_leg_pos = qpos[:5] | ||||||
|  |         right_leg_pos = qpos[9:] | ||||||
|  |         left_leg_vel = qvel[:5] | ||||||
|  |         right_leg_vel = qvel[9:] | ||||||
|  |         left_leg_default = default_pose[:5] | ||||||
|  |         right_leg_default = default_pose[9:] | ||||||
|  |         pose_cost = np.sum(np.abs(left_leg_pos - left_leg_default)) + np.sum( | ||||||
|  |             np.abs(right_leg_pos - right_leg_default) | ||||||
|  |         ) | ||||||
|  |         vel_cost = np.sum(np.abs(left_leg_vel)) + np.sum(np.abs(right_leg_vel)) | ||||||
|  | 
 | ||||||
|  |     return np.nan_to_num(pose_cost + vel_cost) * (cmd_norm < 0.01) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_termination(done): | ||||||
|  |     return done | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_alive(): | ||||||
|  |     return np.array(1.0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Pose-related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cost_head_pos(joints_qpos, joints_qvel, cmd): | ||||||
|  |     move_cmd_norm = np.linalg.norm(cmd[:3]) | ||||||
|  |     head_cmd = cmd[3:] | ||||||
|  |     head_pos = joints_qpos[5:9] | ||||||
|  |     # head_vel = joints_qvel[5:9] | ||||||
|  | 
 | ||||||
|  |     # target_head_qvel = np.zeros_like(head_cmd) | ||||||
|  | 
 | ||||||
|  |     head_pos_error = np.sum(np.square(head_pos - head_cmd)) | ||||||
|  | 
 | ||||||
|  |     # head_vel_error = np.sum(np.square(head_vel - target_head_qvel)) | ||||||
|  | 
 | ||||||
|  |     return np.nan_to_num(head_pos_error) * (move_cmd_norm > 0.01) | ||||||
|  |     # return np.nan_to_num(head_pos_error + head_vel_error) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_joint_deviation_hip(qpos, cmd, hip_indices, default_pose): | ||||||
|  |     cost = np.sum(np.abs(qpos[hip_indices] - default_pose[hip_indices])) | ||||||
|  |     cost *= np.abs(cmd[1]) > 0.1 | ||||||
|  |     return np.nan_to_num(cost) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_joint_deviation_knee(qpos, knee_indices, default_pose): | ||||||
|  |     return np.nan_to_num( | ||||||
|  |         np.sum(np.abs(qpos[knee_indices] - default_pose[knee_indices])) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_pose(qpos, default_pose, weights): | ||||||
|  |     return np.nan_to_num(np.sum(np.square(qpos - default_pose) * weights)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Feet related rewards. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_feet_slip(contact, global_linvel): | ||||||
|  |     body_vel = global_linvel[:2] | ||||||
|  |     reward = np.sum(np.linalg.norm(body_vel, axis=-1) * contact) | ||||||
|  |     return np.nan_to_num(reward) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_feet_clearance(feet_vel, foot_pos, max_foot_height): | ||||||
|  |     # feet_vel = data.sensordata[self._foot_linvel_sensor_adr] | ||||||
|  |     vel_xy = feet_vel[..., :2] | ||||||
|  |     vel_norm = np.sqrt(np.linalg.norm(vel_xy, axis=-1)) | ||||||
|  |     # foot_pos = data.site_xpos[self._feet_site_id] | ||||||
|  |     foot_z = foot_pos[..., -1] | ||||||
|  |     delta = np.abs(foot_z - max_foot_height) | ||||||
|  |     return np.nan_to_num(np.sum(delta * vel_norm)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def cost_feet_height(swing_peak, first_contact, max_foot_height): | ||||||
|  |     error = swing_peak / max_foot_height - 1.0 | ||||||
|  |     return np.nan_to_num(np.sum(np.square(error) * first_contact)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def reward_feet_air_time( | ||||||
|  |     air_time, first_contact, commands, threshold_min=0.1, threshold_max=0.5  # 0.2 | ||||||
|  | ): | ||||||
|  |     cmd_norm = np.linalg.norm(commands[:3]) | ||||||
|  |     air_time = (air_time - threshold_min) * first_contact | ||||||
|  |     air_time = np.clip(air_time, max=threshold_max - threshold_min) | ||||||
|  |     reward = np.sum(air_time) | ||||||
|  |     reward *= cmd_norm > 0.01  # No reward for zero commands. | ||||||
|  |     return np.nan_to_num(reward) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME | ||||||
|  | def reward_feet_phase(foot_pos, rz): | ||||||
|  |     # Reward for tracking the desired foot height. | ||||||
|  |     # foot_pos = data.site_xpos[self._feet_site_id] | ||||||
|  |     foot_z = foot_pos[..., -1] | ||||||
|  |     # rz = gait.get_rz(phase, swing_height=foot_height) | ||||||
|  |     error = np.sum(np.square(foot_z - rz)) | ||||||
|  |     reward = np.exp(-error / 0.01) | ||||||
|  |     # TODO(kevin): Ensure no movement at 0 command. | ||||||
|  |     # cmd_norm = np.linalg.norm(commands) | ||||||
|  |     # reward *= cmd_norm > 0.1  # No reward for zero commands. | ||||||
|  |     return np.nan_to_num(reward) | ||||||
							
								
								
									
										118
									
								
								Open_Duck_Playground/playground/common/runner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								Open_Duck_Playground/playground/common/runner.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | |||||||
|  | """ | ||||||
|  | Defines a common runner between the different robots. | ||||||
|  | Inspired from https://github.com/kscalelabs/mujoco_playground/blob/master/playground/common/runner.py | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from pathlib import Path | ||||||
|  | from abc import ABC | ||||||
|  | import argparse | ||||||
|  | import functools | ||||||
|  | from datetime import datetime | ||||||
|  | from flax.training import orbax_utils | ||||||
|  | from tensorboardX import SummaryWriter | ||||||
|  | 
 | ||||||
|  | import os | ||||||
|  | from brax.training.agents.ppo import networks as ppo_networks, train as ppo | ||||||
|  | from mujoco_playground import wrapper | ||||||
|  | from mujoco_playground.config import locomotion_params | ||||||
|  | from orbax import checkpoint as ocp | ||||||
|  | import jax | ||||||
|  | 
 | ||||||
|  | from playground.common.export_onnx import export_onnx | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseRunner(ABC): | ||||||
|  |     def __init__(self, args: argparse.Namespace) -> None: | ||||||
|  |         """Initialize the Runner class. | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             args (argparse.Namespace): Command line arguments. | ||||||
|  |         """ | ||||||
|  |         self.args = args | ||||||
|  |         self.output_dir = args.output_dir | ||||||
|  |         self.output_dir = Path.cwd() / Path(self.output_dir) | ||||||
|  | 
 | ||||||
|  |         self.env_config = None | ||||||
|  |         self.env = None | ||||||
|  |         self.eval_env = None | ||||||
|  |         self.randomizer = None | ||||||
|  |         self.writer = SummaryWriter(log_dir=self.output_dir) | ||||||
|  |         self.action_size = None | ||||||
|  |         self.obs_size = None | ||||||
|  |         self.num_timesteps = args.num_timesteps | ||||||
|  |         self.restore_checkpoint_path = None | ||||||
|  |          | ||||||
|  |         # CACHE STUFF | ||||||
|  |         os.makedirs(".tmp", exist_ok=True) | ||||||
|  |         jax.config.update("jax_compilation_cache_dir", ".tmp/jax_cache") | ||||||
|  |         jax.config.update("jax_persistent_cache_min_entry_size_bytes", -1) | ||||||
|  |         jax.config.update("jax_persistent_cache_min_compile_time_secs", 0) | ||||||
|  |         jax.config.update( | ||||||
|  |             "jax_persistent_cache_enable_xla_caches", | ||||||
|  |             "xla_gpu_per_fusion_autotune_cache_dir", | ||||||
|  |         ) | ||||||
|  |         os.environ["JAX_COMPILATION_CACHE_DIR"] = ".tmp/jax_cache" | ||||||
|  | 
 | ||||||
|  |     def progress_callback(self, num_steps: int, metrics: dict) -> None: | ||||||
|  | 
 | ||||||
|  |         for metric_name, metric_value in metrics.items(): | ||||||
|  |             # Convert to float, but watch out for 0-dim JAX arrays | ||||||
|  |             self.writer.add_scalar(metric_name, metric_value, num_steps) | ||||||
|  | 
 | ||||||
|  |         print("-----------") | ||||||
|  |         print( | ||||||
|  |             f'STEP: {num_steps} reward: {metrics["eval/episode_reward"]} reward_std: {metrics["eval/episode_reward_std"]}' | ||||||
|  |         ) | ||||||
|  |         print("-----------") | ||||||
|  | 
 | ||||||
|  |     def policy_params_fn(self, current_step, make_policy, params): | ||||||
|  |         # save checkpoints | ||||||
|  | 
 | ||||||
|  |         orbax_checkpointer = ocp.PyTreeCheckpointer() | ||||||
|  |         save_args = orbax_utils.save_args_from_target(params) | ||||||
|  |         d = datetime.now().strftime("%Y_%m_%d_%H%M%S") | ||||||
|  |         path = f"{self.output_dir}/{d}_{current_step}" | ||||||
|  |         print(f"Saving checkpoint (step: {current_step}): {path}") | ||||||
|  |         orbax_checkpointer.save(path, params, force=True, save_args=save_args) | ||||||
|  |         onnx_export_path = f"{self.output_dir}/{d}_{current_step}.onnx" | ||||||
|  |         export_onnx( | ||||||
|  |             params, | ||||||
|  |             self.action_size, | ||||||
|  |             self.ppo_params, | ||||||
|  |             self.obs_size,  # may not work | ||||||
|  |             output_path=onnx_export_path | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def train(self) -> None: | ||||||
|  |         self.ppo_params = locomotion_params.brax_ppo_config( | ||||||
|  |             "BerkeleyHumanoidJoystickFlatTerrain" | ||||||
|  |         )  # TODO | ||||||
|  |         self.ppo_training_params = dict(self.ppo_params) | ||||||
|  |         # self.ppo_training_params["num_timesteps"] = 150000000 * 20 | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  |         if "network_factory" in self.ppo_params: | ||||||
|  |             network_factory = functools.partial( | ||||||
|  |                 ppo_networks.make_ppo_networks, **self.ppo_params.network_factory | ||||||
|  |             ) | ||||||
|  |             del self.ppo_training_params["network_factory"] | ||||||
|  |         else: | ||||||
|  |             network_factory = ppo_networks.make_ppo_networks | ||||||
|  |         self.ppo_training_params["num_timesteps"] = self.num_timesteps | ||||||
|  |         print(f"PPO params: {self.ppo_training_params}") | ||||||
|  | 
 | ||||||
|  |         train_fn = functools.partial( | ||||||
|  |             ppo.train, | ||||||
|  |             **self.ppo_training_params, | ||||||
|  |             network_factory=network_factory, | ||||||
|  |             randomization_fn=self.randomizer, | ||||||
|  |             progress_fn=self.progress_callback, | ||||||
|  |             policy_params_fn=self.policy_params_fn, | ||||||
|  |             restore_checkpoint_path=self.restore_checkpoint_path, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         _, params, _ = train_fn( | ||||||
|  |             environment=self.env, | ||||||
|  |             eval_env=self.eval_env, | ||||||
|  |             wrap_env_fn=wrapper.wrap_for_brax_training, | ||||||
|  |         ) | ||||||
							
								
								
									
										25
									
								
								Open_Duck_Playground/playground/common/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Open_Duck_Playground/playground/common/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | import jax.numpy as jp | ||||||
|  | import jax | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LowPassActionFilter: | ||||||
|  |     def __init__(self, control_freq, cutoff_frequency=30.0): | ||||||
|  |         self.last_action = 0 | ||||||
|  |         self.current_action = 0 | ||||||
|  |         self.control_freq = float(control_freq) | ||||||
|  |         self.cutoff_frequency = float(cutoff_frequency) | ||||||
|  |         self.alpha = self.compute_alpha() | ||||||
|  | 
 | ||||||
|  |     def compute_alpha(self): | ||||||
|  |         return (1.0 / self.cutoff_frequency) / ( | ||||||
|  |             1.0 / self.control_freq + 1.0 / self.cutoff_frequency | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def push(self, action: jax.Array) -> None: | ||||||
|  |         self.current_action = jp.array(action) | ||||||
|  | 
 | ||||||
|  |     def get_filtered_action(self) -> jax.Array: | ||||||
|  |         self.last_action = ( | ||||||
|  |             self.alpha * self.last_action + (1 - self.alpha) * self.current_action | ||||||
|  |         ) | ||||||
|  |         return self.last_action | ||||||
							
								
								
									
										291
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/base.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,291 @@ | |||||||
|  | # Copyright 2025 DeepMind Technologies Limited | ||||||
|  | # Copyright 2025 Antoine Pirrone - Steve Nguyen | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | # ============================================================================== | ||||||
|  | """Base classes for Open Duck Mini V2. (based on Berkeley Humanoid)""" | ||||||
|  | 
 | ||||||
|  | from typing import Any, Dict, Optional, Union | ||||||
|  | 
 | ||||||
|  | from etils import epath | ||||||
|  | import jax | ||||||
|  | import jax.numpy as jp | ||||||
|  | from ml_collections import config_dict | ||||||
|  | import mujoco | ||||||
|  | from mujoco import mjx | ||||||
|  | 
 | ||||||
|  | from mujoco_playground._src import mjx_env | ||||||
|  | from . import constants | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_assets() -> Dict[str, bytes]: | ||||||
|  |     assets = {} | ||||||
|  |     mjx_env.update_assets(assets, constants.ROOT_PATH / "xmls", "*.xml") | ||||||
|  |     mjx_env.update_assets(assets, constants.ROOT_PATH / "xmls" / "assets") | ||||||
|  |     path = constants.ROOT_PATH | ||||||
|  |     mjx_env.update_assets(assets, path, "*.xml") | ||||||
|  |     mjx_env.update_assets(assets, path / "assets") | ||||||
|  |     return assets | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OpenDuckMiniV2Env(mjx_env.MjxEnv): | ||||||
|  |     """Base class for Open Duck Mini V2 environments.""" | ||||||
|  | 
 | ||||||
|  |     def __init__( | ||||||
|  |         self, | ||||||
|  |         xml_path: str, | ||||||
|  |         config: config_dict.ConfigDict, | ||||||
|  |         config_overrides: Optional[Dict[str, Union[str, int, list[Any]]]] = None, | ||||||
|  |     ) -> None: | ||||||
|  |         super().__init__(config, config_overrides) | ||||||
|  | 
 | ||||||
|  |         print(f"xml: {xml_path}") | ||||||
|  |         self._mj_model = mujoco.MjModel.from_xml_string( | ||||||
|  |             epath.Path(xml_path).read_text(), assets=get_assets() | ||||||
|  |         ) | ||||||
|  |         self._mj_model.opt.timestep = self.sim_dt | ||||||
|  | 
 | ||||||
|  |         self._mj_model.vis.global_.offwidth = 3840 | ||||||
|  |         self._mj_model.vis.global_.offheight = 2160 | ||||||
|  | 
 | ||||||
|  |         self._mjx_model = mjx.put_model(self._mj_model) | ||||||
|  |         self._xml_path = xml_path | ||||||
|  |         self.floating_base_name= [self._mj_model.jnt(k).name for k in range(0, self._mj_model.njnt) if self._mj_model.jnt(k).type == 0][0] #assuming only one floating object! | ||||||
|  |         self.actuator_names = [ | ||||||
|  |             self._mj_model.actuator(k).name for k in range(0, self._mj_model.nu) | ||||||
|  |         ]  # will be useful to get only the actuators we care about | ||||||
|  |         self.joint_names = [ #njnt = all joints (including floating base, actuators and backlash joints) | ||||||
|  |             self._mj_model.jnt(k).name for k in range(0, self._mj_model.njnt) | ||||||
|  |         ]  # all the joint (including the backlash joints) | ||||||
|  |         self.backlash_joint_names = [ | ||||||
|  |             j for j in self.joint_names if j not in self.actuator_names and j not in self.floating_base_name | ||||||
|  |         ]  # only the dummy backlash joint | ||||||
|  |         self.all_joint_ids = [self.get_joint_id_from_name(n) for n in self.joint_names] | ||||||
|  |         self.all_joint_qpos_addr = [self.get_joint_addr_from_name(n) for n in self.joint_names] | ||||||
|  | 
 | ||||||
|  |         self.actuator_joint_ids = [ | ||||||
|  |             self.get_joint_id_from_name(n) for n in self.actuator_names | ||||||
|  |         ] | ||||||
|  |         self.actuator_joint_qpos_addr = [ | ||||||
|  |             self.get_joint_addr_from_name(n) for n in self.actuator_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.backlash_joint_ids=[ | ||||||
|  |             self.get_joint_id_from_name(n) for n in self.backlash_joint_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.backlash_joint_qpos_addr=[ | ||||||
|  |             self.get_joint_addr_from_name(n) for n in self.backlash_joint_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.all_qvel_addr=jp.array([self._mj_model.jnt_dofadr[jad] for jad in self.all_joint_ids]) | ||||||
|  |         self.actuator_qvel_addr=jp.array([self._mj_model.jnt_dofadr[jad] for jad in self.actuator_joint_ids]) | ||||||
|  | 
 | ||||||
|  |         self.actuator_joint_dict = { | ||||||
|  |             n: self.get_joint_id_from_name(n) for n in self.actuator_names | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self._floating_base_qpos_addr = self._mj_model.jnt_qposadr[ | ||||||
|  |             jp.where(self._mj_model.jnt_type == 0) | ||||||
|  |         ][ | ||||||
|  |             0 | ||||||
|  |         ]  # Assuming there is only one floating base! the jnt_type==0 is a floating joint. 3 is a hinge | ||||||
|  | 
 | ||||||
|  |         self._floating_base_qvel_addr = self._mj_model.jnt_dofadr[ | ||||||
|  |             jp.where(self._mj_model.jnt_type == 0) | ||||||
|  |         ][ | ||||||
|  |             0 | ||||||
|  |         ]  # Assuming there is only one floating base! the jnt_type==0 is a floating joint. 3 is a hinge | ||||||
|  | 
 | ||||||
|  |         self._floating_base_id = self._mj_model.joint(self.floating_base_name).id | ||||||
|  | 
 | ||||||
|  |         # self.all_joint_no_backlash_ids=jp.zeros(7+self._mj_model.nu) | ||||||
|  |         # all_idx=self.actuator_joint_ids+list(range(self._floating_base_qpos_addr,self._floating_base_qpos_addr+7)) | ||||||
|  |         # all_idx=jp.array(all_idx).sort() | ||||||
|  |         all_idx=self.actuator_joint_ids+list([self.get_joint_id_from_name("trunk_assembly_freejoint")]) | ||||||
|  |         all_idx=jp.array(all_idx).sort() | ||||||
|  |         # self.all_joint_no_backlash_ids=[idx for idx in self.all_joint_ids if idx not in self.backlash_joint_ids]+list(range(self._floating_base_add,self._floating_base_add+7)) | ||||||
|  |         self.all_joint_no_backlash_ids=[idx for idx in all_idx] | ||||||
|  |         # print(f"ALL: {self.all_joint_no_backlash_ids} back_id: {self.backlash_joint_ids} base_id: {list(range(self._floating_base_qpos_addr,self._floating_base_qpos_addr+7))}") | ||||||
|  | 
 | ||||||
|  |         self.backlash_idx_to_add = [] | ||||||
|  | 
 | ||||||
|  |         for i, actuator_name in enumerate(self.actuator_names): | ||||||
|  |             if actuator_name + "_backlash" not in self.backlash_joint_names: | ||||||
|  |                 self.backlash_idx_to_add.append(i) | ||||||
|  | 
 | ||||||
|  |         print(f"actuators: {self.actuator_names}") | ||||||
|  |         print(f"joints: {self.joint_names}") | ||||||
|  |         print(f"backlash joints: {self.backlash_joint_names}") | ||||||
|  |         print(f"actuator joints ids: {self.actuator_joint_ids}") | ||||||
|  |         print(f"actuator joints dict: {self.actuator_joint_dict}") | ||||||
|  |         print(f"floating qpos addr: {self._floating_base_qpos_addr} qvel addr: {self._floating_base_qvel_addr}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def get_actuator_id_from_name(self, name: str) -> int: | ||||||
|  |         """Return the id of a specified actuator""" | ||||||
|  |         return mujoco.mj_name2id(self._mj_model, mujoco.mjtObj.mjOBJ_ACTUATOR, name) | ||||||
|  | 
 | ||||||
|  |     def get_joint_id_from_name(self, name: str) -> int: | ||||||
|  |         """Return the id of a specified joint""" | ||||||
|  |         return mujoco.mj_name2id(self._mj_model, mujoco.mjtObj.mjOBJ_JOINT, name) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def get_joint_addr_from_name(self, name: str) -> int: | ||||||
|  |         """Return the address of a specified joint""" | ||||||
|  |         return self._mj_model.joint(name).qposadr | ||||||
|  | 
 | ||||||
|  |     def get_dof_id_from_name(self, name: str) -> int: | ||||||
|  |         """Return the id of a specified dof""" | ||||||
|  |         return mujoco.mj_name2id(self._mj_model, mujoco.mjtObj.mjOBJ_DOF, name) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joint_qpos_from_name(self, data: jax.Array, name: str) -> jax.Array: | ||||||
|  |         """Return the qpos of a given actual joint""" | ||||||
|  |         addr = self._mj_model.jnt_qposadr[self.actuator_joint_dict[name]] | ||||||
|  |         return data[addr] | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joints_qpos_addr(self) -> jax.Array: | ||||||
|  |         """Return the all the idx of actual joints""" | ||||||
|  |         addr = jp.array( | ||||||
|  |             [self._mj_model.jnt_qposadr[idx] for idx in self.actuator_joint_ids] | ||||||
|  |         ) | ||||||
|  |         return addr | ||||||
|  | 
 | ||||||
|  |     def get_floating_base_qpos(self, data:jax.Array) -> jax.Array: | ||||||
|  |         return data[self._floating_base_qpos_addr:self._floating_base_qvel_addr+7] | ||||||
|  | 
 | ||||||
|  |     def get_floating_base_qvel(self, data:jax.Array) -> jax.Array: | ||||||
|  |         return data[self._floating_base_qvel_addr:self._floating_base_qvel_addr+6] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def set_floating_base_qpos(self, new_qpos:jax.Array, qpos:jax.Array) -> jax.Array: | ||||||
|  |         return qpos.at[self._floating_base_qpos_addr:self._floating_base_qpos_addr+7].set(new_qpos) | ||||||
|  | 
 | ||||||
|  |     def set_floating_base_qvel(self, new_qvel:jax.Array, qvel:jax.Array) -> jax.Array: | ||||||
|  |         return qvel.at[self._floating_base_qvel_addr:self._floating_base_qvel_addr+6].set(new_qvel) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def exclude_backlash_joints_addr(self) -> jax.Array: | ||||||
|  |         """Return the all the idx of actual joints and floating base""" | ||||||
|  |         addr = jp.array( | ||||||
|  |             [self._mj_model.jnt_qposadr[idx] for idx in self.all_joint_no_backlash_ids] | ||||||
|  |         ) | ||||||
|  |         return addr | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def get_all_joints_addr(self) -> jax.Array: | ||||||
|  |         """Return the all the idx of all joints""" | ||||||
|  |         addr = jp.array([self._mj_model.jnt_qposadr[idx] for idx in self.all_joint_ids]) | ||||||
|  |         return addr | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joints_qpos(self, data: jax.Array) -> jax.Array: | ||||||
|  |         """Return the all the qpos of actual joints""" | ||||||
|  |         return data[self.get_actuator_joints_qpos_addr()] | ||||||
|  | 
 | ||||||
|  |     def set_actuator_joints_qpos(self, new_qpos: jax.Array, qpos: jax.Array) -> jax.Array: | ||||||
|  |         """Set the qpos only for the actual joints (omit the backlash joint)""" | ||||||
|  |         return qpos.at[self.get_actuator_joints_qpos_addr()].set(new_qpos) | ||||||
|  | 
 | ||||||
|  |     def get_actuator_backlash_qpos(self, data: jax.Array) -> jax.Array: | ||||||
|  |         """Return the all the qpos of backlash joints""" | ||||||
|  |         if self.backlash_joint_qpos_addr == []: | ||||||
|  |             return jp.array([]) | ||||||
|  |         return data[jp.array(self.backlash_joint_qpos_addr)] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joints_qvel(self, data: jax.Array) -> jax.Array: | ||||||
|  |         """Return the all the qvel of actual joints""" | ||||||
|  |         return data[self.actuator_qvel_addr] | ||||||
|  | 
 | ||||||
|  |     def set_actuator_joints_qvel(self, new_qvel: jax.Array, qvel: jax.Array) -> jax.Array: | ||||||
|  |         """Set the qvel only for the actual joints (omit the backlash joint)""" | ||||||
|  |         return qvel.at[self.actuator_qvel_addr].set(new_qvel) | ||||||
|  | 
 | ||||||
|  |     def get_all_joints_qpos(self, data: jax.Array) -> jax.Array: | ||||||
|  |         """Return the all the qpos of all joints""" | ||||||
|  |         return data[self.get_all_joints_addr()] | ||||||
|  | 
 | ||||||
|  |     def get_all_joints_qvel(self, data: jax.Array) -> jax.Array: | ||||||
|  |         """Return the all the qvel of all joints""" | ||||||
|  |         return data[self.all_qvel_addr] | ||||||
|  | 
 | ||||||
|  |     def get_joints_nobacklash_qpos(self, data: jax.Array) -> jax.Array: | ||||||
|  |         """Return the all the qpos of actual joints with the floating base""" | ||||||
|  |         return data[self.exclude_backlash_joints_addr()] | ||||||
|  | 
 | ||||||
|  |     def set_complete_qpos_from_joints(self, no_backlash_qpos: jax.Array, full_qpos: jax.Array) -> jax.Array: | ||||||
|  |         """In the case of backlash joints, we want to ignore them (remove them) but we still need to set the complete state incuding them""" | ||||||
|  |         full_qpos.at[self.exclude_backlash_joints_addr()].set(no_backlash_qpos) | ||||||
|  |         return jp.array(full_qpos) | ||||||
|  | 
 | ||||||
|  |     # Sensor readings. | ||||||
|  |     def get_gravity(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the gravity vector in the world frame.""" | ||||||
|  |         return mjx_env.get_sensor_data(self.mj_model, data, constants.GRAVITY_SENSOR) | ||||||
|  | 
 | ||||||
|  |     def get_global_linvel(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the linear velocity of the robot in the world frame.""" | ||||||
|  |         return mjx_env.get_sensor_data( | ||||||
|  |             self.mj_model, data, constants.GLOBAL_LINVEL_SENSOR | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def get_global_angvel(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the angular velocity of the robot in the world frame.""" | ||||||
|  |         return mjx_env.get_sensor_data( | ||||||
|  |             self.mj_model, data, constants.GLOBAL_ANGVEL_SENSOR | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def get_local_linvel(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the linear velocity of the robot in the local frame.""" | ||||||
|  |         return mjx_env.get_sensor_data( | ||||||
|  |             self.mj_model, data, constants.LOCAL_LINVEL_SENSOR | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def get_accelerometer(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the accelerometer readings in the local frame.""" | ||||||
|  |         return mjx_env.get_sensor_data( | ||||||
|  |             self.mj_model, data, constants.ACCELEROMETER_SENSOR | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def get_gyro(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the gyroscope readings in the local frame.""" | ||||||
|  |         return mjx_env.get_sensor_data(self.mj_model, data, constants.GYRO_SENSOR) | ||||||
|  | 
 | ||||||
|  |     def get_feet_pos(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         """Return the position of the feet in the world frame.""" | ||||||
|  |         return jp.vstack( | ||||||
|  |             [ | ||||||
|  |                 mjx_env.get_sensor_data(self.mj_model, data, sensor_name) | ||||||
|  |                 for sensor_name in constants.FEET_POS_SENSOR | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     # Accessors. | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def xml_path(self) -> str: | ||||||
|  |         return self._xml_path | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def action_size(self) -> int: | ||||||
|  |         return self._mjx_model.nu | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def mj_model(self) -> mujoco.MjModel: | ||||||
|  |         return self._mj_model | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def mjx_model(self) -> mjx.Model: | ||||||
|  |         return self._mjx_model | ||||||
| @ -0,0 +1,89 @@ | |||||||
|  | # Copyright 2025 DeepMind Technologies Limited | ||||||
|  | # Copyright 2025 Antoine Pirrone - Steve Nguyen | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | # ============================================================================== | ||||||
|  | """Constants for Open Duck Mini V2. (based on Berkeley Humanoid)""" | ||||||
|  | 
 | ||||||
|  | from etils import epath | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ROOT_PATH = epath.Path(__file__).parent | ||||||
|  | FLAT_TERRAIN_XML = ROOT_PATH / "xmls" / "scene_flat_terrain.xml" | ||||||
|  | ROUGH_TERRAIN_XML = ROOT_PATH / "xmls" / "scene_rough_terrain.xml" | ||||||
|  | FLAT_TERRAIN_BACKLASH_XML = ROOT_PATH / "xmls" / "scene_flat_terrain_backlash.xml" | ||||||
|  | ROUGH_TERRAIN_BACKLASH_XML = ROOT_PATH / "xmls" / "scene_rough_terrain_backlash.xml" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def task_to_xml(task_name: str) -> epath.Path: | ||||||
|  |     return { | ||||||
|  |         "flat_terrain": FLAT_TERRAIN_XML, | ||||||
|  |         "rough_terrain": ROUGH_TERRAIN_XML, | ||||||
|  |         "flat_terrain_backlash": FLAT_TERRAIN_BACKLASH_XML, | ||||||
|  |         "rough_terrain_backlash": ROUGH_TERRAIN_BACKLASH_XML, | ||||||
|  |     }[task_name] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | FEET_SITES = [ | ||||||
|  |     "left_foot", | ||||||
|  |     "right_foot", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | LEFT_FEET_GEOMS = [ | ||||||
|  |     "left_foot_bottom_tpu", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | RIGHT_FEET_GEOMS = [ | ||||||
|  |     "right_foot_bottom_tpu", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | HIP_JOINT_NAMES = [ | ||||||
|  |     "left_hip_yaw", | ||||||
|  |     "left_hip_roll", | ||||||
|  |     "left_hip_pitch", | ||||||
|  |     "right_hip_yaw", | ||||||
|  |     "right_hip_roll", | ||||||
|  |     "right_hip_pitch", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | KNEE_JOINT_NAMES = [ | ||||||
|  |     "left_knee", | ||||||
|  |     "right_knee", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | # There should be a way to get that from the mjModel... | ||||||
|  | JOINTS_ORDER_NO_HEAD = [ | ||||||
|  |     "left_hip_yaw", | ||||||
|  |     "left_hip_roll", | ||||||
|  |     "left_hip_pitch", | ||||||
|  |     "left_knee", | ||||||
|  |     "left_ankle", | ||||||
|  |     "right_hip_yaw", | ||||||
|  |     "right_hip_roll", | ||||||
|  |     "right_hip_pitch", | ||||||
|  |     "right_knee", | ||||||
|  |     "right_ankle", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | FEET_GEOMS = LEFT_FEET_GEOMS + RIGHT_FEET_GEOMS | ||||||
|  | 
 | ||||||
|  | FEET_POS_SENSOR = [f"{site}_pos" for site in FEET_SITES] | ||||||
|  | 
 | ||||||
|  | ROOT_BODY = "trunk_assembly" | ||||||
|  | 
 | ||||||
|  | GRAVITY_SENSOR = "upvector" | ||||||
|  | GLOBAL_LINVEL_SENSOR = "global_linvel" | ||||||
|  | GLOBAL_ANGVEL_SENSOR = "global_angvel" | ||||||
|  | LOCAL_LINVEL_SENSOR = "local_linvel" | ||||||
|  | ACCELEROMETER_SENSOR = "accelerometer" | ||||||
|  | GYRO_SENSOR = "gyro" | ||||||
| @ -0,0 +1,149 @@ | |||||||
|  | import jax | ||||||
|  | import jax.numpy as jp | ||||||
|  | 
 | ||||||
|  | def reward_imitation( | ||||||
|  |     base_qpos: jax.Array, | ||||||
|  |     base_qvel: jax.Array, | ||||||
|  |     joints_qpos: jax.Array, | ||||||
|  |     joints_qvel: jax.Array, | ||||||
|  |     contacts: jax.Array, | ||||||
|  |     reference_frame: jax.Array, | ||||||
|  |     cmd: jax.Array, | ||||||
|  |     use_imitation_reward: bool = False, | ||||||
|  | ) -> jax.Array: | ||||||
|  |     if not use_imitation_reward: | ||||||
|  |         return jp.nan_to_num(0.0) | ||||||
|  | 
 | ||||||
|  |     # TODO don't reward for moving when the command is zero. | ||||||
|  |     cmd_norm = jp.linalg.norm(cmd[:3]) | ||||||
|  | 
 | ||||||
|  |     w_torso_pos = 1.0 | ||||||
|  |     w_torso_orientation = 1.0 | ||||||
|  |     w_lin_vel_xy = 1.0 | ||||||
|  |     w_lin_vel_z = 1.0 | ||||||
|  |     w_ang_vel_xy = 0.5 | ||||||
|  |     w_ang_vel_z = 0.5 | ||||||
|  |     w_joint_pos = 15.0 | ||||||
|  |     w_joint_vel = 1.0e-3 | ||||||
|  |     w_contact = 1.0 | ||||||
|  | 
 | ||||||
|  |     #  TODO : double check if the slices are correct | ||||||
|  |     linear_vel_slice_start = 34 | ||||||
|  |     linear_vel_slice_end = 37 | ||||||
|  | 
 | ||||||
|  |     angular_vel_slice_start = 37 | ||||||
|  |     angular_vel_slice_end = 40 | ||||||
|  | 
 | ||||||
|  |     joint_pos_slice_start = 0 | ||||||
|  |     joint_pos_slice_end = 16 | ||||||
|  | 
 | ||||||
|  |     joint_vels_slice_start = 16 | ||||||
|  |     joint_vels_slice_end = 32 | ||||||
|  | 
 | ||||||
|  |     # root_pos_slice_start = 0 | ||||||
|  |     # root_pos_slice_end = 3 | ||||||
|  | 
 | ||||||
|  |     root_quat_slice_start = 3 | ||||||
|  |     root_quat_slice_end = 7 | ||||||
|  | 
 | ||||||
|  |     # left_toe_pos_slice_start = 23 | ||||||
|  |     # left_toe_pos_slice_end = 26 | ||||||
|  | 
 | ||||||
|  |     # right_toe_pos_slice_start = 26 | ||||||
|  |     # right_toe_pos_slice_end = 29 | ||||||
|  | 
 | ||||||
|  |     foot_contacts_slice_start = 32 | ||||||
|  |     foot_contacts_slice_end = 34 | ||||||
|  | 
 | ||||||
|  |     # ref_base_pos = reference_frame[root_pos_slice_start:root_pos_slice_end] | ||||||
|  |     # base_pos = qpos[:3] | ||||||
|  | 
 | ||||||
|  |     ref_base_orientation_quat = reference_frame[ | ||||||
|  |         root_quat_slice_start:root_quat_slice_end | ||||||
|  |     ] | ||||||
|  |     ref_base_orientation_quat = ref_base_orientation_quat / jp.linalg.norm( | ||||||
|  |         ref_base_orientation_quat | ||||||
|  |     )  # normalize the quat | ||||||
|  |     base_orientation = base_qpos[3:7] | ||||||
|  |     base_orientation = base_orientation / jp.linalg.norm( | ||||||
|  |         base_orientation | ||||||
|  |     )  # normalize the quat | ||||||
|  | 
 | ||||||
|  |     ref_base_lin_vel = reference_frame[linear_vel_slice_start:linear_vel_slice_end] | ||||||
|  |     base_lin_vel = base_qvel[:3] | ||||||
|  | 
 | ||||||
|  |     ref_base_ang_vel = reference_frame[angular_vel_slice_start:angular_vel_slice_end] | ||||||
|  |     base_ang_vel = base_qvel[3:6] | ||||||
|  | 
 | ||||||
|  |     ref_joint_pos = reference_frame[joint_pos_slice_start:joint_pos_slice_end] | ||||||
|  |     # remove neck head and antennas | ||||||
|  |     ref_joint_pos = jp.concatenate([ref_joint_pos[:5], ref_joint_pos[11:]]) | ||||||
|  |     # joint_pos = joints_qpos | ||||||
|  |     joint_pos = jp.concatenate([joints_qpos[:5], joints_qpos[9:]]) | ||||||
|  | 
 | ||||||
|  |     ref_joint_vels = reference_frame[joint_vels_slice_start:joint_vels_slice_end] | ||||||
|  |     # remove neck head and antennas | ||||||
|  |     ref_joint_vels = jp.concatenate([ref_joint_vels[:5], ref_joint_vels[11:]]) | ||||||
|  |     # joint_vel = joints_qvel | ||||||
|  |     joint_vel = jp.concatenate([joints_qvel[:5], joints_qvel[9:]]) | ||||||
|  | 
 | ||||||
|  |     # ref_left_toe_pos = reference_frame[left_toe_pos_slice_start:left_toe_pos_slice_end] | ||||||
|  |     # ref_right_toe_pos = reference_frame[right_toe_pos_slice_start:right_toe_pos_slice_end] | ||||||
|  | 
 | ||||||
|  |     ref_foot_contacts = reference_frame[ | ||||||
|  |         foot_contacts_slice_start:foot_contacts_slice_end | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     # reward | ||||||
|  |     # torso_pos_rew = jp.exp(-200.0 * jp.sum(jp.square(base_pos[:2] - ref_base_pos[:2]))) * w_torso_pos | ||||||
|  | 
 | ||||||
|  |     # real quaternion angle doesn't have the expected  effect, switching back for now | ||||||
|  |     # torso_orientation_rew = jp.exp(-20 * self.quaternion_angle(base_orientation, ref_base_orientation_quat)) * w_torso_orientation | ||||||
|  | 
 | ||||||
|  |     # TODO ignore yaw here, we just want xy orientation | ||||||
|  |     torso_orientation_rew = ( | ||||||
|  |         jp.exp(-20.0 * jp.sum(jp.square(base_orientation - ref_base_orientation_quat))) | ||||||
|  |         * w_torso_orientation | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     lin_vel_xy_rew = ( | ||||||
|  |         jp.exp(-8.0 * jp.sum(jp.square(base_lin_vel[:2] - ref_base_lin_vel[:2]))) | ||||||
|  |         * w_lin_vel_xy | ||||||
|  |     ) | ||||||
|  |     lin_vel_z_rew = ( | ||||||
|  |         jp.exp(-8.0 * jp.sum(jp.square(base_lin_vel[2] - ref_base_lin_vel[2]))) | ||||||
|  |         * w_lin_vel_z | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     ang_vel_xy_rew = ( | ||||||
|  |         jp.exp(-2.0 * jp.sum(jp.square(base_ang_vel[:2] - ref_base_ang_vel[:2]))) | ||||||
|  |         * w_ang_vel_xy | ||||||
|  |     ) | ||||||
|  |     ang_vel_z_rew = ( | ||||||
|  |         jp.exp(-2.0 * jp.sum(jp.square(base_ang_vel[2] - ref_base_ang_vel[2]))) | ||||||
|  |         * w_ang_vel_z | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     joint_pos_rew = -jp.sum(jp.square(joint_pos - ref_joint_pos)) * w_joint_pos | ||||||
|  |     joint_vel_rew = -jp.sum(jp.square(joint_vel - ref_joint_vels)) * w_joint_vel | ||||||
|  | 
 | ||||||
|  |     ref_foot_contacts = jp.where( | ||||||
|  |         ref_foot_contacts > 0.5, | ||||||
|  |         jp.ones_like(ref_foot_contacts), | ||||||
|  |         jp.zeros_like(ref_foot_contacts), | ||||||
|  |     ) | ||||||
|  |     contact_rew = jp.sum(contacts == ref_foot_contacts) * w_contact | ||||||
|  | 
 | ||||||
|  |     reward = ( | ||||||
|  |         lin_vel_xy_rew | ||||||
|  |         + lin_vel_z_rew | ||||||
|  |         + ang_vel_xy_rew | ||||||
|  |         + ang_vel_z_rew | ||||||
|  |         + joint_pos_rew | ||||||
|  |         + joint_vel_rew | ||||||
|  |         + contact_rew | ||||||
|  |         # + torso_orientation_rew | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     reward *= cmd_norm > 0.01  # No reward for zero commands. | ||||||
|  |     return jp.nan_to_num(reward) | ||||||
| @ -0,0 +1,151 @@ | |||||||
|  | # import jax | ||||||
|  | # import jax.numpy as np | ||||||
|  | import numpy as np | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def reward_imitation( | ||||||
|  |     base_qpos, | ||||||
|  |     base_qvel, | ||||||
|  |     joints_qpos, | ||||||
|  |     joints_qvel, | ||||||
|  |     contacts, | ||||||
|  |     reference_frame, | ||||||
|  |     cmd, | ||||||
|  |     use_imitation_reward=False, | ||||||
|  | ): | ||||||
|  |     if not use_imitation_reward: | ||||||
|  |         return np.nan_to_num(0.0) | ||||||
|  | 
 | ||||||
|  |     # TODO don't reward for moving when the command is zero. | ||||||
|  |     cmd_norm = np.linalg.norm(cmd[:3]) | ||||||
|  | 
 | ||||||
|  |     w_torso_pos = 1.0 | ||||||
|  |     w_torso_orientation = 1.0 | ||||||
|  |     w_lin_vel_xy = 1.0 | ||||||
|  |     w_lin_vel_z = 1.0 | ||||||
|  |     w_ang_vel_xy = 0.5 | ||||||
|  |     w_ang_vel_z = 0.5 | ||||||
|  |     w_joint_pos = 15.0 | ||||||
|  |     w_joint_vel = 1.0e-3 | ||||||
|  |     w_contact = 1.0 | ||||||
|  | 
 | ||||||
|  |     #  TODO : double check if the slices are correct | ||||||
|  |     linear_vel_slice_start = 34 | ||||||
|  |     linear_vel_slice_end = 37 | ||||||
|  | 
 | ||||||
|  |     angular_vel_slice_start = 37 | ||||||
|  |     angular_vel_slice_end = 40 | ||||||
|  | 
 | ||||||
|  |     joint_pos_slice_start = 0 | ||||||
|  |     joint_pos_slice_end = 16 | ||||||
|  | 
 | ||||||
|  |     joint_vels_slice_start = 16 | ||||||
|  |     joint_vels_slice_end = 32 | ||||||
|  | 
 | ||||||
|  |     # root_pos_slice_start = 0 | ||||||
|  |     # root_pos_slice_end = 3 | ||||||
|  | 
 | ||||||
|  |     root_quat_slice_start = 3 | ||||||
|  |     root_quat_slice_end = 7 | ||||||
|  | 
 | ||||||
|  |     # left_toe_pos_slice_start = 23 | ||||||
|  |     # left_toe_pos_slice_end = 26 | ||||||
|  | 
 | ||||||
|  |     # right_toe_pos_slice_start = 26 | ||||||
|  |     # right_toe_pos_slice_end = 29 | ||||||
|  | 
 | ||||||
|  |     foot_contacts_slice_start = 32 | ||||||
|  |     foot_contacts_slice_end = 34 | ||||||
|  | 
 | ||||||
|  |     # ref_base_pos = reference_frame[root_pos_slice_start:root_pos_slice_end] | ||||||
|  |     # base_pos = qpos[:3] | ||||||
|  | 
 | ||||||
|  |     ref_base_orientation_quat = reference_frame[ | ||||||
|  |         root_quat_slice_start:root_quat_slice_end | ||||||
|  |     ] | ||||||
|  |     ref_base_orientation_quat = ref_base_orientation_quat / np.linalg.norm( | ||||||
|  |         ref_base_orientation_quat | ||||||
|  |     )  # normalize the quat | ||||||
|  |     base_orientation = base_qpos[3:7] | ||||||
|  |     base_orientation = base_orientation / np.linalg.norm( | ||||||
|  |         base_orientation | ||||||
|  |     )  # normalize the quat | ||||||
|  | 
 | ||||||
|  |     ref_base_lin_vel = reference_frame[linear_vel_slice_start:linear_vel_slice_end] | ||||||
|  |     base_lin_vel = base_qvel[:3] | ||||||
|  | 
 | ||||||
|  |     ref_base_ang_vel = reference_frame[angular_vel_slice_start:angular_vel_slice_end] | ||||||
|  |     base_ang_vel = base_qvel[3:6] | ||||||
|  | 
 | ||||||
|  |     ref_joint_pos = reference_frame[joint_pos_slice_start:joint_pos_slice_end] | ||||||
|  |     # remove neck head and antennas | ||||||
|  |     ref_joint_pos = np.concatenate([ref_joint_pos[:5], ref_joint_pos[11:]]) | ||||||
|  |     # joint_pos = joints_qpos | ||||||
|  |     joint_pos = np.concatenate([joints_qpos[:5], joints_qpos[9:]]) | ||||||
|  | 
 | ||||||
|  |     ref_joint_vels = reference_frame[joint_vels_slice_start:joint_vels_slice_end] | ||||||
|  |     # remove neck head and antennas | ||||||
|  |     ref_joint_vels = np.concatenate([ref_joint_vels[:5], ref_joint_vels[11:]]) | ||||||
|  |     # joint_vel = joints_qvel | ||||||
|  |     joint_vel = np.concatenate([joints_qvel[:5], joints_qvel[9:]]) | ||||||
|  | 
 | ||||||
|  |     # ref_left_toe_pos = reference_frame[left_toe_pos_slice_start:left_toe_pos_slice_end] | ||||||
|  |     # ref_right_toe_pos = reference_frame[right_toe_pos_slice_start:right_toe_pos_slice_end] | ||||||
|  | 
 | ||||||
|  |     ref_foot_contacts = reference_frame[ | ||||||
|  |         foot_contacts_slice_start:foot_contacts_slice_end | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     # reward | ||||||
|  |     # torso_pos_rew = np.exp(-200.0 * np.sum(np.square(base_pos[:2] - ref_base_pos[:2]))) * w_torso_pos | ||||||
|  | 
 | ||||||
|  |     # real quaternion angle doesn't have the expected  effect, switching back for now | ||||||
|  |     # torso_orientation_rew = np.exp(-20 * self.quaternion_angle(base_orientation, ref_base_orientation_quat)) * w_torso_orientation | ||||||
|  | 
 | ||||||
|  |     # TODO ignore yaw here, we just want xy orientation | ||||||
|  |     torso_orientation_rew = ( | ||||||
|  |         np.exp(-20.0 * np.sum(np.square(base_orientation - ref_base_orientation_quat))) | ||||||
|  |         * w_torso_orientation | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     lin_vel_xy_rew = ( | ||||||
|  |         np.exp(-8.0 * np.sum(np.square(base_lin_vel[:2] - ref_base_lin_vel[:2]))) | ||||||
|  |         * w_lin_vel_xy | ||||||
|  |     ) | ||||||
|  |     lin_vel_z_rew = ( | ||||||
|  |         np.exp(-8.0 * np.sum(np.square(base_lin_vel[2] - ref_base_lin_vel[2]))) | ||||||
|  |         * w_lin_vel_z | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     ang_vel_xy_rew = ( | ||||||
|  |         np.exp(-2.0 * np.sum(np.square(base_ang_vel[:2] - ref_base_ang_vel[:2]))) | ||||||
|  |         * w_ang_vel_xy | ||||||
|  |     ) | ||||||
|  |     ang_vel_z_rew = ( | ||||||
|  |         np.exp(-2.0 * np.sum(np.square(base_ang_vel[2] - ref_base_ang_vel[2]))) | ||||||
|  |         * w_ang_vel_z | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     joint_pos_rew = -np.sum(np.square(joint_pos - ref_joint_pos)) * w_joint_pos | ||||||
|  |     joint_vel_rew = -np.sum(np.square(joint_vel - ref_joint_vels)) * w_joint_vel | ||||||
|  | 
 | ||||||
|  |     ref_foot_contacts = np.where( | ||||||
|  |         np.array(ref_foot_contacts) > 0.5, | ||||||
|  |         np.ones_like(ref_foot_contacts), | ||||||
|  |         np.zeros_like(ref_foot_contacts), | ||||||
|  |     ) | ||||||
|  |     contact_rew = np.sum(contacts == ref_foot_contacts) * w_contact | ||||||
|  | 
 | ||||||
|  |     reward = ( | ||||||
|  |         lin_vel_xy_rew | ||||||
|  |         + lin_vel_z_rew | ||||||
|  |         + ang_vel_xy_rew | ||||||
|  |         + ang_vel_z_rew | ||||||
|  |         + joint_pos_rew | ||||||
|  |         + joint_vel_rew | ||||||
|  |         + contact_rew | ||||||
|  |         # + torso_orientation_rew | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     reward *= cmd_norm > 0.01  # No reward for zero commands. | ||||||
|  |     return np.nan_to_num(reward) | ||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										725
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/joystick.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										725
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/joystick.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,725 @@ | |||||||
|  | # Copyright 2025 DeepMind Technologies Limited | ||||||
|  | # Copyright 2025 Antoine Pirrone - Steve Nguyen | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | # ============================================================================== | ||||||
|  | """Joystick task for Open Duck Mini V2. (based on Berkeley Humanoid)""" | ||||||
|  | 
 | ||||||
|  | from typing import Any, Dict, Optional, Union | ||||||
|  | import jax | ||||||
|  | import jax.numpy as jp | ||||||
|  | from ml_collections import config_dict | ||||||
|  | from mujoco import mjx | ||||||
|  | from mujoco.mjx._src import math | ||||||
|  | import numpy as np | ||||||
|  | 
 | ||||||
|  | from mujoco_playground._src import mjx_env | ||||||
|  | from mujoco_playground._src.collision import geoms_colliding | ||||||
|  | 
 | ||||||
|  | from . import constants | ||||||
|  | from . import base as open_duck_mini_v2_base | ||||||
|  | 
 | ||||||
|  | # from playground.common.utils import LowPassActionFilter | ||||||
|  | from playground.common.poly_reference_motion import PolyReferenceMotion | ||||||
|  | from playground.common.rewards import ( | ||||||
|  |     reward_tracking_lin_vel, | ||||||
|  |     reward_tracking_ang_vel, | ||||||
|  |     cost_torques, | ||||||
|  |     cost_action_rate, | ||||||
|  |     cost_stand_still, | ||||||
|  |     reward_alive, | ||||||
|  | ) | ||||||
|  | from playground.open_duck_mini_v2.custom_rewards import reward_imitation | ||||||
|  | 
 | ||||||
|  | # if set to false, won't require the reference data to be present and won't compute the reference motions polynoms for nothing | ||||||
|  | USE_IMITATION_REWARD = True | ||||||
|  | USE_MOTOR_SPEED_LIMITS = True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def default_config() -> config_dict.ConfigDict: | ||||||
|  |     return config_dict.create( | ||||||
|  |         ctrl_dt=0.02, | ||||||
|  |         sim_dt=0.002, | ||||||
|  |         episode_length=1000, | ||||||
|  |         action_repeat=1, | ||||||
|  |         action_scale=0.25, | ||||||
|  |         dof_vel_scale=0.05, | ||||||
|  |         history_len=0, | ||||||
|  |         soft_joint_pos_limit_factor=0.95, | ||||||
|  |         max_motor_velocity=5.24,  # rad/s | ||||||
|  |         noise_config=config_dict.create( | ||||||
|  |             level=1.0,  # Set to 0.0 to disable noise. | ||||||
|  |             action_min_delay=0,  # env steps | ||||||
|  |             action_max_delay=3,  # env steps | ||||||
|  |             imu_min_delay=0,  # env steps | ||||||
|  |             imu_max_delay=3,  # env steps | ||||||
|  |             scales=config_dict.create( | ||||||
|  |                 hip_pos=0.03,  # rad, for each hip joint | ||||||
|  |                 knee_pos=0.05,  # rad, for each knee joint | ||||||
|  |                 ankle_pos=0.08,  # rad, for each ankle joint | ||||||
|  |                 joint_vel=2.5,  # rad/s # Was 1.5 | ||||||
|  |                 gravity=0.1, | ||||||
|  |                 linvel=0.1, | ||||||
|  |                 gyro=0.1, | ||||||
|  |                 accelerometer=0.05, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         reward_config=config_dict.create( | ||||||
|  |             scales=config_dict.create( | ||||||
|  |                 tracking_lin_vel=2.5, | ||||||
|  |                 tracking_ang_vel=6.0, | ||||||
|  |                 torques=-1.0e-3, | ||||||
|  |                 action_rate=-0.5,  # was -1.5 | ||||||
|  |                 stand_still=-0.2,  # was -1.0 TODO try to relax this a bit ? | ||||||
|  |                 alive=20.0, | ||||||
|  |                 imitation=1.0, | ||||||
|  |             ), | ||||||
|  |             tracking_sigma=0.01,  # was working at 0.01 | ||||||
|  |         ), | ||||||
|  |         push_config=config_dict.create( | ||||||
|  |             enable=True, | ||||||
|  |             interval_range=[5.0, 10.0], | ||||||
|  |             magnitude_range=[0.1, 1.0], | ||||||
|  |         ), | ||||||
|  |         lin_vel_x=[-0.15, 0.15], | ||||||
|  |         lin_vel_y=[-0.2, 0.2], | ||||||
|  |         ang_vel_yaw=[-1.0, 1.0],  # [-1.0, 1.0] | ||||||
|  |         neck_pitch_range=[-0.34, 1.1], | ||||||
|  |         head_pitch_range=[-0.78, 0.78], | ||||||
|  |         head_yaw_range=[-1.5, 1.5], | ||||||
|  |         head_roll_range=[-0.5, 0.5], | ||||||
|  |         head_range_factor=1.0,  # to make it easier | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Joystick(open_duck_mini_v2_base.OpenDuckMiniV2Env): | ||||||
|  |     """Track a joystick command.""" | ||||||
|  | 
 | ||||||
|  |     def __init__( | ||||||
|  |         self, | ||||||
|  |         task: str = "flat_terrain", | ||||||
|  |         config: config_dict.ConfigDict = default_config(), | ||||||
|  |         config_overrides: Optional[Dict[str, Union[str, int, list[Any]]]] = None, | ||||||
|  |     ): | ||||||
|  |         super().__init__( | ||||||
|  |             xml_path=constants.task_to_xml(task).as_posix(), | ||||||
|  |             config=config, | ||||||
|  |             config_overrides=config_overrides, | ||||||
|  |         ) | ||||||
|  |         self._post_init() | ||||||
|  | 
 | ||||||
|  |     def _post_init(self) -> None: | ||||||
|  | 
 | ||||||
|  |         self._init_q = jp.array(self._mj_model.keyframe("home").qpos) | ||||||
|  |         self._default_actuator = self._mj_model.keyframe( | ||||||
|  |             "home" | ||||||
|  |         ).ctrl  # ctrl of all the actual joints (no floating base and no backlash) | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             self.PRM = PolyReferenceMotion( | ||||||
|  |                 "playground/open_duck_mini_v2/data/polynomial_coefficients.pkl" | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         # Note: First joint is freejoint. | ||||||
|  |         # get the range of the joints | ||||||
|  |         self._lowers, self._uppers = self.mj_model.jnt_range[1:].T | ||||||
|  |         c = (self._lowers + self._uppers) / 2 | ||||||
|  |         r = self._uppers - self._lowers | ||||||
|  |         self._soft_lowers = c - 0.5 * r * self._config.soft_joint_pos_limit_factor | ||||||
|  |         self._soft_uppers = c + 0.5 * r * self._config.soft_joint_pos_limit_factor | ||||||
|  | 
 | ||||||
|  |         # weights for computing the cost of each joints compared to a reference pose | ||||||
|  |         self._weights = jp.array( | ||||||
|  |             [ | ||||||
|  |                 1.0, | ||||||
|  |                 1.0, | ||||||
|  |                 0.01, | ||||||
|  |                 0.01, | ||||||
|  |                 1.0,  # left leg. | ||||||
|  |                 # 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, #head | ||||||
|  |                 1.0, | ||||||
|  |                 1.0, | ||||||
|  |                 0.01, | ||||||
|  |                 0.01, | ||||||
|  |                 1.0,  # right leg. | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self._njoints = self._mj_model.njnt  # number of joints | ||||||
|  |         self._actuators = self._mj_model.nu  # number of actuators | ||||||
|  | 
 | ||||||
|  |         self._torso_body_id = self._mj_model.body(constants.ROOT_BODY).id | ||||||
|  |         self._torso_mass = self._mj_model.body_subtreemass[self._torso_body_id] | ||||||
|  |         self._site_id = self._mj_model.site("imu").id | ||||||
|  | 
 | ||||||
|  |         self._feet_site_id = np.array( | ||||||
|  |             [self._mj_model.site(name).id for name in constants.FEET_SITES] | ||||||
|  |         ) | ||||||
|  |         self._floor_geom_id = self._mj_model.geom("floor").id | ||||||
|  |         self._feet_geom_id = np.array( | ||||||
|  |             [self._mj_model.geom(name).id for name in constants.FEET_GEOMS] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         foot_linvel_sensor_adr = [] | ||||||
|  |         for site in constants.FEET_SITES: | ||||||
|  |             sensor_id = self._mj_model.sensor(f"{site}_global_linvel").id | ||||||
|  |             sensor_adr = self._mj_model.sensor_adr[sensor_id] | ||||||
|  |             sensor_dim = self._mj_model.sensor_dim[sensor_id] | ||||||
|  |             foot_linvel_sensor_adr.append( | ||||||
|  |                 list(range(sensor_adr, sensor_adr + sensor_dim)) | ||||||
|  |             ) | ||||||
|  |         self._foot_linvel_sensor_adr = jp.array(foot_linvel_sensor_adr) | ||||||
|  | 
 | ||||||
|  |         # # noise in the simu? | ||||||
|  |         qpos_noise_scale = np.zeros(self._actuators) | ||||||
|  | 
 | ||||||
|  |         hip_ids = [ | ||||||
|  |             idx for idx, j in enumerate(constants.JOINTS_ORDER_NO_HEAD) if "_hip" in j | ||||||
|  |         ] | ||||||
|  |         knee_ids = [ | ||||||
|  |             idx for idx, j in enumerate(constants.JOINTS_ORDER_NO_HEAD) if "_knee" in j | ||||||
|  |         ] | ||||||
|  |         ankle_ids = [ | ||||||
|  |             idx for idx, j in enumerate(constants.JOINTS_ORDER_NO_HEAD) if "_ankle" in j | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         qpos_noise_scale[hip_ids] = self._config.noise_config.scales.hip_pos | ||||||
|  |         qpos_noise_scale[knee_ids] = self._config.noise_config.scales.knee_pos | ||||||
|  |         qpos_noise_scale[ankle_ids] = self._config.noise_config.scales.ankle_pos | ||||||
|  |         # qpos_noise_scale[faa_ids] = self._config.noise_config.scales.faa_pos | ||||||
|  |         self._qpos_noise_scale = jp.array(qpos_noise_scale) | ||||||
|  | 
 | ||||||
|  |         # self.action_filter = LowPassActionFilter( | ||||||
|  |         #     1 / self._config.ctrl_dt, cutoff_frequency=37.5 | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|  |     def reset(self, rng: jax.Array) -> mjx_env.State: | ||||||
|  |         qpos = self._init_q  # the complete qpos | ||||||
|  |         # print(f'DEBUG0 init qpos: {qpos}') | ||||||
|  |         qvel = jp.zeros(self.mjx_model.nv) | ||||||
|  | 
 | ||||||
|  |         # init position/orientation in environment | ||||||
|  |         # x=+U(-0.05, 0.05), y=+U(-0.05, 0.05), yaw=U(-3.14, 3.14). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         dxy = jax.random.uniform(key, (2,), minval=-0.05, maxval=0.05) | ||||||
|  | 
 | ||||||
|  |         # floating base | ||||||
|  |         base_qpos = self.get_floating_base_qpos(qpos) | ||||||
|  |         base_qpos = base_qpos.at[0:2].set( | ||||||
|  |             qpos[self._floating_base_qpos_addr : self._floating_base_qpos_addr + 2] | ||||||
|  |             + dxy | ||||||
|  |         )  # x y noise | ||||||
|  | 
 | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         yaw = jax.random.uniform(key, (1,), minval=-3.14, maxval=3.14) | ||||||
|  |         quat = math.axis_angle_to_quat(jp.array([0, 0, 1]), yaw) | ||||||
|  |         new_quat = math.quat_mul( | ||||||
|  |             qpos[self._floating_base_qpos_addr + 3 : self._floating_base_qpos_addr + 7], | ||||||
|  |             quat, | ||||||
|  |         )  # yaw noise | ||||||
|  | 
 | ||||||
|  |         base_qpos = base_qpos.at[3:7].set(new_quat) | ||||||
|  | 
 | ||||||
|  |         qpos = self.set_floating_base_qpos(base_qpos, qpos) | ||||||
|  |         # print(f'DEBUG1 base qpos: {qpos}') | ||||||
|  |         # init joint position | ||||||
|  |         # qpos[7:]=*U(0.0, 0.1) | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  | 
 | ||||||
|  |         # multiply actual joints with noise (excluding floating base and backlash) | ||||||
|  |         qpos_j = self.get_actuator_joints_qpos(qpos) * jax.random.uniform( | ||||||
|  |             key, (self._actuators,), minval=0.5, maxval=1.5 | ||||||
|  |         ) | ||||||
|  |         qpos = self.set_actuator_joints_qpos(qpos_j, qpos) | ||||||
|  |         # print(f'DEBUG2 joint qpos: {qpos}') | ||||||
|  |         # init joint vel | ||||||
|  |         # d(xyzrpy)=U(-0.05, 0.05) | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         # qvel = qvel.at[self._floating_base_qvel_addr : self._floating_base_qvel_addr + 6].set( | ||||||
|  |         #     jax.random.uniform(key, (6,), minval=-0.5, maxval=0.5) | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|  |         qvel = self.set_floating_base_qvel( | ||||||
|  |             jax.random.uniform(key, (6,), minval=-0.05, maxval=0.05), qvel | ||||||
|  |         ) | ||||||
|  |         # print(f'DEBUG3 base qvel: {qvel}') | ||||||
|  |         ctrl = self.get_actuator_joints_qpos(qpos) | ||||||
|  |         # print(f'DEBUG4 ctrl: {ctrl}') | ||||||
|  |         data = mjx_env.init(self.mjx_model, qpos=qpos, qvel=qvel, ctrl=ctrl) | ||||||
|  |         rng, cmd_rng = jax.random.split(rng) | ||||||
|  |         cmd = self.sample_command(cmd_rng) | ||||||
|  | 
 | ||||||
|  |         # Sample push interval. | ||||||
|  |         rng, push_rng = jax.random.split(rng) | ||||||
|  |         push_interval = jax.random.uniform( | ||||||
|  |             push_rng, | ||||||
|  |             minval=self._config.push_config.interval_range[0], | ||||||
|  |             maxval=self._config.push_config.interval_range[1], | ||||||
|  |         ) | ||||||
|  |         push_interval_steps = jp.round(push_interval / self.dt).astype(jp.int32) | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             current_reference_motion = self.PRM.get_reference_motion( | ||||||
|  |                 cmd[0], cmd[1], cmd[2], 0 | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             current_reference_motion = jp.zeros(0) | ||||||
|  | 
 | ||||||
|  |         info = { | ||||||
|  |             "rng": rng, | ||||||
|  |             "step": 0, | ||||||
|  |             "command": cmd, | ||||||
|  |             "last_act": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "last_last_act": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "last_last_last_act": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "motor_targets": self._default_actuator, | ||||||
|  |             "feet_air_time": jp.zeros(2), | ||||||
|  |             "last_contact": jp.zeros(2, dtype=bool), | ||||||
|  |             "swing_peak": jp.zeros(2), | ||||||
|  |             # Push related. | ||||||
|  |             "push": jp.array([0.0, 0.0]), | ||||||
|  |             "push_step": 0, | ||||||
|  |             "push_interval_steps": push_interval_steps, | ||||||
|  |             # History related. | ||||||
|  |             "action_history": jp.zeros( | ||||||
|  |                 self._config.noise_config.action_max_delay * self._actuators | ||||||
|  |             ), | ||||||
|  |             "imu_history": jp.zeros(self._config.noise_config.imu_max_delay * 3), | ||||||
|  |             # imitation related | ||||||
|  |             "imitation_i": 0, | ||||||
|  |             "current_reference_motion": current_reference_motion, | ||||||
|  |             "imitation_phase": jp.zeros(2), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         metrics = {} | ||||||
|  |         for k, v in self._config.reward_config.scales.items(): | ||||||
|  |             if v != 0: | ||||||
|  |                 if v > 0: | ||||||
|  |                     metrics[f"reward/{k}"] = jp.zeros(()) | ||||||
|  |                 else: | ||||||
|  |                     metrics[f"cost/{k}"] = jp.zeros(()) | ||||||
|  |         metrics["swing_peak"] = jp.zeros(()) | ||||||
|  | 
 | ||||||
|  |         contact = jp.array( | ||||||
|  |             [ | ||||||
|  |                 geoms_colliding(data, geom_id, self._floor_geom_id) | ||||||
|  |                 for geom_id in self._feet_geom_id | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         obs = self._get_obs(data, info, contact) | ||||||
|  |         reward, done = jp.zeros(2) | ||||||
|  |         return mjx_env.State(data, obs, reward, done, metrics, info) | ||||||
|  | 
 | ||||||
|  |     def step(self, state: mjx_env.State, action: jax.Array) -> mjx_env.State: | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             state.info["imitation_i"] += 1 | ||||||
|  |             state.info["imitation_i"] = ( | ||||||
|  |                 state.info["imitation_i"] % self.PRM.nb_steps_in_period | ||||||
|  |             )  # not critical, is already moduloed in get_reference_motion | ||||||
|  |             state.info["imitation_phase"] = jp.array( | ||||||
|  |                 [ | ||||||
|  |                     jp.cos( | ||||||
|  |                         (state.info["imitation_i"] / self.PRM.nb_steps_in_period) | ||||||
|  |                         * 2 | ||||||
|  |                         * jp.pi | ||||||
|  |                     ), | ||||||
|  |                     jp.sin( | ||||||
|  |                         (state.info["imitation_i"] / self.PRM.nb_steps_in_period) | ||||||
|  |                         * 2 | ||||||
|  |                         * jp.pi | ||||||
|  |                     ), | ||||||
|  |                 ] | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             state.info["imitation_i"] = 0 | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             state.info["current_reference_motion"] = self.PRM.get_reference_motion( | ||||||
|  |                 state.info["command"][0], | ||||||
|  |                 state.info["command"][1], | ||||||
|  |                 state.info["command"][2], | ||||||
|  |                 state.info["imitation_i"], | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             state.info["current_reference_motion"] = jp.zeros(0) | ||||||
|  | 
 | ||||||
|  |         state.info["rng"], push1_rng, push2_rng, action_delay_rng = jax.random.split( | ||||||
|  |             state.info["rng"], 4 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Handle action delay | ||||||
|  |         action_history = ( | ||||||
|  |             jp.roll(state.info["action_history"], self._actuators) | ||||||
|  |             .at[: self._actuators] | ||||||
|  |             .set(action) | ||||||
|  |         ) | ||||||
|  |         state.info["action_history"] = action_history | ||||||
|  |         action_idx = jax.random.randint( | ||||||
|  |             action_delay_rng, | ||||||
|  |             (1,), | ||||||
|  |             minval=self._config.noise_config.action_min_delay, | ||||||
|  |             maxval=self._config.noise_config.action_max_delay, | ||||||
|  |         ) | ||||||
|  |         action_w_delay = action_history.reshape((-1, self._actuators))[ | ||||||
|  |             action_idx[0] | ||||||
|  |         ]  # action with delay | ||||||
|  | 
 | ||||||
|  |         # self.action_filter.push(action_w_delay) | ||||||
|  |         # action_w_delay = self.action_filter.get_filtered_action() | ||||||
|  | 
 | ||||||
|  |         push_theta = jax.random.uniform(push1_rng, maxval=2 * jp.pi) | ||||||
|  |         push_magnitude = jax.random.uniform( | ||||||
|  |             push2_rng, | ||||||
|  |             minval=self._config.push_config.magnitude_range[0], | ||||||
|  |             maxval=self._config.push_config.magnitude_range[1], | ||||||
|  |         ) | ||||||
|  |         push = jp.array([jp.cos(push_theta), jp.sin(push_theta)]) | ||||||
|  |         push *= ( | ||||||
|  |             jp.mod(state.info["push_step"] + 1, state.info["push_interval_steps"]) == 0 | ||||||
|  |         ) | ||||||
|  |         push *= self._config.push_config.enable | ||||||
|  |         qvel = state.data.qvel | ||||||
|  |         qvel = qvel.at[ | ||||||
|  |             self._floating_base_qvel_addr : self._floating_base_qvel_addr + 2 | ||||||
|  |         ].set( | ||||||
|  |             push * push_magnitude | ||||||
|  |             + qvel[self._floating_base_qvel_addr : self._floating_base_qvel_addr + 2] | ||||||
|  |         )  # floating base x,y | ||||||
|  |         data = state.data.replace(qvel=qvel) | ||||||
|  |         state = state.replace(data=data) | ||||||
|  | 
 | ||||||
|  |         #### | ||||||
|  | 
 | ||||||
|  |         motor_targets = ( | ||||||
|  |             self._default_actuator + action_w_delay * self._config.action_scale | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         if USE_MOTOR_SPEED_LIMITS: | ||||||
|  |             prev_motor_targets = state.info["motor_targets"] | ||||||
|  | 
 | ||||||
|  |             motor_targets = jp.clip( | ||||||
|  |                 motor_targets, | ||||||
|  |                 prev_motor_targets | ||||||
|  |                 - self._config.max_motor_velocity * self.dt,  # control dt | ||||||
|  |                 prev_motor_targets | ||||||
|  |                 + self._config.max_motor_velocity * self.dt,  # control dt | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         # motor_targets.at[5:9].set(state.info["command"][3:])  # head joints | ||||||
|  |         data = mjx_env.step(self.mjx_model, state.data, motor_targets, self.n_substeps) | ||||||
|  | 
 | ||||||
|  |         state.info["motor_targets"] = motor_targets | ||||||
|  | 
 | ||||||
|  |         contact = jp.array( | ||||||
|  |             [ | ||||||
|  |                 geoms_colliding(data, geom_id, self._floor_geom_id) | ||||||
|  |                 for geom_id in self._feet_geom_id | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         contact_filt = contact | state.info["last_contact"] | ||||||
|  |         first_contact = (state.info["feet_air_time"] > 0.0) * contact_filt | ||||||
|  |         state.info["feet_air_time"] += self.dt | ||||||
|  |         p_f = data.site_xpos[self._feet_site_id] | ||||||
|  |         p_fz = p_f[..., -1] | ||||||
|  |         state.info["swing_peak"] = jp.maximum(state.info["swing_peak"], p_fz) | ||||||
|  | 
 | ||||||
|  |         obs = self._get_obs(data, state.info, contact) | ||||||
|  |         done = self._get_termination(data) | ||||||
|  | 
 | ||||||
|  |         rewards = self._get_reward( | ||||||
|  |             data, action, state.info, state.metrics, done, first_contact, contact | ||||||
|  |         ) | ||||||
|  |         # FIXME | ||||||
|  |         rewards = { | ||||||
|  |             k: v * self._config.reward_config.scales[k] for k, v in rewards.items() | ||||||
|  |         } | ||||||
|  |         reward = jp.clip(sum(rewards.values()) * self.dt, 0.0, 10000.0) | ||||||
|  |         # jax.debug.print('STEP REWARD: {}',reward) | ||||||
|  |         state.info["push"] = push | ||||||
|  |         state.info["step"] += 1 | ||||||
|  |         state.info["push_step"] += 1 | ||||||
|  |         state.info["last_last_last_act"] = state.info["last_last_act"] | ||||||
|  |         state.info["last_last_act"] = state.info["last_act"] | ||||||
|  |         state.info["last_act"] = action  # was | ||||||
|  |         # state.info["last_act"] = motor_targets  # became | ||||||
|  |         state.info["rng"], cmd_rng = jax.random.split(state.info["rng"]) | ||||||
|  |         state.info["command"] = jp.where( | ||||||
|  |             state.info["step"] > 500, | ||||||
|  |             self.sample_command(cmd_rng), | ||||||
|  |             state.info["command"], | ||||||
|  |         ) | ||||||
|  |         state.info["step"] = jp.where( | ||||||
|  |             done | (state.info["step"] > 500), | ||||||
|  |             0, | ||||||
|  |             state.info["step"], | ||||||
|  |         ) | ||||||
|  |         state.info["feet_air_time"] *= ~contact | ||||||
|  |         state.info["last_contact"] = contact | ||||||
|  |         state.info["swing_peak"] *= ~contact | ||||||
|  |         for k, v in rewards.items(): | ||||||
|  |             rew_scale = self._config.reward_config.scales[k] | ||||||
|  |             if rew_scale != 0: | ||||||
|  |                 if rew_scale > 0: | ||||||
|  |                     state.metrics[f"reward/{k}"] = v | ||||||
|  |                 else: | ||||||
|  |                     state.metrics[f"cost/{k}"] = -v | ||||||
|  |         state.metrics["swing_peak"] = jp.mean(state.info["swing_peak"]) | ||||||
|  | 
 | ||||||
|  |         done = done.astype(reward.dtype) | ||||||
|  |         state = state.replace(data=data, obs=obs, reward=reward, done=done) | ||||||
|  |         return state | ||||||
|  | 
 | ||||||
|  |     def _get_termination(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         fall_termination = self.get_gravity(data)[-1] < 0.0 | ||||||
|  |         return fall_termination | jp.isnan(data.qpos).any() | jp.isnan(data.qvel).any() | ||||||
|  | 
 | ||||||
|  |     def _get_obs( | ||||||
|  |         self, data: mjx.Data, info: dict[str, Any], contact: jax.Array | ||||||
|  |     ) -> mjx_env.Observation: | ||||||
|  | 
 | ||||||
|  |         gyro = self.get_gyro(data) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_gyro = ( | ||||||
|  |             gyro | ||||||
|  |             + (2 * jax.random.uniform(noise_rng, shape=gyro.shape) - 1) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.gyro | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         accelerometer = self.get_accelerometer(data) | ||||||
|  |         # accelerometer[0] += 1.3 # TODO testing | ||||||
|  |         accelerometer.at[0].set(accelerometer[0] + 1.3) | ||||||
|  | 
 | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_accelerometer = ( | ||||||
|  |             accelerometer | ||||||
|  |             + (2 * jax.random.uniform(noise_rng, shape=accelerometer.shape) - 1) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.accelerometer | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         gravity = data.site_xmat[self._site_id].T @ jp.array([0, 0, -1]) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_gravity = ( | ||||||
|  |             gravity | ||||||
|  |             + (2 * jax.random.uniform(noise_rng, shape=gravity.shape) - 1) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.gravity | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Handle IMU delay | ||||||
|  |         imu_history = jp.roll(info["imu_history"], 3).at[:3].set(noisy_gravity) | ||||||
|  |         info["imu_history"] = imu_history | ||||||
|  |         imu_idx = jax.random.randint( | ||||||
|  |             noise_rng, | ||||||
|  |             (1,), | ||||||
|  |             minval=self._config.noise_config.imu_min_delay, | ||||||
|  |             maxval=self._config.noise_config.imu_max_delay, | ||||||
|  |         ) | ||||||
|  |         noisy_gravity = imu_history.reshape((-1, 3))[imu_idx[0]] | ||||||
|  | 
 | ||||||
|  |         # joint_angles = data.qpos[7:] | ||||||
|  | 
 | ||||||
|  |         # Handling backlash | ||||||
|  |         joint_angles = self.get_actuator_joints_qpos(data.qpos) | ||||||
|  |         joint_backlash = self.get_actuator_backlash_qpos(data.qpos) | ||||||
|  | 
 | ||||||
|  |         for i in self.backlash_idx_to_add: | ||||||
|  |             joint_backlash = jp.insert(joint_backlash, i, 0) | ||||||
|  | 
 | ||||||
|  |         joint_angles = joint_angles + joint_backlash | ||||||
|  | 
 | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_joint_angles = ( | ||||||
|  |             joint_angles | ||||||
|  |             + (2.0 * jax.random.uniform(noise_rng, shape=joint_angles.shape) - 1.0) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._qpos_noise_scale | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # joint_vel = data.qvel[6:] | ||||||
|  |         joint_vel = self.get_actuator_joints_qvel(data.qvel) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_joint_vel = ( | ||||||
|  |             joint_vel | ||||||
|  |             + (2.0 * jax.random.uniform(noise_rng, shape=joint_vel.shape) - 1.0) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.joint_vel | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         linvel = self.get_local_linvel(data) | ||||||
|  |         # info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         # noisy_linvel = ( | ||||||
|  |         #     linvel | ||||||
|  |         #     + (2 * jax.random.uniform(noise_rng, shape=linvel.shape) - 1) | ||||||
|  |         #     * self._config.noise_config.level | ||||||
|  |         #     * self._config.noise_config.scales.linvel | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|  |         state = jp.hstack( | ||||||
|  |             [ | ||||||
|  |                 # noisy_linvel,  # 3 | ||||||
|  |                 # noisy_gyro,  # 3 | ||||||
|  |                 # noisy_gravity,  # 3 | ||||||
|  |                 noisy_gyro,  # 3 | ||||||
|  |                 noisy_accelerometer,  # 3 | ||||||
|  |                 info["command"],  # 3 | ||||||
|  |                 noisy_joint_angles - self._default_actuator,  # 10 | ||||||
|  |                 noisy_joint_vel * self._config.dof_vel_scale,  # 10 | ||||||
|  |                 info["last_act"],  # 10 | ||||||
|  |                 info["last_last_act"],  # 10 | ||||||
|  |                 info["last_last_last_act"],  # 10 | ||||||
|  |                 info["motor_targets"],  # 10 | ||||||
|  |                 contact,  # 2 | ||||||
|  |                 # info["current_reference_motion"], | ||||||
|  |                 # info["imitation_i"], | ||||||
|  |                 info["imitation_phase"], | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         accelerometer = self.get_accelerometer(data) | ||||||
|  |         global_angvel = self.get_global_angvel(data) | ||||||
|  |         feet_vel = data.sensordata[self._foot_linvel_sensor_adr].ravel() | ||||||
|  |         root_height = data.qpos[self._floating_base_qpos_addr + 2] | ||||||
|  | 
 | ||||||
|  |         privileged_state = jp.hstack( | ||||||
|  |             [ | ||||||
|  |                 state, | ||||||
|  |                 gyro,  # 3 | ||||||
|  |                 accelerometer,  # 3 | ||||||
|  |                 gravity,  # 3 | ||||||
|  |                 linvel,  # 3 | ||||||
|  |                 global_angvel,  # 3 | ||||||
|  |                 joint_angles - self._default_actuator, | ||||||
|  |                 joint_vel, | ||||||
|  |                 root_height,  # 1 | ||||||
|  |                 data.actuator_force,  # 10 | ||||||
|  |                 contact,  # 2 | ||||||
|  |                 feet_vel,  # 4*3 | ||||||
|  |                 info["feet_air_time"],  # 2 | ||||||
|  |                 info["current_reference_motion"], | ||||||
|  |                 info["imitation_i"], | ||||||
|  |                 info["imitation_phase"], | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             "state": state, | ||||||
|  |             "privileged_state": privileged_state, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     def _get_reward( | ||||||
|  |         self, | ||||||
|  |         data: mjx.Data, | ||||||
|  |         action: jax.Array, | ||||||
|  |         info: dict[str, Any], | ||||||
|  |         metrics: dict[str, Any], | ||||||
|  |         done: jax.Array, | ||||||
|  |         first_contact: jax.Array, | ||||||
|  |         contact: jax.Array, | ||||||
|  |     ) -> dict[str, jax.Array]: | ||||||
|  |         del metrics  # Unused. | ||||||
|  | 
 | ||||||
|  |         ret = { | ||||||
|  |             "tracking_lin_vel": reward_tracking_lin_vel( | ||||||
|  |                 info["command"], | ||||||
|  |                 self.get_local_linvel(data), | ||||||
|  |                 self._config.reward_config.tracking_sigma, | ||||||
|  |             ), | ||||||
|  |             "tracking_ang_vel": reward_tracking_ang_vel( | ||||||
|  |                 info["command"], | ||||||
|  |                 self.get_gyro(data), | ||||||
|  |                 self._config.reward_config.tracking_sigma, | ||||||
|  |             ), | ||||||
|  |             # "orientation": cost_orientation(self.get_gravity(data)), | ||||||
|  |             "torques": cost_torques(data.actuator_force), | ||||||
|  |             "action_rate": cost_action_rate(action, info["last_act"]), | ||||||
|  |             "alive": reward_alive(), | ||||||
|  |             "imitation": reward_imitation(  # FIXME, this reward is so adhoc... | ||||||
|  |                 self.get_floating_base_qpos(data.qpos),  # floating base qpos | ||||||
|  |                 self.get_floating_base_qvel(data.qvel),  # floating base qvel | ||||||
|  |                 self.get_actuator_joints_qpos(data.qpos), | ||||||
|  |                 self.get_actuator_joints_qvel(data.qvel), | ||||||
|  |                 contact, | ||||||
|  |                 info["current_reference_motion"], | ||||||
|  |                 info["command"], | ||||||
|  |                 USE_IMITATION_REWARD, | ||||||
|  |             ), | ||||||
|  |             "stand_still": cost_stand_still( | ||||||
|  |                 # info["command"], data.qpos[7:], data.qvel[6:], self._default_pose | ||||||
|  |                 info["command"], | ||||||
|  |                 self.get_actuator_joints_qpos(data.qpos), | ||||||
|  |                 self.get_actuator_joints_qvel(data.qvel), | ||||||
|  |                 self._default_actuator, | ||||||
|  |                 ignore_head=False, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return ret | ||||||
|  | 
 | ||||||
|  |     def sample_command(self, rng: jax.Array) -> jax.Array: | ||||||
|  |         rng1, rng2, rng3, rng4, rng5, rng6, rng7, rng8 = jax.random.split(rng, 8) | ||||||
|  | 
 | ||||||
|  |         lin_vel_x = jax.random.uniform( | ||||||
|  |             rng1, minval=self._config.lin_vel_x[0], maxval=self._config.lin_vel_x[1] | ||||||
|  |         ) | ||||||
|  |         lin_vel_y = jax.random.uniform( | ||||||
|  |             rng2, minval=self._config.lin_vel_y[0], maxval=self._config.lin_vel_y[1] | ||||||
|  |         ) | ||||||
|  |         ang_vel_yaw = jax.random.uniform( | ||||||
|  |             rng3, | ||||||
|  |             minval=self._config.ang_vel_yaw[0], | ||||||
|  |             maxval=self._config.ang_vel_yaw[1], | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         neck_pitch = jax.random.uniform( | ||||||
|  |             rng5, | ||||||
|  |             minval=self._config.neck_pitch_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.neck_pitch_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         head_pitch = jax.random.uniform( | ||||||
|  |             rng6, | ||||||
|  |             minval=self._config.head_pitch_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.head_pitch_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         head_yaw = jax.random.uniform( | ||||||
|  |             rng7, | ||||||
|  |             minval=self._config.head_yaw_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.head_yaw_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         head_roll = jax.random.uniform( | ||||||
|  |             rng8, | ||||||
|  |             minval=self._config.head_roll_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.head_roll_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # With 10% chance, set everything to zero. | ||||||
|  |         return jp.where( | ||||||
|  |             jax.random.bernoulli(rng4, p=0.1), | ||||||
|  |             jp.zeros(7), | ||||||
|  |             jp.hstack( | ||||||
|  |                 [ | ||||||
|  |                     lin_vel_x, | ||||||
|  |                     lin_vel_y, | ||||||
|  |                     ang_vel_yaw, | ||||||
|  |                     neck_pitch, | ||||||
|  |                     head_pitch, | ||||||
|  |                     head_yaw, | ||||||
|  |                     head_roll, | ||||||
|  |                 ] | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
| @ -0,0 +1,266 @@ | |||||||
|  | import mujoco | ||||||
|  | import pickle | ||||||
|  | import numpy as np | ||||||
|  | import mujoco | ||||||
|  | import mujoco.viewer | ||||||
|  | import time | ||||||
|  | import argparse | ||||||
|  | from playground.common.onnx_infer import OnnxInfer | ||||||
|  | from playground.common.poly_reference_motion_numpy import PolyReferenceMotion | ||||||
|  | from playground.common.utils import LowPassActionFilter | ||||||
|  | 
 | ||||||
|  | from playground.open_duck_mini_v2.mujoco_infer_base import MJInferBase | ||||||
|  | 
 | ||||||
|  | USE_MOTOR_SPEED_LIMITS = True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class MjInfer(MJInferBase): | ||||||
|  |     def __init__( | ||||||
|  |         self, model_path: str, reference_data: str, onnx_model_path: str, standing: bool | ||||||
|  |     ): | ||||||
|  |         super().__init__(model_path) | ||||||
|  | 
 | ||||||
|  |         self.standing = standing | ||||||
|  |         self.head_control_mode = self.standing | ||||||
|  | 
 | ||||||
|  |         # Params | ||||||
|  |         self.linearVelocityScale = 1.0 | ||||||
|  |         self.angularVelocityScale = 1.0 | ||||||
|  |         self.dof_pos_scale = 1.0 | ||||||
|  |         self.dof_vel_scale = 0.05 | ||||||
|  |         self.action_scale = 0.25 | ||||||
|  | 
 | ||||||
|  |         self.action_filter = LowPassActionFilter(50, cutoff_frequency=37.5) | ||||||
|  | 
 | ||||||
|  |         if not self.standing: | ||||||
|  |             self.PRM = PolyReferenceMotion(reference_data) | ||||||
|  | 
 | ||||||
|  |         self.policy = OnnxInfer(onnx_model_path, awd=True) | ||||||
|  | 
 | ||||||
|  |         self.COMMANDS_RANGE_X = [-0.15, 0.15] | ||||||
|  |         self.COMMANDS_RANGE_Y = [-0.2, 0.2] | ||||||
|  |         self.COMMANDS_RANGE_THETA = [-1.0, 1.0]  # [-1.0, 1.0] | ||||||
|  | 
 | ||||||
|  |         self.NECK_PITCH_RANGE = [-0.34, 1.1] | ||||||
|  |         self.HEAD_PITCH_RANGE = [-0.78, 0.78] | ||||||
|  |         self.HEAD_YAW_RANGE = [-1.5, 1.5] | ||||||
|  |         self.HEAD_ROLL_RANGE = [-0.5, 0.5] | ||||||
|  | 
 | ||||||
|  |         self.last_action = np.zeros(self.num_dofs) | ||||||
|  |         self.last_last_action = np.zeros(self.num_dofs) | ||||||
|  |         self.last_last_last_action = np.zeros(self.num_dofs) | ||||||
|  |         self.commands = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] | ||||||
|  | 
 | ||||||
|  |         self.imitation_i = 0 | ||||||
|  |         self.imitation_phase = np.array([0, 0]) | ||||||
|  |         self.saved_obs = [] | ||||||
|  | 
 | ||||||
|  |         self.max_motor_velocity = 5.24  # rad/s | ||||||
|  | 
 | ||||||
|  |         self.phase_frequency_factor = 1.0 | ||||||
|  | 
 | ||||||
|  |         print(f"joint names: {self.joint_names}") | ||||||
|  |         print(f"actuator names: {self.actuator_names}") | ||||||
|  |         print(f"backlash joint names: {self.backlash_joint_names}") | ||||||
|  |         # print(f"actual joints idx: {self.get_actual_joints_idx()}") | ||||||
|  | 
 | ||||||
|  |     def get_obs( | ||||||
|  |         self, | ||||||
|  |         data, | ||||||
|  |         command,  # , qvel_history, qpos_error_history, gravity_history | ||||||
|  |     ): | ||||||
|  |         gyro = self.get_gyro(data) | ||||||
|  |         accelerometer = self.get_accelerometer(data) | ||||||
|  |         accelerometer[0] += 1.3 | ||||||
|  | 
 | ||||||
|  |         joint_angles = self.get_actuator_joints_qpos(data.qpos) | ||||||
|  |         joint_vel = self.get_actuator_joints_qvel(data.qvel) | ||||||
|  | 
 | ||||||
|  |         contacts = self.get_feet_contacts(data) | ||||||
|  | 
 | ||||||
|  |         # if not self.standing: | ||||||
|  |         # ref = self.PRM.get_reference_motion(*command[:3], self.imitation_i) | ||||||
|  | 
 | ||||||
|  |         obs = np.concatenate( | ||||||
|  |             [ | ||||||
|  |                 gyro, | ||||||
|  |                 accelerometer, | ||||||
|  |                 # gravity, | ||||||
|  |                 command, | ||||||
|  |                 joint_angles - self.default_actuator, | ||||||
|  |                 joint_vel * self.dof_vel_scale, | ||||||
|  |                 self.last_action, | ||||||
|  |                 self.last_last_action, | ||||||
|  |                 self.last_last_last_action, | ||||||
|  |                 self.motor_targets, | ||||||
|  |                 contacts, | ||||||
|  |                 # ref if not self.standing else np.array([]), | ||||||
|  |                 # [self.imitation_i] | ||||||
|  |                 self.imitation_phase, | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return obs | ||||||
|  | 
 | ||||||
|  |     def key_callback(self, keycode): | ||||||
|  |         print(f"key: {keycode}") | ||||||
|  |         if keycode == 72:  # h | ||||||
|  |             self.head_control_mode = not self.head_control_mode | ||||||
|  |         lin_vel_x = 0 | ||||||
|  |         lin_vel_y = 0 | ||||||
|  |         ang_vel = 0 | ||||||
|  |         if not self.head_control_mode: | ||||||
|  |             if keycode == 265:  # arrow up | ||||||
|  |                 lin_vel_x = self.COMMANDS_RANGE_X[1] | ||||||
|  |             if keycode == 264:  # arrow down | ||||||
|  |                 lin_vel_x = self.COMMANDS_RANGE_X[0] | ||||||
|  |             if keycode == 263:  # arrow left | ||||||
|  |                 lin_vel_y = self.COMMANDS_RANGE_Y[1] | ||||||
|  |             if keycode == 262:  # arrow right | ||||||
|  |                 lin_vel_y = self.COMMANDS_RANGE_Y[0] | ||||||
|  |             if keycode == 81:  # a | ||||||
|  |                 ang_vel = self.COMMANDS_RANGE_THETA[1] | ||||||
|  |             if keycode == 69:  # e | ||||||
|  |                 ang_vel = self.COMMANDS_RANGE_THETA[0] | ||||||
|  |             if keycode == 80:  # p | ||||||
|  |                 self.phase_frequency_factor += 0.1 | ||||||
|  |             if keycode == 59:  # m | ||||||
|  |                 self.phase_frequency_factor -= 0.1 | ||||||
|  |         else: | ||||||
|  |             neck_pitch = 0 | ||||||
|  |             head_pitch = 0 | ||||||
|  |             head_yaw = 0 | ||||||
|  |             head_roll = 0 | ||||||
|  |             if keycode == 265:  # arrow up | ||||||
|  |                 head_pitch = self.NECK_PITCH_RANGE[1] | ||||||
|  |             if keycode == 264:  # arrow down | ||||||
|  |                 head_pitch = self.NECK_PITCH_RANGE[0] | ||||||
|  |             if keycode == 263:  # arrow left | ||||||
|  |                 head_yaw = self.HEAD_YAW_RANGE[1] | ||||||
|  |             if keycode == 262:  # arrow right | ||||||
|  |                 head_yaw = self.HEAD_YAW_RANGE[0] | ||||||
|  |             if keycode == 81:  # a | ||||||
|  |                 head_roll = self.HEAD_ROLL_RANGE[1] | ||||||
|  |             if keycode == 69:  # e | ||||||
|  |                 head_roll = self.HEAD_ROLL_RANGE[0] | ||||||
|  | 
 | ||||||
|  |             self.commands[3] = neck_pitch | ||||||
|  |             self.commands[4] = head_pitch | ||||||
|  |             self.commands[5] = head_yaw | ||||||
|  |             self.commands[6] = head_roll | ||||||
|  | 
 | ||||||
|  |         self.commands[0] = lin_vel_x | ||||||
|  |         self.commands[1] = lin_vel_y | ||||||
|  |         self.commands[2] = ang_vel | ||||||
|  | 
 | ||||||
|  |     def run(self): | ||||||
|  |         try: | ||||||
|  |             with mujoco.viewer.launch_passive( | ||||||
|  |                 self.model, | ||||||
|  |                 self.data, | ||||||
|  |                 show_left_ui=False, | ||||||
|  |                 show_right_ui=False, | ||||||
|  |                 key_callback=self.key_callback, | ||||||
|  |             ) as viewer: | ||||||
|  |                 counter = 0 | ||||||
|  |                 while True: | ||||||
|  | 
 | ||||||
|  |                     step_start = time.time() | ||||||
|  | 
 | ||||||
|  |                     mujoco.mj_step(self.model, self.data) | ||||||
|  | 
 | ||||||
|  |                     counter += 1 | ||||||
|  | 
 | ||||||
|  |                     if counter % self.decimation == 0: | ||||||
|  |                         if not self.standing: | ||||||
|  |                             self.imitation_i += 1.0 * self.phase_frequency_factor | ||||||
|  |                             self.imitation_i = ( | ||||||
|  |                                 self.imitation_i % self.PRM.nb_steps_in_period | ||||||
|  |                             ) | ||||||
|  |                             # print(self.PRM.nb_steps_in_period) | ||||||
|  |                             # exit() | ||||||
|  |                             self.imitation_phase = np.array( | ||||||
|  |                                 [ | ||||||
|  |                                     np.cos( | ||||||
|  |                                         self.imitation_i | ||||||
|  |                                         / self.PRM.nb_steps_in_period | ||||||
|  |                                         * 2 | ||||||
|  |                                         * np.pi | ||||||
|  |                                     ), | ||||||
|  |                                     np.sin( | ||||||
|  |                                         self.imitation_i | ||||||
|  |                                         / self.PRM.nb_steps_in_period | ||||||
|  |                                         * 2 | ||||||
|  |                                         * np.pi | ||||||
|  |                                     ), | ||||||
|  |                                 ] | ||||||
|  |                             ) | ||||||
|  |                         obs = self.get_obs( | ||||||
|  |                             self.data, | ||||||
|  |                             self.commands, | ||||||
|  |                         ) | ||||||
|  |                         self.saved_obs.append(obs) | ||||||
|  |                         action = self.policy.infer(obs) | ||||||
|  | 
 | ||||||
|  |                         # self.action_filter.push(action) | ||||||
|  |                         # action = self.action_filter.get_filtered_action() | ||||||
|  | 
 | ||||||
|  |                         self.last_last_last_action = self.last_last_action.copy() | ||||||
|  |                         self.last_last_action = self.last_action.copy() | ||||||
|  |                         self.last_action = action.copy() | ||||||
|  | 
 | ||||||
|  |                         self.motor_targets = ( | ||||||
|  |                             self.default_actuator + action * self.action_scale | ||||||
|  |                         ) | ||||||
|  | 
 | ||||||
|  |                         if USE_MOTOR_SPEED_LIMITS: | ||||||
|  |                             self.motor_targets = np.clip( | ||||||
|  |                                 self.motor_targets, | ||||||
|  |                                 self.prev_motor_targets | ||||||
|  |                                 - self.max_motor_velocity | ||||||
|  |                                 * (self.sim_dt * self.decimation), | ||||||
|  |                                 self.prev_motor_targets | ||||||
|  |                                 + self.max_motor_velocity | ||||||
|  |                                 * (self.sim_dt * self.decimation), | ||||||
|  |                             ) | ||||||
|  | 
 | ||||||
|  |                             self.prev_motor_targets = self.motor_targets.copy() | ||||||
|  | 
 | ||||||
|  |                         # head_targets = self.commands[3:] | ||||||
|  |                         # self.motor_targets[5:9] = head_targets | ||||||
|  |                         self.data.ctrl = self.motor_targets.copy() | ||||||
|  | 
 | ||||||
|  |                     viewer.sync() | ||||||
|  | 
 | ||||||
|  |                     time_until_next_step = self.model.opt.timestep - ( | ||||||
|  |                         time.time() - step_start | ||||||
|  |                     ) | ||||||
|  |                     if time_until_next_step > 0: | ||||||
|  |                         time.sleep(time_until_next_step) | ||||||
|  |         except KeyboardInterrupt: | ||||||
|  |             pickle.dump(self.saved_obs, open("mujoco_saved_obs.pkl", "wb")) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  | 
 | ||||||
|  |     parser = argparse.ArgumentParser() | ||||||
|  |     parser.add_argument("-o", "--onnx_model_path", type=str, required=True) | ||||||
|  |     # parser.add_argument("-k", action="store_true", default=False) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "--reference_data", | ||||||
|  |         type=str, | ||||||
|  |         default="playground/open_duck_mini_v2/data/polynomial_coefficients.pkl", | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "--model_path", | ||||||
|  |         type=str, | ||||||
|  |         default="playground/open_duck_mini_v2/xmls/scene_flat_terrain.xml", | ||||||
|  |     ) | ||||||
|  |     parser.add_argument("--standing", action="store_true", default=False) | ||||||
|  | 
 | ||||||
|  |     args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  |     mjinfer = MjInfer( | ||||||
|  |         args.model_path, args.reference_data, args.onnx_model_path, args.standing | ||||||
|  |     ) | ||||||
|  |     mjinfer.run() | ||||||
| @ -0,0 +1,283 @@ | |||||||
|  | import mujoco | ||||||
|  | import numpy as np | ||||||
|  | from etils import epath | ||||||
|  | from playground.open_duck_mini_v2 import base | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class MJInferBase: | ||||||
|  |     def __init__(self, model_path): | ||||||
|  | 
 | ||||||
|  |         self.model = mujoco.MjModel.from_xml_string( | ||||||
|  |             epath.Path(model_path).read_text(), assets=base.get_assets() | ||||||
|  |         ) | ||||||
|  |         print(model_path) | ||||||
|  | 
 | ||||||
|  |         self.sim_dt = 0.002 | ||||||
|  |         self.decimation = 10 | ||||||
|  |         self.model.opt.timestep = self.sim_dt | ||||||
|  |         self.data = mujoco.MjData(self.model) | ||||||
|  |         mujoco.mj_step(self.model, self.data) | ||||||
|  | 
 | ||||||
|  |         self.num_dofs = self.model.nu | ||||||
|  |         self.floating_base_name = [ | ||||||
|  |             self.model.jnt(k).name | ||||||
|  |             for k in range(0, self.model.njnt) | ||||||
|  |             if self.model.jnt(k).type == 0 | ||||||
|  |         ][ | ||||||
|  |             0 | ||||||
|  |         ]  # assuming only one floating object! | ||||||
|  |         self.actuator_names = [ | ||||||
|  |             self.model.actuator(k).name for k in range(0, self.model.nu) | ||||||
|  |         ]  # will be useful to get only the actuators we care about | ||||||
|  |         self.joint_names = [  # njnt = all joints (including floating base, actuators and backlash joints) | ||||||
|  |             self.model.jnt(k).name for k in range(0, self.model.njnt) | ||||||
|  |         ]  # all the joint (including the backlash joints) | ||||||
|  |         self.backlash_joint_names = [ | ||||||
|  |             j | ||||||
|  |             for j in self.joint_names | ||||||
|  |             if j not in self.actuator_names and j not in self.floating_base_name | ||||||
|  |         ]  # only the dummy backlash joint | ||||||
|  |         self.all_joint_ids = [self.get_joint_id_from_name(n) for n in self.joint_names] | ||||||
|  |         self.all_joint_qpos_addr = [ | ||||||
|  |             self.get_joint_addr_from_name(n) for n in self.joint_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.actuator_joint_ids = [ | ||||||
|  |             self.get_joint_id_from_name(n) for n in self.actuator_names | ||||||
|  |         ] | ||||||
|  |         self.actuator_joint_qpos_addr = [ | ||||||
|  |             self.get_joint_addr_from_name(n) for n in self.actuator_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.backlash_joint_ids = [ | ||||||
|  |             self.get_joint_id_from_name(n) for n in self.backlash_joint_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.backlash_joint_qpos_addr = [ | ||||||
|  |             self.get_joint_addr_from_name(n) for n in self.backlash_joint_names | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         self.all_qvel_addr = np.array( | ||||||
|  |             [self.model.jnt_dofadr[jad] for jad in self.all_joint_ids] | ||||||
|  |         ) | ||||||
|  |         self.actuator_qvel_addr = np.array( | ||||||
|  |             [self.model.jnt_dofadr[jad] for jad in self.actuator_joint_ids] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self.actuator_joint_dict = { | ||||||
|  |             n: self.get_joint_id_from_name(n) for n in self.actuator_names | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self._floating_base_qpos_addr = self.model.jnt_qposadr[ | ||||||
|  |             np.where(self.model.jnt_type == 0) | ||||||
|  |         ][ | ||||||
|  |             0 | ||||||
|  |         ]  # Assuming there is only one floating base! the jnt_type==0 is a floating joint. 3 is a hinge | ||||||
|  | 
 | ||||||
|  |         self._floating_base_qvel_addr = self.model.jnt_dofadr[ | ||||||
|  |             np.where(self.model.jnt_type == 0) | ||||||
|  |         ][ | ||||||
|  |             0 | ||||||
|  |         ]  # Assuming there is only one floating base! the jnt_type==0 is a floating joint. 3 is a hinge | ||||||
|  | 
 | ||||||
|  |         self._floating_base_id = self.model.joint(self.floating_base_name).id | ||||||
|  | 
 | ||||||
|  |         # self.all_joint_no_backlash_ids=np.zeros(7+self.model.nu) | ||||||
|  |         all_idx = self.backlash_joint_ids + list( | ||||||
|  |             range(self._floating_base_qpos_addr, self._floating_base_qpos_addr + 7) | ||||||
|  |         ) | ||||||
|  |         all_idx.sort() | ||||||
|  | 
 | ||||||
|  |         # self.all_joint_no_backlash_ids=[idx for idx in self.all_joint_ids if idx not in self.backlash_joint_ids]+list(range(self._floating_base_add,self._floating_base_add+7)) | ||||||
|  |         self.all_joint_no_backlash_ids = [idx for idx in all_idx] | ||||||
|  | 
 | ||||||
|  |         self.gyro_id = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_SENSOR, "gyro") | ||||||
|  |         self.gyro_addr = self.model.sensor_adr[self.gyro_id] | ||||||
|  |         self.gyro_dimensions = 3 | ||||||
|  | 
 | ||||||
|  |         self.accelerometer_id = mujoco.mj_name2id( | ||||||
|  |             self.model, mujoco.mjtObj.mjOBJ_SENSOR, "accelerometer" | ||||||
|  |         ) | ||||||
|  |         self.accelerometer_dimensions = 3 | ||||||
|  |         self.accelerometer_addr = self.model.sensor_adr[self.accelerometer_id] | ||||||
|  | 
 | ||||||
|  |         self.linvel_id = mujoco.mj_name2id( | ||||||
|  |             self.model, mujoco.mjtObj.mjOBJ_SENSOR, "local_linvel" | ||||||
|  |         ) | ||||||
|  |         self.linvel_dimensions = 3 | ||||||
|  | 
 | ||||||
|  |         self.imu_site_id = mujoco.mj_name2id( | ||||||
|  |             self.model, mujoco.mjtObj.mjOBJ_SITE, "imu" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self.gravity_id = mujoco.mj_name2id( | ||||||
|  |             self.model, mujoco.mjtObj.mjOBJ_SENSOR, "upvector" | ||||||
|  |         ) | ||||||
|  |         self.gravity_dimensions = 3 | ||||||
|  | 
 | ||||||
|  |         self.init_pos = np.array( | ||||||
|  |             self.get_all_joints_qpos(self.model.keyframe("home").qpos) | ||||||
|  |         )  # pose of all the joints (no floating base) | ||||||
|  |         self.default_actuator = self.model.keyframe( | ||||||
|  |             "home" | ||||||
|  |         ).ctrl  # ctrl of all the actual joints (no floating base and no backlash) | ||||||
|  |         self.motor_targets = self.default_actuator | ||||||
|  |         self.prev_motor_targets = self.default_actuator | ||||||
|  | 
 | ||||||
|  |         self.data.qpos[:] = self.model.keyframe("home").qpos | ||||||
|  |         self.data.ctrl[:] = self.default_actuator | ||||||
|  | 
 | ||||||
|  |     def get_actuator_id_from_name(self, name: str) -> int: | ||||||
|  |         """Return the id of a specified actuator""" | ||||||
|  |         return mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_ACTUATOR, name) | ||||||
|  | 
 | ||||||
|  |     def get_joint_id_from_name(self, name: str) -> int: | ||||||
|  |         """Return the id of a specified joint""" | ||||||
|  |         return mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, name) | ||||||
|  | 
 | ||||||
|  |     def get_joint_addr_from_name(self, name: str) -> int: | ||||||
|  |         """Return the address of a specified joint""" | ||||||
|  |         return self.model.joint(name).qposadr | ||||||
|  | 
 | ||||||
|  |     def get_dof_id_from_name(self, name: str) -> int: | ||||||
|  |         """Return the id of a specified dof""" | ||||||
|  |         return mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_DOF, name) | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joint_qpos_from_name( | ||||||
|  |         self, data: np.ndarray, name: str | ||||||
|  |     ) -> np.ndarray: | ||||||
|  |         """Return the qpos of a given actual joint""" | ||||||
|  |         addr = self.model.jnt_qposadr[self.actuator_joint_dict[name]] | ||||||
|  |         return data[addr] | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joints_addr(self) -> np.ndarray: | ||||||
|  |         """Return the all the idx of actual joints""" | ||||||
|  |         addr = np.array( | ||||||
|  |             [self.model.jnt_qposadr[idx] for idx in self.actuator_joint_ids] | ||||||
|  |         ) | ||||||
|  |         return addr | ||||||
|  | 
 | ||||||
|  |     def get_floating_base_qpos(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         return data[self._floating_base_qpos_addr : self._floating_base_qvel_addr + 7] | ||||||
|  | 
 | ||||||
|  |     def get_floating_base_qvel(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         return data[self._floating_base_qvel_addr : self._floating_base_qvel_addr + 6] | ||||||
|  | 
 | ||||||
|  |     def set_floating_base_qpos( | ||||||
|  |         self, new_qpos: np.ndarray, qpos: np.ndarray | ||||||
|  |     ) -> np.ndarray: | ||||||
|  |         qpos[self._floating_base_qpos_addr : self._floating_base_qpos_addr + 7] = ( | ||||||
|  |             new_qpos | ||||||
|  |         ) | ||||||
|  |         return qpos | ||||||
|  | 
 | ||||||
|  |     def set_floating_base_qvel( | ||||||
|  |         self, new_qvel: np.ndarray, qvel: np.ndarray | ||||||
|  |     ) -> np.ndarray: | ||||||
|  |         qvel[self._floating_base_qvel_addr : self._floating_base_qvel_addr + 6] = ( | ||||||
|  |             new_qvel | ||||||
|  |         ) | ||||||
|  |         return qvel | ||||||
|  | 
 | ||||||
|  |     def exclude_backlash_joints_addr(self) -> np.ndarray: | ||||||
|  |         """Return the all the idx of actual joints and floating base""" | ||||||
|  |         addr = np.array( | ||||||
|  |             [self.model.jnt_qposadr[idx] for idx in self.all_joint_no_backlash_ids] | ||||||
|  |         ) | ||||||
|  |         return addr | ||||||
|  | 
 | ||||||
|  |     def get_all_joints_addr(self) -> np.ndarray: | ||||||
|  |         """Return the all the idx of all joints""" | ||||||
|  |         addr = np.array([self.model.jnt_qposadr[idx] for idx in self.all_joint_ids]) | ||||||
|  |         return addr | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joints_qpos(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         """Return the all the qpos of actual joints""" | ||||||
|  |         return data[self.get_actuator_joints_addr()] | ||||||
|  | 
 | ||||||
|  |     def set_actuator_joints_qpos( | ||||||
|  |         self, new_qpos: np.ndarray, qpos: np.ndarray | ||||||
|  |     ) -> np.ndarray: | ||||||
|  |         """Set the qpos only for the actual joints (omit the backlash joint)""" | ||||||
|  |         qpos[self.get_actuator_joints_addr()] = new_qpos | ||||||
|  |         return qpos | ||||||
|  | 
 | ||||||
|  |     def get_actuator_joints_qvel(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         """Return the all the qvel of actual joints""" | ||||||
|  |         return data[self.actuator_qvel_addr] | ||||||
|  | 
 | ||||||
|  |     def set_actuator_joints_qvel( | ||||||
|  |         self, new_qvel: np.ndarray, qvel: np.ndarray | ||||||
|  |     ) -> np.ndarray: | ||||||
|  |         """Set the qvel only for the actual joints (omit the backlash joint)""" | ||||||
|  |         qvel[self.actuator_qvel_addr] = new_qvel | ||||||
|  |         return qvel | ||||||
|  | 
 | ||||||
|  |     def get_all_joints_qpos(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         """Return the all the qpos of all joints""" | ||||||
|  |         return data[self.get_all_joints_addr()] | ||||||
|  | 
 | ||||||
|  |     def get_all_joints_qvel(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         """Return the all the qvel of all joints""" | ||||||
|  |         return data[self.all_qvel_addr] | ||||||
|  | 
 | ||||||
|  |     def get_joints_nobacklash_qpos(self, data: np.ndarray) -> np.ndarray: | ||||||
|  |         """Return the all the qpos of actual joints with the floating base""" | ||||||
|  |         return data[self.exclude_backlash_joints_addr()] | ||||||
|  | 
 | ||||||
|  |     def set_complete_qpos_from_joints( | ||||||
|  |         self, no_backlash_qpos: np.ndarray, full_qpos: np.ndarray | ||||||
|  |     ) -> np.ndarray: | ||||||
|  |         """In the case of backlash joints, we want to ignore them (remove them) but we still need to set the complete state incuding them""" | ||||||
|  |         full_qpos[self.exclude_backlash_joints_addr()] = no_backlash_qpos | ||||||
|  |         return np.array(full_qpos) | ||||||
|  | 
 | ||||||
|  |     def get_sensor(self, data, name, dimensions): | ||||||
|  |         i = self.model.sensor_name2id(name) | ||||||
|  |         return data.sensordata[i : i + dimensions] | ||||||
|  | 
 | ||||||
|  |     def get_gyro(self, data): | ||||||
|  |         return data.sensordata[self.gyro_addr : self.gyro_addr + self.gyro_dimensions] | ||||||
|  | 
 | ||||||
|  |     def get_accelerometer(self, data): | ||||||
|  |         return data.sensordata[ | ||||||
|  |             self.accelerometer_addr : self.accelerometer_addr | ||||||
|  |             + self.accelerometer_dimensions | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     def get_linvel(self, data): | ||||||
|  |         return data.sensordata[self.linvel_id : self.linvel_id + self.linvel_dimensions] | ||||||
|  | 
 | ||||||
|  |     # def get_gravity(self, data): | ||||||
|  |     #     return data.site_xmat[self.imu_site_id].reshape((3, 3)).T @ np.array([0, 0, -1]) | ||||||
|  | 
 | ||||||
|  |     def get_gravity(self, data): | ||||||
|  |         return data.sensordata[ | ||||||
|  |             self.gravity_id : self.gravity_id + self.gravity_dimensions | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     def check_contact(self, data, body1_name, body2_name): | ||||||
|  |         body1_id = data.body(body1_name).id | ||||||
|  |         body2_id = data.body(body2_name).id | ||||||
|  | 
 | ||||||
|  |         for i in range(data.ncon): | ||||||
|  |             try: | ||||||
|  |                 contact = data.contact[i] | ||||||
|  |             except Exception as e: | ||||||
|  |                 return False | ||||||
|  | 
 | ||||||
|  |             if ( | ||||||
|  |                 self.model.geom_bodyid[contact.geom1] == body1_id | ||||||
|  |                 and self.model.geom_bodyid[contact.geom2] == body2_id | ||||||
|  |             ) or ( | ||||||
|  |                 self.model.geom_bodyid[contact.geom1] == body2_id | ||||||
|  |                 and self.model.geom_bodyid[contact.geom2] == body1_id | ||||||
|  |             ): | ||||||
|  |                 return True | ||||||
|  | 
 | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  |     def get_feet_contacts(self, data): | ||||||
|  |         left_contact = self.check_contact(data, "foot_assembly", "floor") | ||||||
|  |         right_contact = self.check_contact(data, "foot_assembly_2", "floor") | ||||||
|  |         return left_contact, right_contact | ||||||
| @ -0,0 +1,214 @@ | |||||||
|  | import mujoco | ||||||
|  | import numpy as np | ||||||
|  | import time | ||||||
|  | import argparse | ||||||
|  | import os, sys | ||||||
|  | import pygame | ||||||
|  | from etils import epath | ||||||
|  | import mujoco.viewer | ||||||
|  | 
 | ||||||
|  | # Import the reference motion class. | ||||||
|  | from playground.open_duck_mini_v2 import base | ||||||
|  | 
 | ||||||
|  | from playground.common.poly_reference_motion_numpy import PolyReferenceMotion | ||||||
|  | 
 | ||||||
|  | SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) | ||||||
|  | SCENE_PATH = f"{SCRIPT_PATH}/xmls" | ||||||
|  | 
 | ||||||
|  | COMMANDS_RANGE_X = [-0.15, 0.15] | ||||||
|  | COMMANDS_RANGE_Y = [-0.2, 0.2] | ||||||
|  | COMMANDS_RANGE_THETA = [-1.0, 1.0]  # [-1.0, 1.0] | ||||||
|  | 
 | ||||||
|  | available_scenes = [] | ||||||
|  | if os.path.isdir(SCENE_PATH): | ||||||
|  |     for name in os.listdir(SCENE_PATH): | ||||||
|  |         if ( | ||||||
|  |             name.startswith("scene_") | ||||||
|  |             and name.endswith(".xml") | ||||||
|  |             and os.path.isfile(os.path.join(SCENE_PATH, name)) | ||||||
|  |         ): | ||||||
|  |             scene_name = name[len("scene_") : -len(".xml")] | ||||||
|  |             available_scenes.append(scene_name) | ||||||
|  | if len(available_scenes) == 0: | ||||||
|  |     print(f"No scenes found in: {SCENE_PATH}") | ||||||
|  |     sys.exit(1) | ||||||
|  | 
 | ||||||
|  | # Parse command-line arguments. | ||||||
|  | parser = argparse.ArgumentParser(description="Reference Motion Viewer") | ||||||
|  | parser.add_argument( | ||||||
|  |     "--reference_data", | ||||||
|  |     type=str, | ||||||
|  |     default="playground/go_bdx/data/polynomial_coefficients.pkl", | ||||||
|  |     help="Path to the polynomial coefficients pickle file.", | ||||||
|  | ) | ||||||
|  | parser.add_argument( | ||||||
|  |     "-joystick", action="store_true", default=False, help="Use joystick control" | ||||||
|  | ) | ||||||
|  | # Command parameters: dx, dy, dtheta | ||||||
|  | parser.add_argument( | ||||||
|  |     "--command", | ||||||
|  |     nargs=3, | ||||||
|  |     type=float, | ||||||
|  |     default=[0.0, -0.05, -0.1], | ||||||
|  |     help="Reference motion command as three floats: dx, dy, dtheta.", | ||||||
|  | ) | ||||||
|  | parser.add_argument( | ||||||
|  |     "--scene", | ||||||
|  |     type=str, | ||||||
|  |     choices=available_scenes, | ||||||
|  |     default="flat_terrain", | ||||||
|  | ) | ||||||
|  | args = parser.parse_args() | ||||||
|  | # model_path = f"playground/go_bdx/xmls/scene_mjx_{args.scene}.xml" | ||||||
|  | model_path = f"playground/open_duck_mini_v2/xmls/scene_{args.scene}.xml" | ||||||
|  | 
 | ||||||
|  | command = args.command | ||||||
|  | 
 | ||||||
|  | joystick1 = None | ||||||
|  | joystick2 = None | ||||||
|  | if args.joystick: | ||||||
|  |     pygame.init() | ||||||
|  |     pygame.joystick.init() | ||||||
|  |     if pygame.joystick.get_count() > 0: | ||||||
|  |         joystick1 = pygame.joystick.Joystick(0) | ||||||
|  |         joystick1.init() | ||||||
|  |         command = [0.0, 0.0, 0.0] | ||||||
|  |         print("Joystick initialized:", joystick1.get_name()) | ||||||
|  |         if pygame.joystick.get_count() > 1: | ||||||
|  |             joystick2 = pygame.joystick.Joystick(1) | ||||||
|  |             joystick2.init() | ||||||
|  |             print("Joystick 2 (theta) initialized:", joystick2.get_name()) | ||||||
|  |         else: | ||||||
|  |             print( | ||||||
|  |                 "Only one joystick detected; theta via second joystick will be disabled." | ||||||
|  |             ) | ||||||
|  |     else: | ||||||
|  |         print("No joystick found!") | ||||||
|  | 
 | ||||||
|  | # Load the Mujoco model XML. | ||||||
|  | model = mujoco.MjModel.from_xml_string( | ||||||
|  |     epath.Path(model_path).read_text(), assets=base.get_assets() | ||||||
|  | ) | ||||||
|  | data = mujoco.MjData(model) | ||||||
|  | model.opt.timestep = 0.002 | ||||||
|  | 
 | ||||||
|  | # Step the simulation once to initialize. | ||||||
|  | mujoco.mj_step(model, data) | ||||||
|  | 
 | ||||||
|  | # Load the polynomial reference motion. | ||||||
|  | PRM = PolyReferenceMotion(args.reference_data) | ||||||
|  | 
 | ||||||
|  | # Get the "home" keyframe to use as a default pose. | ||||||
|  | home_frame = model.keyframe("home") | ||||||
|  | default_qpos = np.array(home_frame.qpos) | ||||||
|  | default_ctrl = np.array(home_frame.ctrl) | ||||||
|  | default_qpos[2] += 0.2  # Increase the base height by 0.2 meters | ||||||
|  | 
 | ||||||
|  | # Set initial state. | ||||||
|  | data.qpos[:] = default_qpos.copy() | ||||||
|  | data.ctrl[:] = default_ctrl.copy() | ||||||
|  | 
 | ||||||
|  | decimation = 10  # 50 hz | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def key_callback(keycode): | ||||||
|  |     if joystick1 is not None: | ||||||
|  |         return | ||||||
|  | 
 | ||||||
|  |     print(f"key: {keycode}") | ||||||
|  |     lin_vel_x = 0 | ||||||
|  |     lin_vel_y = 0 | ||||||
|  |     ang_vel = 0 | ||||||
|  |     if keycode == 265:  # arrow up | ||||||
|  |         lin_vel_x = COMMANDS_RANGE_X[1] | ||||||
|  |     if keycode == 264:  # arrow down | ||||||
|  |         lin_vel_x = COMMANDS_RANGE_X[0] | ||||||
|  |     if keycode == 263:  # arrow left | ||||||
|  |         lin_vel_y = COMMANDS_RANGE_Y[1] | ||||||
|  |     if keycode == 262:  # arrow right | ||||||
|  |         lin_vel_y = COMMANDS_RANGE_Y[0] | ||||||
|  |     if keycode == 81:  # a | ||||||
|  |         ang_vel = COMMANDS_RANGE_THETA[1] | ||||||
|  |     if keycode == 69:  # e | ||||||
|  |         ang_vel = COMMANDS_RANGE_THETA[0] | ||||||
|  | 
 | ||||||
|  |     command[0] = lin_vel_x | ||||||
|  |     command[1] = lin_vel_y | ||||||
|  |     command[2] = ang_vel | ||||||
|  |     print(f"command: {command}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def handle_joystick(): | ||||||
|  |     if joystick1 is None: | ||||||
|  |         return | ||||||
|  | 
 | ||||||
|  |     joy_z = 0 | ||||||
|  |     pygame.event.pump() | ||||||
|  |     joy_y = joystick1.get_axis(1) | ||||||
|  |     joy_x = joystick1.get_axis(0) | ||||||
|  |     if joystick2 is not None: | ||||||
|  |         joy_z = joystick2.get_axis(0) | ||||||
|  |     if joy_y < 0: | ||||||
|  |         lin_vel_x = (-joy_y) * COMMANDS_RANGE_X[1] | ||||||
|  |     else: | ||||||
|  |         lin_vel_x = -joy_y * abs(COMMANDS_RANGE_X[0]) | ||||||
|  |     lin_vel_y = -joy_x * COMMANDS_RANGE_Y[1] | ||||||
|  |     lin_vel_z = -joy_z * COMMANDS_RANGE_THETA[1] | ||||||
|  | 
 | ||||||
|  |     command[0] = lin_vel_x | ||||||
|  |     command[1] = lin_vel_y | ||||||
|  |     command[2] = lin_vel_z | ||||||
|  |     print(f"command: {command}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Create a Mujoco viewer to display the model. | ||||||
|  | with mujoco.viewer.launch_passive( | ||||||
|  |     model, data, show_left_ui=False, show_right_ui=False, key_callback=key_callback | ||||||
|  | ) as viewer: | ||||||
|  |     step = 0 | ||||||
|  |     dt = model.opt.timestep | ||||||
|  |     counter = 0 | ||||||
|  |     new_qpos = default_qpos.copy() | ||||||
|  |     while viewer.is_running(): | ||||||
|  |         step_start = time.time() | ||||||
|  |         handle_joystick() | ||||||
|  |         counter += 1 | ||||||
|  |         new_qpos[:7] = default_qpos[:7].copy() | ||||||
|  |         if counter % decimation == 0: | ||||||
|  |             new_qpos = default_qpos.copy() | ||||||
|  |             if not all(val == 0.0 for val in command): | ||||||
|  |                 imitation_i = step % PRM.nb_steps_in_period | ||||||
|  | 
 | ||||||
|  |                 ref_motion = PRM.get_reference_motion( | ||||||
|  |                     command[0], command[1], command[2], imitation_i | ||||||
|  |                 ) | ||||||
|  |                 ref_motion = np.array(ref_motion) | ||||||
|  | 
 | ||||||
|  |                 if ref_motion.shape[0] == 40: | ||||||
|  |                     joints_pos = ref_motion[0:16] | ||||||
|  |                     ref_joint_pos = np.concatenate([joints_pos[:9], joints_pos[11:]]) | ||||||
|  |                 else: | ||||||
|  |                     print( | ||||||
|  |                         "Error: Unexpected reference motion dimension:", | ||||||
|  |                         ref_motion.shape, | ||||||
|  |                     ) | ||||||
|  |                     sys.exit(1) | ||||||
|  | 
 | ||||||
|  |                 new_qpos = default_qpos.copy() | ||||||
|  |                 if new_qpos[7 : 7 + 14].shape[0] == ref_joint_pos.shape[0]: | ||||||
|  |                     new_qpos[7 : 7 + 14] = ref_joint_pos | ||||||
|  |                 else: | ||||||
|  |                     print( | ||||||
|  |                         "Error: Actuated joint dimension mismatch. Using default pose." | ||||||
|  |                     ) | ||||||
|  |                 step += 1 | ||||||
|  |             else: | ||||||
|  |                 step = 0 | ||||||
|  |         data.qpos[:] = new_qpos | ||||||
|  | 
 | ||||||
|  |         # Step the simulation to update any dependent quantities. | ||||||
|  |         mujoco.mj_step(model, data) | ||||||
|  |         viewer.sync() | ||||||
|  |         time_until_next_step = model.opt.timestep - (time.time() - step_start) | ||||||
|  |         if time_until_next_step > 0: | ||||||
|  |             time.sleep(time_until_next_step) | ||||||
							
								
								
									
										64
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/runner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/runner.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | """Runs training and evaluation loop for Open Duck Mini V2.""" | ||||||
|  | 
 | ||||||
|  | import argparse | ||||||
|  | 
 | ||||||
|  | from playground.common import randomize | ||||||
|  | from playground.common.runner import BaseRunner | ||||||
|  | from playground.open_duck_mini_v2 import joystick, standing | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OpenDuckMiniV2Runner(BaseRunner): | ||||||
|  | 
 | ||||||
|  |     def __init__(self, args): | ||||||
|  |         super().__init__(args) | ||||||
|  |         available_envs = { | ||||||
|  |             "joystick": (joystick, joystick.Joystick), | ||||||
|  |             "standing": (standing, standing.Standing), | ||||||
|  |         } | ||||||
|  |         if args.env not in available_envs: | ||||||
|  |             raise ValueError(f"Unknown env {args.env}") | ||||||
|  | 
 | ||||||
|  |         self.env_file = available_envs[args.env] | ||||||
|  | 
 | ||||||
|  |         self.env_config = self.env_file[0].default_config() | ||||||
|  |         self.env = self.env_file[1](task=args.task) | ||||||
|  |         self.eval_env = self.env_file[1](task=args.task) | ||||||
|  |         self.randomizer = randomize.domain_randomize | ||||||
|  |         self.action_size = self.env.action_size | ||||||
|  |         self.obs_size = int( | ||||||
|  |             self.env.observation_size["state"][0] | ||||||
|  |         )  # 0: state 1: privileged_state | ||||||
|  |         self.restore_checkpoint_path = args.restore_checkpoint_path | ||||||
|  |         print(f"Observation size: {self.obs_size}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main() -> None: | ||||||
|  |     parser = argparse.ArgumentParser(description="Open Duck Mini Runner Script") | ||||||
|  |     parser.add_argument( | ||||||
|  |         "--output_dir", | ||||||
|  |         type=str, | ||||||
|  |         default="checkpoints", | ||||||
|  |         help="Where to save the checkpoints", | ||||||
|  |     ) | ||||||
|  |     # parser.add_argument("--num_timesteps", type=int, default=300000000) | ||||||
|  |     parser.add_argument("--num_timesteps", type=int, default=150000000) | ||||||
|  |     parser.add_argument("--env", type=str, default="joystick", help="env") | ||||||
|  |     parser.add_argument("--task", type=str, default="flat_terrain", help="Task to run") | ||||||
|  |     parser.add_argument( | ||||||
|  |         "--restore_checkpoint_path", | ||||||
|  |         type=str, | ||||||
|  |         default=None, | ||||||
|  |         help="Resume training from this checkpoint", | ||||||
|  |     ) | ||||||
|  |     # parser.add_argument( | ||||||
|  |     #     "--debug", action="store_true", help="Run in debug mode with minimal parameters" | ||||||
|  |     # ) | ||||||
|  |     args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  |     runner = OpenDuckMiniV2Runner(args) | ||||||
|  | 
 | ||||||
|  |     runner.train() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
							
								
								
									
										661
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/standing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										661
									
								
								Open_Duck_Playground/playground/open_duck_mini_v2/standing.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,661 @@ | |||||||
|  | # Copyright 2025 DeepMind Technologies Limited | ||||||
|  | # Copyright 2025 Antoine Pirrone - Steve Nguyen | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | # ============================================================================== | ||||||
|  | """Standing task for Open Duck Mini V2. (based on Berkeley Humanoid)""" | ||||||
|  | 
 | ||||||
|  | from typing import Any, Dict, Optional, Union | ||||||
|  | import jax | ||||||
|  | import jax.numpy as jp | ||||||
|  | from ml_collections import config_dict | ||||||
|  | from mujoco import mjx | ||||||
|  | from mujoco.mjx._src import math | ||||||
|  | import numpy as np | ||||||
|  | 
 | ||||||
|  | from mujoco_playground._src import mjx_env | ||||||
|  | from mujoco_playground._src.collision import geoms_colliding | ||||||
|  | 
 | ||||||
|  | from . import constants | ||||||
|  | from . import base as open_duck_mini_v2_base | ||||||
|  | from playground.common.poly_reference_motion import PolyReferenceMotion | ||||||
|  | from playground.common.rewards import ( | ||||||
|  |     cost_orientation, | ||||||
|  |     cost_torques, | ||||||
|  |     cost_action_rate, | ||||||
|  |     cost_stand_still, | ||||||
|  |     reward_alive, | ||||||
|  |     cost_head_pos, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # if set to false, won't require the reference data to be present and won't compute the reference motions polynoms for nothing | ||||||
|  | USE_IMITATION_REWARD = False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def default_config() -> config_dict.ConfigDict: | ||||||
|  |     return config_dict.create( | ||||||
|  |         ctrl_dt=0.02, | ||||||
|  |         sim_dt=0.002, | ||||||
|  |         # episode_length=450, | ||||||
|  |         episode_length=1000, | ||||||
|  |         action_repeat=1, | ||||||
|  |         action_scale=0.25, | ||||||
|  |         dof_vel_scale=0.05, | ||||||
|  |         history_len=0, | ||||||
|  |         soft_joint_pos_limit_factor=0.95, | ||||||
|  |         noise_config=config_dict.create( | ||||||
|  |             level=1.0,  # Set to 0.0 to disable noise. | ||||||
|  |             action_min_delay=0,  # env steps | ||||||
|  |             action_max_delay=3,  # env steps | ||||||
|  |             imu_min_delay=0,  # env steps | ||||||
|  |             imu_max_delay=3,  # env steps | ||||||
|  |             scales=config_dict.create( | ||||||
|  |                 hip_pos=0.03,  # rad, for each hip joint | ||||||
|  |                 knee_pos=0.05,  # rad, for each knee joint | ||||||
|  |                 ankle_pos=0.08,  # rad, for each ankle joint | ||||||
|  |                 joint_vel=2.5,  # rad/s # Was 1.5 | ||||||
|  |                 gravity=0.1, | ||||||
|  |                 linvel=0.1, | ||||||
|  |                 gyro=0.05, | ||||||
|  |                 accelerometer=0.005, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         reward_config=config_dict.create( | ||||||
|  |             scales=config_dict.create( | ||||||
|  |                 # tracking_lin_vel=2.5, | ||||||
|  |                 # tracking_ang_vel=4.0, | ||||||
|  |                 orientation=-0.5, | ||||||
|  |                 torques=-1.0e-3, | ||||||
|  |                 action_rate=-0.375,  # was -1.5 | ||||||
|  |                 stand_still=-0.3,  # was -1.0 TODO try to relax this a bit ? | ||||||
|  |                 alive=20.0, | ||||||
|  |                 # imitation=1.0, | ||||||
|  |                 head_pos=-2.0, | ||||||
|  |             ), | ||||||
|  |             tracking_sigma=0.01,  # was working at 0.01 | ||||||
|  |         ), | ||||||
|  |         push_config=config_dict.create( | ||||||
|  |             enable=True, | ||||||
|  |             interval_range=[5.0, 10.0], | ||||||
|  |             magnitude_range=[0.1, 1.0], | ||||||
|  |         ), | ||||||
|  |         # lin_vel_x=[-0.1, 0.15], | ||||||
|  |         # lin_vel_y=[-0.2, 0.2], | ||||||
|  |         # ang_vel_yaw=[-1.0, 1.0],  # [-1.0, 1.0] | ||||||
|  |         neck_pitch_range=[-0.34, 1.1], | ||||||
|  |         head_pitch_range=[-0.78, 0.78], | ||||||
|  |         head_yaw_range=[-2.7, 2.7], | ||||||
|  |         head_roll_range=[-0.5, 0.5], | ||||||
|  |         head_range_factor=1.0, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Standing(open_duck_mini_v2_base.OpenDuckMiniV2Env): | ||||||
|  |     """Standing policy""" | ||||||
|  | 
 | ||||||
|  |     def __init__( | ||||||
|  |         self, | ||||||
|  |         task: str = "flat_terrain", | ||||||
|  |         config: config_dict.ConfigDict = default_config(), | ||||||
|  |         config_overrides: Optional[Dict[str, Union[str, int, list[Any]]]] = None, | ||||||
|  |     ): | ||||||
|  |         super().__init__( | ||||||
|  |             xml_path=constants.task_to_xml(task).as_posix(), | ||||||
|  |             config=config, | ||||||
|  |             config_overrides=config_overrides, | ||||||
|  |         ) | ||||||
|  |         self._post_init() | ||||||
|  | 
 | ||||||
|  |     def _post_init(self) -> None: | ||||||
|  | 
 | ||||||
|  |         self._init_q = jp.array(self._mj_model.keyframe("home").qpos) | ||||||
|  |         self._default_actuator = self._mj_model.keyframe( | ||||||
|  |             "home" | ||||||
|  |         ).ctrl  # ctrl of all the actual joints (no floating base and no backlash) | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             self.PRM = PolyReferenceMotion( | ||||||
|  |                 "playground/open_duck_mini_v2/data/polynomial_coefficients.pkl" | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         # Note: First joint is freejoint. | ||||||
|  |         # get the range of the joints | ||||||
|  |         self._lowers, self._uppers = self.mj_model.jnt_range[1:].T | ||||||
|  |         c = (self._lowers + self._uppers) / 2 | ||||||
|  |         r = self._uppers - self._lowers | ||||||
|  |         self._soft_lowers = c - 0.5 * r * self._config.soft_joint_pos_limit_factor | ||||||
|  |         self._soft_uppers = c + 0.5 * r * self._config.soft_joint_pos_limit_factor | ||||||
|  | 
 | ||||||
|  |         # weights for computing the cost of each joints compared to a reference pose | ||||||
|  |         self._weights = jp.array( | ||||||
|  |             [ | ||||||
|  |                 1.0, | ||||||
|  |                 1.0, | ||||||
|  |                 0.01, | ||||||
|  |                 0.01, | ||||||
|  |                 1.0,  # left leg. | ||||||
|  |                 # 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, #head | ||||||
|  |                 1.0, | ||||||
|  |                 1.0, | ||||||
|  |                 0.01, | ||||||
|  |                 0.01, | ||||||
|  |                 1.0,  # right leg. | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         self._njoints = self._mj_model.njnt  # number of joints | ||||||
|  |         self._actuators = self._mj_model.nu  # number of actuators | ||||||
|  | 
 | ||||||
|  |         self._torso_body_id = self._mj_model.body(constants.ROOT_BODY).id | ||||||
|  |         self._torso_mass = self._mj_model.body_subtreemass[self._torso_body_id] | ||||||
|  |         self._site_id = self._mj_model.site("imu").id | ||||||
|  | 
 | ||||||
|  |         self._feet_site_id = np.array( | ||||||
|  |             [self._mj_model.site(name).id for name in constants.FEET_SITES] | ||||||
|  |         ) | ||||||
|  |         self._floor_geom_id = self._mj_model.geom("floor").id | ||||||
|  |         self._feet_geom_id = np.array( | ||||||
|  |             [self._mj_model.geom(name).id for name in constants.FEET_GEOMS] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         foot_linvel_sensor_adr = [] | ||||||
|  |         for site in constants.FEET_SITES: | ||||||
|  |             sensor_id = self._mj_model.sensor(f"{site}_global_linvel").id | ||||||
|  |             sensor_adr = self._mj_model.sensor_adr[sensor_id] | ||||||
|  |             sensor_dim = self._mj_model.sensor_dim[sensor_id] | ||||||
|  |             foot_linvel_sensor_adr.append( | ||||||
|  |                 list(range(sensor_adr, sensor_adr + sensor_dim)) | ||||||
|  |             ) | ||||||
|  |         self._foot_linvel_sensor_adr = jp.array(foot_linvel_sensor_adr) | ||||||
|  | 
 | ||||||
|  |         # noise in the simu? | ||||||
|  |         qpos_noise_scale = np.zeros(self._actuators) | ||||||
|  | 
 | ||||||
|  |         hip_ids = [ | ||||||
|  |             idx for idx, j in enumerate(constants.JOINTS_ORDER_NO_HEAD) if "_hip" in j | ||||||
|  |         ] | ||||||
|  |         knee_ids = [ | ||||||
|  |             idx for idx, j in enumerate(constants.JOINTS_ORDER_NO_HEAD) if "_knee" in j | ||||||
|  |         ] | ||||||
|  |         ankle_ids = [ | ||||||
|  |             idx for idx, j in enumerate(constants.JOINTS_ORDER_NO_HEAD) if "_ankle" in j | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         qpos_noise_scale[hip_ids] = self._config.noise_config.scales.hip_pos | ||||||
|  |         qpos_noise_scale[knee_ids] = self._config.noise_config.scales.knee_pos | ||||||
|  |         qpos_noise_scale[ankle_ids] = self._config.noise_config.scales.ankle_pos | ||||||
|  |         # qpos_noise_scale[faa_ids] = self._config.noise_config.scales.faa_pos | ||||||
|  |         self._qpos_noise_scale = jp.array(qpos_noise_scale) | ||||||
|  | 
 | ||||||
|  |     def reset(self, rng: jax.Array) -> mjx_env.State: | ||||||
|  |         qpos = self._init_q  # the complete qpos | ||||||
|  |         # print(f'DEBUG0 init qpos: {qpos}') | ||||||
|  |         qvel = jp.zeros(self.mjx_model.nv) | ||||||
|  | 
 | ||||||
|  |         # init position/orientation in environment | ||||||
|  |         # x=+U(-0.05, 0.05), y=+U(-0.05, 0.05), yaw=U(-3.14, 3.14). | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         dxy = jax.random.uniform(key, (2,), minval=-0.05, maxval=0.05) | ||||||
|  | 
 | ||||||
|  |         # floating base | ||||||
|  |         base_qpos = self.get_floating_base_qpos(qpos) | ||||||
|  |         base_qpos = base_qpos.at[0:2].set( | ||||||
|  |             qpos[self._floating_base_qpos_addr : self._floating_base_qpos_addr + 2] | ||||||
|  |             + dxy | ||||||
|  |         )  # x y noise | ||||||
|  | 
 | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         yaw = jax.random.uniform(key, (1,), minval=-3.14, maxval=3.14) | ||||||
|  |         quat = math.axis_angle_to_quat(jp.array([0, 0, 1]), yaw) | ||||||
|  |         new_quat = math.quat_mul( | ||||||
|  |             qpos[self._floating_base_qpos_addr + 3 : self._floating_base_qpos_addr + 7], | ||||||
|  |             quat, | ||||||
|  |         )  # yaw noise | ||||||
|  | 
 | ||||||
|  |         base_qpos = base_qpos.at[3:7].set(new_quat) | ||||||
|  | 
 | ||||||
|  |         qpos = self.set_floating_base_qpos(base_qpos, qpos) | ||||||
|  |         # print(f'DEBUG1 base qpos: {qpos}') | ||||||
|  |         # init joint position | ||||||
|  |         # qpos[7:]=*U(0.0, 0.1) | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  | 
 | ||||||
|  |         # multiply actual joints with noise (excluding floating base and backlash) | ||||||
|  |         qpos_j = self.get_actuator_joints_qpos(qpos) * jax.random.uniform( | ||||||
|  |             key, (self._actuators,), minval=0.5, maxval=1.5 | ||||||
|  |         ) | ||||||
|  |         qpos = self.set_actuator_joints_qpos(qpos_j, qpos) | ||||||
|  |         # print(f'DEBUG2 joint qpos: {qpos}') | ||||||
|  |         # init joint vel | ||||||
|  |         # d(xyzrpy)=U(-0.05, 0.05) | ||||||
|  |         rng, key = jax.random.split(rng) | ||||||
|  |         # qvel = qvel.at[self._floating_base_qvel_addr : self._floating_base_qvel_addr + 6].set( | ||||||
|  |         #     jax.random.uniform(key, (6,), minval=-0.5, maxval=0.5) | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|  |         qvel = self.set_floating_base_qvel( | ||||||
|  |             jax.random.uniform(key, (6,), minval=-0.5, maxval=0.5), qvel | ||||||
|  |         ) | ||||||
|  |         # print(f'DEBUG3 base qvel: {qvel}') | ||||||
|  |         ctrl = self.get_actuator_joints_qpos(qpos) | ||||||
|  |         # print(f'DEBUG4 ctrl: {ctrl}') | ||||||
|  |         data = mjx_env.init(self.mjx_model, qpos=qpos, qvel=qvel, ctrl=ctrl) | ||||||
|  |         rng, cmd_rng = jax.random.split(rng) | ||||||
|  |         cmd = self.sample_command(cmd_rng) | ||||||
|  | 
 | ||||||
|  |         # Sample push interval. | ||||||
|  |         rng, push_rng = jax.random.split(rng) | ||||||
|  |         push_interval = jax.random.uniform( | ||||||
|  |             push_rng, | ||||||
|  |             minval=self._config.push_config.interval_range[0], | ||||||
|  |             maxval=self._config.push_config.interval_range[1], | ||||||
|  |         ) | ||||||
|  |         push_interval_steps = jp.round(push_interval / self.dt).astype(jp.int32) | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             current_reference_motion = self.PRM.get_reference_motion( | ||||||
|  |                 cmd[0], cmd[1], cmd[2], 0 | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             current_reference_motion = jp.zeros(0) | ||||||
|  | 
 | ||||||
|  |         info = { | ||||||
|  |             "rng": rng, | ||||||
|  |             "step": 0, | ||||||
|  |             "command": cmd, | ||||||
|  |             "last_act": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "last_last_act": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "last_last_last_act": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "motor_targets": jp.zeros(self.mjx_model.nu), | ||||||
|  |             "feet_air_time": jp.zeros(2), | ||||||
|  |             "last_contact": jp.zeros(2, dtype=bool), | ||||||
|  |             "swing_peak": jp.zeros(2), | ||||||
|  |             # Push related. | ||||||
|  |             "push": jp.array([0.0, 0.0]), | ||||||
|  |             "push_step": 0, | ||||||
|  |             "push_interval_steps": push_interval_steps, | ||||||
|  |             # History related. | ||||||
|  |             "action_history": jp.zeros( | ||||||
|  |                 self._config.noise_config.action_max_delay * self._actuators | ||||||
|  |             ), | ||||||
|  |             "imu_history": jp.zeros(self._config.noise_config.imu_max_delay * 3), | ||||||
|  |             # imitation related | ||||||
|  |             "imitation_i": 0, | ||||||
|  |             "current_reference_motion": current_reference_motion, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         metrics = {} | ||||||
|  |         for k, v in self._config.reward_config.scales.items(): | ||||||
|  |             if v != 0: | ||||||
|  |                 if v > 0: | ||||||
|  |                     metrics[f"reward/{k}"] = jp.zeros(()) | ||||||
|  |                 else: | ||||||
|  |                     metrics[f"cost/{k}"] = jp.zeros(()) | ||||||
|  |         metrics["swing_peak"] = jp.zeros(()) | ||||||
|  | 
 | ||||||
|  |         contact = jp.array( | ||||||
|  |             [ | ||||||
|  |                 geoms_colliding(data, geom_id, self._floor_geom_id) | ||||||
|  |                 for geom_id in self._feet_geom_id | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         obs = self._get_obs(data, info, contact) | ||||||
|  |         reward, done = jp.zeros(2) | ||||||
|  |         return mjx_env.State(data, obs, reward, done, metrics, info) | ||||||
|  | 
 | ||||||
|  |     def step(self, state: mjx_env.State, action: jax.Array) -> mjx_env.State: | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             state.info["imitation_i"] += 1 | ||||||
|  |             state.info["imitation_i"] = ( | ||||||
|  |                 state.info["imitation_i"] % self.PRM.nb_steps_in_period | ||||||
|  |             )  # not critical, is already moduloed in get_reference_motion | ||||||
|  |         else: | ||||||
|  |             state.info["imitation_i"] = 0 | ||||||
|  | 
 | ||||||
|  |         if USE_IMITATION_REWARD: | ||||||
|  |             state.info["current_reference_motion"] = self.PRM.get_reference_motion( | ||||||
|  |                 state.info["command"][0], | ||||||
|  |                 state.info["command"][1], | ||||||
|  |                 state.info["command"][2], | ||||||
|  |                 state.info["imitation_i"], | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             state.info["current_reference_motion"] = jp.zeros(0) | ||||||
|  | 
 | ||||||
|  |         state.info["rng"], push1_rng, push2_rng, action_delay_rng = jax.random.split( | ||||||
|  |             state.info["rng"], 4 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Handle action delay | ||||||
|  |         action_history = ( | ||||||
|  |             jp.roll(state.info["action_history"], self._actuators) | ||||||
|  |             .at[: self._actuators] | ||||||
|  |             .set(action) | ||||||
|  |         ) | ||||||
|  |         state.info["action_history"] = action_history | ||||||
|  |         action_idx = jax.random.randint( | ||||||
|  |             action_delay_rng, | ||||||
|  |             (1,), | ||||||
|  |             minval=self._config.noise_config.action_min_delay, | ||||||
|  |             maxval=self._config.noise_config.action_max_delay, | ||||||
|  |         ) | ||||||
|  |         action_w_delay = action_history.reshape((-1, self._actuators))[ | ||||||
|  |             action_idx[0] | ||||||
|  |         ]  # action with delay | ||||||
|  | 
 | ||||||
|  |         push_theta = jax.random.uniform(push1_rng, maxval=2 * jp.pi) | ||||||
|  |         push_magnitude = jax.random.uniform( | ||||||
|  |             push2_rng, | ||||||
|  |             minval=self._config.push_config.magnitude_range[0], | ||||||
|  |             maxval=self._config.push_config.magnitude_range[1], | ||||||
|  |         ) | ||||||
|  |         push = jp.array([jp.cos(push_theta), jp.sin(push_theta)]) | ||||||
|  |         push *= ( | ||||||
|  |             jp.mod(state.info["push_step"] + 1, state.info["push_interval_steps"]) == 0 | ||||||
|  |         ) | ||||||
|  |         push *= self._config.push_config.enable | ||||||
|  |         qvel = state.data.qvel | ||||||
|  |         qvel = qvel.at[ | ||||||
|  |             self._floating_base_qvel_addr : self._floating_base_qvel_addr + 2 | ||||||
|  |         ].set( | ||||||
|  |             push * push_magnitude | ||||||
|  |             + qvel[self._floating_base_qvel_addr : self._floating_base_qvel_addr + 2] | ||||||
|  |         )  # floating base x,y | ||||||
|  |         data = state.data.replace(qvel=qvel) | ||||||
|  |         state = state.replace(data=data) | ||||||
|  | 
 | ||||||
|  |         motor_targets = ( | ||||||
|  |             self._default_actuator + action_w_delay * self._config.action_scale | ||||||
|  |         ) | ||||||
|  |         data = mjx_env.step(self.mjx_model, state.data, motor_targets, self.n_substeps) | ||||||
|  |         state.info["motor_targets"] = motor_targets | ||||||
|  | 
 | ||||||
|  |         contact = jp.array( | ||||||
|  |             [ | ||||||
|  |                 geoms_colliding(data, geom_id, self._floor_geom_id) | ||||||
|  |                 for geom_id in self._feet_geom_id | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         contact_filt = contact | state.info["last_contact"] | ||||||
|  |         first_contact = (state.info["feet_air_time"] > 0.0) * contact_filt | ||||||
|  |         state.info["feet_air_time"] += self.dt | ||||||
|  |         p_f = data.site_xpos[self._feet_site_id] | ||||||
|  |         p_fz = p_f[..., -1] | ||||||
|  |         state.info["swing_peak"] = jp.maximum(state.info["swing_peak"], p_fz) | ||||||
|  | 
 | ||||||
|  |         obs = self._get_obs(data, state.info, contact) | ||||||
|  |         done = self._get_termination(data) | ||||||
|  | 
 | ||||||
|  |         rewards = self._get_reward( | ||||||
|  |             data, action, state.info, state.metrics, done, first_contact, contact | ||||||
|  |         ) | ||||||
|  |         # FIXME | ||||||
|  |         rewards = { | ||||||
|  |             k: v * self._config.reward_config.scales[k] for k, v in rewards.items() | ||||||
|  |         } | ||||||
|  |         reward = jp.clip(sum(rewards.values()) * self.dt, 0.0, 10000.0) | ||||||
|  |         # jax.debug.print('STEP REWARD: {}',reward) | ||||||
|  |         state.info["push"] = push | ||||||
|  |         state.info["step"] += 1 | ||||||
|  |         state.info["push_step"] += 1 | ||||||
|  |         state.info["last_last_last_act"] = state.info["last_last_act"] | ||||||
|  |         state.info["last_last_act"] = state.info["last_act"] | ||||||
|  |         state.info["last_act"] = action | ||||||
|  |         state.info["rng"], cmd_rng = jax.random.split(state.info["rng"]) | ||||||
|  |         state.info["command"] = jp.where( | ||||||
|  |             state.info["step"] > 500, | ||||||
|  |             self.sample_command(cmd_rng), | ||||||
|  |             state.info["command"], | ||||||
|  |         ) | ||||||
|  |         state.info["step"] = jp.where( | ||||||
|  |             done | (state.info["step"] > 500), | ||||||
|  |             0, | ||||||
|  |             state.info["step"], | ||||||
|  |         ) | ||||||
|  |         state.info["feet_air_time"] *= ~contact | ||||||
|  |         state.info["last_contact"] = contact | ||||||
|  |         state.info["swing_peak"] *= ~contact | ||||||
|  |         for k, v in rewards.items(): | ||||||
|  |             rew_scale = self._config.reward_config.scales[k] | ||||||
|  |             if rew_scale != 0: | ||||||
|  |                 if rew_scale > 0: | ||||||
|  |                     state.metrics[f"reward/{k}"] = v | ||||||
|  |                 else: | ||||||
|  |                     state.metrics[f"cost/{k}"] = -v | ||||||
|  |         state.metrics["swing_peak"] = jp.mean(state.info["swing_peak"]) | ||||||
|  | 
 | ||||||
|  |         done = done.astype(reward.dtype) | ||||||
|  |         state = state.replace(data=data, obs=obs, reward=reward, done=done) | ||||||
|  |         return state | ||||||
|  | 
 | ||||||
|  |     def _get_termination(self, data: mjx.Data) -> jax.Array: | ||||||
|  |         fall_termination = self.get_gravity(data)[-1] < 0.0 | ||||||
|  |         return fall_termination | jp.isnan(data.qpos).any() | jp.isnan(data.qvel).any() | ||||||
|  | 
 | ||||||
|  |     def _get_obs( | ||||||
|  |         self, data: mjx.Data, info: dict[str, Any], contact: jax.Array | ||||||
|  |     ) -> mjx_env.Observation: | ||||||
|  | 
 | ||||||
|  |         gyro = self.get_gyro(data) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_gyro = ( | ||||||
|  |             gyro | ||||||
|  |             + (2 * jax.random.uniform(noise_rng, shape=gyro.shape) - 1) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.gyro | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         accelerometer = self.get_accelerometer(data) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_accelerometer = ( | ||||||
|  |             accelerometer | ||||||
|  |             + (2 * jax.random.uniform(noise_rng, shape=accelerometer.shape) - 1) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.accelerometer | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         gravity = data.site_xmat[self._site_id].T @ jp.array([0, 0, -1]) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_gravity = ( | ||||||
|  |             gravity | ||||||
|  |             + (2 * jax.random.uniform(noise_rng, shape=gravity.shape) - 1) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.gravity | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Handle IMU delay | ||||||
|  |         imu_history = jp.roll(info["imu_history"], 3).at[:3].set(noisy_gravity) | ||||||
|  |         info["imu_history"] = imu_history | ||||||
|  |         imu_idx = jax.random.randint( | ||||||
|  |             noise_rng, | ||||||
|  |             (1,), | ||||||
|  |             minval=self._config.noise_config.imu_min_delay, | ||||||
|  |             maxval=self._config.noise_config.imu_max_delay, | ||||||
|  |         ) | ||||||
|  |         noisy_gravity = imu_history.reshape((-1, 3))[imu_idx[0]] | ||||||
|  | 
 | ||||||
|  |         # joint_angles = data.qpos[7:] | ||||||
|  | 
 | ||||||
|  |         # Handling backlash | ||||||
|  |         joint_angles = self.get_actuator_joints_qpos(data.qpos) | ||||||
|  |         joint_backlash = self.get_actuator_backlash_qpos(data.qpos) | ||||||
|  | 
 | ||||||
|  |         for i in self.backlash_idx_to_add: | ||||||
|  |             joint_backlash = jp.insert(joint_backlash, i, 0) | ||||||
|  | 
 | ||||||
|  |         joint_angles = joint_angles + joint_backlash | ||||||
|  | 
 | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_joint_angles = ( | ||||||
|  |             joint_angles | ||||||
|  |             + (2.0 * jax.random.uniform(noise_rng, shape=joint_angles.shape) - 1.0) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._qpos_noise_scale | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # joint_vel = data.qvel[6:] | ||||||
|  |         joint_vel = self.get_actuator_joints_qvel(data.qvel) | ||||||
|  |         info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         noisy_joint_vel = ( | ||||||
|  |             joint_vel | ||||||
|  |             + (2.0 * jax.random.uniform(noise_rng, shape=joint_vel.shape) - 1.0) | ||||||
|  |             * self._config.noise_config.level | ||||||
|  |             * self._config.noise_config.scales.joint_vel | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         linvel = self.get_local_linvel(data) | ||||||
|  |         # info["rng"], noise_rng = jax.random.split(info["rng"]) | ||||||
|  |         # noisy_linvel = ( | ||||||
|  |         #     linvel | ||||||
|  |         #     + (2 * jax.random.uniform(noise_rng, shape=linvel.shape) - 1) | ||||||
|  |         #     * self._config.noise_config.level | ||||||
|  |         #     * self._config.noise_config.scales.linvel | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|  |         state = jp.hstack( | ||||||
|  |             [ | ||||||
|  |                 # noisy_linvel,  # 3 | ||||||
|  |                 # noisy_gyro,  # 3 | ||||||
|  |                 # noisy_gravity,  # 3 | ||||||
|  |                 noisy_gyro,  # 3 | ||||||
|  |                 noisy_accelerometer,  # 3 | ||||||
|  |                 info["command"],  # 3 | ||||||
|  |                 noisy_joint_angles - self._default_actuator,  # 10 | ||||||
|  |                 noisy_joint_vel * self._config.dof_vel_scale,  # 10 | ||||||
|  |                 info["last_act"],  # 10 | ||||||
|  |                 info["last_last_act"],  # 10 | ||||||
|  |                 info["last_last_last_act"],  # 10 | ||||||
|  |                 contact,  # 2 | ||||||
|  |                 info["current_reference_motion"], | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         accelerometer = self.get_accelerometer(data) | ||||||
|  |         global_angvel = self.get_global_angvel(data) | ||||||
|  |         feet_vel = data.sensordata[self._foot_linvel_sensor_adr].ravel() | ||||||
|  |         root_height = data.qpos[self._floating_base_qpos_addr + 2] | ||||||
|  | 
 | ||||||
|  |         privileged_state = jp.hstack( | ||||||
|  |             [ | ||||||
|  |                 state, | ||||||
|  |                 gyro,  # 3 | ||||||
|  |                 accelerometer,  # 3 | ||||||
|  |                 gravity,  # 3 | ||||||
|  |                 linvel,  # 3 | ||||||
|  |                 global_angvel,  # 3 | ||||||
|  |                 joint_angles - self._default_actuator, | ||||||
|  |                 joint_vel, | ||||||
|  |                 root_height,  # 1 | ||||||
|  |                 data.actuator_force,  # 10 | ||||||
|  |                 contact,  # 2 | ||||||
|  |                 feet_vel,  # 4*3 | ||||||
|  |                 info["feet_air_time"],  # 2 | ||||||
|  |                 info["current_reference_motion"], | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             "state": state, | ||||||
|  |             "privileged_state": privileged_state, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     def _get_reward( | ||||||
|  |         self, | ||||||
|  |         data: mjx.Data, | ||||||
|  |         action: jax.Array, | ||||||
|  |         info: dict[str, Any], | ||||||
|  |         metrics: dict[str, Any], | ||||||
|  |         done: jax.Array, | ||||||
|  |         first_contact: jax.Array, | ||||||
|  |         contact: jax.Array, | ||||||
|  |     ) -> dict[str, jax.Array]: | ||||||
|  |         del metrics  # Unused. | ||||||
|  | 
 | ||||||
|  |         ret = { | ||||||
|  |             "orientation": cost_orientation(self.get_gravity(data)), | ||||||
|  |             "torques": cost_torques(data.actuator_force), | ||||||
|  |             "action_rate": cost_action_rate(action, info["last_act"]), | ||||||
|  |             "alive": reward_alive(), | ||||||
|  |             "stand_still": cost_stand_still( | ||||||
|  |                 # info["command"], data.qpos[7:], data.qvel[6:], self._default_pose | ||||||
|  |                 info["command"], | ||||||
|  |                 self.get_actuator_joints_qpos(data.qpos), | ||||||
|  |                 self.get_actuator_joints_qvel(data.qvel), | ||||||
|  |                 self._default_actuator, | ||||||
|  |                 True | ||||||
|  |             ), | ||||||
|  |             "head_pos": cost_head_pos( | ||||||
|  |                 self.get_actuator_joints_qpos(data.qpos), | ||||||
|  |                 self.get_actuator_joints_qvel(data.qvel), | ||||||
|  |                 info["command"], | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return ret | ||||||
|  | 
 | ||||||
|  |     def sample_command(self, rng: jax.Array) -> jax.Array: | ||||||
|  |         rng1, rng2, rng3, rng4, rng5, rng6, rng7, rng8 = jax.random.split(rng, 8) | ||||||
|  | 
 | ||||||
|  |         # lin_vel_x = jax.random.uniform( | ||||||
|  |         #     rng1, minval=self._config.lin_vel_x[0], maxval=self._config.lin_vel_x[1] | ||||||
|  |         # ) | ||||||
|  |         # lin_vel_y = jax.random.uniform( | ||||||
|  |         #     rng2, minval=self._config.lin_vel_y[0], maxval=self._config.lin_vel_y[1] | ||||||
|  |         # ) | ||||||
|  |         # ang_vel_yaw = jax.random.uniform( | ||||||
|  |         #     rng3, | ||||||
|  |         #     minval=self._config.ang_vel_yaw[0], | ||||||
|  |         #     maxval=self._config.ang_vel_yaw[1], | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|  |         neck_pitch = jax.random.uniform( | ||||||
|  |             rng5, | ||||||
|  |             minval=self._config.neck_pitch_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.neck_pitch_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         head_pitch = jax.random.uniform( | ||||||
|  |             rng6, | ||||||
|  |             minval=self._config.head_pitch_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.head_pitch_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         head_yaw = jax.random.uniform( | ||||||
|  |             rng7, | ||||||
|  |             minval=self._config.head_yaw_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.head_yaw_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         head_roll = jax.random.uniform( | ||||||
|  |             rng8, | ||||||
|  |             minval=self._config.head_roll_range[0] * self._config.head_range_factor, | ||||||
|  |             maxval=self._config.head_roll_range[1] * self._config.head_range_factor, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # With 10% chance, set everything to zero. | ||||||
|  |         return jp.where( | ||||||
|  |             jax.random.bernoulli(rng4, p=0.1), | ||||||
|  |             jp.zeros(7), | ||||||
|  |             jp.hstack( | ||||||
|  |                 [ | ||||||
|  |                     0.0, | ||||||
|  |                     0.0, | ||||||
|  |                     0.0, | ||||||
|  |                     neck_pitch, | ||||||
|  |                     head_pitch, | ||||||
|  |                     head_yaw, | ||||||
|  |                     head_roll, | ||||||
|  |                 ] | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MmXLbiyIJZ1T9tiX3", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "antenna <1>", | ||||||
|  |     "partId": "RGPD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MfCA08JspCUYCoOzy", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "body_back <1>", | ||||||
|  |     "partId": "RqKD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MAILAHna+4EMDjwEB", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "body_front <1>", | ||||||
|  |     "partId": "RbKD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "Mi7/ZBgz7j8FhNN8n", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "body_middle_bottom <1>", | ||||||
|  |     "partId": "R/KH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MwLs2m7WqJdIp5rCM", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "body_middle_top <1>", | ||||||
|  |     "partId": "R/KD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MbWaXbNFhwXrvfPr1", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "foot_bottom_pla <1>", | ||||||
|  |     "partId": "R7GH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MSRJjnMROc2yqGlBV", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "foot_bottom_tpu <1>", | ||||||
|  |     "partId": "R7GD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "M9QvdrmpVd96BQQ3J", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "foot_side <1>", | ||||||
|  |     "partId": "RsGD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MryNg763VjsN4xBWU", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "foot_top <1>", | ||||||
|  |     "partId": "RsGH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MGPnRRHSCLRuHbf9s", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "head <1>", | ||||||
|  |     "partId": "RXMD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MB88Zh5nm8uQRQJB5", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "head_bot_sheet <1>", | ||||||
|  |     "partId": "RwMD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MMYrJ8GU6Tulh9Ezl", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "head_pitch_to_yaw <1>", | ||||||
|  |     "partId": "RGCD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MSSQrz89LHCpx23/c", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "head_yaw_to_roll <1>", | ||||||
|  |     "partId": "RyCD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 166 KiB | 
| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MqpShx/iKn+WMc6nz", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "left_antenna_holder <1>", | ||||||
|  |     "partId": "R3PL", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "Mh206kD+LDwOu9+3I", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "left_cache <1>", | ||||||
|  |     "partId": "RELD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MkexR4anxdIDvoNnP", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "left_knee_to_ankle_left_sheet <1>", | ||||||
|  |     "partId": "RyDD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MFSYykuSOyFPl8MEE", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "left_knee_to_ankle_right_sheet <1>", | ||||||
|  |     "partId": "RyDH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "Mg7gXWiWLFdTkX/WZ", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "left_roll_to_pitch <1>", | ||||||
|  |     "partId": "RRFD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "M1AJs3nTP2bD6DgAF", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "leg_spacer <1>", | ||||||
|  |     "partId": "RfED", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "M8Je0JZqWOimCS7cY", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "neck_left_sheet <1>", | ||||||
|  |     "partId": "RgLD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "M8fJckSPn4u+ct8pO", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "neck_right_sheet <1>", | ||||||
|  |     "partId": "RgLH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "M/p8PGArLA1nrXwe8", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "right_antenna_holder <1>", | ||||||
|  |     "partId": "R0OD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MBtFsLan/52XkruX7", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "right_cache <1>", | ||||||
|  |     "partId": "R3PH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "Mvz4SlAytL1YUO5L4", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "right_roll_to_pitch <1>", | ||||||
|  |     "partId": "R3PD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MjsDdlagt/LopvMjR", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "roll_motor_bottom <1>", | ||||||
|  |     "partId": "RGHD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MoZYOvTkbbGLGW0df", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "roll_motor_top <1>", | ||||||
|  |     "partId": "RyHD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MMYHrJ4hqH24cpa8I", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "trunk_bottom <1>", | ||||||
|  |     "partId": "RvJH", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "configuration": "default", | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "documentMicroversion": "78f84c802a6a701851609660", | ||||||
|  |     "elementId": "560456e30aa4a28c1f2fba59", | ||||||
|  |     "fullConfiguration": "default", | ||||||
|  |     "id": "MNg8avQSkDpa0UkRX", | ||||||
|  |     "isStandardContent": false, | ||||||
|  |     "name": "trunk_top <1>", | ||||||
|  |     "partId": "RvJD", | ||||||
|  |     "suppressed": false, | ||||||
|  |     "type": "Part" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | { | ||||||
|  |     // NEED TO WRAP all in a body named "base" and add site  | ||||||
|  |     // <site name="imu" pos="-0.08 -0.0 0.05"/> | ||||||
|  |     // and comment the default kp frictionloss etc | ||||||
|  |     "documentId": "64074dfcfa379b37d8a47762", | ||||||
|  |     "outputFormat": "mujoco", | ||||||
|  |     "assemblyName": "Open_Duck_Mini_v2", | ||||||
|  |     "robot_name": "open_duck_mini_v2", | ||||||
|  |     "output_filename": "open_duck_mini_v2", | ||||||
|  |     // Simplify STL meshes (default: false) | ||||||
|  |     "simplify_stls": true, | ||||||
|  |     // Maximum size of the STL files in MB (default: 3) | ||||||
|  |     "max_stl_size": 0.005, | ||||||
|  |     // "collisions_as_visual": true, | ||||||
|  |     "ignore": { | ||||||
|  |         "wj*": "visual", | ||||||
|  |         "drive_palonier": "visual", | ||||||
|  |         "passive_palonier": "visual", | ||||||
|  |         "bulb": "visual", | ||||||
|  |         "cam": "visual", | ||||||
|  |         "sg90": "visual", | ||||||
|  |         "head_roll_mount": "visual", | ||||||
|  |         "raspberrypizerow": "visual", | ||||||
|  |         "speaker_stand": "visual", | ||||||
|  |         "speaker_interface": "visual", | ||||||
|  |         "holder": "visual", | ||||||
|  |         "cell": "visual", | ||||||
|  |         "bms": "visual", | ||||||
|  |         "usb_c_charger": "visual", | ||||||
|  |         "battery_pack_lid": "visual", | ||||||
|  |         "right_eye": "visual", | ||||||
|  |         "left_eye": "visual", | ||||||
|  |         "glass": "visual", | ||||||
|  |         "full_speaker": "visual", | ||||||
|  |         "roll_bearing": "visual", | ||||||
|  |         "power_switch": "visual", | ||||||
|  |         "board": "visual", | ||||||
|  |         "bno055": "visual", | ||||||
|  |         "*": "collision", | ||||||
|  |         "!foot_bottom_tpu": "collision" | ||||||
|  |     }, | ||||||
|  |     "joint_properties": { | ||||||
|  |         "default": { | ||||||
|  |             "class": "sts3215" | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "additional_xml": [ | ||||||
|  |         "sensors.xml", | ||||||
|  |         "joints_properties.xml" | ||||||
|  |     ] | ||||||
|  | } | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | <default> | ||||||
|  |     <default class="sts3215"> | ||||||
|  |         <geom contype="0" conaffinity="0"/> | ||||||
|  |         <joint damping="0.60" frictionloss="0.052" armature="0.028"/> | ||||||
|  |         <position kp="17.8" kv="0.0" forcerange="-3.35 3.35"/> | ||||||
|  |     </default> | ||||||
|  |     <default class="backlash"> | ||||||
|  |         <!-- +/- 0.5° of backlash --> | ||||||
|  |         <joint damping="0.01" frictionloss="0" armature="0.01" limited="true" | ||||||
|  |             range="-0.008726646259971648 0.008726646259971648"/> | ||||||
|  |     </default> | ||||||
|  | </default> | ||||||
| @ -0,0 +1,503 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <!-- Generated using onshape-to-robot --> | ||||||
|  | <!-- Onshape document_id: 64074dfcfa379b37d8a47762 --> | ||||||
|  | <mujoco model="open_duck_mini_v2"> | ||||||
|  | 
 | ||||||
|  |   <option iterations="1" ls_iterations="5"> | ||||||
|  |     <flag eulerdamp="disable"/> | ||||||
|  |   </option> | ||||||
|  |   <compiler angle="radian" meshdir="assets"/> | ||||||
|  | 
 | ||||||
|  |   <!-- <option noslip_iterations="1"/> | ||||||
|  |   <compiler angle="radian" meshdir="assets" autolimits="true"/> --> | ||||||
|  |   <default> | ||||||
|  |     <default class="open_duck_mini_v2"> | ||||||
|  |       <joint frictionloss="0.1" armature="0.005"/> | ||||||
|  |       <position kp="50" dampratio="1"/> | ||||||
|  |       <default class="visual"> | ||||||
|  |         <geom type="mesh" contype="0" conaffinity="0" group="2"/> | ||||||
|  |       </default> | ||||||
|  |       <default class="collision"> | ||||||
|  |         <geom group="3"/> | ||||||
|  |       </default> | ||||||
|  |     </default> | ||||||
|  |   </default> | ||||||
|  |   <!-- Additional sensors.xml --> | ||||||
|  |   <sensor> | ||||||
|  |     <gyro site="imu" name="gyro"/> | ||||||
|  |     <velocimeter site="imu" name="local_linvel"/> | ||||||
|  |     <accelerometer site="imu" name="accelerometer"/> | ||||||
|  |     <framezaxis objtype="site" objname="imu" name="upvector"/> | ||||||
|  |     <framexaxis objtype="site" objname="imu" name="forwardvector"/> | ||||||
|  |     <framelinvel objtype="site" objname="imu" name="global_linvel"/> | ||||||
|  |     <frameangvel objtype="site" objname="imu" name="global_angvel"/> | ||||||
|  |     <framepos objtype="site" objname="imu" name="position"/> | ||||||
|  |     <framequat objtype="site" objname="imu" name="orientation"/> | ||||||
|  |     <framelinvel objtype="site" objname="right_foot" name="right_foot_global_linvel"/> | ||||||
|  |     <framelinvel objtype="site" objname="left_foot" name="left_foot_global_linvel"/> | ||||||
|  |     <framexaxis objtype="site" objname="left_foot" name="left_foot_upvector"/> | ||||||
|  |     <framexaxis objtype="site" objname="right_foot" name="right_foot_upvector"/> | ||||||
|  |     <framepos objtype="site" objname="left_foot" name="left_foot_pos"/> | ||||||
|  |     <framepos objtype="site" objname="right_foot" name="right_foot_pos"/> | ||||||
|  |   </sensor> | ||||||
|  |   <!-- Additional joints_properties.xml --> | ||||||
|  |   <default> | ||||||
|  |     <default class="sts3215"> | ||||||
|  |       <geom contype="0" conaffinity="0"/> | ||||||
|  |       <joint damping="0.56" frictionloss="0.068" armature="0.027"/> | ||||||
|  |       <!-- <position kp="17.12" kv="0.0" forcerange="-3.23 3.23"/> --> | ||||||
|  |       <position kp="13.37" kv="0.0" forcerange="-3.23 3.23"/> | ||||||
|  |     </default> | ||||||
|  |     <default class="backlash"> | ||||||
|  |       <!-- +/- 0.5° of backlash --> | ||||||
|  |       <joint damping="0.01" frictionloss="0" armature="0.01" limited="true" | ||||||
|  |         range="-0.008726646259971648 0.008726646259971648"/> | ||||||
|  |     </default> | ||||||
|  |   </default> | ||||||
|  |   <worldbody> | ||||||
|  |     <body name="base" pos="0 0 0.22"> | ||||||
|  |       <freejoint name="floating_base"/> | ||||||
|  |       <site name="imu" pos="-0.08 -0.0 0.05"/> | ||||||
|  |       <!-- Link trunk_assembly --> | ||||||
|  |       <body name="trunk_assembly" pos="0 0 0.0" quat="1 0 0 0" childclass="open_duck_mini_v2"> | ||||||
|  |         <inertial pos="-0.0483259 -9.97823e-05 0.0384971" mass="0.698526" | ||||||
|  |           fullinertia="0.00167701 0.00344479 0.00292635 -1.31662e-05 -3.24953e-05 -2.15581e-06"/> | ||||||
|  |         <!-- Part body_front --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 0 0.0648909" quat="1 0 0 0" mesh="body_front" | ||||||
|  |           material="body_front_material"/> | ||||||
|  |         <!-- Part usb_c_charger --> | ||||||
|  |         <!-- Part body_back --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 3.46945e-18 0.0648909" quat="1 0 0 0" mesh="body_back" | ||||||
|  |           material="body_back_material"/> | ||||||
|  |         <!-- Part body_middle_bottom --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 -3.46945e-18 0.0648909" quat="1 0 0 0" mesh="body_middle_bottom" | ||||||
|  |           material="body_middle_bottom_material"/> | ||||||
|  |         <!-- Part power_switch --> | ||||||
|  |         <!-- Part bms --> | ||||||
|  |         <!-- Part battery_pack_lid --> | ||||||
|  |         <!-- Part body_middle_top --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 0 0.0648909" quat="1 0 0 0" mesh="body_middle_top" | ||||||
|  |           material="body_middle_top_material"/> | ||||||
|  |         <!-- Part bno055 --> | ||||||
|  |         <!-- Part roll_bearing --> | ||||||
|  |         <!-- Part trunk_bottom --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 9.1073e-18 0.0648909" quat="1 -0 -0 5.42101e-20" | ||||||
|  |           mesh="trunk_bottom" material="trunk_bottom_material"/> | ||||||
|  |         <!-- Part trunk_top --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 -2.42428e-16 0.0648909" | ||||||
|  |           quat="1 -4.82108e-17 -5.5355e-15 2.77556e-17" mesh="trunk_top" material="trunk_top_material"/> | ||||||
|  |         <!-- Part board --> | ||||||
|  |         <!-- Part roll_bearing_2 --> | ||||||
|  |         <!-- Part cell --> | ||||||
|  |         <!-- Part holder --> | ||||||
|  |         <!-- Part cell_2 --> | ||||||
|  |         <!-- Part wj_wk00_0123middlecase_56 --> | ||||||
|  |         <!-- Part drive_palonier --> | ||||||
|  |         <!-- Part wj_wk00_0124bottomcase_45 --> | ||||||
|  |         <!-- Part wj_wk00_0122topcabinetcase_95 --> | ||||||
|  |         <!-- Part passive_palonier --> | ||||||
|  |         <!-- Part wj_wk00_0123middlecase_56_2 --> | ||||||
|  |         <!-- Part drive_palonier_2 --> | ||||||
|  |         <!-- Part wj_wk00_0124bottomcase_45_2 --> | ||||||
|  |         <!-- Part wj_wk00_0122topcabinetcase_95_2 --> | ||||||
|  |         <!-- Part passive_palonier_2 --> | ||||||
|  |         <!-- Part wj_wk00_0123middlecase_56_3 --> | ||||||
|  |         <!-- Part drive_palonier_3 --> | ||||||
|  |         <!-- Part wj_wk00_0124bottomcase_45_3 --> | ||||||
|  |         <!-- Part wj_wk00_0122topcabinetcase_95_3 --> | ||||||
|  |         <!-- Part passive_palonier_3 --> | ||||||
|  |         <!-- Frame trunk --> | ||||||
|  |         <site group="0" name="trunk" pos="-0.024 -2.41127e-16 0.0881909" quat="1 -2.77556e-17 -0 -0"/> | ||||||
|  |         <!-- Frame imu --> | ||||||
|  |         <!-- <site group="0" name="imu" pos="-0.08711 1.249e-16 0.0422909" quat="1 -7.03105e-17 -2.77556e-17 | ||||||
|  |         -3.14419e-16"/> --> | ||||||
|  |         <!-- Link hip_roll_assembly --> | ||||||
|  |         <body name="hip_roll_assembly" pos="-0.019 0.035 0.0459409" quat="1 -1.17663e-16 -1.09357e-14 2.77556e-17"> | ||||||
|  |           <!-- Joint from trunk_assembly to hip_roll_assembly --> | ||||||
|  |           <joint name="left_hip_yaw" type="hinge" range="-0.5235987755982979 0.5235987755982997" class="sts3215"/> | ||||||
|  |           <inertial pos="0.000795081 -5.46541e-06 -0.03306" mass="0.06648" | ||||||
|  |             fullinertia="2.44411e-05 2.81818e-05 1.42687e-05 2.35658e-09 -3.81059e-07 -7.50036e-09"/> | ||||||
|  |           <!-- Part roll_motor_bottom --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-3.33067e-16 -0.035 0.01905" | ||||||
|  |             quat="1 -4.38499e-16 2.22045e-16 -3.34802e-16" mesh="roll_motor_bottom" | ||||||
|  |             material="roll_motor_bottom_material"/> | ||||||
|  |           <!-- Part roll_motor_top --> | ||||||
|  |           <geom type="mesh" class="visual" pos="0 -0.035 0.01905" quat="1 -0 -1.80411e-16 -4.16334e-17" | ||||||
|  |             mesh="roll_motor_top" material="roll_motor_top_material"/> | ||||||
|  |           <!-- Part wj_wk00_0123middlecase_56_4 --> | ||||||
|  |           <!-- Part drive_palonier_4 --> | ||||||
|  |           <!-- Part wj_wk00_0124bottomcase_45_4 --> | ||||||
|  |           <!-- Part wj_wk00_0122topcabinetcase_95_4 --> | ||||||
|  |           <!-- Part passive_palonier_4 --> | ||||||
|  |           <!-- Link left_roll_to_pitch_assembly --> | ||||||
|  |           <body name="left_roll_to_pitch_assembly" pos="0.01905 4.61436e-15 -0.046015" quat="0.5 -0.5 -0.5 0.5"> | ||||||
|  |             <!-- Joint from hip_roll_assembly to left_roll_to_pitch_assembly --> | ||||||
|  |             <joint name="left_hip_roll" type="hinge" range="-0.4363323129985815 0.43633231299858327" class="sts3215"/> | ||||||
|  |             <inertial pos="0.0508042 -0.00041105 0.0204704" mass="0.07516" | ||||||
|  |               fullinertia="2.52335e-05 4.13855e-05 2.8127e-05 1.22133e-07 -5.90499e-07 -1.07544e-07"/> | ||||||
|  |             <!-- Part left_roll_to_pitch --> | ||||||
|  |             <geom type="mesh" class="visual" pos="-0.035 -0.065 0.01905" quat="0.5 0.5 0.5 -0.5" | ||||||
|  |               mesh="left_roll_to_pitch" material="left_roll_to_pitch_material"/> | ||||||
|  |             <!-- Part wj_wk00_0123middlecase_56_5 --> | ||||||
|  |             <!-- Part drive_palonier_5 --> | ||||||
|  |             <!-- Part wj_wk00_0124bottomcase_45_5 --> | ||||||
|  |             <!-- Part wj_wk00_0122topcabinetcase_95_5 --> | ||||||
|  |             <!-- Part passive_palonier_5 --> | ||||||
|  |             <!-- Link knee_and_ankle_assembly --> | ||||||
|  |             <body name="knee_and_ankle_assembly" pos="0.07415 -3.25261e-17 0.03511" | ||||||
|  |               quat="8.49887e-24 -0.707107 -1.11022e-16 0.707107"> | ||||||
|  |               <!-- Joint from left_roll_to_pitch_assembly to knee_and_ankle_assembly --> | ||||||
|  |               <joint name="left_hip_pitch" type="hinge" range="-1.2217304763960306 0.5235987755982988" class="sts3215"/> | ||||||
|  |               <inertial pos="0.00253369 -0.0390636 0.0102776" mass="0.12407" | ||||||
|  |                 fullinertia="0.000211406 7.83759e-05 0.000225619 -2.68706e-05 3.81906e-06 2.08009e-05"/> | ||||||
|  |               <!-- Part left_cache --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.065 0.10915" | ||||||
|  |                 quat="0.707107 -0.707107 3.33067e-16 5.76761e-16" mesh="left_cache" material="left_cache_material"/> | ||||||
|  |               <!-- Part leg_spacer --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10925" | ||||||
|  |                 quat="0.707107 -0.707107 3.33067e-16 5.94711e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_right_sheet --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 -5.93802e-24" | ||||||
|  |                 mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_left_sheet --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 5.12923e-30" | ||||||
|  |                 mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |               <!-- Part wj_wk00_0123middlecase_56_6 --> | ||||||
|  |               <!-- Part drive_palonier_6 --> | ||||||
|  |               <!-- Part wj_wk00_0124bottomcase_45_6 --> | ||||||
|  |               <!-- Part wj_wk00_0122topcabinetcase_95_6 --> | ||||||
|  |               <!-- Part passive_palonier_6 --> | ||||||
|  |               <!-- Link knee_and_ankle_assembly_2 --> | ||||||
|  |               <body name="knee_and_ankle_assembly_2" pos="0 -0.07865 0.0001" | ||||||
|  |                 quat="1 1.11022e-16 1.11022e-16 -2.64698e-23"> | ||||||
|  |                 <!-- Joint from knee_and_ankle_assembly to knee_and_ankle_assembly_2 --> | ||||||
|  |                 <joint name="left_knee" type="hinge" range="-1.5707963267948966 1.5707963267948966" class="sts3215"/> | ||||||
|  |                 <inertial pos="5.01859e-06 -0.0577465 0.0181136" mass="0.07259" | ||||||
|  |                   fullinertia="4.99575e-05 1.87273e-05 4.23932e-05 1.03817e-08 2.25175e-09 8.81049e-08"/> | ||||||
|  |                 <!-- Part leg_spacer_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10925" | ||||||
|  |                   quat="0.707107 -0.707107 3.33067e-16 5.72905e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_right_sheet_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 1.33119e-24" | ||||||
|  |                   mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_left_sheet_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 -5.36743e-30" | ||||||
|  |                   mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |                 <!-- Part wj_wk00_0123middlecase_56_7 --> | ||||||
|  |                 <!-- Part drive_palonier_7 --> | ||||||
|  |                 <!-- Part wj_wk00_0124bottomcase_45_7 --> | ||||||
|  |                 <!-- Part wj_wk00_0122topcabinetcase_95_7 --> | ||||||
|  |                 <!-- Part passive_palonier_7 --> | ||||||
|  |                 <!-- Link foot_assembly --> | ||||||
|  |                 <body name="foot_assembly" pos="1.8735e-16 -0.07865 0.0001" quat="1 2.77556e-16 5.55112e-16 1.52656e-16"> | ||||||
|  |                   <!-- Joint from knee_and_ankle_assembly_2 to foot_assembly --> | ||||||
|  |                   <joint name="left_ankle" type="hinge" range="-1.5707963267948957 1.5707963267948974" class="sts3215"/> | ||||||
|  |                   <inertial pos="0.0110718 -0.0246608 0.0190626" mass="0.07524" | ||||||
|  |                     fullinertia="1.87482e-05 6.74427e-05 6.061e-05 1.58839e-06 -6.52354e-09 5.67391e-08"/> | ||||||
|  |                   <!-- Part foot_side --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -4.61924e-30" mesh="foot_side" material="foot_side_material"/> | ||||||
|  |                   <!-- Part foot_bottom_tpu --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" quat="0.707107 -0.707107 -0 1.72496e-18" | ||||||
|  |                     mesh="foot_bottom_tpu" material="foot_bottom_tpu_material"/> | ||||||
|  |                   <geom type="mesh" class="collision" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 1.72496e-18" mesh="foot_bottom_tpu" material="foot_bottom_tpu_material" | ||||||
|  |                     name="left_foot_bottom_tpu"/> | ||||||
|  |                   <!-- Part foot_bottom_pla --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" quat="0.707107 -0.707107 -0 1.72496e-18" | ||||||
|  |                     mesh="foot_bottom_pla" material="foot_bottom_pla_material"/> | ||||||
|  |                   <!-- Part foot_top --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -4.61924e-30" mesh="foot_top" material="foot_top_material"/> | ||||||
|  |                   <!-- Frame left_foot --> | ||||||
|  |                   <site group="0" name="left_foot" pos="0.0005 -0.036225 0.01955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 1.72494e-18"/> | ||||||
|  |                 </body> | ||||||
|  |               </body> | ||||||
|  |             </body> | ||||||
|  |           </body> | ||||||
|  |         </body> | ||||||
|  |         <!-- Link neck_pitch_assembly --> | ||||||
|  |         <body name="neck_pitch_assembly" pos="0.001 0.01915 0.0900009" quat="0.707107 0.707107 -7.88258e-15 7.79925e-15"> | ||||||
|  |           <!-- Joint from trunk_assembly to neck_pitch_assembly --> | ||||||
|  |           <joint name="neck_pitch" type="hinge" range="-0.3490658503988437 1.1344640137963364" class="sts3215"/> | ||||||
|  |           <inertial pos="-5.63137e-06 0.0492968 0.0181786" mass="0.06618" | ||||||
|  |             fullinertia="3.49456e-05 1.70231e-05 2.80043e-05 8.75633e-09 -2.15592e-09 -1.85188e-08"/> | ||||||
|  |           <!-- Part neck_left_sheet --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-0.02 -0.02511 0.01905" quat="0.707107 -0.707107 0 -5.1423e-31" | ||||||
|  |             mesh="neck_left_sheet" material="neck_left_sheet_material"/> | ||||||
|  |           <!-- Part neck_right_sheet --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-0.02 -0.02511 0.01905" quat="0.707107 -0.707107 -0 3.06492e-17" | ||||||
|  |             mesh="neck_right_sheet" material="neck_right_sheet_material"/> | ||||||
|  |           <!-- Part wj_wk00_0123middlecase_56_8 --> | ||||||
|  |           <!-- Part drive_palonier_8 --> | ||||||
|  |           <!-- Part wj_wk00_0124bottomcase_45_8 --> | ||||||
|  |           <!-- Part wj_wk00_0122topcabinetcase_95_8 --> | ||||||
|  |           <!-- Part passive_palonier_8 --> | ||||||
|  |           <!-- Link head_pitch_to_yaw --> | ||||||
|  |           <body name="head_pitch_to_yaw" pos="-2.9924e-17 0.066 -6.93889e-18" quat="1 -1.7203e-16 0 1.10553e-17"> | ||||||
|  |             <!-- Joint from neck_pitch_assembly to head_pitch_to_yaw --> | ||||||
|  |             <joint name="head_pitch" type="hinge" range="-0.7853981633974483 0.7853981633974483" class="sts3215"/> | ||||||
|  |             <inertial pos="-0.00766247 0.026015 0.0186681" mass="0.0169378" | ||||||
|  |               fullinertia="9.43036e-06 4.64763e-06 8.02915e-06 1.35195e-06 3.05899e-08 -8.42802e-08"/> | ||||||
|  |             <!-- Part head_pitch_to_yaw --> | ||||||
|  |             <geom type="mesh" class="visual" pos="-0.02 -0.09111 0.01905" quat="0.707107 -0.707107 0 1.82143e-39" | ||||||
|  |               mesh="head_pitch_to_yaw" material="head_pitch_to_yaw_material"/> | ||||||
|  |             <!-- Link neck_yaw_assembly --> | ||||||
|  |             <body name="neck_yaw_assembly" pos="-4.33681e-19 0.057 0.01905" | ||||||
|  |               quat="0.707107 -0.707107 1.11022e-16 -5.88785e-17"> | ||||||
|  |               <!-- Joint from head_pitch_to_yaw to neck_yaw_assembly --> | ||||||
|  |               <joint name="head_yaw" type="hinge" range="-2.792526803190927 2.792526803190927" class="sts3215"/> | ||||||
|  |               <inertial pos="0.00412907 3.95745e-06 -0.0222828" mass="0.0918099" | ||||||
|  |                 fullinertia="3.11706e-05 6.94935e-05 7.04025e-05 4.29429e-09 -4.41251e-06 -3.82028e-09"/> | ||||||
|  |               <!-- Part head_yaw_to_roll --> | ||||||
|  |               <geom type="mesh" class="visual" pos="-0.02 1.4985e-16 -0.14821" | ||||||
|  |                 quat="1 6.93889e-18 -5.55112e-17 7.04614e-17" mesh="head_yaw_to_roll" | ||||||
|  |                 material="head_yaw_to_roll_material"/> | ||||||
|  |               <!-- Part wj_wk00_0123middlecase_56_9 --> | ||||||
|  |               <!-- Part drive_palonier_9 --> | ||||||
|  |               <!-- Part wj_wk00_0124bottomcase_45_9 --> | ||||||
|  |               <!-- Part wj_wk00_0122topcabinetcase_95_9 --> | ||||||
|  |               <!-- Part passive_palonier_9 --> | ||||||
|  |               <!-- Link head_assembly --> | ||||||
|  |               <body name="head_assembly" pos="0.04095 2.1764e-16 -0.01915" | ||||||
|  |                 quat="0.707107 2.89132e-14 -0.707107 -2.86438e-14"> | ||||||
|  |                 <!-- Joint from neck_yaw_assembly to head_assembly --> | ||||||
|  |                 <joint name="head_roll" type="hinge" range="-0.523598775598218 0.5235987755983796" class="sts3215"/> | ||||||
|  |                 <inertial pos="0.00815416 -0.00390754 0.0227726" mass="0.406607" | ||||||
|  |                   fullinertia="0.00244627 0.0016864 0.00108002 -2.02404e-06 9.2382e-05 -1.84944e-05"/> | ||||||
|  |                 <!-- Part right_antenna_holder --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 -2.4549e-16 0.06095" | ||||||
|  |                   quat="0.707107 5.22659e-16 0.707107 3.88578e-15" mesh="right_antenna_holder" | ||||||
|  |                   material="right_antenna_holder_material"/> | ||||||
|  |                 <!-- Part antenna --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.0830595 0.182212 0.06095" | ||||||
|  |                   quat="0.685585 0.173128 0.685585 -0.173128" mesh="antenna" material="antenna_material"/> | ||||||
|  |                 <!-- Part antenna_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12808 0.000247519 0.06095" | ||||||
|  |                   quat="0.707107 5.42285e-16 0.707107 4.21885e-15" mesh="antenna" material="antenna_material"/> | ||||||
|  |                 <!-- Part left_antenna_holder --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 3.26887e-17 0.06095" | ||||||
|  |                   quat="0.707107 2.12052e-15 0.707107 4.996e-15" mesh="left_antenna_holder" | ||||||
|  |                   material="left_antenna_holder_material"/> | ||||||
|  |                 <!-- Part cam --> | ||||||
|  |                 <!-- Part head_bot_sheet --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 1.72388e-17 0.06095" | ||||||
|  |                   quat="0.707107 2.48509e-16 0.707107 5.27356e-16" mesh="head_bot_sheet" | ||||||
|  |                   material="head_bot_sheet_material"/> | ||||||
|  |                 <!-- Part head_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 1.92717e-17 0.06095" | ||||||
|  |                   quat="0.707107 2.80428e-16 0.707107 5.82867e-16" mesh="head" material="head_material"/> | ||||||
|  |                 <!-- Part bulb --> | ||||||
|  |                 <!-- Part glass --> | ||||||
|  |                 <!-- Part head_roll_mount --> | ||||||
|  |                 <!-- Part raspberrypizerow --> | ||||||
|  |                 <!-- Part left_eye --> | ||||||
|  |                 <!-- Part full_speaker --> | ||||||
|  |                 <!-- Part roll_bearing_3 --> | ||||||
|  |                 <!-- Part sg90 --> | ||||||
|  |                 <!-- Part glass_2 --> | ||||||
|  |                 <!-- Part sg90_2 --> | ||||||
|  |                 <!-- Part bulb_2 --> | ||||||
|  |                 <!-- Part speaker_interface --> | ||||||
|  |                 <!-- Part speaker_stand --> | ||||||
|  |                 <!-- Part right_eye --> | ||||||
|  |                 <!-- Part glass_3 --> | ||||||
|  |                 <!-- Part glass_4 --> | ||||||
|  |                 <!-- Part wj_wk00_0123middlecase_56_10 --> | ||||||
|  |                 <!-- Part drive_palonier_10 --> | ||||||
|  |                 <!-- Part wj_wk00_0124bottomcase_45_10 --> | ||||||
|  |                 <!-- Part wj_wk00_0122topcabinetcase_95_10 --> | ||||||
|  |                 <!-- Part passive_palonier_10 --> | ||||||
|  |                 <!-- Frame head --> | ||||||
|  |                 <site group="0" name="head" pos="0.04245 1.88922e-17 0.03595" | ||||||
|  |                   quat="0.707107 2.48509e-16 0.707107 5.27356e-16"/> | ||||||
|  |               </body> | ||||||
|  |             </body> | ||||||
|  |           </body> | ||||||
|  |         </body> | ||||||
|  |         <!-- Link hip_roll_assembly_2 --> | ||||||
|  |         <body name="hip_roll_assembly_2" pos="-0.019 -0.035 0.0459409" quat="1 -4.25414e-16 -1.07137e-14 2.77556e-17"> | ||||||
|  |           <!-- Joint from trunk_assembly to hip_roll_assembly_2 --> | ||||||
|  |           <joint name="right_hip_yaw" type="hinge" range="-0.523598775598297 0.5235987755983006" class="sts3215"/> | ||||||
|  |           <inertial pos="0.000795081 -5.46541e-06 -0.03306" mass="0.06648" | ||||||
|  |             fullinertia="2.44411e-05 2.81818e-05 1.42687e-05 2.35658e-09 -3.81059e-07 -7.50036e-09"/> | ||||||
|  |           <!-- Part roll_motor_bottom_2 --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-3.64292e-16 -0.035 0.01905" quat="1 -0 -0 -1.52656e-16" | ||||||
|  |             mesh="roll_motor_bottom" material="roll_motor_bottom_material"/> | ||||||
|  |           <!-- Part roll_motor_top_2 --> | ||||||
|  |           <geom type="mesh" class="visual" pos="3.46945e-18 -0.035 0.01905" quat="1 0 2.22045e-16 -1.11022e-16" | ||||||
|  |             mesh="roll_motor_top" material="roll_motor_top_material"/> | ||||||
|  |           <!-- Part wj_wk00_0123middlecase_56_11 --> | ||||||
|  |           <!-- Part drive_palonier_11 --> | ||||||
|  |           <!-- Part wj_wk00_0124bottomcase_45_11 --> | ||||||
|  |           <!-- Part wj_wk00_0122topcabinetcase_95_11 --> | ||||||
|  |           <!-- Part passive_palonier_11 --> | ||||||
|  |           <!-- Link right_roll_to_pitch_assembly --> | ||||||
|  |           <body name="right_roll_to_pitch_assembly" pos="0.01905 4.05787e-14 -0.046015" quat="0.5 -0.5 -0.5 0.5"> | ||||||
|  |             <!-- Joint from hip_roll_assembly_2 to right_roll_to_pitch_assembly --> | ||||||
|  |             <joint name="right_hip_roll" type="hinge" range="-0.4363323129985797 0.43633231299858505" class="sts3215"/> | ||||||
|  |             <inertial pos="-0.0508042 -0.000420742 0.0204704" mass="0.07516" | ||||||
|  |               fullinertia="2.52329e-05 4.13855e-05 2.81264e-05 -1.30528e-07 5.90499e-07 -9.13427e-08"/> | ||||||
|  |             <!-- Part right_roll_to_pitch --> | ||||||
|  |             <geom type="mesh" class="visual" pos="0.035 -0.065 0.01905" quat="0.5 0.5 0.5 -0.5" | ||||||
|  |               mesh="right_roll_to_pitch" material="right_roll_to_pitch_material"/> | ||||||
|  |             <!-- Part wj_wk00_0123middlecase_56_12 --> | ||||||
|  |             <!-- Part drive_palonier_12 --> | ||||||
|  |             <!-- Part wj_wk00_0124bottomcase_45_12 --> | ||||||
|  |             <!-- Part wj_wk00_0122topcabinetcase_95_12 --> | ||||||
|  |             <!-- Part passive_palonier_12 --> | ||||||
|  |             <!-- Link knee_and_ankle_assembly_3 --> | ||||||
|  |             <body name="knee_and_ankle_assembly_3" pos="-0.07415 2.48437e-15 0.03511" | ||||||
|  |               quat="0.707107 6.41892e-22 0.707107 -2.22045e-16"> | ||||||
|  |               <!-- Joint from right_roll_to_pitch_assembly to knee_and_ankle_assembly_3 --> | ||||||
|  |               <joint name="right_hip_pitch" type="hinge" range="-0.5235987755982988 1.2217304763960306" class="sts3215"/> | ||||||
|  |               <inertial pos="0.00253369 0.0390636 0.010809" mass="0.12407" | ||||||
|  |                 fullinertia="0.000212363 7.93324e-05 0.000225619 2.68706e-05 3.9656e-06 -2.17407e-05"/> | ||||||
|  |               <!-- Part right_cache --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.065 0.1092" | ||||||
|  |                 quat="0.707107 0.707107 -3.33067e-16 -3.92523e-16" mesh="right_cache" material="right_cache_material"/> | ||||||
|  |               <!-- Part leg_spacer_3 --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.14365 -0.07215" | ||||||
|  |                 quat="0.707107 0.707107 -3.33067e-16 -3.92523e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_right_sheet_3 --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.14365 -0.07205" quat="0.707107 0.707107 0 -1.74653e-29" | ||||||
|  |                 mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_left_sheet_3 --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.14365 -0.07205" quat="0.707107 0.707107 0 -3.2666e-25" | ||||||
|  |                 mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |               <!-- Part wj_wk00_0123middlecase_56_13 --> | ||||||
|  |               <!-- Part drive_palonier_13 --> | ||||||
|  |               <!-- Part wj_wk00_0124bottomcase_45_13 --> | ||||||
|  |               <!-- Part wj_wk00_0122topcabinetcase_95_13 --> | ||||||
|  |               <!-- Part passive_palonier_13 --> | ||||||
|  |               <!-- Link knee_and_ankle_assembly_4 --> | ||||||
|  |               <body name="knee_and_ankle_assembly_4" pos="1.38778e-16 0.07865 0.037" | ||||||
|  |                 quat="1.32068e-16 1 -1.10334e-26 3.55346e-22"> | ||||||
|  |                 <!-- Joint from knee_and_ankle_assembly_3 to knee_and_ankle_assembly_4 --> | ||||||
|  |                 <joint name="right_knee" type="hinge" range="-1.5707963267948966 1.5707963267948966" class="sts3215"/> | ||||||
|  |                 <inertial pos="5.01859e-06 -0.0577465 0.0181136" mass="0.07259" | ||||||
|  |                   fullinertia="4.99575e-05 1.87273e-05 4.23932e-05 1.03817e-08 2.25175e-09 8.81049e-08"/> | ||||||
|  |                 <!-- Part leg_spacer_4 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10925" | ||||||
|  |                   quat="0.707107 -0.707107 -3.33067e-16 3.92523e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_right_sheet_4 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 -2.51202e-22" | ||||||
|  |                   mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_left_sheet_4 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 5.41603e-30" | ||||||
|  |                   mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |                 <!-- Part wj_wk00_0123middlecase_56_14 --> | ||||||
|  |                 <!-- Part drive_palonier_14 --> | ||||||
|  |                 <!-- Part wj_wk00_0124bottomcase_45_14 --> | ||||||
|  |                 <!-- Part wj_wk00_0122topcabinetcase_95_14 --> | ||||||
|  |                 <!-- Part passive_palonier_14 --> | ||||||
|  |                 <!-- Link foot_assembly_2 --> | ||||||
|  |                 <body name="foot_assembly_2" pos="1.32533e-15 -0.07865 0.0001" | ||||||
|  |                   quat="1 3.88578e-16 -3.33067e-16 5.55111e-16"> | ||||||
|  |                   <!-- Joint from knee_and_ankle_assembly_4 to foot_assembly_2 --> | ||||||
|  |                   <joint name="right_ankle" type="hinge" range="-1.5707963267948957 1.5707963267948974" class="sts3215"/> | ||||||
|  |                   <inertial pos="0.0110718 -0.0246608 0.0190626" mass="0.07524" | ||||||
|  |                     fullinertia="1.87482e-05 6.74427e-05 6.061e-05 1.58839e-06 -6.52354e-09 5.67391e-08"/> | ||||||
|  |                   <!-- Part foot_side_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_side" material="foot_side_material"/> | ||||||
|  |                   <!-- Part foot_bottom_tpu_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_bottom_tpu" material="foot_bottom_tpu_material"/> | ||||||
|  |                   <geom type="mesh" class="collision" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_bottom_tpu" material="foot_bottom_tpu_material" | ||||||
|  |                     name="right_foot_bottom_tpu"/> | ||||||
|  |                   <!-- Part foot_bottom_pla_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_bottom_pla" material="foot_bottom_pla_material"/> | ||||||
|  |                   <!-- Part foot_top_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_top" material="foot_top_material"/> | ||||||
|  |                   <!-- Frame right_foot --> | ||||||
|  |                   <site group="0" name="right_foot" pos="0.0005 -0.036225 0.01955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 1.26645e-25"/> | ||||||
|  |                 </body> | ||||||
|  |               </body> | ||||||
|  |             </body> | ||||||
|  |           </body> | ||||||
|  |         </body> | ||||||
|  |       </body> | ||||||
|  |     </body> | ||||||
|  |   </worldbody> | ||||||
|  |   <asset> | ||||||
|  |     <mesh file="body_back.stl"/> | ||||||
|  |     <mesh file="foot_bottom_pla.stl"/> | ||||||
|  |     <mesh file="neck_left_sheet.stl"/> | ||||||
|  |     <mesh file="right_roll_to_pitch.stl"/> | ||||||
|  |     <mesh file="trunk_bottom.stl"/> | ||||||
|  |     <mesh file="body_middle_bottom.stl"/> | ||||||
|  |     <mesh file="antenna.stl"/> | ||||||
|  |     <mesh file="foot_side.stl"/> | ||||||
|  |     <mesh file="left_roll_to_pitch.stl"/> | ||||||
|  |     <mesh file="foot_top.stl"/> | ||||||
|  |     <mesh file="head.stl"/> | ||||||
|  |     <mesh file="foot_bottom_tpu.stl"/> | ||||||
|  |     <mesh file="neck_right_sheet.stl"/> | ||||||
|  |     <mesh file="head_yaw_to_roll.stl"/> | ||||||
|  |     <mesh file="roll_motor_top.stl"/> | ||||||
|  |     <mesh file="left_knee_to_ankle_left_sheet.stl"/> | ||||||
|  |     <mesh file="body_middle_top.stl"/> | ||||||
|  |     <mesh file="left_antenna_holder.stl"/> | ||||||
|  |     <mesh file="head_pitch_to_yaw.stl"/> | ||||||
|  |     <mesh file="right_antenna_holder.stl"/> | ||||||
|  |     <mesh file="roll_motor_bottom.stl"/> | ||||||
|  |     <mesh file="right_cache.stl"/> | ||||||
|  |     <mesh file="left_knee_to_ankle_right_sheet.stl"/> | ||||||
|  |     <mesh file="body_front.stl"/> | ||||||
|  |     <mesh file="leg_spacer.stl"/> | ||||||
|  |     <mesh file="trunk_top.stl"/> | ||||||
|  |     <mesh file="head_bot_sheet.stl"/> | ||||||
|  |     <mesh file="left_cache.stl"/> | ||||||
|  |     <material name="body_front_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="body_back_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="body_middle_bottom_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="body_middle_top_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="trunk_bottom_material" rgba="0.180392 0.180392 0.180392 1"/> | ||||||
|  |     <material name="trunk_top_material" rgba="0.180392 0.180392 0.180392 1"/> | ||||||
|  |     <material name="roll_motor_bottom_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="roll_motor_top_material" rgba="0.901961 0.901961 0.901961 1"/> | ||||||
|  |     <material name="left_roll_to_pitch_material" rgba="0.909804 0.572549 0.164706 1"/> | ||||||
|  |     <material name="left_cache_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="leg_spacer_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="left_knee_to_ankle_right_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="left_knee_to_ankle_left_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="foot_side_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="foot_bottom_tpu_material" rgba="0.305882 0.298039 0.278431 1"/> | ||||||
|  |     <material name="foot_bottom_pla_material" rgba="0.305882 0.298039 0.278431 1"/> | ||||||
|  |     <material name="foot_top_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="neck_left_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="neck_right_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="head_pitch_to_yaw_material" rgba="0.4 0.4 0.4 1"/> | ||||||
|  |     <material name="head_yaw_to_roll_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="right_antenna_holder_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="antenna_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="left_antenna_holder_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="head_bot_sheet_material" rgba="0.411765 0.411765 0.411765 1"/> | ||||||
|  |     <material name="head_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="right_roll_to_pitch_material" rgba="0.909804 0.572549 0.164706 1"/> | ||||||
|  |     <material name="right_cache_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |   </asset> | ||||||
|  |   <actuator> | ||||||
|  |     <position class="sts3215" name="left_hip_yaw" joint="left_hip_yaw" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_hip_roll" joint="left_hip_roll" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_hip_pitch" joint="left_hip_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_knee" joint="left_knee" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_ankle" joint="left_ankle" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="neck_pitch" joint="neck_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="head_pitch" joint="head_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="head_yaw" joint="head_yaw" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="head_roll" joint="head_roll" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_hip_yaw" joint="right_hip_yaw" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_hip_roll" joint="right_hip_roll" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_hip_pitch" joint="right_hip_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_knee" joint="right_knee" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_ankle" joint="right_ankle" inheritrange="1"/> | ||||||
|  |   </actuator> | ||||||
|  |   <equality/> | ||||||
|  | </mujoco> | ||||||
| @ -0,0 +1,514 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <!-- Generated using onshape-to-robot --> | ||||||
|  | <!-- Onshape document_id: 64074dfcfa379b37d8a47762 --> | ||||||
|  | <mujoco model="open_duck_mini_v2"> | ||||||
|  | 
 | ||||||
|  |   <option iterations="1" ls_iterations="5"> | ||||||
|  |     <flag eulerdamp="disable"/> | ||||||
|  |   </option> | ||||||
|  |   <compiler angle="radian" meshdir="assets"/> | ||||||
|  | 
 | ||||||
|  |   <!-- <option noslip_iterations="1"/> | ||||||
|  |   <compiler angle="radian" meshdir="assets" autolimits="true"/> --> | ||||||
|  |   <default> | ||||||
|  |     <default class="open_duck_mini_v2"> | ||||||
|  |       <!-- <joint frictionloss="0.1" armature="0.005"/> | ||||||
|  |       <position kp="50" dampratio="1"/> --> | ||||||
|  |       <default class="visual"> | ||||||
|  |         <geom type="mesh" contype="0" conaffinity="0" group="2"/> | ||||||
|  |       </default> | ||||||
|  |       <default class="collision"> | ||||||
|  |         <geom group="3"/> | ||||||
|  |       </default> | ||||||
|  |     </default> | ||||||
|  |   </default> | ||||||
|  |   <!-- Additional sensors.xml --> | ||||||
|  |   <sensor> | ||||||
|  |     <gyro site="imu" name="gyro"/> | ||||||
|  |     <velocimeter site="imu" name="local_linvel"/> | ||||||
|  |     <accelerometer site="imu" name="accelerometer"/> | ||||||
|  |     <framezaxis objtype="site" objname="imu" name="upvector"/> | ||||||
|  |     <framexaxis objtype="site" objname="imu" name="forwardvector"/> | ||||||
|  |     <framelinvel objtype="site" objname="imu" name="global_linvel"/> | ||||||
|  |     <frameangvel objtype="site" objname="imu" name="global_angvel"/> | ||||||
|  |     <framepos objtype="site" objname="imu" name="position"/> | ||||||
|  |     <framequat objtype="site" objname="imu" name="orientation"/> | ||||||
|  |     <framelinvel objtype="site" objname="right_foot" name="right_foot_global_linvel"/> | ||||||
|  |     <framelinvel objtype="site" objname="left_foot" name="left_foot_global_linvel"/> | ||||||
|  |     <framexaxis objtype="site" objname="left_foot" name="left_foot_upvector"/> | ||||||
|  |     <framexaxis objtype="site" objname="right_foot" name="right_foot_upvector"/> | ||||||
|  |     <framepos objtype="site" objname="left_foot" name="left_foot_pos"/> | ||||||
|  |     <framepos objtype="site" objname="right_foot" name="right_foot_pos"/> | ||||||
|  |   </sensor> | ||||||
|  |   <!-- Additional joints_properties.xml --> | ||||||
|  |   <default> | ||||||
|  |     <default class="sts3215"> | ||||||
|  |       <geom contype="0" conaffinity="0"/> | ||||||
|  |       <!-- <joint damping="0.60" frictionloss="0.052" armature="0.028"/> | ||||||
|  |       <position kp="17.8" kv="0.0" forcerange="-3.35 3.35"/> --> | ||||||
|  |       <joint damping="0.56" frictionloss="0.068" armature="0.027"/> | ||||||
|  |       <position kp="17.11" kv="0.0" forcerange="-3.23 3.23"/> | ||||||
|  |     </default> | ||||||
|  |     <default class="backlash"> | ||||||
|  |       <!-- +/- 0.5° of backlash --> | ||||||
|  |       <joint damping="0.01" frictionloss="0" armature="0.01" limited="true" | ||||||
|  |         range="-0.008726646259971648 0.008726646259971648"/> | ||||||
|  |     </default> | ||||||
|  |   </default> | ||||||
|  |   <worldbody> | ||||||
|  |     <body name="base" pos="0 0 0.22"> | ||||||
|  |       <!-- <freejoint name="trunk_assembly_freejoint"/> --> | ||||||
|  |       <freejoint name="floating_base"/> | ||||||
|  |       <site name="imu" pos="-0.08 -0.0 0.05"/> | ||||||
|  |       <!-- Link trunk_assembly --> | ||||||
|  |       <body name="trunk_assembly" pos="0 0 0.0" quat="1 0 0 0" childclass="open_duck_mini_v2"> | ||||||
|  |         <inertial pos="-0.0483259 -9.97823e-05 0.0384971" mass="0.698526" | ||||||
|  |           fullinertia="0.00167701 0.00344479 0.00292635 -1.31662e-05 -3.24953e-05 -2.15581e-06"/> | ||||||
|  |         <!-- Part body_front --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 0 0.0648909" quat="1 0 0 0" mesh="body_front" | ||||||
|  |           material="body_front_material"/> | ||||||
|  |         <!-- Part usb_c_charger --> | ||||||
|  |         <!-- Part body_back --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 3.46945e-18 0.0648909" quat="1 0 0 0" mesh="body_back" | ||||||
|  |           material="body_back_material"/> | ||||||
|  |         <!-- Part body_middle_bottom --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 -3.46945e-18 0.0648909" quat="1 0 0 0" mesh="body_middle_bottom" | ||||||
|  |           material="body_middle_bottom_material"/> | ||||||
|  |         <!-- Part power_switch --> | ||||||
|  |         <!-- Part bms --> | ||||||
|  |         <!-- Part battery_pack_lid --> | ||||||
|  |         <!-- Part body_middle_top --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 0 0.0648909" quat="1 0 0 0" mesh="body_middle_top" | ||||||
|  |           material="body_middle_top_material"/> | ||||||
|  |         <!-- Part bno055 --> | ||||||
|  |         <!-- Part roll_bearing --> | ||||||
|  |         <!-- Part trunk_bottom --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 9.1073e-18 0.0648909" quat="1 -0 -0 5.42101e-20" | ||||||
|  |           mesh="trunk_bottom" material="trunk_bottom_material"/> | ||||||
|  |         <!-- Part trunk_top --> | ||||||
|  |         <geom type="mesh" class="visual" pos="-0.019 -2.42428e-16 0.0648909" | ||||||
|  |           quat="1 -4.82108e-17 -5.5355e-15 2.77556e-17" mesh="trunk_top" material="trunk_top_material"/> | ||||||
|  |         <!-- Part board --> | ||||||
|  |         <!-- Part roll_bearing_2 --> | ||||||
|  |         <!-- Part cell --> | ||||||
|  |         <!-- Part holder --> | ||||||
|  |         <!-- Part cell_2 --> | ||||||
|  |         <!-- Part wj_wk00_0123middlecase_56 --> | ||||||
|  |         <!-- Part drive_palonier --> | ||||||
|  |         <!-- Part wj_wk00_0124bottomcase_45 --> | ||||||
|  |         <!-- Part wj_wk00_0122topcabinetcase_95 --> | ||||||
|  |         <!-- Part passive_palonier --> | ||||||
|  |         <!-- Part wj_wk00_0123middlecase_56_2 --> | ||||||
|  |         <!-- Part drive_palonier_2 --> | ||||||
|  |         <!-- Part wj_wk00_0124bottomcase_45_2 --> | ||||||
|  |         <!-- Part wj_wk00_0122topcabinetcase_95_2 --> | ||||||
|  |         <!-- Part passive_palonier_2 --> | ||||||
|  |         <!-- Part wj_wk00_0123middlecase_56_3 --> | ||||||
|  |         <!-- Part drive_palonier_3 --> | ||||||
|  |         <!-- Part wj_wk00_0124bottomcase_45_3 --> | ||||||
|  |         <!-- Part wj_wk00_0122topcabinetcase_95_3 --> | ||||||
|  |         <!-- Part passive_palonier_3 --> | ||||||
|  |         <!-- Frame trunk --> | ||||||
|  |         <site group="0" name="trunk" pos="-0.024 -2.41127e-16 0.0881909" quat="1 -2.77556e-17 -0 -0"/> | ||||||
|  |         <!-- Frame imu --> | ||||||
|  |         <!-- <site group="0" name="imu" pos="-0.08711 1.249e-16 0.0422909" quat="1 -7.03105e-17 -2.77556e-17 -3.14419e-16"/> --> | ||||||
|  |         <!-- Link hip_roll_assembly --> | ||||||
|  |         <body name="hip_roll_assembly" pos="-0.019 0.035 0.0459409" quat="1 -1.17663e-16 -1.09357e-14 2.77556e-17"> | ||||||
|  |           <!-- Joint from trunk_assembly to hip_roll_assembly --> | ||||||
|  |           <joint name="left_hip_yaw" type="hinge" range="-0.5235987755982979 0.5235987755982997" class="sts3215"/> | ||||||
|  |           <joint name="left_hip_yaw_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |           <inertial pos="0.000795081 -5.46541e-06 -0.03306" mass="0.06648" | ||||||
|  |             fullinertia="2.44411e-05 2.81818e-05 1.42687e-05 2.35658e-09 -3.81059e-07 -7.50036e-09"/> | ||||||
|  |           <!-- Part roll_motor_bottom --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-3.33067e-16 -0.035 0.01905" | ||||||
|  |             quat="1 -4.38499e-16 2.22045e-16 -3.34802e-16" mesh="roll_motor_bottom" | ||||||
|  |             material="roll_motor_bottom_material"/> | ||||||
|  |           <!-- Part roll_motor_top --> | ||||||
|  |           <geom type="mesh" class="visual" pos="0 -0.035 0.01905" quat="1 -0 -1.80411e-16 -4.16334e-17" | ||||||
|  |             mesh="roll_motor_top" material="roll_motor_top_material"/> | ||||||
|  |           <!-- Part wj_wk00_0123middlecase_56_4 --> | ||||||
|  |           <!-- Part drive_palonier_4 --> | ||||||
|  |           <!-- Part wj_wk00_0124bottomcase_45_4 --> | ||||||
|  |           <!-- Part wj_wk00_0122topcabinetcase_95_4 --> | ||||||
|  |           <!-- Part passive_palonier_4 --> | ||||||
|  |           <!-- Link left_roll_to_pitch_assembly --> | ||||||
|  |           <body name="left_roll_to_pitch_assembly" pos="0.01905 4.61436e-15 -0.046015" quat="0.5 -0.5 -0.5 0.5"> | ||||||
|  |             <!-- Joint from hip_roll_assembly to left_roll_to_pitch_assembly --> | ||||||
|  |             <joint name="left_hip_roll" type="hinge" range="-0.4363323129985815 0.43633231299858327" class="sts3215"/> | ||||||
|  |             <joint name="left_hip_roll_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |             <inertial pos="0.0508042 -0.00041105 0.0204704" mass="0.07516" | ||||||
|  |               fullinertia="2.52335e-05 4.13855e-05 2.8127e-05 1.22133e-07 -5.90499e-07 -1.07544e-07"/> | ||||||
|  |             <!-- Part left_roll_to_pitch --> | ||||||
|  |             <geom type="mesh" class="visual" pos="-0.035 -0.065 0.01905" quat="0.5 0.5 0.5 -0.5" | ||||||
|  |               mesh="left_roll_to_pitch" material="left_roll_to_pitch_material"/> | ||||||
|  |             <!-- Part wj_wk00_0123middlecase_56_5 --> | ||||||
|  |             <!-- Part drive_palonier_5 --> | ||||||
|  |             <!-- Part wj_wk00_0124bottomcase_45_5 --> | ||||||
|  |             <!-- Part wj_wk00_0122topcabinetcase_95_5 --> | ||||||
|  |             <!-- Part passive_palonier_5 --> | ||||||
|  |             <!-- Link knee_and_ankle_assembly --> | ||||||
|  |             <body name="knee_and_ankle_assembly" pos="0.07415 -3.25261e-17 0.03511" | ||||||
|  |               quat="8.49887e-24 -0.707107 -1.11022e-16 0.707107"> | ||||||
|  |               <!-- Joint from left_roll_to_pitch_assembly to knee_and_ankle_assembly --> | ||||||
|  |               <joint name="left_hip_pitch" type="hinge" range="-1.2217304763960306 0.5235987755982988" class="sts3215"/> | ||||||
|  |               <joint name="left_hip_pitch_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |               <inertial pos="0.00253369 -0.0390636 0.0102776" mass="0.12407" | ||||||
|  |                 fullinertia="0.000211406 7.83759e-05 0.000225619 -2.68706e-05 3.81906e-06 2.08009e-05"/> | ||||||
|  |               <!-- Part left_cache --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.065 0.10915" | ||||||
|  |                 quat="0.707107 -0.707107 3.33067e-16 5.76761e-16" mesh="left_cache" material="left_cache_material"/> | ||||||
|  |               <!-- Part leg_spacer --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10925" | ||||||
|  |                 quat="0.707107 -0.707107 3.33067e-16 5.94711e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_right_sheet --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 -5.93802e-24" | ||||||
|  |                 mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_left_sheet --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 5.12923e-30" | ||||||
|  |                 mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |               <!-- Part wj_wk00_0123middlecase_56_6 --> | ||||||
|  |               <!-- Part drive_palonier_6 --> | ||||||
|  |               <!-- Part wj_wk00_0124bottomcase_45_6 --> | ||||||
|  |               <!-- Part wj_wk00_0122topcabinetcase_95_6 --> | ||||||
|  |               <!-- Part passive_palonier_6 --> | ||||||
|  |               <!-- Link knee_and_ankle_assembly_2 --> | ||||||
|  |               <body name="knee_and_ankle_assembly_2" pos="0 -0.07865 0.0001" | ||||||
|  |                 quat="1 1.11022e-16 1.11022e-16 -2.64698e-23"> | ||||||
|  |                 <!-- Joint from knee_and_ankle_assembly to knee_and_ankle_assembly_2 --> | ||||||
|  |                 <joint name="left_knee" type="hinge" range="-1.5707963267948966 1.5707963267948966" class="sts3215"/> | ||||||
|  |                 <joint name="left_knee_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |                 <inertial pos="5.01859e-06 -0.0577465 0.0181136" mass="0.07259" | ||||||
|  |                   fullinertia="4.99575e-05 1.87273e-05 4.23932e-05 1.03817e-08 2.25175e-09 8.81049e-08"/> | ||||||
|  |                 <!-- Part leg_spacer_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10925" | ||||||
|  |                   quat="0.707107 -0.707107 3.33067e-16 5.72905e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_right_sheet_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 1.33119e-24" | ||||||
|  |                   mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_left_sheet_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 -5.36743e-30" | ||||||
|  |                   mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |                 <!-- Part wj_wk00_0123middlecase_56_7 --> | ||||||
|  |                 <!-- Part drive_palonier_7 --> | ||||||
|  |                 <!-- Part wj_wk00_0124bottomcase_45_7 --> | ||||||
|  |                 <!-- Part wj_wk00_0122topcabinetcase_95_7 --> | ||||||
|  |                 <!-- Part passive_palonier_7 --> | ||||||
|  |                 <!-- Link foot_assembly --> | ||||||
|  |                 <body name="foot_assembly" pos="1.8735e-16 -0.07865 0.0001" quat="1 2.77556e-16 5.55112e-16 1.52656e-16"> | ||||||
|  |                   <!-- Joint from knee_and_ankle_assembly_2 to foot_assembly --> | ||||||
|  |                   <joint name="left_ankle" type="hinge" range="-1.5707963267948957 1.5707963267948974" class="sts3215"/> | ||||||
|  |                   <joint name="left_ankle_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |                   <inertial pos="0.0110718 -0.0246608 0.0190626" mass="0.07524" | ||||||
|  |                     fullinertia="1.87482e-05 6.74427e-05 6.061e-05 1.58839e-06 -6.52354e-09 5.67391e-08"/> | ||||||
|  |                   <!-- Part foot_side --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -4.61924e-30" mesh="foot_side" material="foot_side_material"/> | ||||||
|  |                   <!-- Part foot_bottom_tpu --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" quat="0.707107 -0.707107 -0 1.72496e-18" | ||||||
|  |                     mesh="foot_bottom_tpu" material="foot_bottom_tpu_material"/> | ||||||
|  |                   <geom type="mesh" class="collision" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 1.72496e-18" mesh="foot_bottom_tpu" material="foot_bottom_tpu_material" | ||||||
|  |                     name="left_foot_bottom_tpu"/> | ||||||
|  |                   <!-- Part foot_bottom_pla --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" quat="0.707107 -0.707107 -0 1.72496e-18" | ||||||
|  |                     mesh="foot_bottom_pla" material="foot_bottom_pla_material"/> | ||||||
|  |                   <!-- Part foot_top --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -4.61924e-30" mesh="foot_top" material="foot_top_material"/> | ||||||
|  |                   <!-- Frame left_foot --> | ||||||
|  |                   <site group="0" name="left_foot" pos="0.0005 -0.036225 0.01955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 1.72494e-18"/> | ||||||
|  |                 </body> | ||||||
|  |               </body> | ||||||
|  |             </body> | ||||||
|  |           </body> | ||||||
|  |         </body> | ||||||
|  |         <!-- Link neck_pitch_assembly --> | ||||||
|  |         <body name="neck_pitch_assembly" pos="0.001 0.01915 0.0900009" quat="0.707107 0.707107 -7.88258e-15 7.79925e-15"> | ||||||
|  |           <!-- Joint from trunk_assembly to neck_pitch_assembly --> | ||||||
|  |           <joint name="neck_pitch" type="hinge" range="-0.3490658503988437 1.1344640137963364" class="sts3215"/> | ||||||
|  |           <inertial pos="-5.63137e-06 0.0492968 0.0181786" mass="0.06618" | ||||||
|  |             fullinertia="3.49456e-05 1.70231e-05 2.80043e-05 8.75633e-09 -2.15592e-09 -1.85188e-08"/> | ||||||
|  |           <!-- Part neck_left_sheet --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-0.02 -0.02511 0.01905" quat="0.707107 -0.707107 0 -5.1423e-31" | ||||||
|  |             mesh="neck_left_sheet" material="neck_left_sheet_material"/> | ||||||
|  |           <!-- Part neck_right_sheet --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-0.02 -0.02511 0.01905" quat="0.707107 -0.707107 -0 3.06492e-17" | ||||||
|  |             mesh="neck_right_sheet" material="neck_right_sheet_material"/> | ||||||
|  |           <!-- Part wj_wk00_0123middlecase_56_8 --> | ||||||
|  |           <!-- Part drive_palonier_8 --> | ||||||
|  |           <!-- Part wj_wk00_0124bottomcase_45_8 --> | ||||||
|  |           <!-- Part wj_wk00_0122topcabinetcase_95_8 --> | ||||||
|  |           <!-- Part passive_palonier_8 --> | ||||||
|  |           <!-- Link head_pitch_to_yaw --> | ||||||
|  |           <body name="head_pitch_to_yaw" pos="-2.9924e-17 0.066 -6.93889e-18" quat="1 -1.7203e-16 0 1.10553e-17"> | ||||||
|  |             <!-- Joint from neck_pitch_assembly to head_pitch_to_yaw --> | ||||||
|  |             <joint name="head_pitch" type="hinge" range="-0.7853981633974483 0.7853981633974483" class="sts3215"/> | ||||||
|  |             <inertial pos="-0.00766247 0.026015 0.0186681" mass="0.0169378" | ||||||
|  |               fullinertia="9.43036e-06 4.64763e-06 8.02915e-06 1.35195e-06 3.05899e-08 -8.42802e-08"/> | ||||||
|  |             <!-- Part head_pitch_to_yaw --> | ||||||
|  |             <geom type="mesh" class="visual" pos="-0.02 -0.09111 0.01905" quat="0.707107 -0.707107 0 1.82143e-39" | ||||||
|  |               mesh="head_pitch_to_yaw" material="head_pitch_to_yaw_material"/> | ||||||
|  |             <!-- Link neck_yaw_assembly --> | ||||||
|  |             <body name="neck_yaw_assembly" pos="-4.33681e-19 0.057 0.01905" | ||||||
|  |               quat="0.707107 -0.707107 1.11022e-16 -5.88785e-17"> | ||||||
|  |               <!-- Joint from head_pitch_to_yaw to neck_yaw_assembly --> | ||||||
|  |               <joint name="head_yaw" type="hinge" range="-2.792526803190927 2.792526803190927" class="sts3215"/> | ||||||
|  |               <inertial pos="0.00412907 3.95745e-06 -0.0222828" mass="0.0918099" | ||||||
|  |                 fullinertia="3.11706e-05 6.94935e-05 7.04025e-05 4.29429e-09 -4.41251e-06 -3.82028e-09"/> | ||||||
|  |               <!-- Part head_yaw_to_roll --> | ||||||
|  |               <geom type="mesh" class="visual" pos="-0.02 1.4985e-16 -0.14821" | ||||||
|  |                 quat="1 6.93889e-18 -5.55112e-17 7.04614e-17" mesh="head_yaw_to_roll" | ||||||
|  |                 material="head_yaw_to_roll_material"/> | ||||||
|  |               <!-- Part wj_wk00_0123middlecase_56_9 --> | ||||||
|  |               <!-- Part drive_palonier_9 --> | ||||||
|  |               <!-- Part wj_wk00_0124bottomcase_45_9 --> | ||||||
|  |               <!-- Part wj_wk00_0122topcabinetcase_95_9 --> | ||||||
|  |               <!-- Part passive_palonier_9 --> | ||||||
|  |               <!-- Link head_assembly --> | ||||||
|  |               <body name="head_assembly" pos="0.04095 2.1764e-16 -0.01915" | ||||||
|  |                 quat="0.707107 2.89132e-14 -0.707107 -2.86438e-14"> | ||||||
|  |                 <!-- Joint from neck_yaw_assembly to head_assembly --> | ||||||
|  |                 <joint name="head_roll" type="hinge" range="-0.523598775598218 0.5235987755983796" class="sts3215"/> | ||||||
|  |                 <inertial pos="0.00815416 -0.00390754 0.0227726" mass="0.406607" | ||||||
|  |                   fullinertia="0.00244627 0.0016864 0.00108002 -2.02404e-06 9.2382e-05 -1.84944e-05"/> | ||||||
|  |                 <!-- Part right_antenna_holder --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 -2.4549e-16 0.06095" | ||||||
|  |                   quat="0.707107 5.22659e-16 0.707107 3.88578e-15" mesh="right_antenna_holder" | ||||||
|  |                   material="right_antenna_holder_material"/> | ||||||
|  |                 <!-- Part antenna --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.0830595 0.182212 0.06095" | ||||||
|  |                   quat="0.685585 0.173128 0.685585 -0.173128" mesh="antenna" material="antenna_material"/> | ||||||
|  |                 <!-- Part antenna_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12808 0.000247519 0.06095" | ||||||
|  |                   quat="0.707107 5.42285e-16 0.707107 4.21885e-15" mesh="antenna" material="antenna_material"/> | ||||||
|  |                 <!-- Part left_antenna_holder --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 3.26887e-17 0.06095" | ||||||
|  |                   quat="0.707107 2.12052e-15 0.707107 4.996e-15" mesh="left_antenna_holder" | ||||||
|  |                   material="left_antenna_holder_material"/> | ||||||
|  |                 <!-- Part cam --> | ||||||
|  |                 <!-- Part head_bot_sheet --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 1.72388e-17 0.06095" | ||||||
|  |                   quat="0.707107 2.48509e-16 0.707107 5.27356e-16" mesh="head_bot_sheet" | ||||||
|  |                   material="head_bot_sheet_material"/> | ||||||
|  |                 <!-- Part head_2 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="-0.12906 1.92717e-17 0.06095" | ||||||
|  |                   quat="0.707107 2.80428e-16 0.707107 5.82867e-16" mesh="head" material="head_material"/> | ||||||
|  |                 <!-- Part bulb --> | ||||||
|  |                 <!-- Part glass --> | ||||||
|  |                 <!-- Part head_roll_mount --> | ||||||
|  |                 <!-- Part raspberrypizerow --> | ||||||
|  |                 <!-- Part left_eye --> | ||||||
|  |                 <!-- Part full_speaker --> | ||||||
|  |                 <!-- Part roll_bearing_3 --> | ||||||
|  |                 <!-- Part sg90 --> | ||||||
|  |                 <!-- Part glass_2 --> | ||||||
|  |                 <!-- Part sg90_2 --> | ||||||
|  |                 <!-- Part bulb_2 --> | ||||||
|  |                 <!-- Part speaker_interface --> | ||||||
|  |                 <!-- Part speaker_stand --> | ||||||
|  |                 <!-- Part right_eye --> | ||||||
|  |                 <!-- Part glass_3 --> | ||||||
|  |                 <!-- Part glass_4 --> | ||||||
|  |                 <!-- Part wj_wk00_0123middlecase_56_10 --> | ||||||
|  |                 <!-- Part drive_palonier_10 --> | ||||||
|  |                 <!-- Part wj_wk00_0124bottomcase_45_10 --> | ||||||
|  |                 <!-- Part wj_wk00_0122topcabinetcase_95_10 --> | ||||||
|  |                 <!-- Part passive_palonier_10 --> | ||||||
|  |                 <!-- Frame head --> | ||||||
|  |                 <site group="0" name="head" pos="0.04245 1.88922e-17 0.03595" | ||||||
|  |                   quat="0.707107 2.48509e-16 0.707107 5.27356e-16"/> | ||||||
|  |               </body> | ||||||
|  |             </body> | ||||||
|  |           </body> | ||||||
|  |         </body> | ||||||
|  |         <!-- Link hip_roll_assembly_2 --> | ||||||
|  |         <body name="hip_roll_assembly_2" pos="-0.019 -0.035 0.0459409" quat="1 -4.25414e-16 -1.07137e-14 2.77556e-17"> | ||||||
|  |           <!-- Joint from trunk_assembly to hip_roll_assembly_2 --> | ||||||
|  |           <joint name="right_hip_yaw" type="hinge" range="-0.523598775598297 0.5235987755983006" class="sts3215"/> | ||||||
|  |           <joint name="right_hip_yaw_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |           <inertial pos="0.000795081 -5.46541e-06 -0.03306" mass="0.06648" | ||||||
|  |             fullinertia="2.44411e-05 2.81818e-05 1.42687e-05 2.35658e-09 -3.81059e-07 -7.50036e-09"/> | ||||||
|  |           <!-- Part roll_motor_bottom_2 --> | ||||||
|  |           <geom type="mesh" class="visual" pos="-3.64292e-16 -0.035 0.01905" quat="1 -0 -0 -1.52656e-16" | ||||||
|  |             mesh="roll_motor_bottom" material="roll_motor_bottom_material"/> | ||||||
|  |           <!-- Part roll_motor_top_2 --> | ||||||
|  |           <geom type="mesh" class="visual" pos="3.46945e-18 -0.035 0.01905" quat="1 0 2.22045e-16 -1.11022e-16" | ||||||
|  |             mesh="roll_motor_top" material="roll_motor_top_material"/> | ||||||
|  |           <!-- Part wj_wk00_0123middlecase_56_11 --> | ||||||
|  |           <!-- Part drive_palonier_11 --> | ||||||
|  |           <!-- Part wj_wk00_0124bottomcase_45_11 --> | ||||||
|  |           <!-- Part wj_wk00_0122topcabinetcase_95_11 --> | ||||||
|  |           <!-- Part passive_palonier_11 --> | ||||||
|  |           <!-- Link right_roll_to_pitch_assembly --> | ||||||
|  |           <body name="right_roll_to_pitch_assembly" pos="0.01905 4.05787e-14 -0.046015" quat="0.5 -0.5 -0.5 0.5"> | ||||||
|  |             <!-- Joint from hip_roll_assembly_2 to right_roll_to_pitch_assembly --> | ||||||
|  |             <joint name="right_hip_roll" type="hinge" range="-0.4363323129985797 0.43633231299858505" class="sts3215"/> | ||||||
|  |             <joint name="right_hip_roll_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |             <inertial pos="-0.0508042 -0.000420742 0.0204704" mass="0.07516" | ||||||
|  |               fullinertia="2.52329e-05 4.13855e-05 2.81264e-05 -1.30528e-07 5.90499e-07 -9.13427e-08"/> | ||||||
|  |             <!-- Part right_roll_to_pitch --> | ||||||
|  |             <geom type="mesh" class="visual" pos="0.035 -0.065 0.01905" quat="0.5 0.5 0.5 -0.5" | ||||||
|  |               mesh="right_roll_to_pitch" material="right_roll_to_pitch_material"/> | ||||||
|  |             <!-- Part wj_wk00_0123middlecase_56_12 --> | ||||||
|  |             <!-- Part drive_palonier_12 --> | ||||||
|  |             <!-- Part wj_wk00_0124bottomcase_45_12 --> | ||||||
|  |             <!-- Part wj_wk00_0122topcabinetcase_95_12 --> | ||||||
|  |             <!-- Part passive_palonier_12 --> | ||||||
|  |             <!-- Link knee_and_ankle_assembly_3 --> | ||||||
|  |             <body name="knee_and_ankle_assembly_3" pos="-0.07415 2.48437e-15 0.03511" | ||||||
|  |               quat="0.707107 6.41892e-22 0.707107 -2.22045e-16"> | ||||||
|  |               <!-- Joint from right_roll_to_pitch_assembly to knee_and_ankle_assembly_3 --> | ||||||
|  |               <joint name="right_hip_pitch" type="hinge" range="-0.5235987755982988 1.2217304763960306" class="sts3215"/> | ||||||
|  |               <joint name="right_hip_pitch_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |               <inertial pos="0.00253369 0.0390636 0.010809" mass="0.12407" | ||||||
|  |                 fullinertia="0.000212363 7.93324e-05 0.000225619 2.68706e-05 3.9656e-06 -2.17407e-05"/> | ||||||
|  |               <!-- Part right_cache --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.065 0.1092" | ||||||
|  |                 quat="0.707107 0.707107 -3.33067e-16 -3.92523e-16" mesh="right_cache" material="right_cache_material"/> | ||||||
|  |               <!-- Part leg_spacer_3 --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.14365 -0.07215" | ||||||
|  |                 quat="0.707107 0.707107 -3.33067e-16 -3.92523e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_right_sheet_3 --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.14365 -0.07205" quat="0.707107 0.707107 0 -1.74653e-29" | ||||||
|  |                 mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |               <!-- Part left_knee_to_ankle_left_sheet_3 --> | ||||||
|  |               <geom type="mesh" class="visual" pos="0.01606 -0.14365 -0.07205" quat="0.707107 0.707107 0 -3.2666e-25" | ||||||
|  |                 mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |               <!-- Part wj_wk00_0123middlecase_56_13 --> | ||||||
|  |               <!-- Part drive_palonier_13 --> | ||||||
|  |               <!-- Part wj_wk00_0124bottomcase_45_13 --> | ||||||
|  |               <!-- Part wj_wk00_0122topcabinetcase_95_13 --> | ||||||
|  |               <!-- Part passive_palonier_13 --> | ||||||
|  |               <!-- Link knee_and_ankle_assembly_4 --> | ||||||
|  |               <body name="knee_and_ankle_assembly_4" pos="1.38778e-16 0.07865 0.037" | ||||||
|  |                 quat="1.32068e-16 1 -1.10334e-26 3.55346e-22"> | ||||||
|  |                 <!-- Joint from knee_and_ankle_assembly_3 to knee_and_ankle_assembly_4 --> | ||||||
|  |                 <joint name="right_knee" type="hinge" range="-1.5707963267948966 1.5707963267948966" class="sts3215"/> | ||||||
|  |                 <joint name="right_knee_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |                 <inertial pos="5.01859e-06 -0.0577465 0.0181136" mass="0.07259" | ||||||
|  |                   fullinertia="4.99575e-05 1.87273e-05 4.23932e-05 1.03817e-08 2.25175e-09 8.81049e-08"/> | ||||||
|  |                 <!-- Part leg_spacer_4 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10925" | ||||||
|  |                   quat="0.707107 -0.707107 -3.33067e-16 3.92523e-16" mesh="leg_spacer" material="leg_spacer_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_right_sheet_4 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 -2.51202e-22" | ||||||
|  |                   mesh="left_knee_to_ankle_right_sheet" material="left_knee_to_ankle_right_sheet_material"/> | ||||||
|  |                 <!-- Part left_knee_to_ankle_left_sheet_4 --> | ||||||
|  |                 <geom type="mesh" class="visual" pos="0.01606 0.14365 0.10915" quat="0.707107 -0.707107 0 5.41603e-30" | ||||||
|  |                   mesh="left_knee_to_ankle_left_sheet" material="left_knee_to_ankle_left_sheet_material"/> | ||||||
|  |                 <!-- Part wj_wk00_0123middlecase_56_14 --> | ||||||
|  |                 <!-- Part drive_palonier_14 --> | ||||||
|  |                 <!-- Part wj_wk00_0124bottomcase_45_14 --> | ||||||
|  |                 <!-- Part wj_wk00_0122topcabinetcase_95_14 --> | ||||||
|  |                 <!-- Part passive_palonier_14 --> | ||||||
|  |                 <!-- Link foot_assembly_2 --> | ||||||
|  |                 <body name="foot_assembly_2" pos="1.32533e-15 -0.07865 0.0001" | ||||||
|  |                   quat="1 3.88578e-16 -3.33067e-16 5.55111e-16"> | ||||||
|  |                   <!-- Joint from knee_and_ankle_assembly_4 to foot_assembly_2 --> | ||||||
|  |                   <joint name="right_ankle" type="hinge" range="-1.5707963267948957 1.5707963267948974" class="sts3215"/> | ||||||
|  |                   <joint name="right_ankle_backlash" pos="0 0 0" axis="0 0 1" class="backlash"/> | ||||||
|  |                   <inertial pos="0.0110718 -0.0246608 0.0190626" mass="0.07524" | ||||||
|  |                     fullinertia="1.87482e-05 6.74427e-05 6.061e-05 1.58839e-06 -6.52354e-09 5.67391e-08"/> | ||||||
|  |                   <!-- Part foot_side_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_side" material="foot_side_material"/> | ||||||
|  |                   <!-- Part foot_bottom_tpu_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_bottom_tpu" material="foot_bottom_tpu_material"/> | ||||||
|  |                   <geom type="mesh" class="collision" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_bottom_tpu" material="foot_bottom_tpu_material" | ||||||
|  |                     name="right_foot_bottom_tpu"/> | ||||||
|  |                   <!-- Part foot_bottom_pla_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01656 0.2228 0.10955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_bottom_pla" material="foot_bottom_pla_material"/> | ||||||
|  |                   <!-- Part foot_top_2 --> | ||||||
|  |                   <geom type="mesh" class="visual" pos="0.01606 0.2223 0.10905" | ||||||
|  |                     quat="0.707107 -0.707107 -0 -8.61814e-30" mesh="foot_top" material="foot_top_material"/> | ||||||
|  |                   <!-- Frame right_foot --> | ||||||
|  |                   <site group="0" name="right_foot" pos="0.0005 -0.036225 0.01955" | ||||||
|  |                     quat="0.707107 -0.707107 -0 1.26645e-25"/> | ||||||
|  |                 </body> | ||||||
|  |               </body> | ||||||
|  |             </body> | ||||||
|  |           </body> | ||||||
|  |         </body> | ||||||
|  |       </body> | ||||||
|  |     </body> | ||||||
|  |   </worldbody> | ||||||
|  |   <asset> | ||||||
|  |     <mesh file="body_back.stl"/> | ||||||
|  |     <mesh file="foot_bottom_pla.stl"/> | ||||||
|  |     <mesh file="neck_left_sheet.stl"/> | ||||||
|  |     <mesh file="right_roll_to_pitch.stl"/> | ||||||
|  |     <mesh file="trunk_bottom.stl"/> | ||||||
|  |     <mesh file="body_middle_bottom.stl"/> | ||||||
|  |     <mesh file="antenna.stl"/> | ||||||
|  |     <mesh file="foot_side.stl"/> | ||||||
|  |     <mesh file="left_roll_to_pitch.stl"/> | ||||||
|  |     <mesh file="foot_top.stl"/> | ||||||
|  |     <mesh file="head.stl"/> | ||||||
|  |     <mesh file="foot_bottom_tpu.stl"/> | ||||||
|  |     <mesh file="neck_right_sheet.stl"/> | ||||||
|  |     <mesh file="head_yaw_to_roll.stl"/> | ||||||
|  |     <mesh file="roll_motor_top.stl"/> | ||||||
|  |     <mesh file="left_knee_to_ankle_left_sheet.stl"/> | ||||||
|  |     <mesh file="body_middle_top.stl"/> | ||||||
|  |     <mesh file="left_antenna_holder.stl"/> | ||||||
|  |     <mesh file="head_pitch_to_yaw.stl"/> | ||||||
|  |     <mesh file="right_antenna_holder.stl"/> | ||||||
|  |     <mesh file="roll_motor_bottom.stl"/> | ||||||
|  |     <mesh file="right_cache.stl"/> | ||||||
|  |     <mesh file="left_knee_to_ankle_right_sheet.stl"/> | ||||||
|  |     <mesh file="body_front.stl"/> | ||||||
|  |     <mesh file="leg_spacer.stl"/> | ||||||
|  |     <mesh file="trunk_top.stl"/> | ||||||
|  |     <mesh file="head_bot_sheet.stl"/> | ||||||
|  |     <mesh file="left_cache.stl"/> | ||||||
|  |     <material name="body_front_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="body_back_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="body_middle_bottom_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="body_middle_top_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="trunk_bottom_material" rgba="0.180392 0.180392 0.180392 1"/> | ||||||
|  |     <material name="trunk_top_material" rgba="0.180392 0.180392 0.180392 1"/> | ||||||
|  |     <material name="roll_motor_bottom_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="roll_motor_top_material" rgba="0.901961 0.901961 0.901961 1"/> | ||||||
|  |     <material name="left_roll_to_pitch_material" rgba="0.909804 0.572549 0.164706 1"/> | ||||||
|  |     <material name="left_cache_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="leg_spacer_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="left_knee_to_ankle_right_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="left_knee_to_ankle_left_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="foot_side_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="foot_bottom_tpu_material" rgba="0.305882 0.298039 0.278431 1"/> | ||||||
|  |     <material name="foot_bottom_pla_material" rgba="0.305882 0.298039 0.278431 1"/> | ||||||
|  |     <material name="foot_top_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="neck_left_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="neck_right_sheet_material" rgba="0.223529 0.219608 0.219608 1"/> | ||||||
|  |     <material name="head_pitch_to_yaw_material" rgba="0.4 0.4 0.4 1"/> | ||||||
|  |     <material name="head_yaw_to_roll_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="right_antenna_holder_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="antenna_material" rgba="0.647059 0.647059 0.647059 1"/> | ||||||
|  |     <material name="left_antenna_holder_material" rgba="0.980392 0.713725 0.00392157 1"/> | ||||||
|  |     <material name="head_bot_sheet_material" rgba="0.411765 0.411765 0.411765 1"/> | ||||||
|  |     <material name="head_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |     <material name="right_roll_to_pitch_material" rgba="0.909804 0.572549 0.164706 1"/> | ||||||
|  |     <material name="right_cache_material" rgba="0.917647 0.917647 0.917647 1"/> | ||||||
|  |   </asset> | ||||||
|  |   <actuator> | ||||||
|  |     <position class="sts3215" name="left_hip_yaw" joint="left_hip_yaw" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_hip_roll" joint="left_hip_roll" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_hip_pitch" joint="left_hip_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_knee" joint="left_knee" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="left_ankle" joint="left_ankle" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="neck_pitch" joint="neck_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="head_pitch" joint="head_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="head_yaw" joint="head_yaw" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="head_roll" joint="head_roll" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_hip_yaw" joint="right_hip_yaw" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_hip_roll" joint="right_hip_roll" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_hip_pitch" joint="right_hip_pitch" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_knee" joint="right_knee" inheritrange="1"/> | ||||||
|  |     <position class="sts3215" name="right_ankle" joint="right_ankle" inheritrange="1"/> | ||||||
|  |   </actuator> | ||||||
|  |   <equality/> | ||||||
|  | </mujoco> | ||||||
| @ -0,0 +1,76 @@ | |||||||
|  | <mujoco model="scene"> | ||||||
|  |     <include file="open_duck_mini_v2.xml"/> | ||||||
|  | 
 | ||||||
|  |     <visual> | ||||||
|  |         <headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/> | ||||||
|  |         <rgba haze="0.15 0.25 0.35 1"/> | ||||||
|  |         <global azimuth="160" elevation="-20"/> | ||||||
|  |     </visual> | ||||||
|  |     <!--  | ||||||
|  |     <asset> | ||||||
|  |         <texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/> | ||||||
|  |         <texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" | ||||||
|  |             markrgb="0.8 0.8 0.8" width="300" height="300"/> | ||||||
|  |         <material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/> | ||||||
|  |     </asset> --> | ||||||
|  | 
 | ||||||
|  |     <!-- <worldbody> | ||||||
|  |         <body name="floor"> | ||||||
|  |             <light pos="0 0 3.5" dir="0 0 -1" directional="true"/> | ||||||
|  |             <geom name="floor" size="0 0 0.05" pos="0 0 0" type="plane" material="groundplane" contype="1" | ||||||
|  |                 conaffinity="0" priority="1"/> | ||||||
|  |         </body> | ||||||
|  |     </worldbody> --> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     <asset> | ||||||
|  |         <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2="1 1 1" width="800" height="800"/> | ||||||
|  |         <texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="1 1 1" rgb2="1 1 1" markrgb="0 0 0" | ||||||
|  |             width="300" height="300"/> | ||||||
|  |         <material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0"/> | ||||||
|  |     </asset> | ||||||
|  | 
 | ||||||
|  |     <worldbody> | ||||||
|  |         <body name="floor"> | ||||||
|  |             <geom name="floor" size="0 0 0.01" type="plane" material="groundplane" contype="1" conaffinity="0" | ||||||
|  |                 priority="1" friction="0.6" condim="3"/> | ||||||
|  |         </body> | ||||||
|  |     </worldbody> | ||||||
|  |     <keyframe> | ||||||
|  |         <key name="home" | ||||||
|  |             qpos=" | ||||||
|  |     0 0 0.15 | ||||||
|  |     1 0 0 0 | ||||||
|  |       0.002 | ||||||
|  |         0.053 | ||||||
|  |         -0.63 | ||||||
|  |         1.368 | ||||||
|  |         -0.784 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         -0.003 | ||||||
|  |         -0.065 | ||||||
|  |         0.635 | ||||||
|  |         1.379 | ||||||
|  |         -0.796 | ||||||
|  | " | ||||||
|  |             ctrl=" | ||||||
|  |           0.002 | ||||||
|  |         0.053 | ||||||
|  |         -0.63 | ||||||
|  |         1.368 | ||||||
|  |         -0.784 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         -0.003 | ||||||
|  |         -0.065 | ||||||
|  |         0.635 | ||||||
|  |         1.379 | ||||||
|  |         -0.796 | ||||||
|  |           "/> | ||||||
|  |     </keyframe> | ||||||
|  | </mujoco> | ||||||
| @ -0,0 +1,79 @@ | |||||||
|  | <mujoco model="scene"> | ||||||
|  |     <include file="open_duck_mini_v2_backlash.xml"/> | ||||||
|  | 
 | ||||||
|  |     <visual> | ||||||
|  |         <headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/> | ||||||
|  |         <rgba haze="0.15 0.25 0.35 1"/> | ||||||
|  |         <global azimuth="160" elevation="-20"/> | ||||||
|  |     </visual> | ||||||
|  |     <!--  | ||||||
|  |     <asset> | ||||||
|  |         <texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/> | ||||||
|  |         <texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" | ||||||
|  |             markrgb="0.8 0.8 0.8" width="300" height="300"/> | ||||||
|  |         <material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/> | ||||||
|  |     </asset> --> | ||||||
|  | 
 | ||||||
|  |     <!-- <worldbody> | ||||||
|  |         <body name="floor"> | ||||||
|  |             <light pos="0 0 3.5" dir="0 0 -1" directional="true"/> | ||||||
|  |             <geom name="floor" size="0 0 0.05" pos="0 0 0" type="plane" material="groundplane" contype="1" | ||||||
|  |                 conaffinity="0" priority="1"/> | ||||||
|  |         </body> | ||||||
|  |     </worldbody> --> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     <asset> | ||||||
|  |         <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2="1 1 1" width="800" height="800"/> | ||||||
|  |         <texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="1 1 1" rgb2="1 1 1" markrgb="0 0 0" | ||||||
|  |             width="300" height="300"/> | ||||||
|  |         <material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0"/> | ||||||
|  |     </asset> | ||||||
|  | 
 | ||||||
|  |     <worldbody> | ||||||
|  |         <body name="floor"> | ||||||
|  |             <geom name="floor" size="0 0 0.01" type="plane" material="groundplane" contype="1" conaffinity="0" | ||||||
|  |                 priority="1" friction="0.6" condim="3"/> | ||||||
|  |         </body> | ||||||
|  |     </worldbody> | ||||||
|  |     <keyframe> | ||||||
|  |         <key name="home" | ||||||
|  |             qpos=" | ||||||
|  |         0 0 0.15 | ||||||
|  |         1 0 0 0 | ||||||
|  | 
 | ||||||
|  |         0.002 0 | ||||||
|  |         0.053 0 | ||||||
|  |         -0.63 0 | ||||||
|  |         1.368 0 | ||||||
|  |         -0.784 0 | ||||||
|  | 
 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  | 
 | ||||||
|  |         -0.003 0 | ||||||
|  |         -0.065 0 | ||||||
|  |         0.635 0 | ||||||
|  |         1.379 0 | ||||||
|  |         -0.796 0 | ||||||
|  | " | ||||||
|  |             ctrl=" | ||||||
|  |           0.002 | ||||||
|  |         0.053 | ||||||
|  |         -0.63 | ||||||
|  |         1.368 | ||||||
|  |         -0.784 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         -0.003 | ||||||
|  |         -0.065 | ||||||
|  |         0.635 | ||||||
|  |         1.379 | ||||||
|  |         -0.796 | ||||||
|  |           "/> | ||||||
|  |     </keyframe> | ||||||
|  | </mujoco> | ||||||
| @ -0,0 +1,75 @@ | |||||||
|  | <mujoco model="Open Duck Mini V2 rough terrain scene"> | ||||||
|  |   <!-- <include file="open_duck_mini_v2_no_head.xml"/> --> | ||||||
|  |   <include file="open_duck_mini_v2_backlash.xml"/> | ||||||
|  | 
 | ||||||
|  |   <statistic center="0 0 0.1" extent="0.8" meansize="0.04"/> | ||||||
|  | 
 | ||||||
|  |   <visual> | ||||||
|  |     <rgba force="1 0 0 1"/> | ||||||
|  |     <global azimuth="120" elevation="-20"/> | ||||||
|  |     <map force="0.01"/> | ||||||
|  |     <scale forcewidth="0.3" contactwidth="0.5" contactheight="0.2"/> | ||||||
|  |     <quality shadowsize="8192"/> | ||||||
|  |   </visual> | ||||||
|  | 
 | ||||||
|  |   <asset> | ||||||
|  |     <!-- https://polyhaven.com/a/rock_face --> | ||||||
|  |     <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2="1 1 1" width="800" height="800"/> | ||||||
|  |         <texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="1 1 1" rgb2="1 1 1" markrgb="0 0 0" | ||||||
|  |       width="300" height="300"/> | ||||||
|  |     <!-- <texture type="2d" name="groundplane" file="assets/rocky_texture.png"/> --> | ||||||
|  |     <material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance=".8"/> | ||||||
|  |     <hfield name="hfield" file="assets/hfield.png" size="10 10 .01 0.1"/> | ||||||
|  |   </asset> | ||||||
|  | 
 | ||||||
|  |   <worldbody> | ||||||
|  |     <body name="floor"> | ||||||
|  |       <geom name="floor" type="hfield" hfield="hfield" material="groundplane" contype="1" conaffinity="0" priority="1" | ||||||
|  |         friction="1.0"/> | ||||||
|  |     </body> | ||||||
|  |   </worldbody> | ||||||
|  | 
 | ||||||
|  |   <keyframe> | ||||||
|  |     <key name="home" | ||||||
|  |       qpos=" | ||||||
|  |         0 0 0.15 | ||||||
|  |         1 0 0 0 | ||||||
|  | 
 | ||||||
|  |         0.002 0 | ||||||
|  |         0.053 0 | ||||||
|  |         -0.63 0 | ||||||
|  |         1.368 0 | ||||||
|  |         -0.784 0 | ||||||
|  | 
 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  |         0 | ||||||
|  | 
 | ||||||
|  |         -0.003 0 | ||||||
|  |         -0.065 0 | ||||||
|  |         0.635 0 | ||||||
|  |         1.379 0 | ||||||
|  |         -0.796 0 | ||||||
|  | 
 | ||||||
|  |       " | ||||||
|  |       ctrl=" | ||||||
|  |           0.002 | ||||||
|  |           0.053 | ||||||
|  |           -0.63 | ||||||
|  |           1.368 | ||||||
|  |           -0.784 | ||||||
|  | 
 | ||||||
|  |           0 | ||||||
|  |           0 | ||||||
|  |           0 | ||||||
|  |           0 | ||||||
|  |            | ||||||
|  |           -0.003 | ||||||
|  |           -0.065 | ||||||
|  |           0.635 | ||||||
|  |           1.379 | ||||||
|  |           -0.796 | ||||||
|  |       "/> | ||||||
|  |   </keyframe> | ||||||
|  | </mujoco> | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | <sensor> | ||||||
|  |     <gyro site="imu" name="gyro"/> | ||||||
|  |     <velocimeter site="imu" name="local_linvel"/> | ||||||
|  |     <accelerometer site="imu" name="accelerometer"/> | ||||||
|  |     <framezaxis objtype="site" objname="imu" name="upvector"/> | ||||||
|  |     <framexaxis objtype="site" objname="imu" name="forwardvector"/> | ||||||
|  |     <framelinvel objtype="site" objname="imu" name="global_linvel"/> | ||||||
|  |     <frameangvel objtype="site" objname="imu" name="global_angvel"/> | ||||||
|  |     <framepos objtype="site" objname="imu" name="position"/> | ||||||
|  |     <framequat objtype="site" objname="imu" name="orientation"/> | ||||||
|  |     <framelinvel objtype="site" objname="right_foot" name="right_foot_global_linvel"/> | ||||||
|  |     <framelinvel objtype="site" objname="left_foot" name="left_foot_global_linvel"/> | ||||||
|  |     <framexaxis objtype="site" objname="left_foot" name="left_foot_upvector"/> | ||||||
|  |     <framexaxis objtype="site" objname="right_foot" name="right_foot_upvector"/> | ||||||
|  |     <framepos objtype="site" objname="left_foot" name="left_foot_pos"/> | ||||||
|  |     <framepos objtype="site" objname="right_foot" name="right_foot_pos"/> | ||||||
|  | </sensor> | ||||||
							
								
								
									
										23
									
								
								Open_Duck_Playground/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Open_Duck_Playground/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | [project] | ||||||
|  | name = "Open_Duck_Playground" | ||||||
|  | version = "0.1.0" | ||||||
|  | description = "Add your description here" | ||||||
|  | readme = "README.md" | ||||||
|  | requires-python = ">=3.11" | ||||||
|  | dependencies = [ | ||||||
|  |     "framesviewer>=1.0.2", | ||||||
|  |     "jax[cuda12]>=0.5.0", | ||||||
|  |     "jaxlib>=0.5.0", | ||||||
|  |     "jaxtyping>=0.2.38", | ||||||
|  |     "matplotlib>=3.10.0", | ||||||
|  |     "mediapy>=1.2.2", | ||||||
|  |     "onnxruntime>=1.20.1", | ||||||
|  |     "playground>=0.0.3", | ||||||
|  |     "pygame>=2.6.1", | ||||||
|  |     "tensorflow>=2.18.0", | ||||||
|  |     "tf2onnx>=1.16.1", | ||||||
|  | ] | ||||||
|  | [build-system] | ||||||
|  | requires = ["setuptools"] | ||||||
|  | [tool.setuptools] | ||||||
|  | packages = ["playground"] | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user