mishig HF Staff commited on
Commit
2726e1e
·
verified ·
1 Parent(s): 67e7271

Sync from huggingface/lerobot-dataset-visualizer via hub-sync

Browse files
Files changed (1) hide show
  1. src/components/urdf-viewer.tsx +46 -3
src/components/urdf-viewer.tsx CHANGED
@@ -132,6 +132,8 @@ function getRobotConfig(robotType: string | null): {
132
  urdfUrl: string;
133
  usdUrl: string;
134
  scale: number;
 
 
135
  } {
136
  const lower = (robotType ?? "").toLowerCase();
137
  if (lower.includes("g1") || lower.includes("unitree")) {
@@ -139,6 +141,8 @@ function getRobotConfig(robotType: string | null): {
139
  urdfUrl: `${URDF_BASE_URL}/g1/g1_body29_hand14.urdf`,
140
  usdUrl: `${USD_BASE_URL}/g1_body29_hand14/simready_usd/g1_body29_hand14.usd`,
141
  scale: 1,
 
 
142
  };
143
  }
144
  if (lower.includes("openarm")) {
@@ -146,12 +150,14 @@ function getRobotConfig(robotType: string | null): {
146
  urdfUrl: `${URDF_BASE_URL}/openarm/openarm_bimanual_web.urdf`,
147
  usdUrl: `${USD_BASE_URL}/openarm_bimanual/simready_usd/openarm_bimanual.usd`,
148
  scale: 3,
 
149
  };
150
  }
151
  return {
152
  urdfUrl: `${URDF_BASE_URL}/so101/so101_new_calib_web.urdf`,
153
  usdUrl: `${USD_BASE_URL}/so101_new_calib/simready_usd/so101_new_calib.usd`,
154
  scale: 10,
 
155
  };
156
  }
157
 
