Georg commited on
Commit
7e77839
·
1 Parent(s): 25f4c68

multi-axes jogging

Browse files
MUJOCO_LOG.TXT ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Thu Jan 29 17:06:20 2026
2
+ WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited
3
+
4
+ Thu Jan 29 17:13:30 2026
5
+ WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited
6
+
7
+ Fri Jan 30 16:28:31 2026
8
+ WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited
9
+
README.md CHANGED
@@ -432,8 +432,26 @@ Nova-Sim provides a minimal HTTP API for static information:
432
  "action_space": {...},
433
  "observation_space": {...},
434
  "camera_feeds": [
435
- {"name": "main", "label": "Main Camera", "url": "/nova-sim/api/v1/video_feed"},
436
- {"name": "aux_top", "label": "Top View", "url": "/nova-sim/api/v1/camera/aux_top/video_feed"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  ],
438
  "home_pose": [-1.57, -1.57, 1.57, -1.57, -1.57, 0.0]
439
  }
 
432
  "action_space": {...},
433
  "observation_space": {...},
434
  "camera_feeds": [
435
+ {
436
+ "name": "main",
437
+ "label": "Main Camera",
438
+ "url": "/nova-sim/api/v1/video_feed",
439
+ "pose": {
440
+ "position": {"x": 0.75, "y": -0.75, "z": 0.9},
441
+ "orientation": {"w": 0.92, "x": -0.16, "y": -0.36, "z": 0.04}
442
+ },
443
+ "intrinsics": {"fx": 869.1, "fy": 869.1, "cx": 640.0, "cy": 360.0, "width": 1280, "height": 720, "fovy_degrees": 45.0}
444
+ },
445
+ {
446
+ "name": "aux_top",
447
+ "label": "Top View",
448
+ "url": "/nova-sim/api/v1/camera/aux_top/video_feed",
449
+ "pose": {
450
+ "position": {"x": 0.5, "y": 0.2, "z": 0.9},
451
+ "orientation": {"w": 0.99, "x": -0.08, "y": 0.02, "z": 0.02}
452
+ },
453
+ "intrinsics": {"fx": 193.1, "fy": 193.1, "cx": 120.0, "cy": 80.0, "width": 240, "height": 160, "fovy_degrees": 45.0}
454
+ }
455
  ],
456
  "home_pose": [-1.57, -1.57, 1.57, -1.57, -1.57, 0.0]
457
  }
frontend/index.html CHANGED
@@ -885,38 +885,38 @@
885
  <div class="jog-row">
886
  <label style="color: #ff6b6b; font-weight: bold;">X</label>
887
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'x', '-')"
888
- onmouseup="stopJog()"
889
  ontouchstart="startJog('cartesian_translation', 'x', '-'); event.preventDefault()"
890
- ontouchend="stopJog()">−</button>
891
  <span class="val-display" id="pos_x_val">0.00</span>
892
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'x', '+')"
893
- onmouseup="stopJog()"
894
  ontouchstart="startJog('cartesian_translation', 'x', '+'); event.preventDefault()"
895
- ontouchend="stopJog()">+</button>
896
  </div>
897
  <div class="jog-row">
898
  <label style="color: #51cf66; font-weight: bold;">Y</label>
899
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'y', '-')"
900
- onmouseup="stopJog()"
901
  ontouchstart="startJog('cartesian_translation', 'y', '-'); event.preventDefault()"
902
- ontouchend="stopJog()">−</button>
903
  <span class="val-display" id="pos_y_val">0.00</span>
904
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'y', '+')"
905
- onmouseup="stopJog()"
906
  ontouchstart="startJog('cartesian_translation', 'y', '+'); event.preventDefault()"
907
- ontouchend="stopJog()">+</button>
908
  </div>
909
  <div class="jog-row">
910
  <label style="color: #339af0; font-weight: bold;">Z</label>
911
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'z', '-')"
912
- onmouseup="stopJog()"
913
  ontouchstart="startJog('cartesian_translation', 'z', '-'); event.preventDefault()"
914
- ontouchend="stopJog()">−</button>
915
  <span class="val-display" id="pos_z_val">0.00</span>
916
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'z', '+')"
917
- onmouseup="stopJog()"
918
  ontouchstart="startJog('cartesian_translation', 'z', '+'); event.preventDefault()"
919
- ontouchend="stopJog()">+</button>
920
  </div>
921
  </div>
922
  <div style="margin-top: 8px;">
@@ -931,38 +931,38 @@
931
  <div class="jog-row">
932
  <label style="color: #ff6b6b; font-weight: bold;">Rx</label>
933
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'x', '-')"
934
- onmouseup="stopJog()"
935
  ontouchstart="startJog('cartesian_rotation', 'x', '-'); event.preventDefault()"
936
- ontouchend="stopJog()">−</button>
937
  <span class="val-display" id="rot_x_val">0.00</span>
938
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'x', '+')"
939
- onmouseup="stopJog()"
940
  ontouchstart="startJog('cartesian_rotation', 'x', '+'); event.preventDefault()"
941
- ontouchend="stopJog()">+</button>
942
  </div>
943
  <div class="jog-row">
944
  <label style="color: #51cf66; font-weight: bold;">Ry</label>
945
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'y', '-')"
946
- onmouseup="stopJog()"
947
  ontouchstart="startJog('cartesian_rotation', 'y', '-'); event.preventDefault()"
948
- ontouchend="stopJog()">−</button>
949
  <span class="val-display" id="rot_y_val">0.00</span>
950
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'y', '+')"
951
- onmouseup="stopJog()"
952
  ontouchstart="startJog('cartesian_rotation', 'y', '+'); event.preventDefault()"
953
- ontouchend="stopJog()">+</button>
954
  </div>
955
  <div class="jog-row">
956
  <label style="color: #339af0; font-weight: bold;">Rz</label>
957
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'z', '-')"
958
- onmouseup="stopJog()"
959
  ontouchstart="startJog('cartesian_rotation', 'z', '-'); event.preventDefault()"
960
- ontouchend="stopJog()">−</button>
961
  <span class="val-display" id="rot_z_val">0.00</span>
962
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'z', '+')"
963
- onmouseup="stopJog()"
964
  ontouchstart="startJog('cartesian_rotation', 'z', '+'); event.preventDefault()"
965
- ontouchend="stopJog()">+</button>
966
  </div>
967
  </div>
968
  <div style="margin-top: 8px;">
@@ -980,63 +980,63 @@
980
  <div class="jog-controls">
981
  <div class="jog-row">
982
  <label>J1</label>
