leLab / src /lib /urdfViewerHelpers.ts
jurmy24's picture
mega big updates pre-demo
9a9d18a
import {
LoadingManager,
Object3D,
PerspectiveCamera,
Vector3,
Color,
AmbientLight,
DirectionalLight,
Scene,
} from "three";
import { toast } from "@/components/ui/sonner";
import { loadMeshFile } from "./meshLoaders";
// Define the interface for the URDF viewer element
export interface URDFViewerElement extends HTMLElement {
setJointValue: (joint: string, value: number) => void;
loadMeshFunc?: (
path: string,
manager: LoadingManager,
done: (result: Object3D | null, err?: Error) => void
) => void;
// Extended properties for camera fitting
camera: PerspectiveCamera;
controls: {
target: Vector3;
update: () => void;
};
robot: Object3D;
redraw: () => void;
up: string;
scene: Scene;
}
/**
* Creates and configures a URDF viewer element
*/
export function createUrdfViewer(
container: HTMLDivElement,
isDarkMode: boolean
): URDFViewerElement {
// Clear any existing content
container.innerHTML = "";
// Create the urdf-viewer element
const viewer = document.createElement("urdf-viewer") as URDFViewerElement;
viewer.classList.add("w-full", "h-full");
// Add the element to the container
container.appendChild(viewer);
// Set initial viewer properties
viewer.setAttribute("up", "Z");
setViewerColor(viewer, isDarkMode ? "#2c2b3a" : "#eff4ff");
viewer.setAttribute("highlight-color", isDarkMode ? "#df6dd4" : "#b05ffe");
viewer.setAttribute("auto-redraw", "true");
// viewer.setAttribute("display-shadow", ""); // Enable shadows
// Add ambient light to the scene
const ambientLight = new AmbientLight(0xd6d6d6, 1); // Increased intensity to 0.4
viewer.scene.add(ambientLight);
// Add directional light for better shadows and depth
const directionalLight = new DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 30, 5);
directionalLight.castShadow = true;
viewer.scene.add(directionalLight);
// Camera position is no longer adjusted automatically to prevent auto-zooming.
// The user can control the view with the mouse.
return viewer;
}
/**
* Setup mesh loading function for URDF viewer
*/
export function setupMeshLoader(
viewer: URDFViewerElement,
urlModifierFunc: ((url: string) => string) | null
): void {
if ("loadMeshFunc" in viewer) {
viewer.loadMeshFunc = (
path: string,
manager: LoadingManager,
done: (result: Object3D | null, err?: Error) => void
) => {
// Apply URL modifier if available (for custom uploads)
const modifiedPath = urlModifierFunc ? urlModifierFunc(path) : path;
// If loading fails, log the error but continue
try {
loadMeshFile(modifiedPath, manager, (result, err) => {
if (err) {
console.warn(`Error loading mesh ${modifiedPath}:`, err);
// Try to continue with other meshes
done(null);
} else {
done(result);
}
});
} catch (err) {
console.error(`Exception loading mesh ${modifiedPath}:`, err);
done(null, err as Error);
}
};
}
}
/**
* Setup event handlers for joint highlighting
*/
export function setupJointHighlighting(
viewer: URDFViewerElement,
setHighlightedJoint: (joint: string | null) => void
): () => void {
const onJointMouseover = (e: Event) => {
const customEvent = e as CustomEvent;
setHighlightedJoint(customEvent.detail);
};
const onJointMouseout = () => {
setHighlightedJoint(null);
};
// Add event listeners
viewer.addEventListener("joint-mouseover", onJointMouseover);
viewer.addEventListener("joint-mouseout", onJointMouseout);
// Return cleanup function
return () => {
viewer.removeEventListener("joint-mouseover", onJointMouseover);
viewer.removeEventListener("joint-mouseout", onJointMouseout);
};
}
/**
* Setup model loading and error handling
*/
export function setupModelLoading(
viewer: URDFViewerElement,
urdfPath: string,
packagePath: string,
setCustomUrdfPath: (path: string) => void,
alternativeRobotModels: string[] = [] // Add parameter for alternative models
): () => void {
// Add XML content type hint for blob URLs
const loadPath =
urdfPath.startsWith("blob:") && !urdfPath.includes("#.")
? urdfPath + "#.urdf" // Add extension hint if it's a blob URL
: urdfPath;
// Set the URDF path
viewer.setAttribute("urdf", loadPath);
viewer.setAttribute("package", packagePath);
// Handle error loading
const onLoadError = () => {
// toast.error("Failed to load model", {
// description: "There was an error loading the URDF model.",
// duration: 3000,
// });
// Use the provided alternativeRobotModels instead of the global window object
if (alternativeRobotModels.length > 0) {
const nextModel = alternativeRobotModels[0];
if (nextModel) {
setCustomUrdfPath(nextModel);
toast.info("Trying alternative model...", {
description: `First model failed to load. Trying ${
nextModel.split("/").pop() || "alternative model"
}`,
duration: 2000,
});
}
}
};
viewer.addEventListener("error", onLoadError);
// The 'urdf-processed' event that handled auto-zooming has been removed.
// Return cleanup function
return () => {
viewer.removeEventListener("error", onLoadError);
};
}
/**
* Sets the background color of the URDF viewer
*/
export function setViewerColor(viewer: URDFViewerElement, color: string): void {
// Set the ambient color for the scene
// viewer.setAttribute("ambient-color", color);
// Set the background color on the viewer's parent container
const container = viewer.parentElement;
if (container) {
container.style.backgroundColor = color;
}
}
/**
* Updates the viewer's colors based on the current theme
*/
export function updateViewerTheme(
viewer: URDFViewerElement,
isDarkMode: boolean
): void {
// Update the ambient color
setViewerColor(viewer, isDarkMode ? "#2c2b3a" : "#eff4ff");
viewer.setAttribute("highlight-color", isDarkMode ? "#df6dd4" : "#b05ffe");
// // Update the ambient light intensity based on theme
// viewer.scene.traverse((object) => {
// if (object instanceof AmbientLight) {
// object.intensity = isDarkMode ? 0.4 : 0.6; // Brighter in light mode
// }
// });
}