jurmy24 commited on
Commit
6bc7874
·
1 Parent(s): a88f07c

refactor: rename URDF to Urdf

Browse files
.dockerignore CHANGED
@@ -47,7 +47,7 @@ Thumbs.db
47
  Dockerfile
48
  .dockerignore
49
 
50
- # URDF files and STLs (if you don't need them in the image)
51
  *.STL
52
  *.stl
53
 
 
47
  Dockerfile
48
  .dockerignore
49
 
50
+ # Urdf files and STLs (if you don't need them in the image)
51
  *.STL
52
  *.stl
53
 
Dockerfile CHANGED
@@ -22,15 +22,15 @@ RUN npm install
22
  # Copy the entire viewer directory
23
  COPY --chown=user viewer/ .
24
 
25
- # Debug: List all files in the src/components directory
26
- RUN echo "=== Listing src/components directory ===" && \
27
- ls -la src/components && \
28
- echo "=== Listing URDFViewer file specifically ===" && \
29
- ls -la src/components/URDFViewer* && \
30
- echo "=== Current working directory ===" && \
31
- pwd && \
32
- echo "=== Directory structure ===" && \
33
- find . -type f -name "*.tsx" | sort
34
 
35
  # Ensure proper file permissions
36
  RUN chmod -R 755 .
 
22
  # Copy the entire viewer directory
23
  COPY --chown=user viewer/ .
24
 
25
+ # # Debug: List all files in the src/components directory
26
+ # RUN echo "=== Listing src/components directory ===" && \
27
+ # ls -la src/components && \
28
+ # echo "=== Listing UrdfViewer file specifically ===" && \
29
+ # ls -la src/components/UrdfViewer* && \
30
+ # echo "=== Current working directory ===" && \
31
+ # pwd && \
32
+ # echo "=== Directory structure ===" && \
33
+ # find . -type f -name "*.tsx" | sort
34
 
35
  # Ensure proper file permissions
36
  RUN chmod -R 755 .
README.md CHANGED
@@ -6,7 +6,7 @@ colorTo: indigo
6
  sdk: docker
7
  app_port: 7860
8
  pinned: false
9
- short_description: Upload a URDF folder to view and interact with your robot.
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
6
  sdk: docker
7
  app_port: 7860
8
  pinned: false
9
+ short_description: Upload a Urdf folder to view and interact with your robot.
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
viewer/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # URDF Web Visualizer
2
 
3
  **URL**: https://urdf-web-visualizer.lovable.app/
4
 
 
1
+ # Urdf Web Visualizer
2
 
3
  **URL**: https://urdf-web-visualizer.lovable.app/
4
 
viewer/index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>placeholder</title>
7
- <meta name="description" content="A simple tool to visualize URDF files." />
8
  <meta name="author" content="Victor Oldensand" />
9
  <meta property="og:image" content="/og-image.png" />
10
  </head>
 
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>placeholder</title>
7
+ <meta name="description" content="A simple tool to visualize Urdf files." />
8
  <meta name="author" content="Victor Oldensand" />
9
  <meta property="og:image" content="/og-image.png" />
10
  </head>
viewer/public/urdf/SO_5DOF_ARM100_05d/urdf/SO_5DOF_ARM100_05d.urdf CHANGED
@@ -1,5 +1,5 @@
1
  <?xml version="1.0" encoding="utf-8"?>
2
- <!-- This URDF was automatically created by SolidWorks to URDF Exporter! Originally created by Stephen Brawner ([email protected])
3
  Commit Version: 1.6.0-1-g15f4949 Build Version: 1.6.7594.29634
4
  For more information, please see http://wiki.ros.org/sw_urdf_exporter -->
5
  <robot
 
1
  <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- This Urdf was automatically created by SolidWorks to Urdf Exporter! Originally created by Stephen Brawner ([email protected])
3
  Commit Version: 1.6.0-1-g15f4949 Build Version: 1.6.7594.29634
4
  For more information, please see http://wiki.ros.org/sw_urdf_exporter -->
5
  <robot
viewer/src/components/UrdfSelectionModalContainer.tsx CHANGED
@@ -2,7 +2,7 @@ import { useUrdf } from "@/hooks/useUrdf";
2
  import { UrdfSelectionModal } from "@/components/ui/UrdfSelectionModal";
3
 
4
  /**
5
- * Container component that manages the URDF selection modal.
6
  * This is meant to be placed in the application layout to ensure the modal
7
  * is accessible throughout the application without nesting issues.
8
  */
 
2
  import { UrdfSelectionModal } from "@/components/ui/UrdfSelectionModal";
3
 
4
  /**
5
+ * Container component that manages the Urdf selection modal.
6
  * This is meant to be placed in the application layout to ensure the modal
7
  * is accessible throughout the application without nesting issues.
8
  */
viewer/src/components/layout/Layout.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import React, { useState, useEffect } from "react";
2
- import URDFViewer from "@/components/URDFViewer";
3
  import { Sidebar, SidebarBody } from "@/components/ui/sidebar";
4
  import { LayoutDashboard, PanelLeft, RotateCcw } from "lucide-react";
5
  import { useUrdf } from "@/hooks/useUrdf";
@@ -42,7 +42,7 @@ const Layout: React.FC = () => {
42
  <div className="absolute inset-0 overflow-hidden">
43
  {/* Main content that fills the container */}
44
  <div className="w-full h-full">
45
- <URDFViewer />
46
  </div>
47
  </div>
48
  );
@@ -52,7 +52,7 @@ const Layout: React.FC = () => {
52
  <div className="relative w-full h-full overflow-hidden">
53
  {/* Main content that fills the container */}
54
  <div className="w-full h-full">
55
- <URDFViewer />
56
  </div>
57
  {/* Sidebar positioned above with z-index, with top padding for header */}
58
  <div
 
1
  import React, { useState, useEffect } from "react";
2
+ import UrdfViewer from "@/components/UrdfViewer";
3
  import { Sidebar, SidebarBody } from "@/components/ui/sidebar";
4
  import { LayoutDashboard, PanelLeft, RotateCcw } from "lucide-react";
5
  import { useUrdf } from "@/hooks/useUrdf";
 
42
  <div className="absolute inset-0 overflow-hidden">
43
  {/* Main content that fills the container */}
44
  <div className="w-full h-full">
45
+ <UrdfViewer />
46
  </div>
47
  </div>
48
  );
 
52
  <div className="relative w-full h-full overflow-hidden">
53
  {/* Main content that fills the container */}
54
  <div className="w-full h-full">
55
+ <UrdfViewer />
56
  </div>
57
  {/* Sidebar positioned above with z-index, with top padding for header */}
58
  <div