983
- <button class="jog-btn" onmousedown="startJog('joint', 1, '-')" onmouseup="stopJog()"
984
  ontouchstart="startJog('joint', 1, '-'); event.preventDefault()"
985
- ontouchend="stopJog()">−</button>
986
  <span class="val-display" id="joint_0_val">-1.57</span>
987
- <button class="jog-btn" onmousedown="startJog('joint', 1, '+')" onmouseup="stopJog()"
988
  ontouchstart="startJog('joint', 1, '+'); event.preventDefault()"
989
- ontouchend="stopJog()">+</button>
990
  </div>
991
  <div class="jog-row">
992
  <label>J2</label>
993
- <button class="jog-btn" onmousedown="startJog('joint', 2, '-')" onmouseup="stopJog()"
994
  ontouchstart="startJog('joint', 2, '-'); event.preventDefault()"
995
- ontouchend="stopJog()">−</button>
996
  <span class="val-display" id="joint_1_val">-1.57</span>
997
- <button class="jog-btn" onmousedown="startJog('joint', 2, '+')" onmouseup="stopJog()"
998
  ontouchstart="startJog('joint', 2, '+'); event.preventDefault()"
999
- ontouchend="stopJog()">+</button>
1000
  </div>
1001
  <div class="jog-row">
1002
  <label>J3</label>
1003
- <button class="jog-btn" onmousedown="startJog('joint', 3, '-')" onmouseup="stopJog()"
1004
  ontouchstart="startJog('joint', 3, '-'); event.preventDefault()"
1005
- ontouchend="stopJog()">−</button>
1006
  <span class="val-display" id="joint_2_val">1.57</span>
1007
- <button class="jog-btn" onmousedown="startJog('joint', 3, '+')" onmouseup="stopJog()"
1008
  ontouchstart="startJog('joint', 3, '+'); event.preventDefault()"
1009
- ontouchend="stopJog()">+</button>
1010
  </div>
1011
  <div class="jog-row">
1012
  <label>J4</label>
1013
- <button class="jog-btn" onmousedown="startJog('joint', 4, '-')" onmouseup="stopJog()"
1014
  ontouchstart="startJog('joint', 4, '-'); event.preventDefault()"
1015
- ontouchend="stopJog()">−</button>
1016
  <span class="val-display" id="joint_3_val">-1.57</span>
1017
- <button class="jog-btn" onmousedown="startJog('joint', 4, '+')" onmouseup="stopJog()"
1018
  ontouchstart="startJog('joint', 4, '+'); event.preventDefault()"
1019
- ontouchend="stopJog()">+</button>
1020
  </div>
1021
  <div class="jog-row">
1022
  <label>J5</label>
1023
- <button class="jog-btn" onmousedown="startJog('joint', 5, '-')" onmouseup="stopJog()"
1024
  ontouchstart="startJog('joint', 5, '-'); event.preventDefault()"
1025
- ontouchend="stopJog()">−</button>
1026
  <span class="val-display" id="joint_4_val">-1.57</span>
1027
- <button class="jog-btn" onmousedown="startJog('joint', 5, '+')" onmouseup="stopJog()"
1028
  ontouchstart="startJog('joint', 5, '+'); event.preventDefault()"
1029
- ontouchend="stopJog()">+</button>
1030
  </div>
1031
  <div class="jog-row">
1032
  <label>J6</label>
1033
- <button class="jog-btn" onmousedown="startJog('joint', 6, '-')" onmouseup="stopJog()"
1034
  ontouchstart="startJog('joint', 6, '-'); event.preventDefault()"
1035
- ontouchend="stopJog()">−</button>
1036
  <span class="val-display" id="joint_5_val">0.00</span>
1037
- <button class="jog-btn" onmousedown="startJog('joint', 6, '+')" onmouseup="stopJog()"
1038
  ontouchstart="startJog('joint', 6, '+'); event.preventDefault()"
1039
- ontouchend="stopJog()">+</button>
1040
  </div>
1041
  </div>
1042
  <div style="margin-top: 8px;">
@@ -2187,29 +2187,72 @@
2187
  document.getElementById('joint_vel_val').innerText = jointVelocity.toFixed(1);
2188
  }
2189
 
2190
- function startJog(jogType, axisOrJoint, direction) {
 
 
 
 
 
 
 
 
 
 
 
2191
  const actionData = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2192
 
2193
- if (jogType === 'cartesian_translation') {
2194
- const velocity = (direction === '+' ? 1 : -1) * (transVelocity / 1000.0); // Convert mm/s to m/s
2195
- if (axisOrJoint === 'x') actionData.vx = velocity;
2196
- else if (axisOrJoint === 'y') actionData.vy = velocity;
2197
- else if (axisOrJoint === 'z') actionData.vz = velocity;
2198
- } else if (jogType === 'cartesian_rotation') {
2199
- const velocity = (direction === '+' ? 1 : -1) * rotVelocity;
2200
- if (axisOrJoint === 'x') actionData.vrx = velocity;
2201
- else if (axisOrJoint === 'y') actionData.vry = velocity;
2202
- else if (axisOrJoint === 'z') actionData.vrz = velocity;
2203
- } else if (jogType === 'joint') {
2204
- const velocity = (direction === '+' ? 1 : -1) * jointVelocity;
2205
- actionData['j' + axisOrJoint] = velocity; // j1-j6
 
2206
  }
2207
 
2208
- send('action', actionData);
 
 
 
 
 
 
 
 
 
 
2209
  }
2210
 
2211
- function stopJog() {
2212
- send('action', {}); // Send zero velocities
 
 
2213
  }
2214
 
2215
  function setGripper(action) {
@@ -2290,28 +2333,18 @@
2290
  send('teleop_action', { dx, dy, dz });
2291
  }
2292
 
2293
- function startArmJogForKey(code) {
2294
- const entry = armJogKeyMap[code];
2295
- if (!entry) {
2296
  return;
2297
  }
2298
- startJog(entry.jogType, entry.axis, entry.direction);
2299
-
2300
- }
2301
-
2302
- function handleArmKeyDown(code) {
2303
- startArmJogForKey(code);
2304
  }
2305
 
2306
  function handleArmKeyUp(code) {
2307
  if (!armJogKeyMap[code]) {
2308
  return;
2309
  }
2310
- stopJog();
2311
- const remaining = [...keysPressed].filter((key) => armJogKeyMap[key]);
2312
- if (remaining.length > 0) {
2313
- startArmJogForKey(remaining[remaining.length - 1]);
2314
- }
2315
  }
2316
 
