Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Sync from huggingface/lerobot-dataset-visualizer via hub-sync
Browse files
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 |
-
|
|
|
|
| 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
|