File size: 4,654 Bytes
16ab111
 
 
6bc7874
16ab111
6bc7874
 
16ab111
 
 
 
 
 
 
 
6bc7874
16ab111
 
 
 
6bc7874
16ab111
 
 
 
 
 
6bc7874
16ab111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bc7874
16ab111
 
6bc7874
16ab111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bc7874
16ab111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bc7874
16ab111
 
 
 
 
 
 
 
 
 
 
6bc7874
16ab111
 
 
 
 
 
6bc7874
16ab111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { LoadingManager, Object3D } from "three";
import { toast } from "sonner";
import { loadMeshFile } from "./meshLoaders";
import { UrdfViewerElement } from "./urdfAnimationHelpers";

// Extended Urdf Viewer Element with mesh loading capability
export interface ExtendedUrdfViewerElement extends UrdfViewerElement {
  loadMeshFunc: (
    path: string,
    manager: LoadingManager,
    done: (result: Object3D | null, err?: Error) => void
  ) => void;
}

/**
 * Creates and configures a Urdf viewer element
 */
export function createUrdfViewer(
  container: HTMLDivElement,
  isDarkMode: boolean
): ExtendedUrdfViewerElement {
  // Clear any existing content
  container.innerHTML = "";

  // Create the urdf-viewer element
  const viewer = document.createElement(
    "urdf-viewer"
  ) as ExtendedUrdfViewerElement;
  viewer.classList.add("w-full", "h-full");

  // Add the element to the container
  container.appendChild(viewer);

  // Set initial viewer properties
  viewer.setAttribute("up", "Z");
  viewer.setAttribute("highlight-color", isDarkMode ? "#5b9aff" : "#3373ff");
  viewer.setAttribute("ambient-color", isDarkMode ? "#202a30" : "#cfd8dc");
  viewer.setAttribute("auto-redraw", "true");
  viewer.setAttribute("display-shadow", ""); // Enable shadows

  return viewer;
}

/**
 * Setup mesh loading function for Urdf viewer
 */
export function setupMeshLoader(
  viewer: ExtendedUrdfViewerElement,
  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,
  alternativeUrdfModels: 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 alternativeUrdfModels instead of the global window object
    if (alternativeUrdfModels.length > 0) {
      const nextModel = alternativeUrdfModels[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);

  // Return cleanup function
  return () => {
    viewer.removeEventListener("error", onLoadError);
  };
}

// For backward compatibility - to be removed in the future
declare global {
  interface Window {
    alternativeUrdfModels?: string[];
  }
}