2317
  function handleKeyStateChange() {
@@ -2463,4 +2496,4 @@
2463
  </script>
2464
  </body>
2465
 
2466
- </html>
 
885
  <div class="jog-row">
886
  <label style="color: #ff6b6b; font-weight: bold;">X</label>
887
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'x', '-')"
888
+ onmouseup="stopJog('cartesian_translation', 'x', '-')"
889
  ontouchstart="startJog('cartesian_translation', 'x', '-'); event.preventDefault()"
890
+ ontouchend="stopJog('cartesian_translation', 'x', '-')">−</button>
891
  <span class="val-display" id="pos_x_val">0.00</span>
892
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'x', '+')"
893
+ onmouseup="stopJog('cartesian_translation', 'x', '+')"
894
  ontouchstart="startJog('cartesian_translation', 'x', '+'); event.preventDefault()"
895
+ ontouchend="stopJog('cartesian_translation', 'x', '+')">+</button>
896
  </div>
897
  <div class="jog-row">
898
  <label style="color: #51cf66; font-weight: bold;">Y</label>
899
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'y', '-')"
900
+ onmouseup="stopJog('cartesian_translation', 'y', '-')"
901
  ontouchstart="startJog('cartesian_translation', 'y', '-'); event.preventDefault()"
902
+ ontouchend="stopJog('cartesian_translation', 'y', '-')">−</button>
903
  <span class="val-display" id="pos_y_val">0.00</span>
904
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'y', '+')"
905
+ onmouseup="stopJog('cartesian_translation', 'y', '+')"
906
  ontouchstart="startJog('cartesian_translation', 'y', '+'); event.preventDefault()"
907
+ ontouchend="stopJog('cartesian_translation', 'y', '+')">+</button>
908
  </div>
909
  <div class="jog-row">
910
  <label style="color: #339af0; font-weight: bold;">Z</label>
911
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'z', '-')"
912
+ onmouseup="stopJog('cartesian_translation', 'z', '-')"
913
  ontouchstart="startJog('cartesian_translation', 'z', '-'); event.preventDefault()"
914
+ ontouchend="stopJog('cartesian_translation', 'z', '-')">−</button>
915
  <span class="val-display" id="pos_z_val">0.00</span>
916
  <button class="jog-btn" onmousedown="startJog('cartesian_translation', 'z', '+')"
917
+ onmouseup="stopJog('cartesian_translation', 'z', '+')"
918
  ontouchstart="startJog('cartesian_translation', 'z', '+'); event.preventDefault()"
919
+ ontouchend="stopJog('cartesian_translation', 'z', '+')">+</button>
920
  </div>
921
  </div>
922
  <div style="margin-top: 8px;">
 
931
  <div class="jog-row">
932
  <label style="color: #ff6b6b; font-weight: bold;">Rx</label>
933
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'x', '-')"
934
+ onmouseup="stopJog('cartesian_rotation', 'x', '-')"
935
  ontouchstart="startJog('cartesian_rotation', 'x', '-'); event.preventDefault()"
936
+ ontouchend="stopJog('cartesian_rotation', 'x', '-')">−</button>
937
  <span class="val-display" id="rot_x_val">0.00</span>
938
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'x', '+')"
939
+ onmouseup="stopJog('cartesian_rotation', 'x', '+')"
940
  ontouchstart="startJog('cartesian_rotation', 'x', '+'); event.preventDefault()"
941
+ ontouchend="stopJog('cartesian_rotation', 'x', '+')">+</button>
942
  </div>
943
  <div class="jog-row">
944
  <label style="color: #51cf66; font-weight: bold;">Ry</label>
945
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'y', '-')"
946
+ onmouseup="stopJog('cartesian_rotation', 'y', '-')"
947
  ontouchstart="startJog('cartesian_rotation', 'y', '-'); event.preventDefault()"
948
+ ontouchend="stopJog('cartesian_rotation', 'y', '-')">−</button>
949
  <span class="val-display" id="rot_y_val">0.00</span>
950
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'y', '+')"
951
+ onmouseup="stopJog('cartesian_rotation', 'y', '+')"
952
  ontouchstart="startJog('cartesian_rotation', 'y', '+'); event.preventDefault()"
953
+ ontouchend="stopJog('cartesian_rotation', 'y', '+')">+</button>
954
  </div>
955
  <div class="jog-row">
956
  <label style="color: #339af0; font-weight: bold;">Rz</label>
957
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'z', '-')"
958
+ onmouseup="stopJog('cartesian_rotation', 'z', '-')"
959
  ontouchstart="startJog('cartesian_rotation', 'z', '-'); event.preventDefault()"
960
+ ontouchend="stopJog('cartesian_rotation', 'z', '-')">−</button>
961
  <span class="val-display" id="rot_z_val">0.00</span>
962
  <button class="jog-btn" onmousedown="startJog('cartesian_rotation', 'z', '+')"
963
+ onmouseup="stopJog('cartesian_rotation', 'z', '+')"
964
  ontouchstart="startJog('cartesian_rotation', 'z', '+'); event.preventDefault()"
965
+ ontouchend="stopJog('cartesian_rotation', 'z', '+')">+</button>
966
  </div>
967
  </div>
968
  <div style="margin-top: 8px;">
 
980
  <div class="jog-controls">
981
  <div class="jog-row">
982
  <label>J1</label>
983
+ <button class="jog-btn" onmousedown="startJog('joint', 1, '-')" onmouseup="stopJog('joint', 1, '-')"
984
  ontouchstart="startJog('joint', 1, '-'); event.preventDefault()"
985
+ ontouchend="stopJog('joint', 1, '-')">−</button>
986
  <span class="val-display" id="joint_0_val">-1.57</span>
987
+ <button class="jog-btn" onmousedown="startJog('joint', 1, '+')" onmouseup="stopJog('joint', 1, '+')"
988
  ontouchstart="startJog('joint', 1, '+'); event.preventDefault()"
989
+ ontouchend="stopJog('joint', 1, '+')">+</button>
990
  </div>
991
  <div class="jog-row">
992
  <label>J2</label>
993
+ <button class="jog-btn" onmousedown="startJog('joint', 2, '-')" onmouseup="stopJog('joint', 2, '-')"
994
  ontouchstart="startJog('joint', 2, '-'); event.preventDefault()"
995
+ ontouchend="stopJog('joint', 2, '-')">−</button>
996
  <span class="val-display" id="joint_1_val">-1.57</span>
997
+ <button class="jog-btn" onmousedown="startJog('joint', 2, '+')" onmouseup="stopJog('joint', 2, '+')"
998
  ontouchstart="startJog('joint', 2, '+'); event.preventDefault()"
