Spaces:
Running
Running
File size: 6,361 Bytes
9d3c32a 9a9d18a 9d3c32a 9a9d18a 9d3c32a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
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
// }
// });
}
|