jurmy24 commited on
Commit
912e2d8
·
1 Parent(s): 147427a

misc: add a second default robot

Browse files
viewer/public/urdf/SO_5DOF_ARM100_05d/robot-data.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "SO-ARM100 Robot",
3
+ "description": "This is the SO-ARM100 robot that is fully open source and can be used for a variety of applications.",
4
+ "mass": 0.8,
5
+ "dofs": 6,
6
+ "joints": {
7
+ "revolute": 0,
8
+ "prismatic": 0,
9
+ "continuous": 6,
10
+ "fixed": 0,
11
+ "other": 0
12
+ },
13
+ "links": [
14
+ { "name": "Base", "mass": 0.1 },
15
+ { "name": "Shoulder_Rotation_Pitch", "mass": 0.1 },
16
+ { "name": "Upper_Arm", "mass": 0.2 },
17
+ { "name": "Lower_Arm", "mass": 0.1 },
18
+ { "name": "Wrist_Pitch_Roll", "mass": 0.1 },
19
+ { "name": "Fixed_Gripper", "mass": 0.1 },
20
+ { "name": "Moving_Jaw", "mass": 0.0 }
21
+ ],
22
+ "materials": [
23
+ { "name": "Plastic", "percentage": 95 },
24
+ { "name": "Steel", "percentage": 5 }
25
+ ]
26
+ }
viewer/public/urdf/SO_5DOF_ARM100_05d/urdf/SO_5DOF_ARM100_05d.urdf ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
6
+ name="SO-ARM100">
7
+ <link
8
+ name="Base">
9
+ <inertial>
10
+ <origin
11
+ xyz="-1.55220299024015E-10 0.0275980388649202 0.0272094138963763"
12
+ rpy="0 0 0" />
13
+ <mass
14
+ value="0.146962928243327" />
15
+ <inertia
16
+ ixx="9.5191642834079E-05"
17
+ ixy="2.02405274856147E-12"
18
+ ixz="1.46514387606669E-13"
19
+ iyy="0.000123785814019492"
20
+ iyz="1.84608762035329E-05"
21
+ izz="0.000137926707148466" />
22
+ </inertial>
23
+ <visual>
24
+ <origin
25
+ xyz="0 0 0"
26
+ rpy="0 0 0" />
27
+ <geometry>
28
+ <mesh
29
+ filename="package://SO_5DOF_ARM100_05d/meshes/Base.STL" />
30
+ </geometry>
31
+ <material
32
+ name="">
33
+ <color
34
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
35
+ </material>
36
+ </visual>
37
+ <collision>
38
+ <origin
39
+ xyz="0 0 0"
40
+ rpy="0 0 0" />
41
+ <geometry>
42
+ <mesh
43
+ filename="package://SO_5DOF_ARM100_05d/meshes/Base.STL" />
44
+ </geometry>
45
+ </collision>
46
+ </link>
47
+ <link
48
+ name="Shoulder_Rotation_Pitch">
49
+ <inertial>
50
+ <origin
51
+ xyz="-0.00511938391873139 0.0678779339349912 -0.000127472379243391"
52
+ rpy="0 0 0" />
53
+ <mass
54
+ value="0.111780100254674" />
55
+ <inertia
56
+ ixx="7.03890301713851E-05"
57
+ ixy="-1.55093016866869E-05"
58
+ ixz="1.67387694867946E-07"
59
+ iyy="3.32352621027575E-05"
60
+ iyz="9.30705606418705E-07"
61
+ izz="7.08694473647387E-05" />
62
+ </inertial>
63
+ <visual>
64
+ <origin
65
+ xyz="0 0 0"
66
+ rpy="0 0 0" />
67
+ <geometry>
68
+ <mesh
69
+ filename="package://SO_5DOF_ARM100_05d/meshes/Shoulder_Rotation_Pitch.STL" />
70
+ </geometry>
71
+ <material
72
+ name="">
73
+ <color
74
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
75
+ </material>
76
+ </visual>
77
+ <collision>
78
+ <origin
79
+ xyz="0 0 0"
80
+ rpy="0 0 0" />
81
+ <geometry>
82
+ <mesh
83
+ filename="package://SO_5DOF_ARM100_05d/meshes/Shoulder_Rotation_Pitch.STL" />
84
+ </geometry>
85
+ </collision>
86
+ </link>
87
+ <joint
88
+ name="Shoulder_Rotation"
89
+ type="continuous">
90
+ <origin
91
+ xyz="0 -0.0452 0.0181"
92
+ rpy="1.5708 0 1.5708" />
93
+ <parent
94
+ link="Base" />
95
+ <child
96
+ link="Shoulder_Rotation_Pitch" />
97
+ <axis
98
+ xyz="0 1 0" />
99
+ </joint>
100
+ <link
101
+ name="Upper_Arm">
102
+ <inertial>
103
+ <origin
104
+ xyz="-0.0693113774468845 0.00293741346964818 -7.61279219025209E-07"
105
+ rpy="0 0 0" />
106
+ <mass
107
+ value="0.167601391353176" />
108
+ <inertia
109
+ ixx="7.75332201021328E-05"
110
+ ixy="-2.10765620509824E-06"
111
+ ixz="7.52685919931984E-07"
112
+ iyy="0.000233751202018378"
113
+ iyz="-1.63496162538793E-07"
114
+ izz="0.000180452754687364" />
115
+ </inertial>
116
+ <visual>
117
+ <origin
118
+ xyz="0 0 0"
119
+ rpy="0 0 0" />
120
+ <geometry>
121
+ <mesh
122
+ filename="package://SO_5DOF_ARM100_05d/meshes/Upper_Arm.STL" />
123
+ </geometry>
124
+ <material
125
+ name="">
126
+ <color
127
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
128
+ </material>
129
+ </visual>
130
+ <collision>
131
+ <origin
132
+ xyz="0 0 0"
133
+ rpy="0 0 0" />
134
+ <geometry>
135
+ <mesh
136
+ filename="package://SO_5DOF_ARM100_05d/meshes/Upper_Arm.STL" />
137
+ </geometry>
138
+ </collision>
139
+ </link>
140
+ <joint
141
+ name="Shoulder_Pitch"
142
+ type="continuous">
143
+ <origin
144
+ xyz="0.000125 0.1086 0"
145
+ rpy="3.1416 0 -1.5708" />
146
+ <parent
147
+ link="Shoulder_Rotation_Pitch" />
148
+ <child
149
+ link="Upper_Arm" />
150
+ <axis
151
+ xyz="0 0 1" />
152
+ </joint>
153
+ <link
154
+ name="Lower_Arm">
155
+ <inertial>
156
+ <origin
157
+ xyz="-0.0588290275819227 0.0021495318374051 0.000146772621039401"
158
+ rpy="0 0 0" />
159
+ <mass
160
+ value="0.142523221917339" />
161
+ <inertia
162
+ ixx="6.29078989235053E-05"
163
+ ixy="3.79294618448135E-06"
164
+ ixz="1.70733512134003E-06"
165
+ iyy="0.000146811163948232"
166
+ iyz="-2.1474403445678E-07"
167
+ izz="0.000102145070617562" />
168
+ </inertial>
169
+ <visual>
170
+ <origin
171
+ xyz="0 0 0"
172
+ rpy="0 0 0" />
173
+ <geometry>
174
+ <mesh
175
+ filename="package://SO_5DOF_ARM100_05d/meshes/Lower_Arm.STL" />
176
+ </geometry>
177
+ <material
178
+ name="">
179
+ <color
180
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
181
+ </material>
182
+ </visual>
183
+ <collision>
184
+ <origin
185
+ xyz="0 0 0"
186
+ rpy="0 0 0" />
187
+ <geometry>
188
+ <mesh
189
+ filename="package://SO_5DOF_ARM100_05d/meshes/Lower_Arm.STL" />
190
+ </geometry>
191
+ </collision>
192
+ </link>
193
+ <joint
194
+ name="Elbow"
195
+ type="continuous">
196
+ <origin
197
+ xyz="-0.11238 0.0282 0"
198
+ rpy="0 0 -2.2391" />
199
+ <parent
200
+ link="Upper_Arm" />
201
+ <child
202
+ link="Lower_Arm" />
203
+ <axis
204
+ xyz="0 0 1" />
205
+ </joint>
206
+ <link
207
+ name="Wrist_Pitch_Roll">
208
+ <inertial>
209
+ <origin
210
+ xyz="-6.28656116854598E-09 -0.0087849429576346 -0.0309177852835532"
211
+ rpy="0 0 0" />
212
+ <mass
213
+ value="0.106401896179987" />
214
+ <inertia
215
+ ixx="4.78947074364113E-05"
216
+ ixy="-1.33871782943846E-11"
217
+ ixz="-8.95740683864277E-12"
218
+ iyy="7.01088408487287E-05"
219
+ iyz="-5.49748507471695E-06"
220
+ izz="6.17958653539553E-05" />
221
+ </inertial>
222
+ <visual>
223
+ <origin
224
+ xyz="0 0 0"
225
+ rpy="0 0 0" />
226
+ <geometry>
227
+ <mesh
228
+ filename="package://SO_5DOF_ARM100_05d/meshes/Wrist_Pitch_Roll.STL" />
229
+ </geometry>
230
+ <material
231
+ name="">
232
+ <color
233
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
234
+ </material>
235
+ </visual>
236
+ <collision>
237
+ <origin
238
+ xyz="0 0 0"
239
+ rpy="0 0 0" />
240
+ <geometry>
241
+ <mesh
242
+ filename="package://SO_5DOF_ARM100_05d/meshes/Wrist_Pitch_Roll.STL" />
243
+ </geometry>
244
+ </collision>
245
+ </link>
246
+ <joint
247
+ name="Wrist_Pitch"
248
+ type="continuous">
249
+ <origin
250
+ xyz="-0.1102 0.005375 0"
251
+ rpy="0.90254 1.5708 0" />
252
+ <parent
253
+ link="Lower_Arm" />
254
+ <child
255
+ link="Wrist_Pitch_Roll" />
256
+ <axis
257
+ xyz="1 0 0" />
258
+ </joint>
259
+ <link
260
+ name="Fixed_Gripper">
261
+ <inertial>
262
+ <origin
263
+ xyz="-0.00772179942650385 -0.000555295978140996 0.0316941559340959"
264
+ rpy="0 0 0" />
265
+ <mass
266
+ value="0.11710741874408" />
267
+ <inertia
268
+ ixx="5.67526018031759E-05"
269
+ ixy="1.04098982658207E-06"
270
+ ixz="8.53596077253277E-06"
271
+ iyy="5.78441834179299E-05"
272
+ iyz="-2.86014969245207E-07"
273
+ izz="4.22399193495317E-05" />
274
+ </inertial>
275
+ <visual>
276
+ <origin
277
+ xyz="0 0 0"
278
+ rpy="0 0 0" />
279
+ <geometry>
280
+ <mesh
281
+ filename="package://SO_5DOF_ARM100_05d/meshes/Fixed_Gripper.STL" />
282
+ </geometry>
283
+ <material
284
+ name="">
285
+ <color
286
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
287
+ </material>
288
+ </visual>
289
+ <collision>
290
+ <origin
291
+ xyz="0 0 0"
292
+ rpy="0 0 0" />
293
+ <geometry>
294
+ <mesh
295
+ filename="package://SO_5DOF_ARM100_05d/meshes/Fixed_Gripper.STL" />
296
+ </geometry>
297
+ </collision>
298
+ </link>
299
+ <joint
300
+ name="Wrist_Roll"
301
+ type="continuous">
302
+ <origin
303
+ xyz="0 0.002 -0.0545"
304
+ rpy="3.1416 0 3.1416" />
305
+ <parent
306
+ link="Wrist_Pitch_Roll" />
307
+ <child
308
+ link="Fixed_Gripper" />
309
+ <axis
310
+ xyz="0 0 1" />
311
+ </joint>
312
+ <link
313
+ name="Moving_Jaw">
314
+ <inertial>
315
+ <origin
316
+ xyz="-0.0033838985185846 -0.0322884362122416 0.000144458547748166"
317
+ rpy="0 0 0" />
318
+ <mass
319
+ value="0.0347149174448153" />
320
+ <inertia
321
+ ixx="1.36949844449711E-05"
322
+ ixy="-5.63192124555278E-07"
323
+ ixz="-5.74449907399212E-09"
324
+ iyy="7.04089001130743E-06"
325
+ iyz="-1.05361496046931E-07"
326
+ izz="8.28976960805291E-06" />
327
+ </inertial>
328
+ <visual>
329
+ <origin
330
+ xyz="0 0 0"
331
+ rpy="0 0 0" />
332
+ <geometry>
333
+ <mesh
334
+ filename="package://SO_5DOF_ARM100_05d/meshes/Moving_Jaw.STL" />
335
+ </geometry>
336
+ <material
337
+ name="">
338
+ <color
339
+ rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
340
+ </material>
341
+ </visual>
342
+ <collision>
343
+ <origin
344
+ xyz="0 0 0"
345
+ rpy="0 0 0" />
346
+ <geometry>
347
+ <mesh
348
+ filename="package://SO_5DOF_ARM100_05d/meshes/Moving_Jaw.STL" />
349
+ </geometry>
350
+ </collision>
351
+ </link>
352
+ <joint
353
+ name="Gripper"
354
+ type="continuous">
355
+ <origin
356
+ xyz="0.0202 0 0.024375"
357
+ rpy="-1.5708 0 0" />
358
+ <parent
359
+ link="Fixed_Gripper" />
360
+ <child
361
+ link="Moving_Jaw" />
362
+ <axis
363
+ xyz="0 0 1" />
364
+ </joint>
365
+ </robot>
viewer/src/components/UrdfViewer.tsx CHANGED
@@ -1,27 +1,15 @@
1
  import React, { useEffect, useRef, useState, useMemo } from "react";