999
+ ontouchend="stopJog('joint', 2, '+')">+</button>
1000
  </div>
1001
  <div class="jog-row">
1002
  <label>J3</label>
1003
+ <button class="jog-btn" onmousedown="startJog('joint', 3, '-')" onmouseup="stopJog('joint', 3, '-')"
1004
  ontouchstart="startJog('joint', 3, '-'); event.preventDefault()"
1005
+ ontouchend="stopJog('joint', 3, '-')">−</button>
1006
  <span class="val-display" id="joint_2_val">1.57</span>
1007
+ <button class="jog-btn" onmousedown="startJog('joint', 3, '+')" onmouseup="stopJog('joint', 3, '+')"
1008
  ontouchstart="startJog('joint', 3, '+'); event.preventDefault()"
1009
+ ontouchend="stopJog('joint', 3, '+')">+</button>
1010
  </div>
1011
  <div class="jog-row">
1012
  <label>J4</label>
1013
+ <button class="jog-btn" onmousedown="startJog('joint', 4, '-')" onmouseup="stopJog('joint', 4, '-')"
1014
  ontouchstart="startJog('joint', 4, '-'); event.preventDefault()"
1015
+ ontouchend="stopJog('joint', 4, '-')">−</button>
1016
  <span class="val-display" id="joint_3_val">-1.57</span>
1017
+ <button class="jog-btn" onmousedown="startJog('joint', 4, '+')" onmouseup="stopJog('joint', 4, '+')"
1018
  ontouchstart="startJog('joint', 4, '+'); event.preventDefault()"
1019
+ ontouchend="stopJog('joint', 4, '+')">+</button>
1020
  </div>
1021
  <div class="jog-row">
1022
  <label>J5</label>
1023
+ <button class="jog-btn" onmousedown="startJog('joint', 5, '-')" onmouseup="stopJog('joint', 5, '-')"
1024
  ontouchstart="startJog('joint', 5, '-'); event.preventDefault()"
1025
+ ontouchend="stopJog('joint', 5, '-')">−</button>
1026
  <span class="val-display" id="joint_4_val">-1.57</span>
1027
+ <button class="jog-btn" onmousedown="startJog('joint', 5, '+')" onmouseup="stopJog('joint', 5, '+')"
1028
  ontouchstart="startJog('joint', 5, '+'); event.preventDefault()"
1029
+ ontouchend="stopJog('joint', 5, '+')">+</button>
1030
  </div>
1031
  <div class="jog-row">
1032
  <label>J6</label>
1033
+ <button class="jog-btn" onmousedown="startJog('joint', 6, '-')" onmouseup="stopJog('joint', 6, '-')"
1034
  ontouchstart="startJog('joint', 6, '-'); event.preventDefault()"
1035
+ ontouchend="stopJog('joint', 6, '-')">−</button>
1036
  <span class="val-display" id="joint_5_val">0.00</span>
1037
+ <button class="jog-btn" onmousedown="startJog('joint', 6, '+')" onmouseup="stopJog('joint', 6, '+')"
1038
  ontouchstart="startJog('joint', 6, '+'); event.preventDefault()"
1039
+ ontouchend="stopJog('joint', 6, '+')">+</button>
1040
  </div>
1041
  </div>
1042
  <div style="margin-top: 8px;">
 
2187
  document.getElementById('joint_vel_val').innerText = jointVelocity.toFixed(1);
2188
  }
2189
 
2190
+ const activeJogButtons = new Map();
2191
+
2192
+ function _applyKeyboardJog(translation) {
2193
+ if (keysPressed.has('KeyW')) translation.x += transVelocity / 1000.0;
2194
+ if (keysPressed.has('KeyS')) translation.x -= transVelocity / 1000.0;
2195
+ if (keysPressed.has('KeyA')) translation.y += transVelocity / 1000.0;
2196
+ if (keysPressed.has('KeyD')) translation.y -= transVelocity / 1000.0;
2197
+ if (keysPressed.has('KeyR')) translation.z += transVelocity / 1000.0;
2198
+ if (keysPressed.has('KeyF')) translation.z -= transVelocity / 1000.0;
2199
+ }
2200
+
2201
+ function _updateJogAction() {
2202
  const actionData = {};
2203
+ const translation = { x: 0.0, y: 0.0, z: 0.0 };
2204
+ const rotation = { x: 0.0, y: 0.0, z: 0.0 };
2205
+ const joint = { 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0 };
2206
+
2207
+ for (const entry of activeJogButtons.values()) {
2208
+ const sign = entry.direction === '+' ? 1 : -1;
2209
+ if (entry.jogType === 'cartesian_translation') {
2210
+ translation[entry.axisOrJoint] += sign * (transVelocity / 1000.0);
2211
+ } else if (entry.jogType === 'cartesian_rotation') {
2212
+ rotation[entry.axisOrJoint] += sign * rotVelocity;
2213
+ } else if (entry.jogType === 'joint') {
2214
+ const jointKey = Number(entry.axisOrJoint);
2215
+ if (jointKey >= 1 && jointKey <= 6) {
2216
+ joint[jointKey] += sign * jointVelocity;
2217
+ }
2218
+ }
2219
+ }
2220
+
2221
+ _applyKeyboardJog(translation);
2222
 
2223
+ if (translation.x || translation.y || translation.z) {
2224
+ if (translation.x) actionData.vx = translation.x;
2225
+ if (translation.y) actionData.vy = translation.y;
2226
+ if (translation.z) actionData.vz = translation.z;
2227
+ }
2228
+ if (rotation.x || rotation.y || rotation.z) {
2229
+ if (rotation.x) actionData.vrx = rotation.x;
2230
+ if (rotation.y) actionData.vry = rotation.y;
2231
+ if (rotation.z) actionData.vrz = rotation.z;
2232
+ }
2233
+ for (let j = 1; j <= 6; j += 1) {
2234
+ if (joint[j]) {
2235
+ actionData['j' + j] = joint[j];
2236
+ }
2237
  }
2238
 
2239
+ if (Object.keys(actionData).length) {
2240
+ send('action', actionData);
2241
+ } else {
2242
+ send('action', {}); // Send zero velocities
2243
+ }
2244
+ }
2245
+
2246
+ function startJog(jogType, axisOrJoint, direction) {
2247
+ const key = `${jogType}:${axisOrJoint}:${direction}`;
2248
+ activeJogButtons.set(key, { jogType, axisOrJoint, direction });
2249
+ _updateJogAction();
2250
  }
2251
 