@@ -311,6 +317,23 @@ function autoMatchJoints(
311
  return mapping;
312
  }
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  const SINGLE_ARM_TIP_NAMES = [
315
  "gripper_frame_link",
316
  "gripperframe",
@@ -358,6 +381,7 @@ function RobotScene({
358
  urdfUrl,
359
  usdUrl,
360
  jointValues,
 
361
  onJointsLoaded,
362
  trailEnabled,
363
  trailResetKey,
@@ -366,6 +390,7 @@ function RobotScene({
366
  urdfUrl: string;
367
  usdUrl: string;
368
  jointValues: Record<string, number>;
 
369
  onJointsLoaded: (names: string[]) => void;
370
  trailEnabled: boolean;
371
  trailResetKey: number;
@@ -407,7 +432,7 @@ function RobotScene({
407
  useEffect(() => {
408
  framesSinceInputRef.current = 0;
409
  invalidate();
410
- }, [jointValues, trailEnabled, trailResetKey, invalidate]);
411
 
412
  // Create/destroy trail Line2 objects when tip count changes
413
  const ensureTrails = useCallback(
@@ -561,11 +586,16 @@ function RobotScene({
561
  }, [urdfUrl, usdUrl, scale, scene, onJointsLoaded, ensureTrails, invalidate]);
562
 
563
  const tipWorldPos = useMemo(() => new THREE.Vector3(), []);
 
564
 
565
  useFrame((state) => {
566
  const robot = robotRef.current;
567
  if (!robot) return;
568
 
 
 
 
 
569
  for (const [name, value] of Object.entries(jointValues)) {
570
  robot.setJointValue(name, value);
571
  }
@@ -761,7 +791,7 @@ export default function URDFViewer({
761
  () => getRobotConfig(datasetInfo.robot_type),
762
  [datasetInfo.robot_type],
763
  );
764
- const { urdfUrl, usdUrl, scale } = robotConfig;
765
  const isG1 = urdfUrl.includes("g1");
766
  const isOpenArm = urdfUrl.includes("openarm");
767
  const loaderRobot: LoaderRobot = isG1
@@ -876,6 +906,10 @@ export default function URDFViewer({
876
  );
877
  const [mapping, setMapping] = useState<Record<string, string>>(autoMapping);
878
  useEffect(() => setMapping(autoMapping), [autoMapping]);
 
 
 
 
879
 
880
  // Trail
881
  const [trailEnabled, setTrailEnabled] = useState(true);
@@ -988,6 +1022,13 @@ export default function URDFViewer({
988
  return values;
989
  }, [chartData, frame, gripperRanges, mapping, totalFrames, urdfJointNames]);
990
 
 
 
 
 
 
 
 
991
  if (data.flatChartData.length === 0) {
992
  return (
993
  <div className="text-fg-muted p-8 text-center">
@@ -1074,6 +1115,7 @@ export default function URDFViewer({
1074
  urdfUrl={urdfUrl}
1075
  usdUrl={usdUrl}
1076
  jointValues={jointValues}
 
1077
  onJointsLoaded={onJointsLoaded}
1078
  trailEnabled={trailEnabled}
1079
  trailResetKey={selectedEpisode}
@@ -1088,7 +1130,8 @@ export default function URDFViewer({
1088
  sectionThickness={1}
1089
  sectionColor={dark ? "#475569" : "#9ca3af"}
1090
  fadeDistance={isG1 ? 20 : 10}
1091
- position={[0, 0, 0]}
 
1092
  />
1093
  <OrbitControls
1094
  makeDefault
 
132
  urdfUrl: string;
133
  usdUrl: string;
134
  scale: number;
135
+ /** Height of the URDF's root link above the floor when the data has no base pose. */
136
+ restHeight: number;
137
  } {
138
  const lower = (robotType ?? "").toLowerCase();
139
  if (lower.includes("g1") || lower.includes("unitree")) {
 
141
  urdfUrl: `${URDF_BASE_URL}/g1/g1_body29_hand14.urdf`,
142
  usdUrl: `${USD_BASE_URL}/g1_body29_hand14/simready_usd/g1_body29_hand14.usd`,
143
  scale: 1,
144
+ /// The pelvis, standing on straight legs.
145
+ restHeight: 0.793,
146
  };
147
  }
148
  if (lower.includes("openarm")) {
 
150
  urdfUrl: `${URDF_BASE_URL}/openarm/openarm_bimanual_web.urdf`,
151
  usdUrl: `${USD_BASE_URL}/openarm_bimanual/simready_usd/openarm_bimanual.usd`,
152
  scale: 3,
153
+ restHeight: 0,
154
  };
155
  }
156
  return {
157
  urdfUrl: `${URDF_BASE_URL}/so101/so101_new_calib_web.urdf`,
158
  usdUrl: `${USD_BASE_URL}/so101_new_calib/simready_usd/so101_new_calib.usd`,
159
  scale: 10,
160
+ restHeight: 0,
161
  };
162
  }
163
 
 
317
  return mapping;
318
  }
319
 
320
+ const BASE_POSE_NAMES = ["x", "y", "z", "qx", "qy", "qz", "qw"];
321
+ const Z_UP_TO_Y_UP = new THREE.Quaternion().setFromAxisAngle(
322
+ new THREE.Vector3(1, 0, 0),
323
+ -Math.PI / 2,
324
+ );
325
+
326
+ /// The columns of a floating base's pose in the world (x, y, z, qx, qy, qz, qw), such as Unitree's
327
+ /// `state_base_pose`.
328
+ function findBasePoseColumns(columnKeys: string[]): string[] | null {
329
+ const feature = columnKeys
330
+ .map((k) => k.split(SERIES_DELIM)[0])
331
+ .find((f) => f.endsWith("base_pose"));
332
+ if (!feature) return null;
333
+ const columns = BASE_POSE_NAMES.map((n) => `${feature}${SERIES_DELIM}${n}`);
334
+ return columns.every((c) => columnKeys.includes(c)) ? columns : null;
335
+ }
336
+
337
  const SINGLE_ARM_TIP_NAMES = [
338
  "gripper_frame_link",
339
  "gripperframe",
 
381
  urdfUrl,
382
  usdUrl,
383
  jointValues,
384
+ basePose,
385
  onJointsLoaded,
386
  trailEnabled,
387
  trailResetKey,
 
390
  urdfUrl: string;
391
  usdUrl: string;
392
  jointValues: Record<string, number>;
393
+ basePose: number[];
394
  onJointsLoaded: (names: string[]) => void;
395
  trailEnabled: boolean;
396
  trailResetKey: number;
 
432
  useEffect(() => {
433
  framesSinceInputRef.current = 0;
434
  invalidate();
435
+ }, [jointValues, basePose, trailEnabled, trailResetKey, invalidate]);
436
 
437
  // Create/destroy trail Line2 objects when tip count changes
438
  const ensureTrails = useCallback(
 
586
  }, [urdfUrl, usdUrl, scale, scene, onJointsLoaded, ensureTrails, invalidate]);
587
 
588
  const tipWorldPos = useMemo(() => new THREE.Vector3(), []);
589
+ const baseQuat = useMemo(() => new THREE.Quaternion(), []);
590
 
591
  useFrame((state) => {
592
  const robot = robotRef.current;
593
  if (!robot) return;
594
 
595
+ /// The robot stays above the origin, where the camera looks, and the floor moves under it.
596
+ const [, , z, qx, qy, qz, qw] = basePose;
597
+ robot.position.set(0, z * scale, 0);
598
+ robot.quaternion.copy(Z_UP_TO_Y_UP).multiply(baseQuat.set(qx, qy, qz, qw));
599
  for (const [name, value] of Object.entries(jointValues)) {
600
  robot.setJointValue(name, value);
601
  }
 
791
  () => getRobotConfig(datasetInfo.robot_type),
792
  [datasetInfo.robot_type],
793
  );
794
+ const { urdfUrl, usdUrl, scale, restHeight } = robotConfig;
795
  const isG1 = urdfUrl.includes("g1");
796
  const isOpenArm = urdfUrl.includes("openarm");
797
  const loaderRobot: LoaderRobot = isG1
 
906
  );
907
  const [mapping, setMapping] = useState<Record<string, string>>(autoMapping);
908
  useEffect(() => setMapping(autoMapping), [autoMapping]);
909
+ const basePoseColumns = useMemo(
910
+ () => findBasePoseColumns(selectedColumns),
911
+ [selectedColumns],
912
+ );
913
 
914
  // Trail
915
  const [trailEnabled, setTrailEnabled] = useState(true);
 
1022
  return values;
1023
  }, [chartData, frame, gripperRanges, mapping, totalFrames, urdfJointNames]);
1024
 
1025
+ const basePose = useMemo(() => {
1026
+ if (!basePoseColumns || totalFrames === 0)
1027
+ return [0, 0, restHeight, 0, 0, 0, 1];
1028
+ const row = chartData[Math.min(frame, totalFrames - 1)];
1029
+ return basePoseColumns.map((c) => row[c]);
1030
+ }, [basePoseColumns, chartData, frame, restHeight, totalFrames]);
1031
+
1032
  if (data.flatChartData.length === 0) {
1033
  return (
1034
  <div className="text-fg-muted p-8 text-center">
 
1115
  urdfUrl={urdfUrl}
1116
  usdUrl={usdUrl}
1117
  jointValues={jointValues}
1118
+ basePose={basePose}
1119
  onJointsLoaded={onJointsLoaded}
1120
  trailEnabled={trailEnabled}
1121
  trailResetKey={selectedEpisode}
 
1130
  sectionThickness={1}
1131
  sectionColor={dark ? "#475569" : "#9ca3af"}
1132
  fadeDistance={isG1 ? 20 : 10}
1133
+ infiniteGrid={basePoseColumns !== null}
1134
+ position={[-basePose[0] * scale, 0, basePose[1] * scale]}
1135
  />
1136
  <OrbitControls
1137
  makeDefault