2
- import { Play, Pause } from "lucide-react";
3
  import { cn } from "@/lib/utils";
4
 
5
  import { useTheme } from "@/hooks/useTheme";
6
  import URDFManipulator from "urdf-loader/src/urdf-manipulator-element.js";
7
  import { useUrdf } from "@/hooks/useUrdf";
8
- import {
9
- animateHexapodRobot,
10
- animateRobot,
11
- cassieWalkingConfig,
12
- } from "@/lib/urdfAnimationHelpers";
13
  import {
14
  createUrdfViewer,
15
  setupMeshLoader,
16
  setupJointHighlighting,
17
  setupModelLoading,
18
  } from "@/lib/urdfViewerHelpers";
19
- import {
20
- Tooltip,
21
- TooltipContent,
22
- TooltipProvider,
23
- TooltipTrigger,
24
- } from "@/components/ui/tooltip";
25
  import { ModeToggle } from "./ModeToggle";
26
 
27
  // Register the URDFManipulator as a custom element if it hasn't been already
@@ -35,31 +23,16 @@ interface URDFViewerElement extends HTMLElement {
35
  setJointValue?: (jointName: string, value: number) => void;
36
  }
37
 
38
- interface URDFViewerProps {
39
- hasAnimation?: boolean;
40
- headerHeight?: number;
41
- }
42
-
43
- const URDFViewer: React.FC<URDFViewerProps> = ({
44
- hasAnimation = false,
45
- headerHeight = 0,
46
- }) => {
47
  const { theme } = useTheme();
48
  const isDarkMode = theme === "dark";
49
  const containerRef = useRef<HTMLDivElement>(null);
50
  const [highlightedJoint, setHighlightedJoint] = useState<string | null>(null);
51
- const {
52
- registerUrdfProcessor,
53
- alternativeUrdfModels,
54
- isDefaultModel,
55
- currentAnimationConfig,
56
- } = useUrdf();
57
 
58
  // Add state for animation control
59
- const [isAnimating, setIsAnimating] = useState<boolean>(isDefaultModel);
60
- const [showAnimationControl, setShowAnimationControl] = useState<boolean>(
61
- isDefaultModel || hasAnimation
62
- );
63
  const cleanupAnimationRef = useRef<(() => void) | null>(null);
64
  const viewerRef = useRef<URDFViewerElement | null>(null);
65
  const hasInitializedRef = useRef<boolean>(false);
@@ -93,17 +66,6 @@ const URDFViewer: React.FC<URDFViewerProps> = ({
93
  registerUrdfProcessor(urdfProcessor);
94
  }, [registerUrdfProcessor, urdfProcessor]);
95
 
96
- // Effect to update animation control visibility based on props
97
- useEffect(() => {
98
- // Show animation controls for default model or when hasAnimation is true
99
- setShowAnimationControl(isDefaultModel || hasAnimation);
100
- }, [isDefaultModel, hasAnimation]);
101
-
102
- // Toggle animation function
103
- const toggleAnimation = () => {
104
- setIsAnimating((prev) => !prev);
105
- };
106
-
107
  // Main effect to create and setup the viewer only once
108
  useEffect(() => {
109
  if (!containerRef.current) return;
@@ -116,6 +78,9 @@ const URDFViewer: React.FC<URDFViewerProps> = ({
116
  setupMeshLoader(viewer, urlModifierFunc);
117
 
118
  // Determine which URDF to load
 
 
 
119
  const urdfPath = isDefaultModel
120
  ? "/urdf/T12/urdf/T12.URDF"
121
  : customUrdfPath || "";
@@ -141,29 +106,12 @@ const URDFViewer: React.FC<URDFViewerProps> = ({
141
  // Setup animation event handler for the default model or when hasAnimation is true
142
  const onModelProcessed = () => {
143
  hasInitializedRef.current = true;
144
- if (isAnimating && "setJointValue" in viewer) {
145
  // Clear any existing animation
146
  if (cleanupAnimationRef.current) {
147
  cleanupAnimationRef.current();
148
  cleanupAnimationRef.current = null;
149
  }
150
-
151
- // Use the appropriate animation based on whether it's the default model or hasAnimation
152
- if (isDefaultModel) {
153
- // Start the hexapod animation when it's the default model
154
- cleanupAnimationRef.current = animateHexapodRobot(
155
- viewer as import("@/lib/urdfAnimationHelpers").URDFViewerElement
156
- );
157
- } else if (hasAnimation) {
158
- // Use the custom animation config from the context if available, otherwise fall back to cassieWalkingConfig
159
- const animationConfig = currentAnimationConfig || cassieWalkingConfig;
160
-
161
- // Start the animation using the selected configuration
162
- cleanupAnimationRef.current = animateRobot(
163
- viewer as import("@/lib/urdfAnimationHelpers").URDFViewerElement,
164
- animationConfig
165
- );
166
- }
167
  }
168
  };
169
 
@@ -180,13 +128,7 @@ const URDFViewer: React.FC<URDFViewerProps> = ({
180
  cleanupModelLoading();
181
  viewer.removeEventListener("urdf-processed", onModelProcessed);
182
  };
183
- }, [
184
- isDefaultModel,
185
- customUrdfPath,
186
- urlModifierFunc,
187
- hasAnimation,
188
- currentAnimationConfig,
189
- ]);
190
 
191
  // Separate effect to handle theme changes without recreating the viewer
192
  useEffect(() => {
@@ -202,40 +144,6 @@ const URDFViewer: React.FC<URDFViewerProps> = ({
202
  }
203
  }, [isDarkMode]);
204
 
205
- // Effect to handle animation toggling after initial load
206
- useEffect(() => {
207
- if (!viewerRef.current || !hasInitializedRef.current) return;
208
-
209
- // Only manage animation if viewer has setJointValue (required for animation)
210
- if (!("setJointValue" in viewerRef.current)) return;
211
-
212
- if (isAnimating) {
213
- if (!cleanupAnimationRef.current) {
214
- // Only start animation if it's not already running
215
- if (isDefaultModel) {
216
- cleanupAnimationRef.current = animateHexapodRobot(
217
- viewerRef.current as import("@/lib/urdfAnimationHelpers").URDFViewerElement
218
- );
219
- } else if (hasAnimation) {
220
- // Use the custom animation config from the context if available, otherwise fall back to cassieWalkingConfig
221
- const animationConfig = currentAnimationConfig;
222
-
223
- // Start animation using the selected configuration
224
- cleanupAnimationRef.current = animateRobot(
225
- viewerRef.current as import("@/lib/urdfAnimationHelpers").URDFViewerElement,
226
- animationConfig
227
- );
228
- }
229
- }
230
- } else {
231
- if (cleanupAnimationRef.current) {
232
- // Just cancel the animation frame without resetting anything
233
- cleanupAnimationRef.current();
234
- cleanupAnimationRef.current = null;
235
- }
236
- }
237
- }, [isAnimating, isDefaultModel, hasAnimation, currentAnimationConfig]);
238
-
239
  return (
240
  <div
241
  className={cn(
@@ -247,46 +155,10 @@ const URDFViewer: React.FC<URDFViewerProps> = ({
247
  >
248
  <div ref={containerRef} className="w-full h-full" />
249
 
250
- {/* Control buttons container in top right, with vertical padding for header */}
251
- <div
252
- className="absolute right-4 flex items-center space-x-2 z-10"
253
- style={{ top: `${headerHeight + 16}px` }}
254
- >
255
  {/* ModeToggle button */}
256
  <ModeToggle />
257
-
258
- {/* Animation control button with icon - show for both default model and when animation is available */}
259
- {showAnimationControl && (
260
- <TooltipProvider>
261
- <Tooltip>
262
- <TooltipTrigger asChild>
263
- <button
264
- onClick={toggleAnimation}
265
- className={cn(
266
- "p-2.5 rounded-full shadow-md transition-all duration-300",
267
- isDarkMode
268
- ? "bg-gray-700 hover:bg-gray-600 text-white"
269
- : "bg-white/80 hover:bg-white text-gray-800"
270
- )}
271
- aria-label={
272
- isAnimating ? "Stop Animation" : "Start Animation"
273
- }
274
- >
275
- {isAnimating ? (
276
- <Pause className="h-5 w-5" />
277
- ) : (
278
- <Play className="h-5 w-5" />
279
- )}
280
- </button>
281
- </TooltipTrigger>
282
- <TooltipContent side="bottom">
283
- <p className="font-mono text-xs">
284
- {isAnimating ? "Stop Animation" : "Start Animation"}
285
- </p>
286
- </TooltipContent>
287
- </Tooltip>
288
- </TooltipProvider>
289
- )}
290
  </div>
291
 
292
  {/* Joint highlight indicator */}
 
1
  import React, { useEffect, useRef, useState, useMemo } from "react";
 
2
  import { cn } from "@/lib/utils";
3
 
4
  import { useTheme } from "@/hooks/useTheme";
5
  import URDFManipulator from "urdf-loader/src/urdf-manipulator-element.js";
6
  import { useUrdf } from "@/hooks/useUrdf";
 
 
 
 
 
7
  import {
8
  createUrdfViewer,
9
  setupMeshLoader,
10
  setupJointHighlighting,
11
  setupModelLoading,
12
  } from "@/lib/urdfViewerHelpers";
 
 
 
 
 
 
13
  import { ModeToggle } from "./ModeToggle";
14
 
15
  // Register the URDFManipulator as a custom element if it hasn't been already
 
23
  setJointValue?: (jointName: string, value: number) => void;
24
  }
25
 
26
+ const URDFViewer: React.FC = () => {
 
 
 
 
 
 
 
 
27
  const { theme } = useTheme();
28
  const isDarkMode = theme === "dark";
29
  const containerRef = useRef<HTMLDivElement>(null);
30
  const [highlightedJoint, setHighlightedJoint] = useState<string | null>(null);
31
+ const { registerUrdfProcessor, alternativeUrdfModels, isDefaultModel } =
32
+ useUrdf();
 
 
 
 
33
 
34
  // Add state for animation control
35
+ useState<boolean>(isDefaultModel);
 
 
 
36
  const cleanupAnimationRef = useRef<(() => void) | null>(null);
37
  const viewerRef = useRef<URDFViewerElement | null>(null);
38
  const hasInitializedRef = useRef<boolean>(false);
 
66
  registerUrdfProcessor(urdfProcessor);
67
  }, [registerUrdfProcessor, urdfProcessor]);
68
 
 
 
 
 
 
 
 
 
 
 
 
69
  // Main effect to create and setup the viewer only once
70
  useEffect(() => {
71
  if (!containerRef.current) return;
 
78
  setupMeshLoader(viewer, urlModifierFunc);
79
 
80
  // Determine which URDF to load
81
+ // const urdfPath = isDefaultModel
82
+ // ? "/urdf/SO_5DOF_ARM100_05d/urdf/SO_5DOF_ARM100_05d.urdf"
83
+ // : customUrdfPath || "";
84
  const urdfPath = isDefaultModel
85
  ? "/urdf/T12/urdf/T12.URDF"
86
  : customUrdfPath || "";
 
106
  // Setup animation event handler for the default model or when hasAnimation is true
107
  const onModelProcessed = () => {
108
  hasInitializedRef.current = true;
109
+ if ("setJointValue" in viewer) {
110
  // Clear any existing animation
111
  if (cleanupAnimationRef.current) {
112
  cleanupAnimationRef.current();
113
  cleanupAnimationRef.current = null;
114
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
116
  };
117
 
 
128
  cleanupModelLoading();
129
  viewer.removeEventListener("urdf-processed", onModelProcessed);
130
  };
131
+ }, [isDefaultModel, customUrdfPath, urlModifierFunc]);
 
 
 
 
 
 
132
 
133
  // Separate effect to handle theme changes without recreating the viewer
134
  useEffect(() => {
 
144
  }
145
  }, [isDarkMode]);
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  return (
148
  <div
149
  className={cn(
 
155
  >
156
  <div ref={containerRef} className="w-full h-full" />
157
 
158
+ {/* Control buttons container in top right */}
159
+ <div className="absolute top-4 right-4 flex items-center space-x-2 z-10">
 
 
 
160
  {/* ModeToggle button */}
161
  <ModeToggle />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  </div>
163
 
164
  {/* Joint highlight indicator */}
viewer/src/components/layout/Layout.tsx CHANGED
@@ -20,7 +20,6 @@ const Layout: React.FC = () => {
20
  isDefaultModel,
21
  resetToDefaultModel,
22
  isSelectionModalOpen,
23
- currentAnimationConfig,
24
  } = useUrdf();
25
 
26
  // Log when robot data changes to verify the component is re-rendering
@@ -37,16 +36,13 @@ const Layout: React.FC = () => {
37
  setOpen(true);
38
  }, []);
39
 
40
- // Calculate if we have a custom animation
41
- const hasCustomAnimation = currentAnimationConfig !== null;
42
-
43
  // If there's a selection modal open, render a simplified layout
44
  if (isSelectionModalOpen) {
45
  return (
46
  <div className="absolute inset-0 overflow-hidden">
47
  {/* Main content that fills the container */}
48
  <div className="w-full h-full">
49
- <URDFViewer hasAnimation={hasCustomAnimation} />
50
  </div>
51
  </div>
52
  );
@@ -56,10 +52,7 @@ const Layout: React.FC = () => {
56
  <div className="relative w-full h-full overflow-hidden">
57
  {/* Main content that fills the container */}
58
  <div className="w-full h-full">
59
- <URDFViewer
60
- hasAnimation={hasCustomAnimation}
61
- headerHeight={HEADER_HEIGHT}
62
- />
63
  </div>
64
  {/* Sidebar positioned above with z-index, with top padding for header */}
65
  <div
@@ -123,13 +116,8 @@ const Layout: React.FC = () => {
123
  className="flex flex-col items-center justify-center"
124
  style={{ paddingTop: `${HEADER_HEIGHT}px` }}
125
  >
126
- <div className="flex items-center mt-4">
127
- <LayoutDashboard className="h-7 w-7" />
128
- </div>
129
  <div
130
- className={`flex items-center justify-center hover:bg-muted/80 cursor-pointer rounded-md p-2 m-2 ${
131
- theme === "dark" ? "bg-gray-700" : "bg-white/80"
132
- } shadow-sm`}
133
  onClick={() => setOpen(true)}
134
  >
135
  <div className="flex items-center space-x-1">
 
20
  isDefaultModel,
21
  resetToDefaultModel,
22
  isSelectionModalOpen,
 
23
  } = useUrdf();
24
 
25
  // Log when robot data changes to verify the component is re-rendering
 
36
  setOpen(true);
37
  }, []);
38
 
 
 
 
39
  // If there's a selection modal open, render a simplified layout
40
  if (isSelectionModalOpen) {
41
  return (
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
 
116
  className="flex flex-col items-center justify-center"
117
  style={{ paddingTop: `${HEADER_HEIGHT}px` }}
118
  >
 
 
 
119
  <div
120
+ className={`flex items-center justify-center hover:bg-muted/80 cursor-pointer rounded-md p-2 m-2 ${"bg-gray-700"} shadow-sm`}
 
 
121
  onClick={() => setOpen(true)}
122
  >
123
  <div className="flex items-center space-x-1">
viewer/src/contexts/UrdfContext.tsx CHANGED
@@ -110,7 +110,8 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
110
  const fetchDefaultUrdf = async () => {
111
  try {
112
  // Path to the default T12 URDF file
113
- const defaultUrdfPath = "/urdf/T12/urdf/T12.URDF";
 
114
 
115
  // Fetch the URDF content
116
  const response = await fetch(defaultUrdfPath);
@@ -165,7 +166,7 @@ export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
165
  setCurrentAnimationConfig(null);
166
 
167
  toast.info("Switched to default model", {
168
- description: "The default T12 robot model is now displayed.",
169
  });
170
  }, []);
171
 
 
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);
 
166
  setCurrentAnimationConfig(null);
167
 
168
  toast.info("Switched to default model", {
169
+ description: "The default ARM100 robot model is now displayed.",
170
  });
171
  }, []);
172
 
viewer/src/hooks/useDefaultRobotData.ts CHANGED
@@ -20,7 +20,7 @@ async function fetchRobotData(robotName: string): Promise<UrdfData> {
20
  * @param robotName The name of the robot folder (e.g., 'T12')
21
  * @returns The robot data query result
22
  */
23
- export function useDefaultRobotData(robotName: string = "T12") {
24
  return useQuery({
25
  queryKey: ["defaultRobotData", robotName],
26
  queryFn: () => fetchRobotData(robotName),
@@ -35,7 +35,7 @@ export function useDefaultRobotData(robotName: string = "T12") {
35
  * @returns A promise that resolves to the robot data or null if loading fails
36
  */
37
  export async function loadDefaultRobotData(
38
- robotName: string = "T12"
39
  ): Promise<UrdfData | null> {
40
  try {
41
  return await fetchRobotData(robotName);
 
20
  * @param robotName The name of the robot folder (e.g., 'T12')
21
  * @returns The robot data query result
22
  */
23
+ export function useDefaultRobotData(robotName: string = "SO_5DOF_ARM100_05d") {
24
  return useQuery({
25
  queryKey: ["defaultRobotData", robotName],
26
  queryFn: () => fetchRobotData(robotName),
 
35
  * @returns A promise that resolves to the robot data or null if loading fails
36
  */
37
  export async function loadDefaultRobotData(
38
+ robotName: string = "SO_5DOF_ARM100_05d"
39
  ): Promise<UrdfData | null> {
40
  try {
41
  return await fetchRobotData(robotName);
viewer/src/lib/meshLoaders.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
  Color,
6
  Object3D,
7
  Group,
8
- BufferGeometry,
9
  } from "three";
10
  import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
11
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
 
5
  Color,
6
  Object3D,
7
  Group,
 
8
  } from "three";
9
  import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
10
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
viewer/src/pages/ContentDetail.tsx DELETED
@@ -1,198 +0,0 @@
1
- import React, { useEffect, useState, useRef } from "react";
2
- import { useParams, useNavigate } from "react-router-dom";
3
- import Layout from "@/components/layout/Layout";
4
- import Header from "../components/Header";
5
- import { useUrdf } from "@/hooks/useUrdfData";
6
- import { toast } from "sonner";
7
- import { getImageUrl } from "@/api/urdfApi";
8
- import { useUrdf as useUrdfContext } from "@/hooks/useUrdf";
9
- import { downloadAndExtractZip } from "@/lib/UrdfDragAndDrop";
10
- import { supabase } from "@/integrations/supabase/client";
11
- import { Loader2 } from "lucide-react";
12
-
13
- // This is needed to make TypeScript recognize webkitdirectory as a valid attribute
14
- declare module "react" {
15
- interface InputHTMLAttributes<T> extends React.HTMLAttributes<T> {
16
- directory?: string;
17
- webkitdirectory?: string;
18
- }
19
- }
20
-
21
- const ContentDetail: React.FC = () => {
22
- const { id } = useParams<{ id: string }>();
23
- const navigate = useNavigate();
24
- const { data: content, isLoading, error } = useUrdf(id || "");
25
- const [imageUrl, setImageUrl] = useState<string>("/placeholder.svg");
26
- const { urdfProcessor, processUrdfFiles, currentRobotData } =
27
- useUrdfContext();
28
- const [isLoadingUrdf, setIsLoadingUrdf] = useState(false);
29
- // Reference to track if we've already loaded this model
30
- const hasModelBeenLoaded = useRef(false);
31
- // State to track when the model is fully loaded and ready to display
32
- const [isModelReady, setIsModelReady] = useState(false);
33
-
34
- // If error occurred during fetch, show error and redirect
35
- useEffect(() => {
36
- if (error) {
37
- toast.error("Error loading robot details");
38
- navigate("/");
39
- }
40
- }, [error, navigate]);
41
-
42
- // If content not found and not loading, redirect to home
43
- useEffect(() => {
44
- if (!content && !isLoading && !error) {
45
- toast.error("Robot not found");
46
- navigate("/");
47
- }
48
- }, [content, isLoading, error, navigate]);
49
-
50
- // Fetch the image when content is loaded
51
- useEffect(() => {
52
- if (content?.imageUrl) {
53
- const fetchImage = async () => {
54
- const url = await getImageUrl(content.imageUrl);
55
- setImageUrl(url);
56
- };
57
- fetchImage();
58
- }
59
- }, [content]);
60
-
61
- // Reset the model states when the ID changes
62
- useEffect(() => {
63
- hasModelBeenLoaded.current = false;
64
- setIsModelReady(false);
65
- }, [id]);
66
-
67
- // Check if we already have this model loaded
68
- useEffect(() => {
69
- if (content && currentRobotData?.name === content.title) {
70
- // The model is already loaded
71
- hasModelBeenLoaded.current = true;
72
- setIsModelReady(true);
73
- console.log("Model already loaded and ready to display");
74
- }
75
- }, [content, currentRobotData]);
76
-
77
- // Load the URDF model automatically when the content details are loaded
78
- useEffect(() => {
79
- const loadUrdfModel = async () => {
80
- // Only load if we have content, a processor, not currently loading,
81
- // and haven't already loaded this model
82
- if (
83
- content?.urdfPath &&
84
- urdfProcessor &&
85
- !isLoadingUrdf &&
86
- !hasModelBeenLoaded.current
87
- ) {
88
- // Mark that we've attempted to load this model
89
- hasModelBeenLoaded.current = true;
90
- setIsLoadingUrdf(true);
91
- setIsModelReady(false); // Reset model ready state while loading
92
-
93
- console.log(`Auto-loading URDF model for ${content.title}...`);
94
-
95
- const loadingToast = toast.loading(`Loading ${content.title}...`, {
96
- description: "Downloading and extracting URDF model",
97
- });
98
-
99
- try {
100
- // Get the actual URL for the zip file
101
- let zipUrl = content.urdfPath;
102
-
103
- // If it's a storage path and not a full URL, get a signed URL
104
- if (!zipUrl.startsWith("http")) {
105
- const { data, error } = await supabase.storage
106
- .from("robotbucket")
107
- .createSignedUrl(zipUrl, 3600); // 1 hour expiry
108
-
109
- if (error) {
110
- throw new Error(
111
- `Failed to get URL for zip file: ${error.message}`
112
- );
113
- }
114
-
115
- zipUrl = data.signedUrl;
116
- }
117
-
118
- // Download and extract the zip file
119
- const { files, availableModels } = await downloadAndExtractZip(
120
- zipUrl,
121
- urdfProcessor
122
- );
123
-
124
- // Dismiss loading toast
125
- toast.dismiss(loadingToast);
126
-
127
- // If successful, process the extracted files similar to drag and drop
128
- if (files && availableModels.length > 0) {
129
- await processUrdfFiles(files, availableModels);
130
- toast.success(`Loaded model: ${content.title}`, {
131
- description: "URDF model ready for viewing",
132
- });
133
-
134
- // Set a short timeout to ensure the URDF context is fully updated
135
- setTimeout(() => {
136
- setIsModelReady(true);
137
- }, 1000);
138
- } else {
139
- toast.error("No URDF models found in the zip file");
140
- }
141
- } catch (error) {
142
- // Dismiss loading toast and show error
143
- toast.dismiss(loadingToast);
144
- console.error("Error processing URDF zip:", error);
145
- toast.error("Failed to load URDF model", {
146
- description:
147
- error instanceof Error ? error.message : "Unknown error",
148
- });
149
- } finally {
150
- setIsLoadingUrdf(false);
151
- }
152
- }
153
- };
154
-
155
- loadUrdfModel();
156
- }, [content, urdfProcessor, processUrdfFiles, isLoadingUrdf]);
157
-
158
- // If still loading content data, show initial loading state
159
- if (isLoading || !content) {
160
- return (
161
- <div className="min-h-screen bg-netflix-background flex items-center justify-center">
162
- <div className="text-white text-xl">Loading robot details...</div>
163
- </div>
164
- );
165
- }
166
-
167
- // // If content is loaded but URDF model is still loading or processing, show URDF loading state
168
- // if (!isModelReady || isLoadingUrdf) {
169
- // return (
170
- // <div className="min-h-screen bg-netflix-background flex flex-col items-center justify-center">
171
- // <Loader2 className="h-12 w-12 text-white animate-spin mb-4" />
172
- // <div className="text-white text-xl">
173
- // Loading {content.title} model...
174
- // </div>
175
- // <p className="text-gray-400 mt-2">
176
- // Please wait while we prepare the 3D model
177
- // </p>
178
- // </div>
179
- // );
180
- // }
181
-
182
- // Only render the full layout once the model is ready
183
- return (
184
- <div className="flex flex-col h-screen bg-netflix-background text-white overflow-hidden">
185
- {/* Layout taking full height */}
186
- <div className="w-full h-full">
187
- <Layout />
188
- </div>
189
-
190
- {/* Header absolutely positioned at the top */}
191
- <div className="absolute top-0 left-0 right-0 z-50">
192
- <Header />
193
- </div>
194
- </div>
195
- );
196
- };
197
-
198
- export default ContentDetail;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/Explore.tsx DELETED
@@ -1,112 +0,0 @@
1
- import React from "react";
2
- import Header from "../components/Header";
3
- import Carousel from "../components/Carousel";
4
- import { ContentItem } from "../lib/types";
5
- import ChatWindow from "../components/ChatWindow";
6
- import { useUrdfs, useCategories } from "@/hooks/useUrdfData";
7
- import { Skeleton } from "@/components/ui/skeleton";
8
-
9
- const Explore: React.FC = () => {
10
- const { data: urdfs, isLoading: isLoadingUrdfs } = useUrdfs();
11
- const { data: categories, isLoading: isLoadingCategories } = useCategories();
12
-
13
- // Group content items by categories
14
- const categorizedContent = React.useMemo(() => {
15
- if (!urdfs) return {};
16
-
17
- return urdfs.reduce((acc, item) => {
18
- item.categories.forEach((category) => {
19
- if (!acc[category]) {
20
- acc[category] = [];
21
- }
22
- acc[category].push(item);
23
- });
24
- return acc;
25
- }, {} as Record<string, ContentItem[]>);
26
- }, [urdfs]);
27
-
28
- // Get unique categories
29
- const uniqueCategories = Object.keys(categorizedContent).sort();
30
-
31
- return (
32
- <div className="min-h-screen bg-netflix-background text-netflix-text">
33
- <Header />
34
-
35
- {/* Cosmic background wrapper */}
36
- <div className="cosmic-background fixed inset-0 z-0">
37
- {/* Cosmic background with stars */}
38
- <div className="absolute inset-0 bg-[#0a0a14] overflow-hidden">
39
- <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwMCIgaGVpZ2h0PSIyMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9Im4iPjxmZVR1cmJ1bGVuY2UgdHlwZT0iZnJhY3RhbE5vaXNlIiBiYXNlRnJlcXVlbmN5PSIuNyIgbnVtT2N0YXZlcz0iMTAiIHN0aXRjaFRpbGVzPSJzdGl0Y2giLz48ZmVCbGVuZCBtb2RlPSJzY3JlZW4iLz48L2ZpbHRlcj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWx0ZXI9InVybCgjbikiIG9wYWNpdHk9Ii4xIi8+PC9zdmc+')] bg-repeat opacity-30"></div>
40
- {/* Subtle animated gradient overlay */}
41
- <div className="absolute inset-0 bg-gradient-to-b from-indigo-900/20 via-transparent to-black/30"></div>
42
- {/* Random stars */}
43
- <div className="stars absolute inset-0"></div>
44
- </div>
45
- </div>
46
-
47
- <div className="pt-24 relative z-10">
48
- {/* Main content area */}
49
- <main className="container mx-auto px-4 pb-8 max-w-full">
50
- {/* Page title */}
51
- <div className="mb-6 text-center">
52
- <h1 className="text-4xl md:text-6xl font-bold tracking-tighter text-white animate-fade-in">
53
- Explore All Robots
54
- </h1>
55
- <p className="mt-4 text-xl text-gray-300 max-w-3xl mx-auto">
56
- Browse our complete collection of robotics systems and deployable
57
- URDF units
58
- </p>
59
- </div>
60
-
61
- {/* Chat window right below the title */}
62
- <section className="mb-12 max-w-4xl mx-auto">
63
- <div className="glass-panel">
64
- <ChatWindow />
65
- </div>
66
- </section>
67
-
68
- {/* Content sections */}
69
- <div className="relative z-10">
70
- {isLoadingUrdfs || isLoadingCategories ? (
71
- // Loading skeleton
72
- <div className="space-y-20">
73
- {[1, 2, 3].map((i) => (
74
- <section key={i} className="category-section min-h-[20vh]">
75
- <Skeleton className="h-10 w-48 mb-8" />
76
- <div className="glass-panel p-6 rounded-xl">
77
- <div className="flex gap-4">
78
- {[1, 2, 3, 4].map((j) => (
79
- <Skeleton key={j} className="h-64 w-full" />
80
- ))}
81
- </div>
82
- </div>
83
- </section>
84
- ))}
85
- </div>
86
- ) : (
87
- <div className="space-y-20">
88
- {uniqueCategories.map((category) => (
89
- <section
90
- key={category}
91
- className="category-section min-h-[20vh]"
92
- >
93
- <h2 className="text-3xl font-bold mb-8 text-netflix-text text-glow capitalize">
94
- {category}
95
- </h2>
96
- <Carousel
97
- title={category}
98
- items={categorizedContent[category]}
99
- className="glass-panel p-6 rounded-xl"
100
- />
101
- </section>
102
- ))}
103
- </div>
104
- )}
105
- </div>
106
- </main>
107
- </div>
108
- </div>
109
- );
110
- };
111
-
112
- export default Explore;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/Index.tsx DELETED
@@ -1,129 +0,0 @@
1
-
2
- import React, { useEffect, useRef, useState } from "react";
3
- import ContentCarousel from "../components/ContentCarousel";
4
- import Header from "../components/Header";
5
- import ChatWindow from "../components/ChatWindow";
6
- import TeamSection from "../components/TeamSection";
7
- import SplineViewer from "../components/SplineViewer";
8
- import { useCategories } from "@/hooks/useUrdfData";
9
- import { Skeleton } from "@/components/ui/skeleton";
10
-
11
- const IndexCopy: React.FC = () => {
12
- // const parallaxRef = useRef<HTMLDivElement>(null);
13
- const sectionRefs = useRef<(HTMLElement | null)[]>([]);
14
- const ticking = useRef<boolean>(false);
15
- const [scrollY, setScrollY] = useState<number>(0);
16
- const { data: categories, isLoading } = useCategories();
17
-
18
- // Add parallax effect on scroll using requestAnimationFrame for better performance
19
- useEffect(() => {
20
- const handleScroll = () => {
21
- if (!ticking.current) {
22
- window.requestAnimationFrame(() => {
23
- setScrollY(window.scrollY);
24
- ticking.current = false;
25
- });
26
- ticking.current = true;
27
- }
28
- };
29
-
30
- // Initialize section references
31
- const categoryElements =
32
- document.querySelectorAll<HTMLElement>(".category-section");
33
- sectionRefs.current = Array.from(categoryElements);
34
- window.addEventListener("scroll", handleScroll);
35
- return () => window.removeEventListener("scroll", handleScroll);
36
- }, []);
37
-
38
- // Only get the trending category
39
- const trendingCategory = categories?.find(
40
- (category) => category.id === "trending"
41
- );
42
-
43
- return (
44
- <div className="min-h-screen bg-netflix-background text-netflix-text">
45
- <Header />
46
-
47
- {/* Cosmic background wrapper for the entire page */}
48
- <div className="cosmic-background fixed inset-0 z-0">
49
- {/* Cosmic background with stars */}
50
- <div className="absolute inset-0 bg-[#0a0a14] overflow-hidden">
51
- <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwMCIgaGVpZ2h0PSIyMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9Im4iPjxmZVR1cmJ1bGVuY2UgdHlwZT0iZnJhY3RhbE5vaXNlIiBiYXNlRnJlcXVlbmN5PSIuNyIgbnVtT2N0YXZlcz0iMTAiIHN0aXRjaFRpbGVzPSJzdGl0Y2giLz48ZmVCbGVuZCBtb2RlPSJzY3JlZW4iLz48L2ZpbHRlcj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWx0ZXI9InVybCgjbikiIG9wYWNpdHk9Ii4xIi8+PC9zdmc+')] bg-repeat opacity-30"></div>
52
-
53
- {/* Subtle animated gradient overlay */}
54
- <div className="absolute inset-0 bg-gradient-to-b from-indigo-900/20 via-transparent to-black/30"></div>
55
-
56
- {/* Random stars */}
57
- <div className="stars absolute inset-0"></div>
58
- </div>
59
- </div>
60
-
61
- <main className="container mx-auto px-4 pb-8 max-w-full relative z-10">
62
- {/* Cosmic hero section with parallax text */}
63
- <section className="relative overflow-visible">
64
- <div className="w-full relative mb-8 flex items-start justify-center">
65
- {/* Parallax text container */}
66
- <div className="relative z-10 text-left px- md:px-0 max-w-6xl mx-auto mt-24 pt-16">
67
- <h1 className="text-7xl md:text-[13rem] font-bold tracking-tighter whitespace-nowrap text-white animate-fade-in text-glow leading-none font-sans">
68
- <span className="block -mb-12 text-gradient-white">PROMPT</span>
69
- <span className="block -mb-12 text-gradient-white">
70
- SPATIAL
71
- </span>
72
- <span className="block text-gradient-white pb-4">AGENTS</span>
73
- </h1>
74
-
75
- {/* Spline 3D Asset with increased height and no overflow cutting */}
76
- <div className="w-full h-[400px] relative -mt-20 z-0 overflow-visible">
77
- <SplineViewer
78
- splineUrl="https://prod.spline.design/Ze6evzKLyY-Xq6uh/scene.splinecode"
79
- className="h-full overflow-visible"
80
- />
81
- </div>
82
- </div>
83
- </div>
84
- </section>
85
-
86
- {/* Chat Window Section - Moved right below the title */}
87
- <section className="mb-20 max-w-4xl mx-auto">
88
- <div className="glass-panel">
89
- <ChatWindow />
90
- </div>
91
- </section>
92
-
93
- {/* Content sections with glass panels */}
94
- <div className="relative z-10">
95
- {/* Team Section */}
96
- <TeamSection />
97
-
98
- {/* Only display trending category */}
99
- {isLoading ? (
100
- <section className="category-section min-h-[20vh] mt-12">
101
- <Skeleton className="h-10 w-48 mb-8" />
102
- <Skeleton className="h-64 w-full" />
103
- </section>
104
- ) : trendingCategory ? (
105
- <section className="category-section min-h-[20vh] mt-12">
106
- <h2 className="text-3xl font-bold mb-8 text-netflix-text text-glow">
107
- {trendingCategory.name}
108
- </h2>
109
- <ContentCarousel
110
- key={trendingCategory.id}
111
- category={trendingCategory}
112
- />
113
- <div className="mt-6 text-center">
114
- <a
115
- href="/explore"
116
- className="inline-block bg-sidebar-accent px-6 py-3 rounded-lg text-white font-semibold hover:bg-opacity-80 transition-colors"
117
- >
118
- Explore All Robots
119
- </a>
120
- </div>
121
- </section>
122
- ) : null}
123
- </div>
124
- </main>
125
- </div>
126
- );
127
- };
128
-
129
- export default IndexCopy;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/Movies.tsx DELETED
@@ -1,17 +0,0 @@
1
-
2
- import React from 'react';
3
- import Header from '../components/Header';
4
-
5
- const Movies: React.FC = () => {
6
- return (
7
- <div className="min-h-screen bg-netflix-background text-white">
8
- <Header />
9
- <main className="container mx-auto px-4 py-16">
10
- <h1 className="text-4xl font-bold mb-8">Movies</h1>
11
- <p className="text-xl">Browse all movies</p>
12
- </main>
13
- </div>
14
- );
15
- };
16
-
17
- export default Movies;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/MyList.tsx DELETED
@@ -1,63 +0,0 @@
1
-
2
- import React from 'react';
3
- import Header from '../components/Header';
4
- import { useMyList } from '../hooks/use-my-list';
5
- import { useNavigate } from 'react-router-dom';
6
- import { Star } from 'lucide-react';
7
-
8
- const MyList: React.FC = () => {
9
- const { myList, removeFromMyList } = useMyList();
10
- const navigate = useNavigate();
11
-
12
- const handleItemClick = (id: string) => {
13
- navigate(`/content/${id}`);
14
- };
15
-
16
- return (
17
- <div className="min-h-screen bg-netflix-background text-white">
18
- <Header />
19
- <main className="container mx-auto px-4 py-16">
20
- <h1 className="text-4xl font-bold mb-8">Starred</h1>
21
-
22
- {myList.length === 0 ? (
23
- <div className="text-center py-12">
24
- <p className="text-xl mb-4">Your list is empty</p>
25
- <p className="text-gray-400">Add items by clicking the star icon on content you like</p>
26
- </div>
27
- ) : (
28
- <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
29
- {myList.map((item) => (
30
- <div
31
- key={item.id}
32
- className="relative rounded-md overflow-hidden cursor-pointer group"
33
- onClick={() => handleItemClick(item.id)}
34
- >
35
- <img
36
- src={item.imageUrl}
37
- alt={item.title}
38
- className="w-full aspect-square object-cover transition-transform duration-300 group-hover:scale-105"
39
- />
40
- <div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end">
41
- <h3 className="text-white text-xl p-4 font-medium">{item.title}</h3>
42
- </div>
43
-
44
- {/* Remove from list button */}
45
- <button
46
- className="absolute top-2 right-2 p-2 rounded-full bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-10"
47
- onClick={(e) => {
48
- e.stopPropagation();
49
- removeFromMyList(item.id);
50
- }}
51
- >
52
- <Star size={20} className="fill-yellow-400 text-yellow-400" />
53
- </button>
54
- </div>
55
- ))}
56
- </div>
57
- )}
58
- </main>
59
- </div>
60
- );
61
- };
62
-
63
- export default MyList;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/NewAndPopular.tsx DELETED
@@ -1,31 +0,0 @@
1
-
2
- import React from "react";
3
- import Header from "../components/Header";
4
- import { Skeleton } from "@/components/ui/skeleton";
5
-
6
- const NewAndPopular: React.FC = () => {
7
- return (
8
- <div className="min-h-screen bg-netflix-background">
9
- <Header />
10
- <div className="container mx-auto pt-24 px-4">
11
- <h1 className="text-4xl font-bold text-white mb-8">New & Popular</h1>
12
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
13
- {/* Placeholder content */}
14
- {Array(6)
15
- .fill(null)
16
- .map((_, index) => (
17
- <div key={index} className="bg-netflix-card rounded-md overflow-hidden">
18
- <Skeleton className="h-48 w-full" />
19
- <div className="p-4">
20
- <Skeleton className="h-6 w-3/4 mb-2" />
21
- <Skeleton className="h-4 w-full" />
22
- </div>
23
- </div>
24
- ))}
25
- </div>
26
- </div>
27
- </div>
28
- );
29
- };
30
-
31
- export default NewAndPopular;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/NotFound.tsx DELETED
@@ -1,27 +0,0 @@
1
- import { useLocation } from "react-router-dom";
2
- import { useEffect } from "react";
3
-
4
- const NotFound = () => {
5
- const location = useLocation();
6
-
7
- useEffect(() => {
8
- console.error(
9
- "404 Error: User attempted to access non-existent route:",
10
- location.pathname
11
- );
12
- }, [location.pathname]);
13
-
14
- return (
15
- <div className="min-h-screen flex items-center justify-center bg-gray-100">
16
- <div className="text-center">
17
- <h1 className="text-4xl font-bold mb-4">404</h1>
18
- <p className="text-xl text-gray-600 mb-4">Oops! Page not found</p>
19
- <a href="/" className="text-blue-500 hover:text-blue-700 underline">
20
- Return to Home
21
- </a>
22
- </div>
23
- </div>
24
- );
25
- };
26
-
27
- export default NotFound;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
viewer/src/pages/UrdfView.tsx CHANGED
@@ -1,8 +1,8 @@
1
  import React, { useEffect } from "react";
2
  import Layout from "@/components/layout/Layout";
3
- import Header from "@/components/Header";
4
  import { UrdfSelectionModalContainer } from "@/components/UrdfSelectionModalContainer";
5
  import { useUrdf } from "@/hooks/useUrdf";
 
6
  // This is needed to make TypeScript recognize webkitdirectory as a valid attribute
7
  declare module "react" {
8
  interface InputHTMLAttributes<T> extends React.HTMLAttributes<T> {
 
1
  import React, { useEffect } from "react";
2
  import Layout from "@/components/layout/Layout";
 
3
  import { UrdfSelectionModalContainer } from "@/components/UrdfSelectionModalContainer";
4
  import { useUrdf } from "@/hooks/useUrdf";
5
+
6
  // This is needed to make TypeScript recognize webkitdirectory as a valid attribute
7
  declare module "react" {
8
  interface InputHTMLAttributes<T> extends React.HTMLAttributes<T> {