2252
+ function stopJog(jogType, axisOrJoint, direction) {
2253
+ const key = `${jogType}:${axisOrJoint}:${direction}`;
2254
+ activeJogButtons.delete(key);
2255
+ _updateJogAction();
2256
  }
2257
 
2258
  function setGripper(action) {
 
2333
  send('teleop_action', { dx, dy, dz });
2334
  }
2335
 
2336
+ function handleArmKeyDown(code) {
2337
+ if (!armJogKeyMap[code]) {
 
2338
  return;
2339
  }
2340
+ _updateJogAction();
 
 
 
 
 
2341
  }
2342
 
2343
  function handleArmKeyUp(code) {
2344
  if (!armJogKeyMap[code]) {
2345
  return;
2346
  }
2347
+ _updateJogAction();
 
 
 
 
2348
  }
2349
 
2350
  function handleKeyStateChange() {
 
2496
  </script>
2497
  </body>
2498
 
2499
+ </html>
mujoco_server.py CHANGED
@@ -6,6 +6,7 @@ import json
6
  import base64
7
  import math
8
  import traceback
 
9
  import cv2
10
  import numpy as np
11
  import mujoco
@@ -300,6 +301,104 @@ def _make_mjv_camera(config: dict[str, float]) -> mujoco.MjvCamera:
300
  return cam_obj
301
 
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  def _get_site_forward(env_obj, site_id: int) -> np.ndarray:
304
  """Compute the forward (X axis) vector for a site."""
305
  default = np.array([0.0, 0.0, 1.0], dtype=np.float32)
@@ -1255,47 +1354,27 @@ def handle_ws_message(ws, data):
1255
  if gripper is not None:
1256
  env.set_gripper(float(gripper))
1257
 
1258
- # Check which type of velocity is active (mutually exclusive)
1259
- joint_velocities = [j1, j2, j3, j4, j5, j6]
1260
- cartesian_translation = [vx, vy, vz]
1261
- cartesian_rotation = [vrx, vry, vrz]
1262
-
1263
- # Find which velocity mode is active
1264
- active_joint = None
1265
- for i, vel in enumerate(joint_velocities):
1266
- if abs(vel) > 0.001: # Threshold to ignore noise
1267
- active_joint = (i + 1, vel)
1268
- break
1269
-
1270
- active_translation = None
1271
- for i, (axis, vel) in enumerate(zip(['x', 'y', 'z'], cartesian_translation)):
1272
- if abs(vel) > 0.001:
1273
- active_translation = (axis, vel)
1274
- break
1275
-
1276
- active_rotation = None
1277
- for i, (axis, vel) in enumerate(zip(['x', 'y', 'z'], cartesian_rotation)):
1278
- if abs(vel) > 0.001:
1279
- active_rotation = (axis, vel)
1280
- break
1281
 
1282
  # Apply the appropriate jogging mode
1283
- if active_joint:
1284
- joint, velocity = active_joint
1285
- direction = '+' if velocity > 0 else '-'
1286
- env.start_jog('joint', joint=joint, direction=direction, velocity=abs(velocity))
1287
- elif active_translation:
1288
- axis, velocity = active_translation
1289
- direction = '+' if velocity > 0 else '-'
1290
- # Convert m/s to mm/s
1291
- velocity_mm_s = abs(velocity) * 1000.0
1292
- env.start_jog('cartesian_translation', axis=axis, direction=direction,
1293
- velocity=velocity_mm_s, tcp_id='Flange', coord_system_id='world')
1294
- elif active_rotation:
1295
- axis, velocity = active_rotation
1296
- direction = '+' if velocity > 0 else '-'
1297
- env.start_jog('cartesian_rotation', axis=axis, direction=direction,
1298
- velocity=abs(velocity), tcp_id='Flange', coord_system_id='world')
1299
  else:
1300
  # No active velocity - stop jogging
1301
  env.stop_jog()
@@ -1828,6 +1907,93 @@ def camera_feed(name):
1828
  mimetype='multipart/x-mixed-replace; boundary=frame')
1829
 
1830
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1831
  @app.route(f'{API_PREFIX}/metadata')
1832
  def metadata():
1833
  robots_meta = {}
@@ -1891,21 +2057,39 @@ def get_env_info():
1891
  scene_name = getattr(env, 'scene_name', None)
1892
  control_mode = getattr(env, 'get_control_mode', lambda: None)()
1893
 
1894
- # Build camera feed URLs
1895
  camera_feeds = []
 
 
1896
  camera_feeds.append({
1897
  "name": "main",
1898
  "label": "Main Camera",
1899
- "url": f"{API_PREFIX}/video_feed"
 
 
1900
  })
1901
 
1902
  # Check for overlay cameras based on scene
1903
  configs = (scene_name and OVERLAY_CAMERA_PRESETS.get(scene_name)) or OVERLAY_CAMERA_PRESETS.get(current_robot) or []
1904
  for feed in configs:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1905
  camera_feeds.append({
1906
- "name": feed.get('name', 'aux'),
1907
- "label": feed.get('label', feed.get('name', 'Aux')),
1908
- "url": f"{API_PREFIX}/camera/{feed.get('name', 'aux')}/video_feed"
1909
  })
1910
 
1911
  # Get home pose for current robot
 
6
  import base64
7
  import math
8
  import traceback
9
+ import io
10
  import cv2
11
  import numpy as np
12
  import mujoco
 
301
  return cam_obj
302
 
303
 
