import React, { useEffect, useRef, useState, useMemo } from "react"; import { cn } from "@/lib/utils"; import { useTheme } from "@/hooks/useTheme"; import URDFManipulator from "urdf-loader/src/urdf-manipulator-element.js"; import { useUrdf } from "@/hooks/useUrdf"; import { createUrdfViewer, setupMeshLoader, setupJointHighlighting, setupModelLoading, } from "@/lib/urdfViewerHelpers"; import { ModeToggle } from "./ModeToggle"; // Register the URDFManipulator as a custom element if it hasn't been already if (typeof window !== "undefined" && !customElements.get("urdf-viewer")) { customElements.define("urdf-viewer", URDFManipulator); } // Extend the interface for the URDF viewer element to include background property interface URDFViewerElement extends HTMLElement { background?: string; setJointValue?: (jointName: string, value: number) => void; } const URDFViewer: React.FC = () => { const { theme } = useTheme(); const isDarkMode = theme === "dark"; const containerRef = useRef(null); const [highlightedJoint, setHighlightedJoint] = useState(null); const { registerUrdfProcessor, alternativeUrdfModels, isDefaultModel } = useUrdf(); // Add state for animation control useState(isDefaultModel); const cleanupAnimationRef = useRef<(() => void) | null>(null); const viewerRef = useRef(null); const hasInitializedRef = useRef(false); // Add state for custom URDF path const [customUrdfPath, setCustomUrdfPath] = useState(null); const [urlModifierFunc, setUrlModifierFunc] = useState< ((url: string) => string) | null >(null); const packageRef = useRef(""); // Implement UrdfProcessor interface for drag and drop const urdfProcessor = useMemo( () => ({ loadUrdf: (urdfPath: string) => { setCustomUrdfPath(urdfPath); }, setUrlModifierFunc: (func: (url: string) => string) => { setUrlModifierFunc(() => func); }, getPackage: () => { return packageRef.current; }, }), [] ); // Register the URDF processor with the global drag and drop context useEffect(() => { registerUrdfProcessor(urdfProcessor); }, [registerUrdfProcessor, urdfProcessor]); // Main effect to create and setup the viewer only once useEffect(() => { if (!containerRef.current) return; // Create and configure the URDF viewer element const viewer = createUrdfViewer(containerRef.current, isDarkMode); viewerRef.current = viewer; // Store reference to the viewer // Setup mesh loading function setupMeshLoader(viewer, urlModifierFunc); // Determine which URDF to load // const urdfPath = isDefaultModel // ? "/urdf/SO_5DOF_ARM100_05d/urdf/SO_5DOF_ARM100_05d.urdf" // : customUrdfPath || ""; const urdfPath = isDefaultModel ? "/urdf/T12/urdf/T12.URDF" : customUrdfPath || ""; // Setup model loading if a path is available let cleanupModelLoading = () => {}; if (urdfPath) { cleanupModelLoading = setupModelLoading( viewer, urdfPath, packageRef.current, setCustomUrdfPath, alternativeUrdfModels ); } // Setup joint highlighting const cleanupJointHighlighting = setupJointHighlighting( viewer, setHighlightedJoint ); // Setup animation event handler for the default model or when hasAnimation is true const onModelProcessed = () => { hasInitializedRef.current = true; if ("setJointValue" in viewer) { // Clear any existing animation if (cleanupAnimationRef.current) { cleanupAnimationRef.current(); cleanupAnimationRef.current = null; } } }; viewer.addEventListener("urdf-processed", onModelProcessed); // Return cleanup function return () => { if (cleanupAnimationRef.current) { cleanupAnimationRef.current(); cleanupAnimationRef.current = null; } hasInitializedRef.current = false; cleanupJointHighlighting(); cleanupModelLoading(); viewer.removeEventListener("urdf-processed", onModelProcessed); }; }, [isDefaultModel, customUrdfPath, urlModifierFunc]); // Separate effect to handle theme changes without recreating the viewer useEffect(() => { if (!viewerRef.current) return; // Update only the visual aspects based on theme if (viewerRef.current.background !== undefined) { if (isDarkMode) { viewerRef.current.background = "#1f2937"; // Dark background } else { viewerRef.current.background = "#e0e7ff"; // Light background } } }, [isDarkMode]); return (
{/* Control buttons container in top right */}
{/* ModeToggle button */}
{/* Joint highlight indicator */} {highlightedJoint && (
Joint: {highlightedJoint}
)}
); }; export default URDFViewer;