viewer/src/components/ui/UrdfSelectionModal.tsx CHANGED
@@ -34,9 +34,9 @@ export function UrdfSelectionModal({
34
  <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
35
  <DialogContent className="sm:max-w-[600px]">
36
  <DialogHeader>
37
- <DialogTitle>Select URDF Model</DialogTitle>
38
  <DialogDescription>
39
- Multiple URDF files were found. Please select the model you want to
40
  visualize.
41
  </DialogDescription>
42
  </DialogHeader>
 
34
  <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
35
  <DialogContent className="sm:max-w-[600px]">
36
  <DialogHeader>
37
+ <DialogTitle>Select Urdf Model</DialogTitle>
38
  <DialogDescription>
39
+ Multiple Urdf files were found. Please select the model you want to
40
  visualize.
41
  </DialogDescription>
42
  </DialogHeader>
viewer/src/contexts/DragAndDropContext.tsx CHANGED
@@ -28,7 +28,7 @@ export const DragAndDropProvider: React.FC<DragAndDropProviderProps> = ({
28
  }) => {
29
  const [isDragging, setIsDragging] = useState(false);
30
 
31
- // Get the URDF context
32
  const { urdfProcessor, processUrdfFiles } = useUrdf();
33
 
34
  const handleDragOver = (e: DragEvent) => {
@@ -111,7 +111,7 @@ export const DragAndDropProvider: React.FC<DragAndDropProviderProps> = ({
111
  {isDragging && (
112
  <div className="fixed inset-0 bg-primary/10 pointer-events-none z-50 flex items-center justify-center">
113
  <div className="bg-background p-8 rounded-lg shadow-lg text-center">
114
- <div className="text-3xl font-bold mb-4">Drop URDF Files Here</div>
115
  <p className="text-muted-foreground">
116
  Release to upload your robot model
117
  </p>
 
28
  }) => {
29
  const [isDragging, setIsDragging] = useState(false);
30
 
31
+ // Get the Urdf context
32
  const { urdfProcessor, processUrdfFiles } = useUrdf();
33
 
34
  const handleDragOver = (e: DragEvent) => {
 
111
  {isDragging && (
112
  <div className="fixed inset-0 bg-primary/10 pointer-events-none z-50 flex items-center justify-center">
113
  <div className="bg-background p-8 rounded-lg shadow-lg text-center">
114
+ <div className="text-3xl font-bold mb-4">Drop Urdf Files Here</div>
115
  <p className="text-muted-foreground">
116
  Release to upload your robot model
117
  </p>
viewer/src/contexts/UrdfContext.tsx CHANGED
@@ -13,7 +13,7 @@ import { UrdfData, UrdfFileModel } from "@/lib/types";
13
  import { useDefaultRobotData } from "@/hooks/useDefaultRobotData";
14
  import { RobotAnimationConfig } from "@/lib/types";
15
 
16
- // Define the result interface for URDF detection
17
  interface UrdfDetectionResult {
18
  hasUrdf: boolean;
19
  modelName?: string;
@@ -52,7 +52,7 @@ export type UrdfContextType = {
52
  // These properties are kept for backward compatibility but are considered
53
  // implementation details and should not be used directly in components.
54
  // TODO: Remove these next three once the time is right
55
- parsedRobotData: UrdfData | null; // Data from parsed URDF
56
  customModelName: string;
57
  customModelDescription: string;
58
  };
@@ -68,7 +68,7 @@ interface UrdfProviderProps {
68
  }
69
 
70
  export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
71
- // State for URDF processor
72
  const [urdfProcessor, setUrdfProcessor] = useState<UrdfProcessor | null>(
73
  null
74
  );
@@ -81,7 +81,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
81
  []
82
  );
83
 
84
- // State for the URDF selection modal
85
  const [isSelectionModalOpen, setIsSelectionModalOpen] = useState(false);
86
  const [urdfModelOptions, setUrdfModelOptions] = useState<UrdfFileModel[]>([]);
87
 
@@ -103,34 +103,34 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
103
  // Compute the current robot data based on model state
104
  const currentRobotData = isDefaultModel ? defaultRobotData : parsedRobotData;
105
 
106
- // Fetch the default URDF content when the component mounts
107
  useEffect(() => {
108
  // Only fetch if we don't have content and we're using the default model
109
  if (isDefaultModel && !urdfContent) {
110
  const fetchDefaultUrdf = async () => {
111
  try {
112
- // Path to the default T12 URDF file
113
  const defaultUrdfPath =
114
- "/urdf/SO_5DOF_ARM100_05d/urdf/SO_5DOF_ARM100_05d.URDF";
115
 
116
- // Fetch the URDF content
117
  const response = await fetch(defaultUrdfPath);
118
 
119
  if (!response.ok) {
120
  throw new Error(
121
- `Failed to fetch default URDF: ${response.statusText}`
122
  );
123
  }
124
 
125
  const defaultUrdfContent = await response.text();
126
  console.log(
127
- `📄 Default URDF content loaded, length: ${defaultUrdfContent.length} characters`
128
  );
129
 
130
- // Set the URDF content in state
131
  setUrdfContent(defaultUrdfContent);
132
  } catch (error) {
133
- console.error("❌ Error loading default URDF content:", error);
134
  }
135
  };
136
 
@@ -170,7 +170,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
170
  });
171
  }, []);
172
 
173
- // Register a callback for URDF detection
174
  const onUrdfDetected = useCallback(
175
  (callback: (result: UrdfDetectionResult) => void) => {
176
  urdfCallbacksRef.current.push(callback);
@@ -184,7 +184,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
184
  []
185
  );
186
 
187
- // Register a URDF processor
188
  const registerUrdfProcessor = useCallback((processor: UrdfProcessor) => {
189
  setUrdfProcessor(processor);
190
  }, []);
@@ -192,11 +192,11 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
192
  // Internal function to notify callbacks and update central state
193
  const notifyUrdfCallbacks = useCallback(
194
  (result: UrdfDetectionResult) => {
195
- console.log("📣 Notifying URDF callbacks with result:", result);
196
 
197
  // Update our internal state based on the result
198
  if (result.hasUrdf) {
199
- // Always ensure we set isDefaultModel to false when we have a URDF
200
  setIsDefaultModel(false);
201
 
202
  if (result.parsedData) {
@@ -240,7 +240,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
240
  setParsedRobotData(minimalData);
241
  }
242
  } else {
243
- // If no URDF, reset to default
244
  resetToDefaultModel();
245
  }
246
 
@@ -250,7 +250,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
250
  [resetToDefaultModel]
251
  );
252
 
253
- // Helper function to process the selected URDF model
254
  const processSelectedUrdf = useCallback(
255
  async (model: UrdfFileModel) => {
256
  if (!urdfProcessor) return;
@@ -267,12 +267,12 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
267
  .filter((item) => item !== null);
268
 
269
  if (files.length === 0) {
270
- console.error("❌ Could not find file for selected URDF model");
271
  return;
272
  }
273
 
274
- // Show a toast notification that we're parsing the URDF
275
- const parsingToast = toast.loading("Analyzing URDF model...", {
276
  description: "Extracting robot information",
277
  duration: 10000, // Long duration since we'll dismiss it manually
278
  });
@@ -295,23 +295,23 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
295
  }
296
  );
297
 
298
- // Read the URDF content
299
  const urdfContent = await readUrdfFileContent(file);
300
 
301
  console.log(
302
- `📏 URDF content read, length: ${urdfContent.length} characters`
303
  );
304
 
305
- // Store the URDF content in state
306
  setUrdfContent(urdfContent);
307
 
308
- // Parse the URDF
309
  const parseResult = await parseUrdf(urdfContent);
310
 
311
  // Dismiss the toast
312
  toast.dismiss(parsingToast);
313
 
314
- // Always set isDefaultModel to false when processing a custom URDF
315
  setIsDefaultModel(false);
316
 
317
  if (parseResult) {
@@ -345,7 +345,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
345
  // Update the state with enhanced parsed data
346
  setParsedRobotData(enhancedParsedData);
347
 
348
- toast.success("URDF model loaded successfully", {
349
  description: `Model: ${modelDisplayName}`,
350
  duration: 3000,
351
  });
@@ -373,7 +373,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
373
  setCustomModelDescription(minimalData.description);
374
  setParsedRobotData(minimalData);
375
 
376
- toast.warning("URDF model loaded with limited information", {
377
  description: "Could not fully analyze the robot structure",
378
  duration: 3000,
379
  });
@@ -387,9 +387,9 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
387
  }
388
  } catch (error) {
389
  // Error case
390
- console.error("❌ Error processing selected URDF:", error);
391
  toast.dismiss(parsingToast);
392
- toast.error("Error analyzing URDF", {
393
  description: `Error: ${
394
  error instanceof Error ? error.message : String(error)
395
  }`,
@@ -403,11 +403,11 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
403
  [urdfBlobUrls, urdfProcessor, parseUrdf, notifyUrdfCallbacks]
404
  );
405
 
406
- // Function to handle selecting a URDF model from the modal
407
  const selectUrdfModel = useCallback(
408
  (model: UrdfFileModel) => {
409
  if (!urdfProcessor) {
410
- console.error("❌ No URDF processor available");
411
  return;
412
  }
413
 
@@ -425,7 +425,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
425
  ?.replace(/\.urdf$/i, "") ||
426
  "Unknown";
427
 
428
- // Load the selected URDF model
429
  urdfProcessor.loadUrdf(model.blobUrl);
430
 
431
  // Update our state immediately even before parsing
@@ -451,7 +451,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
451
  [urdfProcessor, notifyUrdfCallbacks, processSelectedUrdf]
452
  );
453
 
454
- // Process URDF files - moved from DragAndDropContext
455
  const processUrdfFiles = useCallback(
456
  async (files: Record<string, File>, availableModels: string[]) => {
457
  // Clear previous blob URLs to prevent memory leaks
@@ -461,10 +461,10 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
461
  setUrdfModelOptions([]);
462
 
463
  try {
464
- // Check if we have any URDF files
465
  if (availableModels.length > 0 && urdfProcessor) {
466
  console.log(
467
- `🤖 Found ${availableModels.length} URDF models:`,
468
  availableModels
469
  );
470
 
@@ -495,7 +495,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
495
 
496
  // If there's only one model, use it directly
497
  if (availableModels.length === 1) {
498
- // Extract model name from the URDF file
499
  const fileName = availableModels[0].split("/").pop() || "";
500
  const modelName = fileName.replace(/\.urdf$/i, "");
501
  console.log(`📄 Using model: ${modelName} (${fileName})`);
@@ -503,21 +503,21 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
503
  // Use the blob URL instead of the file path
504
  const blobUrl = newUrdfBlobUrls[availableModels[0]];
505
  if (blobUrl) {
506
- console.log(`🔗 Using blob URL for URDF: ${blobUrl}`);
507
  urdfProcessor.loadUrdf(blobUrl);
508
 
509
  // Immediately update model state
510
  setIsDefaultModel(false);
511
  setCustomModelName(modelName);
512
 
513
- // Process the URDF file for parsing
514
  if (files[availableModels[0]]) {
515
  console.log(
516
- "📄 Reading URDF content for edge function parsing..."
517
  );
518
 
519
- // Show a toast notification that we're parsing the URDF
520
- const parsingToast = toast.loading("Analyzing URDF model...", {
521
  description: "Extracting robot information",
522
  duration: 10000, // Long duration since we'll dismiss it manually
523
  });
@@ -528,10 +528,10 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
528
  );
529
 
530
  console.log(
531
- `📏 URDF content read, length: ${urdfContent.length} characters`
532
  );
533
 
534
- // Store the URDF content in state
535
  setUrdfContent(urdfContent);
536
 
537
  // Call the parseUrdf function from the hook
@@ -541,7 +541,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
541
  toast.dismiss(parsingToast);
542
 
543
  if (parseResult) {
544
- toast.success("URDF model analyzed successfully", {
545
  description: `Model: ${modelName}`,
546
  duration: 3000,
547
  });
@@ -590,7 +590,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
590
  setParsedRobotData(minimalData);
591
 
592
  toast.warning(
593
- "URDF model loaded with limited information",
594
  {
595
  description:
596
  "Could not fully analyze the robot structure",
@@ -606,9 +606,9 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
606
  });
607
  }
608
  } catch (parseError) {
609
- console.error("❌ Error parsing URDF:", parseError);
610
  toast.dismiss(parsingToast);
611
- toast.error("Error analyzing URDF", {
612
  description: `Error: ${
613
  parseError instanceof Error
614
  ? parseError.message
@@ -625,7 +625,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
625
  }
626
  } else {
627
  console.error(
628
- "❌ Could not find file for URDF model:",
629
  availableModels[0]
630
  );
631
  console.log("📦 Available files:", Object.keys(files));
@@ -653,13 +653,13 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
653
  });
654
  }
655
  } else {
656
- // Multiple URDF files found, show selection modal
657
  console.log(
658
- "📋 Multiple URDF files found, showing selection modal"
659
  );
660
  setIsSelectionModalOpen(true);
661
 
662
- // Notify that URDF files are available but selection is needed
663
  notifyUrdfCallbacks({
664
  hasUrdf: true,
665
  modelName: "Multiple models available",
@@ -667,20 +667,20 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
667
  }
668
  } else {
669
  console.warn(
670
- "❌ No URDF models found in dropped files or no processor available"
671
  );
672
  notifyUrdfCallbacks({ hasUrdf: false, parsedData: null });
673
 
674
- // Reset to default model when no URDF files are found
675
  resetToDefaultModel();
676
 
677
- toast.error("No URDF file found", {
678
  description: "Please upload a folder containing a .urdf file.",
679
  duration: 3000,
680
  });
681
  }
682
  } catch (error) {
683
- console.error("❌ Error processing URDF files:", error);
684
  toast.error("Error processing files", {
685
  description: `Error: ${
686
  error instanceof Error ? error.message : String(error)
 
13
  import { useDefaultRobotData } from "@/hooks/useDefaultRobotData";
14
  import { RobotAnimationConfig } from "@/lib/types";
15
 
16
+ // Define the result interface for Urdf detection
17
  interface UrdfDetectionResult {
18
  hasUrdf: boolean;
19
  modelName?: string;
 
52
  // These properties are kept for backward compatibility but are considered
53
  // implementation details and should not be used directly in components.
54
  // TODO: Remove these next three once the time is right
55
+ parsedRobotData: UrdfData | null; // Data from parsed Urdf
56
  customModelName: string;
57
  customModelDescription: string;
58
  };
 
68
  }
69
 
70
  export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
71
+ // State for Urdf processor
72
  const [urdfProcessor, setUrdfProcessor] = useState<UrdfProcessor | null>(
73
  null
74
  );
 
81
  []
82
  );
83
 
84
+ // State for the Urdf selection modal
85
  const [isSelectionModalOpen, setIsSelectionModalOpen] = useState(false);
86
  const [urdfModelOptions, setUrdfModelOptions] = useState<UrdfFileModel[]>([]);
87
 
 
103
  // Compute the current robot data based on model state
104
  const currentRobotData = isDefaultModel ? defaultRobotData : parsedRobotData;
105
 
106
+ // Fetch the default Urdf content when the component mounts
107
  useEffect(() => {
108
  // Only fetch if we don't have content and we're using the default model
109
  if (isDefaultModel && !urdfContent) {
110
  const fetchDefaultUrdf = async () => {
111
  try {
112
+ // Path to the default T12 Urdf file
113
  const defaultUrdfPath =
114
+ "/urdf/SO_5DOF_ARM100_05d/urdf/SO_5DOF_ARM100_05d.Urdf";
115
 
116
+ // Fetch the Urdf content
117
  const response = await fetch(defaultUrdfPath);
118
 
119
  if (!response.ok) {
120
  throw new Error(
121
+ `Failed to fetch default Urdf: ${response.statusText}`
122
  );
123
  }
124
 
125
  const defaultUrdfContent = await response.text();
126
  console.log(
127
+ `📄 Default Urdf content loaded, length: ${defaultUrdfContent.length} characters`
128
  );
129
 
130
+ // Set the Urdf content in state
131
  setUrdfContent(defaultUrdfContent);
132
  } catch (error) {
133
+ console.error("❌ Error loading default Urdf content:", error);
134
  }
135
  };
136
 
 
170
  });
171
  }, []);
172
 
173
+ // Register a callback for Urdf detection
174
  const onUrdfDetected = useCallback(
175
  (callback: (result: UrdfDetectionResult) => void) => {
176
  urdfCallbacksRef.current.push(callback);
 
184
  []
185
  );
186
 
187
+ // Register a Urdf processor
188
  const registerUrdfProcessor = useCallback((processor: UrdfProcessor) => {
189
  setUrdfProcessor(processor);
190
  }, []);
 
192
  // Internal function to notify callbacks and update central state
193
  const notifyUrdfCallbacks = useCallback(
194
  (result: UrdfDetectionResult) => {
195
+ console.log("📣 Notifying Urdf callbacks with result:", result);
196
 
197
  // Update our internal state based on the result
198
  if (result.hasUrdf) {
199
+ // Always ensure we set isDefaultModel to false when we have a Urdf
200
  setIsDefaultModel(false);
201
 
202
  if (result.parsedData) {
 
240
  setParsedRobotData(minimalData);
241
  }
242
  } else {
243
+ // If no Urdf, reset to default
244
  resetToDefaultModel();
245
  }
246
 
 
250
  [resetToDefaultModel]
251
  );
252
 
253
+ // Helper function to process the selected Urdf model
254
  const processSelectedUrdf = useCallback(
255
  async (model: UrdfFileModel) => {
256
  if (!urdfProcessor) return;
 
267
  .filter((item) => item !== null);
268
 
269
  if (files.length === 0) {
270
+ console.error("❌ Could not find file for selected Urdf model");
271
  return;
272
  }
273
 
274
+ // Show a toast notification that we're parsing the Urdf
275
+ const parsingToast = toast.loading("Analyzing Urdf model...", {
276
  description: "Extracting robot information",
277
  duration: 10000, // Long duration since we'll dismiss it manually
278
  });
 
295
  }
296
  );
297
 
298
+ // Read the Urdf content
299
  const urdfContent = await readUrdfFileContent(file);
300
 
301
  console.log(
302
+ `📏 Urdf content read, length: ${urdfContent.length} characters`
303
  );
304
 
305
+ // Store the Urdf content in state
306
  setUrdfContent(urdfContent);
307
 
308
+ // Parse the Urdf
309
  const parseResult = await parseUrdf(urdfContent);
310
 
311
  // Dismiss the toast
312
  toast.dismiss(parsingToast);
313
 
314
+ // Always set isDefaultModel to false when processing a custom Urdf
315
  setIsDefaultModel(false);
316
 
317
  if (parseResult) {
 
345
  // Update the state with enhanced parsed data
346
  setParsedRobotData(enhancedParsedData);
347
 
348
+ toast.success("Urdf model loaded successfully", {
349
  description: `Model: ${modelDisplayName}`,
350
  duration: 3000,
351
  });
 
373
  setCustomModelDescription(minimalData.description);
374
  setParsedRobotData(minimalData);
375
 
376
+ toast.warning("Urdf model loaded with limited information", {
377
  description: "Could not fully analyze the robot structure",
378
  duration: 3000,
379
  });
 
387
  }
388
  } catch (error) {
389
  // Error case
390
+ console.error("❌ Error processing selected Urdf:", error);
391
  toast.dismiss(parsingToast);
392
+ toast.error("Error analyzing Urdf", {
393
  description: `Error: ${
394
  error instanceof Error ? error.message : String(error)
395
  }`,
 
403
  [urdfBlobUrls, urdfProcessor, parseUrdf, notifyUrdfCallbacks]
404
  );
405
 
406
+ // Function to handle selecting a Urdf model from the modal
407
  const selectUrdfModel = useCallback(
408
  (model: UrdfFileModel) => {
409
  if (!urdfProcessor) {
410
+ console.error("❌ No Urdf processor available");
411
  return;
412
  }
413
 
 
425
  ?.replace(/\.urdf$/i, "") ||
426
  "Unknown";
427
 
428
+ // Load the selected Urdf model
429
  urdfProcessor.loadUrdf(model.blobUrl);
430
 
431
  // Update our state immediately even before parsing
 
451
  [urdfProcessor, notifyUrdfCallbacks, processSelectedUrdf]
452
  );
453
 
454
+ // Process Urdf files - moved from DragAndDropContext
455
  const processUrdfFiles = useCallback(
456
  async (files: Record<string, File>, availableModels: string[]) => {
457
  // Clear previous blob URLs to prevent memory leaks
 
461
  setUrdfModelOptions([]);
462
 
463
  try {
464
+ // Check if we have any Urdf files
465
  if (availableModels.length > 0 && urdfProcessor) {
466
  console.log(
467
+ `🤖 Found ${availableModels.length} Urdf models:`,
468
  availableModels
469
  );
470
 
 
495
 
496
  // If there's only one model, use it directly
497
  if (availableModels.length === 1) {
498
+ // Extract model name from the Urdf file
499
  const fileName = availableModels[0].split("/").pop() || "";
500
  const modelName = fileName.replace(/\.urdf$/i, "");
501
  console.log(`📄 Using model: ${modelName} (${fileName})`);
 
503
  // Use the blob URL instead of the file path
504
  const blobUrl = newUrdfBlobUrls[availableModels[0]];
505
  if (blobUrl) {
506
+ console.log(`🔗 Using blob URL for Urdf: ${blobUrl}`);
507
  urdfProcessor.loadUrdf(blobUrl);
508
 
509
  // Immediately update model state
510
  setIsDefaultModel(false);
511
  setCustomModelName(modelName);
512
 
513
+ // Process the Urdf file for parsing
514
  if (files[availableModels[0]]) {
515
  console.log(
516
+ "📄 Reading Urdf content for edge function parsing..."
517
  );
518
 
519
+ // Show a toast notification that we're parsing the Urdf
520
+ const parsingToast = toast.loading("Analyzing Urdf model...", {
521
  description: "Extracting robot information",
522
  duration: 10000, // Long duration since we'll dismiss it manually
523
  });
 
528
  );
529
 
530
  console.log(
531
+ `📏 Urdf content read, length: ${urdfContent.length} characters`
532
  );
533
 
534
+ // Store the Urdf content in state
535
  setUrdfContent(urdfContent);
536
 
537
  // Call the parseUrdf function from the hook
 
541
  toast.dismiss(parsingToast);
542
 
543
  if (parseResult) {
544
+ toast.success("Urdf model analyzed successfully", {
545
  description: `Model: ${modelName}`,
546
  duration: 3000,
547
  });
 
590
  setParsedRobotData(minimalData);
591
 
592
  toast.warning(
593
+ "Urdf model loaded with limited information",
594
  {
595
  description:
596
  "Could not fully analyze the robot structure",
 
606
  });
607
  }
608
  } catch (parseError) {
609
+ console.error("❌ Error parsing Urdf:", parseError);
610
  toast.dismiss(parsingToast);
611
+ toast.error("Error analyzing Urdf", {
612
  description: `Error: ${
613
  parseError instanceof Error
614
  ? parseError.message
 
625
  }
626
  } else {
627
  console.error(
628
+ "❌ Could not find file for Urdf model:",
629
  availableModels[0]
630
  );
631
  console.log("📦 Available files:", Object.keys(files));
 
653
  });
654
  }
655
  } else {
656
+ // Multiple Urdf files found, show selection modal
657
  console.log(
658
+ "📋 Multiple Urdf files found, showing selection modal"
659
  );
660
  setIsSelectionModalOpen(true);
661
 
662
+ // Notify that Urdf files are available but selection is needed
663
  notifyUrdfCallbacks({
664
  hasUrdf: true,
665
  modelName: "Multiple models available",
 
667
  }
668
  } else {
669
  console.warn(
670
+ "❌ No Urdf models found in dropped files or no processor available"
671
  );
672
  notifyUrdfCallbacks({ hasUrdf: false, parsedData: null });
673
 
674
+ // Reset to default model when no Urdf files are found
675
  resetToDefaultModel();
676
 
677
+ toast.error("No Urdf file found", {
678
  description: "Please upload a folder containing a .urdf file.",
679
  duration: 3000,
680
  });
681
  }
682
  } catch (error) {
683
+ console.error("❌ Error processing Urdf files:", error);
684
  toast.error("Error processing files", {
685
  description: `Error: ${
686
  error instanceof Error ? error.message : String(error)
viewer/src/hooks/useUrdf.ts CHANGED
@@ -1,7 +1,7 @@
1
  import { UrdfContextType, UrdfContext } from "@/contexts/UrdfContext";
2
  import { useContext } from "react";
3
 
4
- // Custom hook to use the URDF context
5
  export const useUrdf = (): UrdfContextType => {
6
  const context = useContext(UrdfContext);
7
  if (context === undefined) {
 
1
  import { UrdfContextType, UrdfContext } from "@/contexts/UrdfContext";
2
  import { useContext } from "react";
3
 
4
+ // Custom hook to use the Urdf context
5
  export const useUrdf = (): UrdfContextType => {
6
  const context = useContext(UrdfContext);
7
  if (context === undefined) {
viewer/src/hooks/useUrdfData.ts CHANGED
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
2
  import { getUrdfs, getCategories, getUrdfById } from "@/api/urdfApi";
3
  import { ContentItem, Category } from "@/lib/types";
4
 
5
- // Hook to fetch all URDFs
6
  export const useUrdfs = () => {
7
  return useQuery({
8
  queryKey: ["urdfs"],
@@ -19,7 +19,7 @@ export const useCategories = () => {
19
  });
20
  };
21
 
22
- // Hook to fetch a specific URDF by ID
23
  export const useUrdf = (id: string) => {
24
  return useQuery({
25
  queryKey: ["urdf", id],
@@ -28,7 +28,7 @@ export const useUrdf = (id: string) => {
28
  });
29
  };
30
 
31
- // Hook to get URDFs filtered by category
32
  export const useUrdfsByCategory = (categoryId: string) => {
33
  const urdfsQuery = useUrdfs();
34
 
 
2
  import { getUrdfs, getCategories, getUrdfById } from "@/api/urdfApi";
3
  import { ContentItem, Category } from "@/lib/types";
4
 
5
+ // Hook to fetch all Urdfs
6
  export const useUrdfs = () => {
7
  return useQuery({
8
  queryKey: ["urdfs"],
 
19
  });
20
  };
21
 
22
+ // Hook to fetch a specific Urdf by ID
23
  export const useUrdf = (id: string) => {
24
  return useQuery({
25
  queryKey: ["urdf", id],
 
28
  });
29
  };
30
 
31
+ // Hook to get Urdfs filtered by category
32
  export const useUrdfsByCategory = (categoryId: string) => {
33
  const urdfsQuery = useUrdfs();
34
 
viewer/src/hooks/useUrdfParser.ts CHANGED
@@ -10,16 +10,16 @@ export const useUrdfParser = () => {
10
 
11
  const parseUrdf = async (urdfContent: string) => {
12
  const requestId = `urdf-${Date.now()}`; // Generate unique ID for tracking this request
13
- console.log(`[${requestId}] 🚀 URDF Parser: Starting parse request`);
14
  console.log(
15
  `[${requestId}] 🔍 Content preview:`,
16
  urdfContent?.substring(0, 100) + "..."
17
  );
18
 
19
- // Check if the URDF content is too large
20
  const MAX_SIZE = 4 * 1024 * 1024; // 4MB limit
21
  if (urdfContent.length > MAX_SIZE) {
22
- const errorMessage = `URDF content too large (${(
23
  urdfContent.length /
24
  1024 /
25
  1024
@@ -41,7 +41,7 @@ export const useUrdfParser = () => {
41
  try {
42
  // Make sure the urdfContent is a string
43
  if (typeof urdfContent !== "string") {
44
- throw new Error("URDF content must be a string");
45
  }
46
 
47
  console.log(
@@ -62,15 +62,15 @@ export const useUrdfParser = () => {
62
  if (error.message.includes("non-2xx status code")) {
63
  // Server error from edge function
64
  const serverErrorMsg =
65
- "The URDF parser encountered a server error. It might be due to the complexity of your URDF file or temporary server issues.";
66
  console.error(`[${requestId}] 🔥 Server error in edge function`);
67
 
68
  // Check if we got fallback data despite the error
69
  if (rawResponse && rawResponse.fallback) {
70
  console.log(`[${requestId}] 🛟 Using fallback data from server`);
71
- toast.warning("Reduced URDF Data", {
72
  description:
73
- "Limited information extracted from your URDF file due to parsing limitations.",
74
  duration: 5000,
75
  });
76
 
@@ -79,7 +79,7 @@ export const useUrdfParser = () => {
79
  return rawResponse.fallback;
80
  }
81
 
82
- toast.error("URDF Parser Error", {
83
  description: serverErrorMsg,
84
  duration: 5000,
85
  });
@@ -88,7 +88,7 @@ export const useUrdfParser = () => {
88
  } else if (error.message.includes("timeout")) {
89
  // Timeout error
90
  const timeoutMsg =
91
- "The URDF parser timed out. Your file might be too complex to process within the allowed time.";
92
  console.error(`[${requestId}] ⏱️ Edge function timed out`);
93
 
94
  toast.error("Processing Timeout", {
@@ -101,7 +101,7 @@ export const useUrdfParser = () => {
101
  // Generic error
102
  setError(error.message);
103
 
104
- toast.error("Error Parsing URDF", {
105
  description: error.message,
106
  duration: 5000,
107
  });
@@ -175,7 +175,7 @@ export const useUrdfParser = () => {
175
  other: jointTypes.other,
176
  },
177
  links: transformedLinks,
178
- // Default materials since we don't have this info from URDF
179
  materials: [{ name: "Metal", percentage: 100 }],
180
  };
181
 
@@ -190,7 +190,7 @@ export const useUrdfParser = () => {
190
  const endTime = performance.now();
191
 
192
  console.error(
193
- `[${requestId}] ❌ Error parsing URDF after ${(
194
  endTime - startTime
195
  ).toFixed(2)}ms:`,
196
  err
@@ -206,7 +206,7 @@ export const useUrdfParser = () => {
206
  } finally {
207
  setIsLoading(false);
208
  console.log(
209
- `[${requestId}] 🏁 URDF parsing request completed, isLoading set to false`
210
  );
211
  }
212
  };
 
10
 
11
  const parseUrdf = async (urdfContent: string) => {
12
  const requestId = `urdf-${Date.now()}`; // Generate unique ID for tracking this request
13
+ console.log(`[${requestId}] 🚀 Urdf Parser: Starting parse request`);
14
  console.log(
15
  `[${requestId}] 🔍 Content preview:`,
16
  urdfContent?.substring(0, 100) + "..."
17
  );
18
 
19
+ // Check if the Urdf content is too large
20
  const MAX_SIZE = 4 * 1024 * 1024; // 4MB limit
21
  if (urdfContent.length > MAX_SIZE) {
22
+ const errorMessage = `Urdf content too large (${(
23
  urdfContent.length /
24
  1024 /
25
  1024
 
41
  try {
42
  // Make sure the urdfContent is a string
43
  if (typeof urdfContent !== "string") {
44
+ throw new Error("Urdf content must be a string");
45
  }
46
 
47
  console.log(
 
62
  if (error.message.includes("non-2xx status code")) {
63
  // Server error from edge function
64
  const serverErrorMsg =
65
+ "The Urdf parser encountered a server error. It might be due to the complexity of your Urdf file or temporary server issues.";
66
  console.error(`[${requestId}] 🔥 Server error in edge function`);
67
 
68
  // Check if we got fallback data despite the error
69
  if (rawResponse && rawResponse.fallback) {
70
  console.log(`[${requestId}] 🛟 Using fallback data from server`);
71
+ toast.warning("Reduced Urdf Data", {
72
  description:
73
+ "Limited information extracted from your Urdf file due to parsing limitations.",
74
  duration: 5000,
75
  });
76
 
 
79
  return rawResponse.fallback;
80
  }
81
 
82
+ toast.error("Urdf Parser Error", {
83
  description: serverErrorMsg,
84
  duration: 5000,
85
  });
 
88
  } else if (error.message.includes("timeout")) {
89
  // Timeout error
90
  const timeoutMsg =
91
+ "The Urdf parser timed out. Your file might be too complex to process within the allowed time.";
92
  console.error(`[${requestId}] ⏱️ Edge function timed out`);
93
 
94
  toast.error("Processing Timeout", {
 
101
  // Generic error
102
  setError(error.message);
103
 
104
+ toast.error("Error Parsing Urdf", {
105
  description: error.message,
106
  duration: 5000,
107
  });
 
175
  other: jointTypes.other,
176
  },
177
  links: transformedLinks,
178
+ // Default materials since we don't have this info from Urdf
179
  materials: [{ name: "Metal", percentage: 100 }],
180
  };
181
 
 
190
  const endTime = performance.now();
191
 
192
  console.error(
193
+ `[${requestId}] ❌ Error parsing Urdf after ${(
194
  endTime - startTime
195
  ).toFixed(2)}ms:`,
196
  err
 
206
  } finally {
207
  setIsLoading(false);
208
  console.log(
209
+ `[${requestId}] 🏁 Urdf parsing request completed, isLoading set to false`
210
  );
211
  }
212
  };
viewer/src/lib/UrdfDragAndDrop.ts CHANGED
@@ -1,8 +1,7 @@
1
-
2
  /**
3
- * URDF Drag and Drop Utility
4
  *
5
- * This file provides functionality for handling drag and drop of URDF folders.
6
  * It converts the dropped files into accessible blobs for visualization.
7
  */
8
 
@@ -136,7 +135,7 @@ export function cleanFilePath(path: string): string {
136
  }
137
 
138
  /**
139
- * Interface representing the structure of an URDF processor
140
  */
141
  export interface UrdfProcessor {
142
  loadUrdf: (path: string) => void;
@@ -148,8 +147,8 @@ export interface UrdfProcessor {
148
  const packageRef = { current: "" };
149
 
150
  /**
151
- * Reads the content of a URDF file
152
- * @param file The URDF file object
153
  * @returns A promise that resolves with the content of the file as a string
154
  */
155
  export function readUrdfFileContent(file: File): Promise<string> {
@@ -159,10 +158,10 @@ export function readUrdfFileContent(file: File): Promise<string> {
159
  if (event.target && event.target.result) {
160
  resolve(event.target.result as string);
161
  } else {
162
- reject(new Error("Failed to read URDF file content"));
163
  }
164
  };
165
- reader.onerror = () => reject(new Error("Error reading URDF file"));
166
  reader.readAsText(file);
167
  });
168
  }
@@ -170,7 +169,7 @@ export function readUrdfFileContent(file: File): Promise<string> {
170
  /**
171
  * Downloads a zip file from a URL and extracts its contents
172
  * @param zipUrl URL of the zip file to download
173
- * @param urdfProcessor The URDF processor to use for loading
174
  * @returns A promise that resolves with the extraction results
175
  */
176
  export async function downloadAndExtractZip(
@@ -182,58 +181,62 @@ export async function downloadAndExtractZip(
182
  blobUrls: Record<string, string>;
183
  }> {
184
  console.log("🔄 Downloading zip file from:", zipUrl);
185
-
186
  try {
187
  // Download the zip file
188
  const response = await fetch(zipUrl);
189
  if (!response.ok) {
190
  throw new Error(`Failed to download zip: ${response.statusText}`);
191
  }
192
-
193
  const zipBlob = await response.blob();
194
-
195
  // Load JSZip dynamically since it's much easier to work with than manual Blob handling
196
  // We use dynamic import to avoid adding a dependency
197
- const JSZip = (await import('jszip')).default;
198
  const zip = new JSZip();
199
-
200
  // Load the zip content
201
  const contents = await zip.loadAsync(zipBlob);
202
-
203
  // Convert zip contents to files
204
  const files: Record<string, File> = {};
205
  const filePromises: Promise<void>[] = [];
206
-
207
  // Process each file in the zip
208
  contents.forEach((relativePath, zipEntry) => {
209
  if (!zipEntry.dir) {
210
- const promise = zipEntry.async('blob').then(blob => {
211
  // Create a file with the proper name and path
212
- const path = '/' + relativePath;
213
- files[path] = new File([blob], relativePath.split('/').pop() || 'unknown', {
214
- type: getMimeType(relativePath.split('.').pop() || '')
215
- });
 
 
 
 
216
  });
217
  filePromises.push(promise);
218
  }
219
  });
220
-
221
  // Wait for all files to be processed
222
  await Promise.all(filePromises);
223
-
224
  // Get all file paths and clean them
225
  const fileNames = Object.keys(files).map((n) => cleanFilePath(n));
226
-
227
- // Filter all files ending in URDF
228
  const availableModels = fileNames.filter((n) => /urdf$/i.test(n));
229
-
230
- // Create blob URLs for URDF files
231
  const blobUrls: Record<string, string> = {};
232
  availableModels.forEach((path) => {
233
  blobUrls[path] = URL.createObjectURL(files[path]);
234
  });
235
-
236
- // Extract the package base path from the first URDF model for reference
237
  let packageBasePath = "";
238
  if (availableModels.length > 0) {
239
  // Extract the main directory path (e.g., '/cassie_description/')
@@ -243,51 +246,51 @@ export async function downloadAndExtractZip(
243
  packageBasePath = packageMatch[1];
244
  }
245
  }
246
-
247
  // Store the package path for future reference
248
  const packagePathRef = packageBasePath;
249
  urdfProcessor.setUrlModifierFunc((url) => {
250
  // Find the matching file given the requested URL
251
-
252
  // Store package reference for future use
253
  if (packagePathRef) {
254
  packageRef.current = packagePathRef;
255
  }
256
-
257
  // Simple approach: just find the first file that matches the end of the URL
258
  const cleaned = cleanFilePath(url);
259
-
260
  // Get the filename from the URL
261
  const urlFilename = cleaned.split("/").pop() || "";
262
-
263
  // Find the first file that ends with this filename
264
  let fileName = fileNames.find((name) => name.endsWith(urlFilename));
265
-
266
  // If no match found, just take the first file with a similar extension
267
  if (!fileName && urlFilename.includes(".")) {
268
  const extension = "." + urlFilename.split(".").pop();
269
  fileName = fileNames.find((name) => name.endsWith(extension));
270
  }
271
-
272
  if (fileName !== undefined && fileName !== null) {
273
  // Extract file extension for content type
274
  const fileExtension = fileName.split(".").pop()?.toLowerCase() || "";
275
-
276
  // Create blob URL with extension in the searchParams to help with format detection
277
  const blob = new Blob([files[fileName]], {
278
  type: getMimeType(fileExtension),
279
  });
280
  const blobUrl = URL.createObjectURL(blob) + "#." + fileExtension;
281
-
282
  // Don't revoke immediately, wait for the mesh to be loaded
283
  setTimeout(() => URL.revokeObjectURL(blobUrl), 5000);
284
  return blobUrl;
285
  }
286
-
287
  console.warn(`No matching file found for: ${url}`);
288
  return url;
289
  });
290
-
291
  return {
292
  files,
293
  availableModels,
@@ -300,7 +303,7 @@ export async function downloadAndExtractZip(
300
  }
301
 
302
  /**
303
- * Processes dropped files and returns information about available URDF models
304
  */
305
  export async function processDroppedFiles(
306
  dataTransfer: DataTransfer,
@@ -319,16 +322,16 @@ export async function processDroppedFiles(
319
  // Get all file paths and clean them
320
  const fileNames = Object.keys(files).map((n) => cleanFilePath(n));
321
 
322
- // Filter all files ending in URDF
323
  const availableModels = fileNames.filter((n) => /urdf$/i.test(n));
324
 
325
- // Create blob URLs for URDF files
326
  const blobUrls: Record<string, string> = {};
327
  availableModels.forEach((path) => {
328
  blobUrls[path] = URL.createObjectURL(files[path]);
329
  });
330
 
331
- // Extract the package base path from the first URDF model for reference
332
  let packageBasePath = "";
333
  if (availableModels.length > 0) {
334
  // Extract the main directory path (e.g., '/cassie_description/')
@@ -410,4 +413,3 @@ function getMimeType(extension: string): string {
410
  return "application/octet-stream";
411
  }
412
  }
413
-
 
 
1
  /**
2
+ * Urdf Drag and Drop Utility
3
  *
4
+ * This file provides functionality for handling drag and drop of Urdf folders.
5
  * It converts the dropped files into accessible blobs for visualization.
6
  */
7
 
 
135
  }
136
 
137
  /**
138
+ * Interface representing the structure of an Urdf processor
139
  */
140
  export interface UrdfProcessor {
141
  loadUrdf: (path: string) => void;
 
147
  const packageRef = { current: "" };
148
 
149
  /**
150
+ * Reads the content of a Urdf file
151
+ * @param file The Urdf file object
152
  * @returns A promise that resolves with the content of the file as a string
153
  */
154
  export function readUrdfFileContent(file: File): Promise<string> {
 
158
  if (event.target && event.target.result) {
159
  resolve(event.target.result as string);
160
  } else {
161
+ reject(new Error("Failed to read Urdf file content"));
162
  }
163
  };
164
+ reader.onerror = () => reject(new Error("Error reading Urdf file"));
165
  reader.readAsText(file);
166
  });
167
  }
 
169
  /**
170
  * Downloads a zip file from a URL and extracts its contents
171
  * @param zipUrl URL of the zip file to download
172
+ * @param urdfProcessor The Urdf processor to use for loading
173
  * @returns A promise that resolves with the extraction results
174
  */
175
  export async function downloadAndExtractZip(
 
181
  blobUrls: Record<string, string>;
182
  }> {
183
  console.log("🔄 Downloading zip file from:", zipUrl);
184
+
185
  try {
186
  // Download the zip file
187
  const response = await fetch(zipUrl);
188
  if (!response.ok) {
189
  throw new Error(`Failed to download zip: ${response.statusText}`);
190
  }
191
+
192
  const zipBlob = await response.blob();
193
+
194
  // Load JSZip dynamically since it's much easier to work with than manual Blob handling
195
  // We use dynamic import to avoid adding a dependency
196
+ const JSZip = (await import("jszip")).default;
197
  const zip = new JSZip();
198
+
199
  // Load the zip content
200
  const contents = await zip.loadAsync(zipBlob);
201
+
202
  // Convert zip contents to files
203
  const files: Record<string, File> = {};
204
  const filePromises: Promise<void>[] = [];
205
+
206
  // Process each file in the zip
207
  contents.forEach((relativePath, zipEntry) => {
208
  if (!zipEntry.dir) {
209
+ const promise = zipEntry.async("blob").then((blob) => {
210
  // Create a file with the proper name and path
211
+ const path = "/" + relativePath;
212
+ files[path] = new File(
213
+ [blob],
214
+ relativePath.split("/").pop() || "unknown",
215
+ {
216
+ type: getMimeType(relativePath.split(".").pop() || ""),
217
+ }
218
+ );
219
  });
220
  filePromises.push(promise);
221
  }
222
  });
223
+
224
  // Wait for all files to be processed
225
  await Promise.all(filePromises);
226
+
227
  // Get all file paths and clean them
228
  const fileNames = Object.keys(files).map((n) => cleanFilePath(n));
229
+
230
+ // Filter all files ending in Urdf
231
  const availableModels = fileNames.filter((n) => /urdf$/i.test(n));
232
+
233
+ // Create blob URLs for Urdf files
234
  const blobUrls: Record<string, string> = {};
235
  availableModels.forEach((path) => {
236
  blobUrls[path] = URL.createObjectURL(files[path]);
237
  });
238
+
239
+ // Extract the package base path from the first Urdf model for reference
240
  let packageBasePath = "";
241
  if (availableModels.length > 0) {
242
  // Extract the main directory path (e.g., '/cassie_description/')
 
246
  packageBasePath = packageMatch[1];
247
  }
248
  }
249
+
250
  // Store the package path for future reference
251
  const packagePathRef = packageBasePath;
252
  urdfProcessor.setUrlModifierFunc((url) => {
253
  // Find the matching file given the requested URL
254
+
255
  // Store package reference for future use
256
  if (packagePathRef) {
257
  packageRef.current = packagePathRef;
258
  }
259
+
260
  // Simple approach: just find the first file that matches the end of the URL
261
  const cleaned = cleanFilePath(url);
262
+
263
  // Get the filename from the URL
264
  const urlFilename = cleaned.split("/").pop() || "";
265
+
266
  // Find the first file that ends with this filename
267
  let fileName = fileNames.find((name) => name.endsWith(urlFilename));
268
+
269
  // If no match found, just take the first file with a similar extension
270
  if (!fileName && urlFilename.includes(".")) {
271
  const extension = "." + urlFilename.split(".").pop();
272
  fileName = fileNames.find((name) => name.endsWith(extension));
273
  }
274
+
275
  if (fileName !== undefined && fileName !== null) {
276
  // Extract file extension for content type
277
  const fileExtension = fileName.split(".").pop()?.toLowerCase() || "";
278
+
279
  // Create blob URL with extension in the searchParams to help with format detection
280
  const blob = new Blob([files[fileName]], {
281
  type: getMimeType(fileExtension),
282
  });
283
  const blobUrl = URL.createObjectURL(blob) + "#." + fileExtension;
284
+
285
  // Don't revoke immediately, wait for the mesh to be loaded
286
  setTimeout(() => URL.revokeObjectURL(blobUrl), 5000);
287
  return blobUrl;
288
  }
289
+
290
  console.warn(`No matching file found for: ${url}`);
291
  return url;
292
  });
293
+
294
  return {
295
  files,
296
  availableModels,
 
303
  }
304
 
305
  /**
306
+ * Processes dropped files and returns information about available Urdf models
307
  */
308
  export async function processDroppedFiles(
309
  dataTransfer: DataTransfer,
 
322
  // Get all file paths and clean them
323
  const fileNames = Object.keys(files).map((n) => cleanFilePath(n));
324
 
325
+ // Filter all files ending in Urdf
326
  const availableModels = fileNames.filter((n) => /urdf$/i.test(n));
327
 
328
+ // Create blob URLs for Urdf files
329
  const blobUrls: Record<string, string> = {};
330
  availableModels.forEach((path) => {
331
  blobUrls[path] = URL.createObjectURL(files[path]);
332
  });
333
 
334
+ // Extract the package base path from the first Urdf model for reference
335
  let packageBasePath = "";
336
  if (availableModels.length > 0) {
337
  // Extract the main directory path (e.g., '/cassie_description/')
 
413
  return "application/octet-stream";
414
  }
415
  }
 
viewer/src/lib/types.ts CHANGED
@@ -1,5 +1,5 @@
1
  /**
2
- * Shared type definitions for URDF parsing from supabase edge function
3
  */
4
 
5
  export interface UrdfData {
@@ -25,11 +25,11 @@ export interface UrdfData {
25
  }
26
 
27
  /**
28
- * Interface representing a URDF file model
29
  */
30
  export interface UrdfFileModel {
31
  /**
32
- * Path to the URDF file
33
  */
34
  path: string;
35
 
@@ -48,7 +48,7 @@ export interface UrdfFileModel {
48
  * Joint animation configuration interface
49
  */
50
  export interface JointAnimationConfig {
51
- /** Joint name in the URDF */
52
  name: string;
53
  /** Animation type (sine, linear, etc.) */
54
  type: "sine" | "linear" | "constant";
 
1
  /**
2
+ * Shared type definitions for Urdf parsing from supabase edge function
3
  */
4
 
5
  export interface UrdfData {
 
25
  }
26
 
27
  /**
28
+ * Interface representing a Urdf file model
29
  */
30
  export interface UrdfFileModel {
31
  /**
32
+ * Path to the Urdf file
33
  */
34
  path: string;
35
 
 
48
  * Joint animation configuration interface
49
  */
50
  export interface JointAnimationConfig {
51
+ /** Joint name in the Urdf */
52
  name: string;
53
  /** Animation type (sine, linear, etc.) */
54
  type: "sine" | "linear" | "constant";
viewer/src/lib/urdfAnimationHelpers.ts CHANGED
@@ -1,19 +1,19 @@
1
  import { MathUtils } from "three";
2
  import { RobotAnimationConfig } from "@/lib/types";
3
 
4
- // Define the interface for the URDF viewer element
5
- export interface URDFViewerElement extends HTMLElement {
6
  setJointValue: (joint: string, value: number) => void;
7
  }
8
 
9
  /**
10
  * Generalized animation function for any robot
11
- * @param viewer The URDF viewer element
12
  * @param config Configuration for the robot's joint animations
13
  * @returns A cleanup function to cancel the animation
14
  */
15
  export function animateRobot(
16
- viewer: URDFViewerElement,
17
  config: RobotAnimationConfig
18
  ): () => void {
19
  let animationFrameId: number | null = null;
@@ -92,10 +92,10 @@ export function animateRobot(
92
 
93
  /**
94
  * Animates a hexapod robot (like T12) with walking motion
95
- * @param viewer The URDF viewer element
96
  * @returns A cleanup function to cancel the animation
97
  */
98
- export function animateHexapodRobot(viewer: URDFViewerElement): () => void {
99
  let animationFrameId: number | null = null;
100
  let isRunning = true;
101
 
 
1
  import { MathUtils } from "three";
2
  import { RobotAnimationConfig } from "@/lib/types";
3
 
4
+ // Define the interface for the Urdf viewer element
5
+ export interface UrdfViewerElement extends HTMLElement {
6
  setJointValue: (joint: string, value: number) => void;
7
  }
8
 
9
  /**
10
  * Generalized animation function for any robot
11
+ * @param viewer The Urdf viewer element
12
  * @param config Configuration for the robot's joint animations
13
  * @returns A cleanup function to cancel the animation
14
  */
15
  export function animateRobot(
16
+ viewer: UrdfViewerElement,
17
  config: RobotAnimationConfig
18
  ): () => void {
19
  let animationFrameId: number | null = null;
 
92
 
93
  /**
94
  * Animates a hexapod robot (like T12) with walking motion
95
+ * @param viewer The Urdf viewer element
96
  * @returns A cleanup function to cancel the animation
97
  */
98
+ export function animateHexapodRobot(viewer: UrdfViewerElement): () => void {
99
  let animationFrameId: number | null = null;
100
  let isRunning = true;
101
 
viewer/src/lib/urdfViewerHelpers.ts CHANGED
@@ -1,10 +1,10 @@
1
  import { LoadingManager, Object3D } from "three";
2
  import { toast } from "sonner";
3
  import { loadMeshFile } from "./meshLoaders";
4
- import { URDFViewerElement } from "./urdfAnimationHelpers";
5
 
6
- // Extended URDF Viewer Element with mesh loading capability
7
- export interface ExtendedURDFViewerElement extends URDFViewerElement {
8
  loadMeshFunc: (
9
  path: string,
10
  manager: LoadingManager,
@@ -13,19 +13,19 @@ export interface ExtendedURDFViewerElement extends URDFViewerElement {
13
  }
14
 
15
  /**
16
- * Creates and configures a URDF viewer element
17
  */
18
  export function createUrdfViewer(
19
  container: HTMLDivElement,
20
  isDarkMode: boolean
21
- ): ExtendedURDFViewerElement {
22
  // Clear any existing content
23
  container.innerHTML = "";
24
 
25
  // Create the urdf-viewer element
26
  const viewer = document.createElement(
27
  "urdf-viewer"
28
- ) as ExtendedURDFViewerElement;
29
  viewer.classList.add("w-full", "h-full");
30
 
31
  // Add the element to the container
@@ -42,10 +42,10 @@ export function createUrdfViewer(
42
  }
43
 
44
  /**
45
- * Setup mesh loading function for URDF viewer
46
  */
47
  export function setupMeshLoader(
48
- viewer: ExtendedURDFViewerElement,
49
  urlModifierFunc: ((url: string) => string) | null
50
  ): void {
51
  if ("loadMeshFunc" in viewer) {
@@ -80,7 +80,7 @@ export function setupMeshLoader(
80
  * Setup event handlers for joint highlighting
81
  */
82
  export function setupJointHighlighting(
83
- viewer: URDFViewerElement,
84
  setHighlightedJoint: (joint: string | null) => void
85
  ): () => void {
86
  const onJointMouseover = (e: Event) => {
@@ -107,7 +107,7 @@ export function setupJointHighlighting(
107
  * Setup model loading and error handling
108
  */
109
  export function setupModelLoading(
110
- viewer: URDFViewerElement,
111
  urdfPath: string,
112
  packagePath: string,
113
  setCustomUrdfPath: (path: string) => void,
@@ -119,14 +119,14 @@ export function setupModelLoading(
119
  ? urdfPath + "#.urdf" // Add extension hint if it's a blob URL
120
  : urdfPath;
121
 
122
- // Set the URDF path
123
  viewer.setAttribute("urdf", loadPath);
124
  viewer.setAttribute("package", packagePath);
125
 
126
  // Handle error loading
127
  const onLoadError = () => {
128
  toast.error("Failed to load model", {
129
- description: "There was an error loading the URDF model.",
130
  duration: 3000,
131
  });
132
 
 
1
  import { LoadingManager, Object3D } from "three";
2
  import { toast } from "sonner";
3
  import { loadMeshFile } from "./meshLoaders";
4
+ import { UrdfViewerElement } from "./urdfAnimationHelpers";
5
 
6
+ // Extended Urdf Viewer Element with mesh loading capability
7
+ export interface ExtendedUrdfViewerElement extends UrdfViewerElement {
8
  loadMeshFunc: (
9
  path: string,
10
  manager: LoadingManager,
 
13
  }
14
 
15
  /**
16
+ * Creates and configures a Urdf viewer element
17
  */
18
  export function createUrdfViewer(
19
  container: HTMLDivElement,
20
  isDarkMode: boolean
21
+ ): ExtendedUrdfViewerElement {
22
  // Clear any existing content
23
  container.innerHTML = "";
24
 
25
  // Create the urdf-viewer element
26
  const viewer = document.createElement(
27
  "urdf-viewer"
28
+ ) as ExtendedUrdfViewerElement;
29
  viewer.classList.add("w-full", "h-full");
30
 
31
  // Add the element to the container
 
42
  }
43
 
44
  /**
45
+ * Setup mesh loading function for Urdf viewer
46
  */
47
  export function setupMeshLoader(
48
+ viewer: ExtendedUrdfViewerElement,
49
  urlModifierFunc: ((url: string) => string) | null
50
  ): void {
51
  if ("loadMeshFunc" in viewer) {
 
80
  * Setup event handlers for joint highlighting
81
  */
82
  export function setupJointHighlighting(
83
+ viewer: UrdfViewerElement,
84
  setHighlightedJoint: (joint: string | null) => void
85
  ): () => void {
86
  const onJointMouseover = (e: Event) => {
 
107
  * Setup model loading and error handling
108
  */
109
  export function setupModelLoading(
110
+ viewer: UrdfViewerElement,
111
  urdfPath: string,
112
  packagePath: string,
113
  setCustomUrdfPath: (path: string) => void,
 
119
  ? urdfPath + "#.urdf" // Add extension hint if it's a blob URL
120
  : urdfPath;
121
 
122
+ // Set the Urdf path
123
  viewer.setAttribute("urdf", loadPath);
124
  viewer.setAttribute("package", packagePath);
125
 
126
  // Handle error loading
127
  const onLoadError = () => {
128
  toast.error("Failed to load model", {
129
+ description: "There was an error loading the Urdf model.",
130
  duration: 3000,
131
  });
132
 
viewer/supabase/functions/README.md CHANGED
@@ -1,23 +1,24 @@
1
-
2
  # Supabase Edge Functions
3
 
4
  This directory contains Edge Functions that can be deployed to Supabase.
5
 
6
  ## Available Functions
7
 
8
- - `urdf-parser`: Extracts metadata from URDF files using OpenAI
9
- - `create-animation`: Generates animations for URDF models based on user instructions
10
 
11
  ## Deployment Instructions
12
 
13
  ### Prerequisites
14
 
15
  1. Install Supabase CLI:
 
16
  ```
17
  npm install -g supabase
18
  ```
19
 
20
  2. Login to Supabase:
 
21
  ```
22
  npx supabase login
23
  ```
 
 
1
  # Supabase Edge Functions
2
 
3
  This directory contains Edge Functions that can be deployed to Supabase.
4
 
5
  ## Available Functions
6
 
7
+ - `urdf-parser`: Extracts metadata from Urdf files using OpenAI
8
+ - `create-animation`: Generates animations for Urdf models based on user instructions
9
 
10
  ## Deployment Instructions
11
 
12
  ### Prerequisites
13
 
14
  1. Install Supabase CLI:
15
+
16
  ```
17
  npm install -g supabase
18
  ```
19
 
20
  2. Login to Supabase:
21
+
22
  ```
23
  npx supabase login
24
  ```
viewer/supabase/functions/urdf-parser/index.ts CHANGED
@@ -1,7 +1,7 @@
1
  import { XMLParser } from "npm:[email protected]";
2
  import { serve } from "https://deno.land/[email protected]/http/server.ts";
3
 
4
- interface URDFLink {
5
  name: string;
6
  mass: number;
7
  inertia: {
@@ -16,7 +16,7 @@ interface URDFLink {
16
  has_collision: boolean;
17
  }
18
 
19
- interface URDFJoint {
20
  name: string;
21
  type: string;
22
  parent: string;
@@ -50,10 +50,10 @@ serve(async (req) => {
50
  const { urdfContent } = await req.json();
51
 
52
  if (!urdfContent) {
53
- throw new Error("No URDF content provided");
54
  }
55
 
56
- // Parse URDF XML using fast-xml-parser
57
  const parser = new XMLParser({
58
  ignoreAttributes: false,
59
  attributeNamePrefix: "",
@@ -63,15 +63,15 @@ serve(async (req) => {
63
  const doc = parser.parse(urdfContent);
64
 
65
  if (!doc || !doc.robot) {
66
- throw new Error("No robot element found in URDF");
67
  }
68
 
69
  // Extract robot data
70
  const robotElement = doc.robot;
71
  const name = robotElement.name || "unnamed_robot";
72
  const version = robotElement.version;
73
- const links: URDFLink[] = [];
74
- const joints: URDFJoint[] = [];
75
  let total_mass = 0;
76
 
77
  // Parse links
@@ -85,7 +85,7 @@ serve(async (req) => {
85
  const mass = inertial?.mass?.value || 0;
86
  total_mass += mass;
87
 
88
- const linkData: URDFLink = {
89
  name: linkName,
90
  mass,
91
  inertia: {
@@ -123,7 +123,7 @@ serve(async (req) => {
123
  ? robotElement.joint
124
  : [robotElement.joint];
125
  jointElements.forEach((joint: any) => {
126
- const jointData: URDFJoint = {
127
  name: joint.name || "",
128
  type: joint.type || "",
129
  parent: joint.parent?.link || "",
@@ -164,7 +164,7 @@ serve(async (req) => {
164
  },
165
  });
166
  } catch (error) {
167
- console.error("Error parsing URDF:", error);
168
  return new Response(
169
  JSON.stringify({
170
  success: false,
 
1
  import { XMLParser } from "npm:[email protected]";
2
  import { serve } from "https://deno.land/[email protected]/http/server.ts";
3
 
4
+ interface UrdfLink {
5
  name: string;
6
  mass: number;
7
  inertia: {
 
16
  has_collision: boolean;
17
  }
18
 
19
+ interface UrdfJoint {
20
  name: string;
21
  type: string;
22
  parent: string;
 
50
  const { urdfContent } = await req.json();
51
 
52
  if (!urdfContent) {
53
+ throw new Error("No Urdf content provided");
54
  }
55
 
56
+ // Parse Urdf XML using fast-xml-parser
57
  const parser = new XMLParser({
58
  ignoreAttributes: false,
59
  attributeNamePrefix: "",
 
63
  const doc = parser.parse(urdfContent);
64
 
65
  if (!doc || !doc.robot) {
66
+ throw new Error("No robot element found in Urdf");
67
  }
68
 
69
  // Extract robot data
70
  const robotElement = doc.robot;
71
  const name = robotElement.name || "unnamed_robot";
72
  const version = robotElement.version;
73
+ const links: UrdfLink[] = [];
74
+ const joints: UrdfJoint[] = [];
75
  let total_mass = 0;
76
 
77
  // Parse links
 
85
  const mass = inertial?.mass?.value || 0;
86
  total_mass += mass;
87
 
88
+ const linkData: UrdfLink = {
89
  name: linkName,
90
  mass,
91
  inertia: {
 
123
  ? robotElement.joint
124
  : [robotElement.joint];
125
  jointElements.forEach((joint: any) => {
126
+ const jointData: UrdfJoint = {
127
  name: joint.name || "",
128
  type: joint.type || "",
129
  parent: joint.parent?.link || "",
 
164
  },
165
  });
166
  } catch (error) {
167
+ console.error("Error parsing Urdf:", error);
168
  return new Response(
169
  JSON.stringify({
170
  success: false,