304
+ def _normalize_vec(vec: np.ndarray) -> np.ndarray:
305
+ norm = np.linalg.norm(vec)
306
+ if norm < 1e-8:
307
+ return vec
308
+ return vec / norm
309
+
310
+
311
+ def _rotation_matrix_to_quaternion(mat: np.ndarray) -> dict[str, float]:
312
+ """Convert a 3x3 rotation matrix to quaternion (w, x, y, z)."""
313
+ m00, m01, m02 = mat[0, 0], mat[0, 1], mat[0, 2]
314
+ m10, m11, m12 = mat[1, 0], mat[1, 1], mat[1, 2]
315
+ m20, m21, m22 = mat[2, 0], mat[2, 1], mat[2, 2]
316
+
317
+ trace = m00 + m11 + m22
318
+ if trace > 0.0:
319
+ s = math.sqrt(trace + 1.0) * 2.0
320
+ w = 0.25 * s
321
+ x = (m21 - m12) / s
322
+ y = (m02 - m20) / s
323
+ z = (m10 - m01) / s
324
+ elif m00 > m11 and m00 > m22:
325
+ s = math.sqrt(1.0 + m00 - m11 - m22) * 2.0
326
+ w = (m21 - m12) / s
327
+ x = 0.25 * s
328
+ y = (m01 + m10) / s
329
+ z = (m02 + m20) / s
330
+ elif m11 > m22:
331
+ s = math.sqrt(1.0 + m11 - m00 - m22) * 2.0
332
+ w = (m02 - m20) / s
333
+ x = (m01 + m10) / s
334
+ y = 0.25 * s
335
+ z = (m12 + m21) / s
336
+ else:
337
+ s = math.sqrt(1.0 + m22 - m00 - m11) * 2.0
338
+ w = (m10 - m01) / s
339
+ x = (m02 + m20) / s
340
+ y = (m12 + m21) / s
341
+ z = 0.25 * s
342
+
343
+ return {"w": float(w), "x": float(x), "y": float(y), "z": float(z)}
344
+
345
+
346
+ def _camera_axes_from_orbit(cam_obj: mujoco.MjvCamera) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
347
+ """Return (right, up, forward) axes for an orbit camera in world coordinates."""
348
+ azimuth = math.radians(float(cam_obj.azimuth))
349
+ elevation = math.radians(float(cam_obj.elevation))
350
+
351
+ right = np.array([math.sin(azimuth), -math.cos(azimuth), 0.0], dtype=np.float32)
352
+ up = np.array([
353
+ -math.cos(azimuth) * math.sin(elevation),
354
+ -math.sin(azimuth) * math.sin(elevation),
355
+ math.cos(elevation),
356
+ ], dtype=np.float32)
357
+ forward = np.array([
358
+ -math.cos(azimuth) * math.cos(elevation),
359
+ -math.sin(azimuth) * math.cos(elevation),
360
+ -math.sin(elevation),
361
+ ], dtype=np.float32)
362
+
363
+ return _normalize_vec(right), _normalize_vec(up), _normalize_vec(forward)
364
+
365
+
366
+ def _camera_pose(cam_obj: mujoco.MjvCamera) -> dict[str, dict[str, float]]:
367
+ """Compute camera pose (position + orientation) in world coordinates."""
368
+ lookat = np.array(cam_obj.lookat, dtype=np.float32)
369
+ right, up, forward = _camera_axes_from_orbit(cam_obj)
370
+ position = lookat - forward * float(cam_obj.distance)
371
+ rotation = np.column_stack((right, up, forward))
372
+ quat = _rotation_matrix_to_quaternion(rotation)
373
+ return {
374
+ "position": {"x": float(position[0]), "y": float(position[1]), "z": float(position[2])},
375
+ "orientation": quat,
376
+ }
377
+
378
+
379
+ def _camera_fovy_degrees(cam_obj: mujoco.MjvCamera, env_obj) -> float:
380
+ fovy = float(getattr(cam_obj, "fovy", 0.0) or 0.0)
381
+ if fovy <= 0.0 and env_obj is not None:
382
+ fovy = float(env_obj.model.vis.global_.fovy)
383
+ return float(fovy) if fovy > 0.0 else 45.0
384
+
385
+
386
+ def _camera_intrinsics(width: int, height: int, fovy_degrees: float) -> dict[str, float]:
387
+ """Compute pinhole intrinsics from image size and vertical field-of-view."""
388
+ fovy_rad = math.radians(float(fovy_degrees))
389
+ fy = (float(height) / 2.0) / math.tan(fovy_rad / 2.0)
390
+ fx = fy
391
+ return {
392
+ "fx": float(fx),
393
+ "fy": float(fy),
394
+ "cx": float(width) / 2.0,
395
+ "cy": float(height) / 2.0,
396
+ "width": int(width),
397
+ "height": int(height),
398
+ "fovy_degrees": float(fovy_degrees),
399
+ }
400
+
401
+
402
  def _get_site_forward(env_obj, site_id: int) -> np.ndarray:
403
  """Compute the forward (X axis) vector for a site."""
404
  default = np.array([0.0, 0.0, 1.0], dtype=np.float32)
 
1354
  if gripper is not None:
1355
  env.set_gripper(float(gripper))
1356
 
1357
+ # Check which type of velocity is active (joint vs cartesian)
1358
+ joint_velocities = [float(j1), float(j2), float(j3), float(j4), float(j5), float(j6)]
1359
+ cartesian_translation = [float(vx), float(vy), float(vz)]
1360
+ cartesian_rotation = [float(vrx), float(vry), float(vrz)]
1361
+
1362
+ any_joint = any(abs(v) > 0.001 for v in joint_velocities)
1363
+ any_cartesian = any(abs(v) > 0.001 for v in cartesian_translation + cartesian_rotation)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1364
 
1365
  # Apply the appropriate jogging mode
1366
+ if any_joint:
1367
+ env.start_multi_joint_jog(joint_velocities)
1368
+ elif any_cartesian:
1369
+ # Convert m/s to mm/s for translations
1370
+ translation_mm_s = [v * 1000.0 for v in cartesian_translation]
1371
+ env.start_jog(
1372
+ 'cartesian_velocity',
1373
+ translation=translation_mm_s,
1374
+ rotation=cartesian_rotation,
1375
+ tcp_id='Flange',
1376
+ coord_system_id='world'
1377
+ )
 
 
 
 
1378
  else:
1379
  # No active velocity - stop jogging
1380
  env.stop_jog()
 
1907
  mimetype='multipart/x-mixed-replace; boundary=frame')
1908
 
1909
 
