diff --git a/judo/controller/controller.py b/judo/controller/controller.py
index bca3361a..75cdc07e 100644
--- a/judo/controller/controller.py
+++ b/judo/controller/controller.py
@@ -14,7 +14,7 @@
from judo.gui import slider
from judo.optimizers import Optimizer, OptimizerConfig, get_registered_optimizers
from judo.tasks import Task, TaskConfig, get_registered_tasks
-from judo.utils.mujoco import RolloutBackend, make_model_data_pairs
+from judo.utils.mujoco_utils import RolloutBackend, make_model_data_pairs
from judo.utils.normalization import (
IdentityNormalizer,
Normalizer,
diff --git a/judo/models/xml/cartpole.xml b/judo/models/xml/cartpole.xml
index 967d91c2..0da82692 100644
--- a/judo/models/xml/cartpole.xml
+++ b/judo/models/xml/cartpole.xml
@@ -42,4 +42,10 @@
+
+
+
+
+
+
diff --git a/judo/models/xml/keith/assets/133026_qsm_m3_2_i.part b/judo/models/xml/keith/assets/133026_qsm_m3_2_i.part
new file mode 100644
index 00000000..068496bc
--- /dev/null
+++ b/judo/models/xml/keith/assets/133026_qsm_m3_2_i.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "3cbdee4fb0d59ba9b7b1e4b5",
+ "fullConfiguration": "default",
+ "id": "IwrkC6+ulJpzO4Zi5",
+ "isStandardContent": false,
+ "name": "133026_QSM-M3-2-I <1>",
+ "partId": "JFH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/92010a778_passivated_18_8_stainless_steel_phillips_flat_head_screw.part b/judo/models/xml/keith/assets/92010a778_passivated_18_8_stainless_steel_phillips_flat_head_screw.part
new file mode 100644
index 00000000..ed446b51
--- /dev/null
+++ b/judo/models/xml/keith/assets/92010a778_passivated_18_8_stainless_steel_phillips_flat_head_screw.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "466338c70cd0bb67d969b077",
+ "fullConfiguration": "default",
+ "id": "MsCBUSIKNSVNIvbg4",
+ "isStandardContent": false,
+ "name": "92010A778_Passivated 18-8 Stainless Steel Phillips Flat Head Screw <1>",
+ "partId": "JFH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/92703a444_18_8_stainless_steel_torx_flat_head_screws.part b/judo/models/xml/keith/assets/92703a444_18_8_stainless_steel_torx_flat_head_screws.part
new file mode 100644
index 00000000..bc463782
--- /dev/null
+++ b/judo/models/xml/keith/assets/92703a444_18_8_stainless_steel_torx_flat_head_screws.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "34c9fe1be3bc8f2bb9950579",
+ "fullConfiguration": "default",
+ "id": "MYTDCRgDoe8b5/Kum",
+ "isStandardContent": false,
+ "name": "92703A444_18-8 Stainless Steel Torx Flat Head Screws <1>",
+ "partId": "JFH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/99178a102_heavy_duty_side_mount_external_retaining_ring.part b/judo/models/xml/keith/assets/99178a102_heavy_duty_side_mount_external_retaining_ring.part
new file mode 100644
index 00000000..9b5bca46
--- /dev/null
+++ b/judo/models/xml/keith/assets/99178a102_heavy_duty_side_mount_external_retaining_ring.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "0ad0a1e78a5098829955e572",
+ "fullConfiguration": "default",
+ "id": "MyvjCdIumerpryizX",
+ "isStandardContent": false,
+ "name": "99178A102_Heavy Duty Side-Mount External Retaining Ring <1>",
+ "partId": "JFH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bearing_10id_15od_3_r_o_as.part b/judo/models/xml/keith/assets/bearing_10id_15od_3_r_o_as.part
new file mode 100644
index 00000000..71b4eee2
--- /dev/null
+++ b/judo/models/xml/keith/assets/bearing_10id_15od_3_r_o_as.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "e084450080eace000d7be481",
+ "fullConfiguration": "default",
+ "id": "MgLzX7sI5NHKRUmac",
+ "isStandardContent": false,
+ "name": "BEARING-10ID-15OD-3-R-O-AS <1>",
+ "partId": "JF7",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bearing_12id_18od_4_r_2z_as.part b/judo/models/xml/keith/assets/bearing_12id_18od_4_r_2z_as.part
new file mode 100644
index 00000000..9f3edab2
--- /dev/null
+++ b/judo/models/xml/keith/assets/bearing_12id_18od_4_r_2z_as.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "3bd80c296bcbfc4cd891f505",
+ "fullConfiguration": "default",
+ "id": "MLCXGkKgcrRNma6Qi",
+ "isStandardContent": false,
+ "name": "BEARING-12ID-18OD-4-R-2Z-AS <1>",
+ "partId": "KFHB",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bearing_15id_21od_4_r_2z_as.part b/judo/models/xml/keith/assets/bearing_15id_21od_4_r_2z_as.part
new file mode 100644
index 00000000..0019629b
--- /dev/null
+++ b/judo/models/xml/keith/assets/bearing_15id_21od_4_r_2z_as.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "47c9270e5dbd6633af48a96c",
+ "fullConfiguration": "default",
+ "id": "MuNnNFBhbP7Db9mr9",
+ "isStandardContent": false,
+ "name": "BEARING-15ID-21OD-4-R-2Z-AS <2>",
+ "partId": "KFLB",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bearing_4id_10od_3_fr_o_as.part b/judo/models/xml/keith/assets/bearing_4id_10od_3_fr_o_as.part
new file mode 100644
index 00000000..019a4319
--- /dev/null
+++ b/judo/models/xml/keith/assets/bearing_4id_10od_3_fr_o_as.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "ef8930f29bca612a0ac2be05",
+ "fullConfiguration": "default",
+ "id": "Mv851xfiVcRV3a4hK",
+ "isStandardContent": false,
+ "name": "BEARING-4ID-10OD-3-FR-O-AS <1>",
+ "partId": "JF3",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bearing_4id_12od_4_r_2z_as.part b/judo/models/xml/keith/assets/bearing_4id_12od_4_r_2z_as.part
new file mode 100644
index 00000000..306e5df5
--- /dev/null
+++ b/judo/models/xml/keith/assets/bearing_4id_12od_4_r_2z_as.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "848abd135acbd0b3ffb9f83a",
+ "fullConfiguration": "default",
+ "id": "MrelTBLagcf6cy0sn",
+ "isStandardContent": false,
+ "name": "BEARING-4ID-12OD-4-R-2Z-AS <2>",
+ "partId": "KFDB",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/blank__cantilever_mount__wrist_b___amp_beta.part b/judo/models/xml/keith/assets/blank__cantilever_mount__wrist_b___amp_beta.part
new file mode 100644
index 00000000..fb8d6f2d
--- /dev/null
+++ b/judo/models/xml/keith/assets/blank__cantilever_mount__wrist_b___amp_beta.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MYdtnH0iT1xGPGn+e",
+ "isStandardContent": false,
+ "name": "BLANK, CANTILEVER MOUNT, WRIST B+, AMP-BETA <1>",
+ "partId": "Ra0D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bogen_magnet__configuration_default.part b/judo/models/xml/keith/assets/bogen_magnet__configuration_default.part
new file mode 100644
index 00000000..a5a0d7c5
--- /dev/null
+++ b/judo/models/xml/keith/assets/bogen_magnet__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mfxa3h75B05DfvlpT",
+ "isStandardContent": false,
+ "name": "BOGEN MAGNET <1>",
+ "partId": "RuJD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/bushing_2id_3_5od_3_nf_p_j_glide.part b/judo/models/xml/keith/assets/bushing_2id_3_5od_3_nf_p_j_glide.part
new file mode 100644
index 00000000..520e23a5
--- /dev/null
+++ b/judo/models/xml/keith/assets/bushing_2id_3_5od_3_nf_p_j_glide.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "d34949da389f0a4bb41729b3",
+ "fullConfiguration": "default",
+ "id": "MIgv0DYRE9dnRKoAQ",
+ "isStandardContent": false,
+ "name": "BUSHING-2ID-3.5OD-3-NF-P-J-Glide <1>",
+ "partId": "JFD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/dist_spring_preload__configuration_default.part b/judo/models/xml/keith/assets/dist_spring_preload__configuration_default.part
new file mode 100644
index 00000000..2e3911fd
--- /dev/null
+++ b/judo/models/xml/keith/assets/dist_spring_preload__configuration_default.part
@@ -0,0 +1,14 @@
+{
+ "configuration": "List_JluiuSI7cF0s7r=Default",
+ "documentId": "d773d7677ed258790773a569",
+ "documentMicroversion": "32a4c75cd788aa567de19013",
+ "documentVersion": "98061ff1d2f9da933e45d984",
+ "elementId": "3dc266b3300e11acb6cbcc7b",
+ "fullConfiguration": "List_JluiuSI7cF0s7r=Default",
+ "id": "M1wbtwoHXMcUkJGwn",
+ "isStandardContent": false,
+ "name": "dist spring preload <1>",
+ "partId": "RyKD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/middle_idler_shaft__configuration_default.part b/judo/models/xml/keith/assets/middle_idler_shaft__configuration_default.part
new file mode 100644
index 00000000..977ccb34
--- /dev/null
+++ b/judo/models/xml/keith/assets/middle_idler_shaft__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MGeaECexj88CgEm5b",
+ "isStandardContent": false,
+ "name": "MIDDLE_IDLER_SHAFT <1>",
+ "partId": "RmRD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/palm_front_clasp.part b/judo/models/xml/keith/assets/palm_front_clasp.part
new file mode 100644
index 00000000..9bc1da2a
--- /dev/null
+++ b/judo/models/xml/keith/assets/palm_front_clasp.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MDMiOEOnjZY5UOtHS",
+ "isStandardContent": false,
+ "name": "palm front clasp <1>",
+ "partId": "RD2H",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/palm_main_structure.part b/judo/models/xml/keith/assets/palm_main_structure.part
new file mode 100644
index 00000000..e974e8c7
--- /dev/null
+++ b/judo/models/xml/keith/assets/palm_main_structure.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MPWHJTLtiqLPS9RbN",
+ "isStandardContent": false,
+ "name": "palm main structure <1>",
+ "partId": "RS4H",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/palm_middle_clasp.part b/judo/models/xml/keith/assets/palm_middle_clasp.part
new file mode 100644
index 00000000..ace8fe39
--- /dev/null
+++ b/judo/models/xml/keith/assets/palm_middle_clasp.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MKgod93hia7VQn8TR",
+ "isStandardContent": false,
+ "name": "palm middle clasp <1>",
+ "partId": "RF2H",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_12.part b/judo/models/xml/keith/assets/part_12.part
new file mode 100644
index 00000000..d6360b46
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_12.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MKEpqUvn5ES+n0nZZ",
+ "isStandardContent": false,
+ "name": "Part 12 <2>",
+ "partId": "RM9D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_14.part b/judo/models/xml/keith/assets/part_14.part
new file mode 100644
index 00000000..fe8eea30
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_14.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MruylCHp0R1csuQnu",
+ "isStandardContent": false,
+ "name": "Part 14 <1>",
+ "partId": "R89D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_15.part b/judo/models/xml/keith/assets/part_15.part
new file mode 100644
index 00000000..cff9f5a8
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_15.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MH3Cg4cLsS8DO0kf/",
+ "isStandardContent": false,
+ "name": "Part 15 <1>",
+ "partId": "RZ+D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_16.part b/judo/models/xml/keith/assets/part_16.part
new file mode 100644
index 00000000..b3d3211f
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_16.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MN+v4WIIFKQDmB07Q",
+ "isStandardContent": false,
+ "name": "Part 16 <1>",
+ "partId": "RZ+H",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_17.part b/judo/models/xml/keith/assets/part_17.part
new file mode 100644
index 00000000..f41c5031
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_17.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MMX7XgIYHQwhWGf+e",
+ "isStandardContent": false,
+ "name": "Part 17 <1>",
+ "partId": "RZ+L",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_18.part b/judo/models/xml/keith/assets/part_18.part
new file mode 100644
index 00000000..dae10bdd
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_18.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "Mfg32XWJ4ul06EIvJ",
+ "isStandardContent": false,
+ "name": "Part 18 <1>",
+ "partId": "RZ+P",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_19.part b/judo/models/xml/keith/assets/part_19.part
new file mode 100644
index 00000000..3b09d873
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_19.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MVePTnF+/7RGdqRoN",
+ "isStandardContent": false,
+ "name": "Part 19 <1>",
+ "partId": "Rc+D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/part_9__configuration_default.part b/judo/models/xml/keith/assets/part_9__configuration_default.part
new file mode 100644
index 00000000..b8204f36
--- /dev/null
+++ b/judo/models/xml/keith/assets/part_9__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MpT/t7mGp3SqWz/YS",
+ "isStandardContent": false,
+ "name": "Part 9 <1>",
+ "partId": "RxEL",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011910_keith_encoder_board__configuration_default.part b/judo/models/xml/keith/assets/rai_011910_keith_encoder_board__configuration_default.part
new file mode 100644
index 00000000..20d141d1
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011910_keith_encoder_board__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MyORXCeyR9iGNLwA2",
+ "isStandardContent": false,
+ "name": "RAI-011910 Keith Encoder Board <1>",
+ "partId": "RqOD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011950_thumb_swivel_phalange__configuration_default.part b/judo/models/xml/keith/assets/rai_011950_thumb_swivel_phalange__configuration_default.part
new file mode 100644
index 00000000..51a5ae20
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011950_thumb_swivel_phalange__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MQ/8nnUV6D29JFzyV",
+ "isStandardContent": false,
+ "name": "RAI-011950 THUMB_SWIVEL_PHALANGE <1>",
+ "partId": "R9BD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011951_thumb_swivel_clamshell__configuration_default.part b/judo/models/xml/keith/assets/rai_011951_thumb_swivel_clamshell__configuration_default.part
new file mode 100644
index 00000000..5ab0e249
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011951_thumb_swivel_clamshell__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MI9nzCWIel+f5Qsjs",
+ "isStandardContent": false,
+ "name": "RAI-011951 THUMB_SWIVEL_CLAMSHELL <1>",
+ "partId": "RzCD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011952_thumb_proximal_phalange__configuration_default.part b/judo/models/xml/keith/assets/rai_011952_thumb_proximal_phalange__configuration_default.part
new file mode 100644
index 00000000..7b3a403b
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011952_thumb_proximal_phalange__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mlz+X40Zx2yPeYxP1",
+ "isStandardContent": false,
+ "name": "RAI-011952 THUMB_PROXIMAL_PHALANGE <1>",
+ "partId": "RxBD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011957_thumb_ad_ab_phalange__configuration_default.part b/judo/models/xml/keith/assets/rai_011957_thumb_ad_ab_phalange__configuration_default.part
new file mode 100644
index 00000000..5ba0540c
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011957_thumb_ad_ab_phalange__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MEQPTyehos2UcUtrb",
+ "isStandardContent": false,
+ "name": "RAI-011957 THUMB_AD_AB_PHALANGE <1>",
+ "partId": "RjBD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011958_distal_4bar__configuration_default.part b/judo/models/xml/keith/assets/rai_011958_distal_4bar__configuration_default.part
new file mode 100644
index 00000000..d09ae1d3
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011958_distal_4bar__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M49DtQRxJzNKlYKCA",
+ "isStandardContent": false,
+ "name": "RAI-011958 DISTAL_4BAR <1>",
+ "partId": "RjKD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011959_shaft_distal__configuration_default.part b/judo/models/xml/keith/assets/rai_011959_shaft_distal__configuration_default.part
new file mode 100644
index 00000000..0d1ad743
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011959_shaft_distal__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MFLuurTyPnFBnnuM6",
+ "isStandardContent": false,
+ "name": "RAI-011959 SHAFT_DISTAL <1>",
+ "partId": "RRLD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011960_shaft_medial__configuration_default.part b/judo/models/xml/keith/assets/rai_011960_shaft_medial__configuration_default.part
new file mode 100644
index 00000000..8f0547a0
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011960_shaft_medial__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MENTWife0qIfl1diA",
+ "isStandardContent": false,
+ "name": "RAI-011960 SHAFT_MEDIAL <1>",
+ "partId": "RWJD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011962_shaft_ab_ad__configuration_default.part b/judo/models/xml/keith/assets/rai_011962_shaft_ab_ad__configuration_default.part
new file mode 100644
index 00000000..881af35d
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011962_shaft_ab_ad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MaRB1Y6gxzkr15C0l",
+ "isStandardContent": false,
+ "name": "RAI-011962 SHAFT_AB/AD <1>",
+ "partId": "RBHD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011967_palm_pad.part b/judo/models/xml/keith/assets/rai_011967_palm_pad.part
new file mode 100644
index 00000000..9d0ffc18
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011967_palm_pad.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MdhaAyUfTJLgsgUGB",
+ "isStandardContent": false,
+ "name": "RAI-011967 PALM PAD <1>",
+ "partId": "R34D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011983_thumb_medial_clamshell_right__configuration_default.part b/judo/models/xml/keith/assets/rai_011983_thumb_medial_clamshell_right__configuration_default.part
new file mode 100644
index 00000000..8327547d
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011983_thumb_medial_clamshell_right__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MbjtJv1xWiWOdO4/f",
+ "isStandardContent": false,
+ "name": "RAI-011983 THUMB_MEDIAL_CLAMSHELL_RIGHT <1>",
+ "partId": "RUTH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011984_thumb_medial_clamshell_left__configuration_default.part b/judo/models/xml/keith/assets/rai_011984_thumb_medial_clamshell_left__configuration_default.part
new file mode 100644
index 00000000..0e0408cd
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011984_thumb_medial_clamshell_left__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M/qW23OxalKalhc/N",
+ "isStandardContent": false,
+ "name": "RAI-011984 THUMB_MEDIAL_CLAMSHELL_LEFT <1>",
+ "partId": "RhJD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011985_shaft_proximal__configuration_default.part b/judo/models/xml/keith/assets/rai_011985_shaft_proximal__configuration_default.part
new file mode 100644
index 00000000..518728d1
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011985_shaft_proximal__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MBGL30pJ58d1y1/gj",
+ "isStandardContent": false,
+ "name": "RAI-011985 SHAFT_PROXIMAL <1>",
+ "partId": "RdED",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011986_thumb_medial_phalange_middle__configuration_default.part b/judo/models/xml/keith/assets/rai_011986_thumb_medial_phalange_middle__configuration_default.part
new file mode 100644
index 00000000..3c7af7bd
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011986_thumb_medial_phalange_middle__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mgb5bCmTWvu7CI2If",
+ "isStandardContent": false,
+ "name": "RAI-011986 THUMB_MEDIAL_PHALANGE_MIDDLE <1>",
+ "partId": "RUTD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_011991_middle_proximal_phalange__configuration_default.part b/judo/models/xml/keith/assets/rai_011991_middle_proximal_phalange__configuration_default.part
new file mode 100644
index 00000000..5e095f34
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_011991_middle_proximal_phalange__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MwuNLyLIAMgUgILh2",
+ "isStandardContent": false,
+ "name": "RAI-011991 MIDDLE_PROXIMAL_PHALANGE <1>",
+ "partId": "RzPD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012001_thumb_swivel_pulley_left__configuration_default.part b/judo/models/xml/keith/assets/rai_012001_thumb_swivel_pulley_left__configuration_default.part
new file mode 100644
index 00000000..0d083dc1
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012001_thumb_swivel_pulley_left__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mbv/FRF1TGmD5KVmE",
+ "isStandardContent": false,
+ "name": "RAI-012001 THUMB_SWIVEL_PULLEY LEFT <1>",
+ "partId": "RMDD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012002_pinky_swivel_pulley_right__configuration_default.part b/judo/models/xml/keith/assets/rai_012002_pinky_swivel_pulley_right__configuration_default.part
new file mode 100644
index 00000000..1c97393f
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012002_pinky_swivel_pulley_right__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "McPFWQMi81aSHjfz9",
+ "isStandardContent": false,
+ "name": "RAI-012002 PINKY_SWIVEL_PULLEY RIGHT <1>",
+ "partId": "RkSD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012003_finger_nail__configuration_default.part b/judo/models/xml/keith/assets/rai_012003_finger_nail__configuration_default.part
new file mode 100644
index 00000000..fb1e06ef
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012003_finger_nail__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M1j/6JQH9cJTaNq/x",
+ "isStandardContent": false,
+ "name": "RAI-012003 FINGER_NAIL <1>",
+ "partId": "RMMH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012004_thumb_distal_phalange__configuration_default.part b/judo/models/xml/keith/assets/rai_012004_thumb_distal_phalange__configuration_default.part
new file mode 100644
index 00000000..e838baa3
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012004_thumb_distal_phalange__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "McKLNqqB9WsuEDFC+",
+ "isStandardContent": false,
+ "name": "RAI-012004 THUMB_DISTAL_PHALANGE <2>",
+ "partId": "RMMD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012005_pulley_block_proximal__configuration_default.part b/judo/models/xml/keith/assets/rai_012005_pulley_block_proximal__configuration_default.part
new file mode 100644
index 00000000..592ddc40
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012005_pulley_block_proximal__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MOfYYlPg0P45KYaeB",
+ "isStandardContent": false,
+ "name": "RAI-012005 PULLEY BLOCK_PROXIMAL <1>",
+ "partId": "R3ED",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012006_ab_ad_cover__configuration_default.part b/judo/models/xml/keith/assets/rai_012006_ab_ad_cover__configuration_default.part
new file mode 100644
index 00000000..b922673c
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012006_ab_ad_cover__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M3bXCumbMcfGOi5gi",
+ "isStandardContent": false,
+ "name": "RAI-012006 AB/AD_COVER <1>",
+ "partId": "RCND",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012007_aesthetic_cover__configuration_default.part b/judo/models/xml/keith/assets/rai_012007_aesthetic_cover__configuration_default.part
new file mode 100644
index 00000000..62b0bbc5
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012007_aesthetic_cover__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M6xXsstwVdcpjj396",
+ "isStandardContent": false,
+ "name": "RAI-012007 AESTHETIC_COVER <1>",
+ "partId": "RATD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012013_proximal_fingerpad__configuration_default.part b/judo/models/xml/keith/assets/rai_012013_proximal_fingerpad__configuration_default.part
new file mode 100644
index 00000000..97a196af
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012013_proximal_fingerpad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mp/cxuxv0ukLRT84X",
+ "isStandardContent": false,
+ "name": "RAI-012013 PROXIMAL_FINGERPAD <1>",
+ "partId": "RHOD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012014_ab_ad_fingerpad__configuration_default.part b/judo/models/xml/keith/assets/rai_012014_ab_ad_fingerpad__configuration_default.part
new file mode 100644
index 00000000..49ade1ec
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012014_ab_ad_fingerpad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M5/yRU2Xs5pEU0mes",
+ "isStandardContent": false,
+ "name": "RAI-012014 AB/AD_FINGERPAD <1>",
+ "partId": "RjND",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012015_medial_fingerpad__configuration_default.part b/judo/models/xml/keith/assets/rai_012015_medial_fingerpad__configuration_default.part
new file mode 100644
index 00000000..18f60e76
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012015_medial_fingerpad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MJvS/+Xg/ttZCA1V8",
+ "isStandardContent": false,
+ "name": "RAI-012015 MEDIAL_FINGERPAD <1>",
+ "partId": "RiMD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012016_distal_fingerpad__configuration_default.part b/judo/models/xml/keith/assets/rai_012016_distal_fingerpad__configuration_default.part
new file mode 100644
index 00000000..54be8fc9
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012016_distal_fingerpad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M6K5c4/s62T+na+Rp",
+ "isStandardContent": false,
+ "name": "RAI-012016 DISTAL_FINGERPAD <1>",
+ "partId": "RlLD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012017_middle_proximal_pad__configuration_default.part b/judo/models/xml/keith/assets/rai_012017_middle_proximal_pad__configuration_default.part
new file mode 100644
index 00000000..0f1bd17e
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012017_middle_proximal_pad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MgOoAVTEdDTm1YAR5",
+ "isStandardContent": false,
+ "name": "RAI-012017 MIDDLE_PROXIMAL_PAD <1>",
+ "partId": "RnSD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012018_pulley_proximal_to_ab_ad__configuration_default.part b/judo/models/xml/keith/assets/rai_012018_pulley_proximal_to_ab_ad__configuration_default.part
new file mode 100644
index 00000000..7e4a2bae
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012018_pulley_proximal_to_ab_ad__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M4iYSqMaQXFOcmpk/",
+ "isStandardContent": false,
+ "name": "RAI-012018 PULLEY PROXIMAL TO AB/AD <1>",
+ "partId": "RxED",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012019_pulley_proximal_to_distal__configuration_default.part b/judo/models/xml/keith/assets/rai_012019_pulley_proximal_to_distal__configuration_default.part
new file mode 100644
index 00000000..91dd0475
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012019_pulley_proximal_to_distal__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mf3fiQHmMgm0YaVIg",
+ "isStandardContent": false,
+ "name": "RAI-012019 PULLEY PROXIMAL TO DISTAL <1>",
+ "partId": "RxEH",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012021_pulley_ab_ad_to_distal__configuration_default.part b/judo/models/xml/keith/assets/rai_012021_pulley_ab_ad_to_distal__configuration_default.part
new file mode 100644
index 00000000..8058caf4
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012021_pulley_ab_ad_to_distal__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "M0JofGeJuFQ5LDP37",
+ "isStandardContent": false,
+ "name": "RAI-012021 PULLEY AB/AD TO DISTAL <1>",
+ "partId": "RNID",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012022_idler__configuration_default.part b/judo/models/xml/keith/assets/rai_012022_idler__configuration_default.part
new file mode 100644
index 00000000..6934a79d
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012022_idler__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MbHDVutt9fv9/itfY",
+ "isStandardContent": false,
+ "name": "RAI-012022 IDLER <1>",
+ "partId": "RYID",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012023__configuration_default.part b/judo/models/xml/keith/assets/rai_012023__configuration_default.part
new file mode 100644
index 00000000..609004b3
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012023__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "MrEx5GmyJUIVlff7I",
+ "isStandardContent": false,
+ "name": "RAI-012023 <2>",
+ "partId": "ROQD",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/rai_012024__configuration_default.part b/judo/models/xml/keith/assets/rai_012024__configuration_default.part
new file mode 100644
index 00000000..a32f85d3
--- /dev/null
+++ b/judo/models/xml/keith/assets/rai_012024__configuration_default.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "List_nsdEm1g0iTLRvx=Default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "30fb69f4eccb67eaf3b1f655",
+ "fullConfiguration": "List_nsdEm1g0iTLRvx=Default",
+ "id": "Mf+txBh027pvJHp9c",
+ "isStandardContent": false,
+ "name": "RAI-012024 <1>",
+ "partId": "RQID",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/swivel_mount_left.part b/judo/models/xml/keith/assets/swivel_mount_left.part
new file mode 100644
index 00000000..200cc81f
--- /dev/null
+++ b/judo/models/xml/keith/assets/swivel_mount_left.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MxAHXdq40rNxaeYi+",
+ "isStandardContent": false,
+ "name": "swivel mount left <1>",
+ "partId": "Rk6D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/assets/swivel_mount_right.part b/judo/models/xml/keith/assets/swivel_mount_right.part
new file mode 100644
index 00000000..6a6c088e
--- /dev/null
+++ b/judo/models/xml/keith/assets/swivel_mount_right.part
@@ -0,0 +1,13 @@
+{
+ "configuration": "default",
+ "documentId": "078d73b5d2e9adfe12abdfb0",
+ "documentMicroversion": "b2a81a5625c018d7210569f4",
+ "elementId": "6e96f0c1b94fd73a107f0195",
+ "fullConfiguration": "default",
+ "id": "MPQnG5Gcm6qIWuQAc",
+ "isStandardContent": false,
+ "name": "swivel mount right <1>",
+ "partId": "RH7D",
+ "suppressed": false,
+ "type": "Part"
+}
diff --git a/judo/models/xml/keith/config.json b/judo/models/xml/keith/config.json
new file mode 100644
index 00000000..8591bb4a
--- /dev/null
+++ b/judo/models/xml/keith/config.json
@@ -0,0 +1,7 @@
+{
+ "url": "https://theaiinstitute.onshape.com/documents/078d73b5d2e9adfe12abdfb0/w/ea8cfa794dc56d979f618f63/e/04bd2b99edf7cc7359393397",
+ "outputFormat": "mujoco",
+ "debug": false,
+ "merge_stls": true,
+ "simplify_stls": true
+}
diff --git a/judo/models/xml/keith/robot.xml b/judo/models/xml/keith/robot.xml
new file mode 100644
index 00000000..da7e8fd6
--- /dev/null
+++ b/judo/models/xml/keith/robot.xml
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/judo/models/xml/keith/scene.xml b/judo/models/xml/keith/scene.xml
new file mode 100644
index 00000000..b995a638
--- /dev/null
+++ b/judo/models/xml/keith/scene.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/judo/utils/mujoco.py b/judo/utils/mujoco_utils.py
similarity index 100%
rename from judo/utils/mujoco.py
rename to judo/utils/mujoco_utils.py
diff --git a/judo/utils/speed_benchmark.py b/judo/utils/speed_benchmark.py
new file mode 100644
index 00000000..725a26ec
--- /dev/null
+++ b/judo/utils/speed_benchmark.py
@@ -0,0 +1,206 @@
+# Copyright (c) 2025 Robotics and AI Institute LLC. All rights reserved.
+
+import time
+from dataclasses import dataclass, field
+from typing import List
+
+import mujoco
+import mujoco.viewer
+import numpy as np
+import tyro
+from mujoco import MjData, MjModel, rollout
+
+from judo import MODEL_PATH
+
+
+def get_state(model: MjModel, data: list[MjData]) -> np.ndarray:
+ """
+ Get the state of the environment.
+
+ Args:
+ model: The mujoco model.
+ data: The mujoco data.
+
+ Returns:
+ The state of the environment.
+
+ Note:
+ The state is a numpy array of shape (num_envs, state_size).
+ """
+ num_envs = len(data)
+ full_physics = mujoco.mjtState.mjSTATE_FULLPHYSICS
+ state_size = mujoco.mj_stateSize(model, full_physics)
+ state = np.zeros((num_envs, state_size), dtype=np.float64)
+ for i in range(num_envs):
+ mujoco.mj_getState(model, data[i], state[i], full_physics)
+ return state
+
+
+@dataclass
+class SpeedBenchmarkConfig:
+ """A class to configure the speed benchmark."""
+
+ # model_path: str = field(default=str(MODEL_PATH / "xml/cartpole.xml"))
+ model_path: str = field(default=str(MODEL_PATH / "xml/keith/scene.xml"))
+ duration: List[float] = field(default_factory=lambda: [0.1, 0.2, 0.5, 1.0, 2.0, 3.0])
+ num_envs: List[int] = field(default_factory=lambda: [1, 2, 4, 8, 16, 32, 48, 64])
+ visualize: bool = field(default=False)
+
+
+class SpeedBenchmark:
+ """A class to benchmark the speed of a function."""
+
+ def __init__(self, config: SpeedBenchmarkConfig) -> None:
+ """
+ Initialize the SpeedBenchmark class.
+
+ Args:
+ config: The configuration for the speed benchmark.
+ """
+ self.config = config
+ self.model = MjModel.from_xml_path(config.model_path)
+ self.rollout_ = rollout.Rollout(nthread=config.num_envs[0])
+ self.max_num_envs = max(config.num_envs)
+ self.data = [MjData(self.model) for _ in range(self.max_num_envs)]
+
+ # Print model keyframe information
+ if self.model.nkey > 0:
+ print("\nModel Keyframes:")
+ print(f" Number of keyframes: {self.model.nkey}")
+ print(f" Key times: {self.model.key_time}")
+ print(f" First keyframe qpos: {self.model.key_qpos[0]}")
+ print(f" First keyframe qvel: {self.model.key_qvel[0]}")
+ print(f" First keyframe ctrl: {self.model.key_ctrl[0]}")
+ else:
+ print("\nModel has no keyframes defined.")
+
+ def reset(self) -> None:
+ """
+ Reset the simulation.
+
+ Note:
+ The simulation is reset to the first keyframe.
+ """
+ assert self.model.nkey > 0, "Model has no keyframes defined."
+ for i in range(self.max_num_envs):
+ mujoco.mj_resetData(self.model, self.data[i])
+ self.data[i].time = self.model.key_time[0]
+ self.data[i].qpos[:] = self.model.key_qpos[0]
+ self.data[i].qvel[:] = self.model.key_qvel[0]
+ self.data[i].ctrl[:] = self.model.key_ctrl[0]
+ mujoco.mj_forward(self.model, self.data[i])
+
+ def update_data(self, state_rollout: np.ndarray) -> None:
+ """
+ Update the data with the last state of the rollout.
+
+ Args:
+ state_rollout: The state rollout. Shape=(num_envs, num_steps, state_dim).
+ """
+ num_envs = state_rollout.shape[0]
+ # update the data with the last state of the rollout
+ for i in range(num_envs):
+ self.data[i].time = state_rollout[i, -1, 0]
+ self.data[i].qpos[:] = state_rollout[i, -1, 1 : self.model.nq + 1]
+ self.data[i].qvel[:] = state_rollout[i, -1, self.model.nq + 1 :]
+ mujoco.mj_forward(self.model, self.data[i])
+
+ def run(self, duration: float, num_envs: int) -> float:
+ """
+ Run the speed benchmark.
+
+ Args:
+ duration: Physics simulation duration in seconds.
+ num_envs: Number of environments to run in parallel.
+
+ Returns:
+ Compute time in seconds.
+ """
+ self.reset()
+ initial_state = get_state(self.model, self.data[:num_envs]) # (num_envs, state_size)
+ num_steps = int(duration / self.model.opt.timestep)
+ rollout_action = np.random.randn(num_envs, num_steps, self.model.nu) # (num_envs, num_steps, action_size)
+
+ rollout_ = rollout.Rollout(nthread=num_envs)
+
+ # Time the rollout
+ start_time = time.perf_counter()
+ state_rollout, sensor_rollout = rollout_.rollout(
+ self.model, self.data[:num_envs], initial_state, rollout_action
+ )
+ compute_time = time.perf_counter() - start_time
+
+ self.update_data(state_rollout)
+ return compute_time
+
+ def benchmark_all(self) -> None:
+ """
+ Run benchmarks for all combinations of num_envs and duration.
+
+ Computes and displays the real-time factor (physics_time / compute_time).
+ """
+ line_width = 120
+ print(f"\n{'=' * line_width}")
+ print("Speed Benchmark Results")
+ print(f"Model: {self.config.model_path}")
+ print(f"{'=' * line_width}")
+ print(
+ f"\n{'duration (s)':<15} {'num_envs':<12} {'compute_time (s)':<18} {'per_thread_real_time_factor':<30} {'real_time_factor':<25}"
+ )
+ print(f"{'-' * line_width}")
+
+ results = []
+ for duration in self.config.duration:
+ print(f"\n{'─' * line_width}")
+ print(f"Duration: {duration:.3f}s")
+ print(f"{'─' * line_width}")
+ for num_envs in self.config.num_envs:
+ compute_time = self.run(duration, num_envs)
+ per_thread_real_time_factor = duration / compute_time
+ real_time_factor = per_thread_real_time_factor * num_envs
+
+ results.append((num_envs, duration, compute_time, per_thread_real_time_factor, real_time_factor))
+
+ print(
+ f"{duration:<15.3f} {num_envs:<12} {compute_time:<18.6f} {per_thread_real_time_factor:<30.2f}x {real_time_factor:<25.2f}x"
+ )
+
+ print(f"{'-' * line_width}\n")
+
+ def visualize_trajectory(self, duration: float) -> None:
+ """
+ Visualize the trajectory of the robot using mujoco viewer.
+
+ Args:
+ duration: Physics simulation duration in seconds.
+ """
+ # Reset the simulation
+ self.reset()
+ data = self.data[0] # Use first data instance for visualization
+
+ num_steps = int(duration / self.model.opt.timestep)
+ action = np.random.randn(1, num_steps, self.model.nu)
+
+ # Create and launch the viewer
+ with mujoco.viewer.launch_passive(self.model, data) as viewer:
+ # Step through the simulation
+ for step in range(num_steps):
+ # Apply control action
+ data.ctrl[:] = action[0, step, :]
+
+ # Step the simulation
+ mujoco.mj_step(self.model, data)
+
+ # Sync viewer with simulation state
+ viewer.sync()
+
+ # Small delay to make visualization visible (optional)
+ time.sleep(self.model.opt.timestep)
+
+
+if __name__ == "__main__":
+ config = tyro.cli(SpeedBenchmarkConfig)
+ benchmark = SpeedBenchmark(config)
+ benchmark.benchmark_all()
+ if config.visualize:
+ benchmark.visualize_trajectory(10.0)
diff --git a/pyproject.toml b/pyproject.toml
index 74da192a..7558e241 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -70,6 +70,7 @@ dev = [
"pytest-cov",
"pytest-xdist",
"ruff",
+ "tyro",
]
[tool.coverage.run]