1910
+ def capture_depth_snapshot(camera_name: str) -> Optional[bytes]:
1911
+ """Capture a depth image from the specified camera and return as PNG bytes.
1912
+
1913
+ Args:
1914
+ camera_name: Name of the camera ("main" or overlay camera name)
1915
+
1916
+ Returns:
1917
+ PNG-encoded depth image as bytes, or None if capture fails
1918
+ """
1919
+ global env, cam, overlay_camera_states
1920
+
1921
+ if env is None:
1922
+ return None
1923
+
1924
+ # Determine which camera to use
1925
+ camera_obj = None
1926
+ if camera_name == "main":
1927
+ camera_obj = cam
1928
+ elif camera_name in overlay_camera_states:
1929
+ camera_obj = overlay_camera_states[camera_name].get("camera")
1930
+
1931
+ if camera_obj is None:
1932
+ return None
1933
+
1934
+ # Create a temporary renderer for depth capture
1935
+ with mujoco_lock:
1936
+ try:
1937
+ depth_renderer = mujoco.Renderer(
1938
+ env.model,
1939
+ height=env.height,
1940
+ width=env.width
1941
+ )
1942
+
1943
+ # Enable depth rendering
1944
+ depth_renderer.enable_depth_rendering()
1945
+
1946
+ # Update scene and render depth
1947
+ depth_renderer.update_scene(env.data, camera=camera_obj)
1948
+ depth_array = depth_renderer.render() # Returns (height, width) float32
1949
+
1950
+ # Close the temporary renderer
1951
+ depth_renderer.close()
1952
+
1953
+ # Convert depth to 16-bit PNG for precision
1954
+ # Normalize to 0-65535 range
1955
+ # Handle invalid/infinite values
1956
+ depth_min = np.min(depth_array[np.isfinite(depth_array)])
1957
+ depth_max = np.max(depth_array[np.isfinite(depth_array)])
1958
+
1959
+ if depth_max > depth_min:
1960
+ depth_normalized = np.clip(
1961
+ (depth_array - depth_min) / (depth_max - depth_min),
1962
+ 0, 1
1963
+ )
1964
+ else:
1965
+ depth_normalized = np.zeros_like(depth_array)
1966
+
1967
+ # Convert to 16-bit
1968
+ depth_16bit = (depth_normalized * 65535).astype(np.uint16)
1969
+
1970
+ # Encode as PNG
1971
+ ret, buffer = cv2.imencode('.png', depth_16bit)
1972
+ if not ret:
1973
+ return None
1974
+
1975
+ return buffer.tobytes()
1976
+
1977
+ except Exception as e:
1978
+ print(f"Error capturing depth snapshot: {e}")
1979
+ traceback.print_exc()
1980
+ return None
1981
+
1982
+
1983
+ @app.route(f'{API_PREFIX}/camera/<name>/depth_snapshot')
1984
+ def camera_depth_snapshot(name):
1985
+ """Endpoint to capture a single depth image as PNG."""
1986
+ depth_png = capture_depth_snapshot(name)
1987
+
1988
+ if depth_png is None:
1989
+ return jsonify({"error": "Failed to capture depth image"}), 500
1990
+
1991
+ response = make_response(depth_png)
1992
+ response.headers['Content-Type'] = 'image/png'
1993
+ response.headers['Content-Disposition'] = f'inline; filename="depth_{name}.png"'
1994
+ return response
1995
+
1996
+
1997
  @app.route(f'{API_PREFIX}/metadata')
1998
  def metadata():
1999
  robots_meta = {}
 
2057
  scene_name = getattr(env, 'scene_name', None)
2058
  control_mode = getattr(env, 'get_control_mode', lambda: None)()
2059
 
2060
+ # Build camera feed URLs with pose + intrinsics
2061
  camera_feeds = []
2062
+ main_fovy = _camera_fovy_degrees(cam, env)
2063
+ main_intrinsics = _camera_intrinsics(env.width, env.height, main_fovy)
2064
  camera_feeds.append({
2065
  "name": "main",
2066
  "label": "Main Camera",
2067
+ "url": f"{API_PREFIX}/video_feed",
2068
+ "pose": _camera_pose(cam),
2069
+ "intrinsics": main_intrinsics,
2070
  })
2071
 
2072
  # Check for overlay cameras based on scene
2073
  configs = (scene_name and OVERLAY_CAMERA_PRESETS.get(scene_name)) or OVERLAY_CAMERA_PRESETS.get(current_robot) or []
2074
  for feed in configs:
2075
+ feed_name = feed.get('name', 'aux')
2076
+ cam_state = overlay_camera_states.get(feed_name, {})
2077
+ cam_obj = cam_state.get("camera")
2078
+ feed_intrinsics = _camera_intrinsics(
2079
+ OVERLAY_RENDER_WIDTH,
2080
+ OVERLAY_RENDER_HEIGHT,
2081
+ _camera_fovy_degrees(cam_obj or cam, env),
2082
+ )
2083
+ feed_payload = {
2084
+ "name": feed_name,
2085
+ "label": feed.get('label', feed_name),
2086
+ "url": f"{API_PREFIX}/camera/{feed_name}/video_feed"
2087
+ }
2088
+ if cam_obj is not None:
2089
+ feed_payload["pose"] = _camera_pose(cam_obj)
2090
+ feed_payload["intrinsics"] = feed_intrinsics
2091
  camera_feeds.append({
2092
+ **feed_payload
 
 
2093
  })
2094
 
2095
  # Get home pose for current robot
protocol_types.py CHANGED
@@ -400,11 +400,30 @@ class ClientNotificationBroadcast(TypedDict):
400
  # HTTP Response Types
401
  # ============================================================================
402
 
403
- class CameraFeed(TypedDict):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  """Camera feed information."""
405
  name: str
406
  label: str
407
  url: str
 
 
408
 
409
 
410
  class EnvResponse(TypedDict, total=False):
 
400
  # HTTP Response Types
401
  # ============================================================================
402
 
403
+ class CameraIntrinsics(TypedDict):
404
+ """Camera intrinsics for pinhole camera model."""
405
+ fx: float
406
+ fy: float
407
+ cx: float
408
+ cy: float
409
+ width: int
410
+ height: int
411
+ fovy_degrees: float
412
+
413
+
414
+ class CameraPose(TypedDict):
415
+ """Camera pose in world coordinates."""
416
+ position: Position
417
+ orientation: Quaternion
418
+
419
+
420
+ class CameraFeed(TypedDict, total=False):
421
  """Camera feed information."""
422
  name: str
423
  label: str
424
  url: str
425
+ pose: CameraPose
426
+ intrinsics: CameraIntrinsics
427
 
428
 
429
  class EnvResponse(TypedDict, total=False):
robots/ur5/nova_jogger.py CHANGED
@@ -322,6 +322,38 @@ class NovaJogger:
322
  if not self._ensure_websocket_for_mode("cartesian", tcp_id, coord_system_id):
323
  return False
324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  try:
326
  # Create translation velocity array [x, y, z] in mm/s
327
  translation = [0.0, 0.0, 0.0]
 
322
  if not self._ensure_websocket_for_mode("cartesian", tcp_id, coord_system_id):
323
  return False
324
 
325
+ def start_cartesian_velocity(self, translation_mm_per_sec: list[float] | tuple[float, float, float],
326
+ rotation_rads_per_sec: list[float] | tuple[float, float, float] = (0.0, 0.0, 0.0),
327
+ tcp_id: str = "Flange",
328
+ coord_system_id: str = "world") -> bool:
329
+ """Start jogging in cartesian space with full translation/rotation vectors."""
330
+ if not self._ensure_websocket_for_mode("cartesian", tcp_id, coord_system_id):
331
+ return False
332
+
333
+ try:
334
+ if len(translation_mm_per_sec) != 3 or len(rotation_rads_per_sec) != 3:
335
+ print("[Nova Jogger] Error: translation/rotation vectors must be length 3")
336
+ return False
337
+
338
+ command = {
339
+ "message_type": "TcpVelocityRequest",
340
+ "translation": [float(v) for v in translation_mm_per_sec],
341
+ "rotation": [float(v) for v in rotation_rads_per_sec],
342
+ "use_tool_coordinate_system": False
343
+ }
344
+ with self._lock:
345
+ self._current_command = command
346
+
347
+ print(
348
+ "[Nova Jogger] Cartesian velocity translation="
349
+ f"{command['translation']} mm/s rotation={command['rotation']} rad/s (continuous)"
350
+ )
351
+ return True
352
+
353
+ except Exception as e:
354
+ print(f"[Nova Jogger] Failed to start cartesian velocity: {e}")
355
+ return False
356
+
357
  try:
358
  # Create translation velocity array [x, y, z] in mm/s
359
  translation = [0.0, 0.0, 0.0]
robots/ur5/ur5_env.py CHANGED
@@ -519,7 +519,7 @@ class UR5Env(gym.Env):
519
  """Start jogging command (Nova or internal IK fallback).
520
 
521
  Args:
522
- jog_type: 'joint', 'cartesian_translation', or 'cartesian_rotation'
523
  **kwargs: Parameters for the jog command (joint, axis, direction, velocity, etc.)
524
 
525
  Returns:
@@ -569,6 +569,13 @@ class UR5Env(gym.Env):
569
  tcp_id=kwargs.get('tcp_id', 'Flange'),
570
  coord_system_id=kwargs.get('coord_system_id', 'world')
571
  )
 
 
 
 
 
 
 
572
  else:
573
  print(f"[Nova] Unknown jog type: {jog_type}")
574
  return False
@@ -578,7 +585,25 @@ class UR5Env(gym.Env):
578
 
579
  # Fallback to internal IK for cartesian movements
580
  print(f"[Internal] Using internal IK for jogging: {jog_type}")
581
- if jog_type in ('cartesian_translation', 'cartesian_rotation'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  axis = kwargs.get('axis', 'x')
583
  direction = kwargs.get('direction', '+')
584
 
@@ -913,6 +938,10 @@ class UR5Env(gym.Env):
913
  # Update target position along specified axis
914
  axis_idx = {'x': 0, 'y': 1, 'z': 2}[jog['axis']]
915
  self._target_pos[axis_idx] += jog['step_size']
 
 
 
 
916
  elif jog['type'] == 'cartesian_rotation':
917
  # Update target orientation around specified axis
918
  axis_idx = {'x': 0, 'y': 1, 'z': 2}[jog['axis']]
@@ -926,6 +955,16 @@ class UR5Env(gym.Env):
926
  self.action_space.low[:6],
927
  self.action_space.high[:6]
928
  )
 
 
 
 
 
 
 
 
 
 
929
 
930
  if self._control_mode == 'ik':
931
  joint_targets = self._compute_joint_targets()
 
519
  """Start jogging command (Nova or internal IK fallback).
520
 
521
  Args:
522
+ jog_type: 'joint', 'cartesian_translation', 'cartesian_rotation', or 'cartesian_velocity'
523
  **kwargs: Parameters for the jog command (joint, axis, direction, velocity, etc.)
524
 
525
  Returns:
 
569
  tcp_id=kwargs.get('tcp_id', 'Flange'),
570
  coord_system_id=kwargs.get('coord_system_id', 'world')
571
  )
572
+ elif jog_type == 'cartesian_velocity':
573
+ return self._nova_jogger.start_cartesian_velocity(
574
+ translation_mm_per_sec=kwargs.get('translation', (0.0, 0.0, 0.0)),
575
+ rotation_rads_per_sec=kwargs.get('rotation', (0.0, 0.0, 0.0)),
576
+ tcp_id=kwargs.get('tcp_id', 'Flange'),
577
+ coord_system_id=kwargs.get('coord_system_id', 'world')
578
+ )
579
  else:
580
  print(f"[Nova] Unknown jog type: {jog_type}")
581
  return False
 
585
 
586
  # Fallback to internal IK for cartesian movements
587
  print(f"[Internal] Using internal IK for jogging: {jog_type}")
588
+ if jog_type in ('cartesian_translation', 'cartesian_rotation', 'cartesian_velocity'):
589
+ if jog_type == 'cartesian_velocity':
590
+ translation = kwargs.get('translation', (0.0, 0.0, 0.0))
591
+ rotation = kwargs.get('rotation', (0.0, 0.0, 0.0))
592
+ if len(translation) != 3 or len(rotation) != 3:
593
+ print("[Internal] Invalid cartesian_velocity vectors")
594
+ return False
595
+
596
+ # Translation is mm/s; convert to m/s and apply timestep.
597
+ translation_step = [(float(v) / 1000.0) * 0.02 for v in translation]
598
+ rotation_step = [float(v) * 0.02 for v in rotation]
599
+
600
+ self._active_jog = {
601
+ 'type': 'cartesian_velocity',
602
+ 'translation_step': translation_step,
603
+ 'rotation_step': rotation_step
604
+ }
605
+ return True
606
+
607
  axis = kwargs.get('axis', 'x')
608
  direction = kwargs.get('direction', '+')
609
 
 
938
  # Update target position along specified axis
939
  axis_idx = {'x': 0, 'y': 1, 'z': 2}[jog['axis']]
940
  self._target_pos[axis_idx] += jog['step_size']
941
+ elif jog['type'] == 'cartesian_velocity':
942
+ # Update target position and orientation using vector steps
943
+ self._target_pos += np.array(jog['translation_step'], dtype=np.float32)
944
+ self._target_euler += np.array(jog['rotation_step'], dtype=np.float32)
945
  elif jog['type'] == 'cartesian_rotation':
946
  # Update target orientation around specified axis
947
  axis_idx = {'x': 0, 'y': 1, 'z': 2}[jog['axis']]
 
955
  self.action_space.low[:6],
956
  self.action_space.high[:6]
957
  )
958
+ elif jog['type'] == 'multi_joint':
959
+ velocities = jog.get('velocities', [0.0] * 6)
960
+ if len(velocities) == 6:
961
+ step = np.array(velocities, dtype=np.float32) * 0.02
962
+ self._joint_targets += step
963
+ self._joint_targets = np.clip(
964
+ self._joint_targets,
965
+ self.action_space.low[:6],
966
+ self.action_space.high[:6]
967
+ )
968
 
969
  if self._control_mode == 'ik':
970
  joint_targets = self._compute_joint_targets()