-
Notifications
You must be signed in to change notification settings - Fork 79
Add tf2 electron demo #1229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add tf2 electron demo #1229
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a comprehensive TF2 electron demo that showcases coordinate frame transformations, turtle simulation, and real-time 3D visualization using rclnodejs, Electron, and Three.js. The demo replicates the functionality of the ROS2 turtle_tf2_py package in a modern web-based interface.
Key changes:
- Adds a complete electron demonstration with TF2 coordinate frame visualization
- Provides interactive turtle control with keyboard input (WASD keys)
- Implements multiple TF2 broadcasters (static, dynamic, fixed frames) with real-time updates
- Updates main README to highlight the new electron visualization capabilities
Reviewed Changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| electron_demo/turtle_tf2/renderer.js | Main Three.js renderer handling 3D visualization, turtle management, and keyboard controls |
| electron_demo/turtle_tf2/package.json | Package configuration with dependencies for electron, rclnodejs, and three.js |
| electron_demo/turtle_tf2/orbit-controls.js | Three.js OrbitControls implementation for 3D camera navigation |
| electron_demo/turtle_tf2/main.js | Electron main process with ROS2 nodes for TF2 broadcasting and turtle control |
| electron_demo/turtle_tf2/index.html | Web interface with control panels, status displays, and 3D viewport |
| electron_demo/turtle_tf2/README.md | Comprehensive documentation for the turtle TF2 demo |
| README.md | Updated main documentation to feature electron visualization capabilities |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| function updateFrame(name, transform) { | ||
| const position = transform.transform.translation; | ||
| const rotation = transform.transform.rotation; | ||
|
|
||
| if (!frames[name]) { | ||
| // Determine color based on frame name | ||
| let color = 0xffffff; | ||
| let scale = 0.5; // Default scale | ||
|
|
||
| if (name.includes('static')) { | ||
| color = 0xff4444; | ||
| scale = 1.0; // Bigger for static frame | ||
| } else if (name.includes('dynamic')) { | ||
| color = 0xff6600; // Bright orange color | ||
| scale = 3.0; // MUCH bigger for dynamic frame - very easy to see! | ||
| console.log('🟠 Creating DYNAMIC FRAME at:', position); | ||
| } else if (name.includes('fixed')) { |
Copilot
AI
Aug 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple calls to name.includes() can be inefficient when checking frame names repeatedly. Consider using a more efficient approach like a Map or Set for frame type lookup, or caching the frame type determination.
| function updateFrame(name, transform) { | |
| const position = transform.transform.translation; | |
| const rotation = transform.transform.rotation; | |
| if (!frames[name]) { | |
| // Determine color based on frame name | |
| let color = 0xffffff; | |
| let scale = 0.5; // Default scale | |
| if (name.includes('static')) { | |
| color = 0xff4444; | |
| scale = 1.0; // Bigger for static frame | |
| } else if (name.includes('dynamic')) { | |
| color = 0xff6600; // Bright orange color | |
| scale = 3.0; // MUCH bigger for dynamic frame - very easy to see! | |
| console.log('🟠 Creating DYNAMIC FRAME at:', position); | |
| } else if (name.includes('fixed')) { | |
| // Helper function to determine frame type | |
| function getFrameType(name) { | |
| if (name.includes('dynamic')) { | |
| return 'dynamic'; | |
| } else if (name.includes('static')) { | |
| return 'static'; | |
| } else if (name.includes('fixed')) { | |
| return 'fixed'; | |
| } | |
| return 'default'; | |
| } | |
| function updateFrame(name, transform) { | |
| const position = transform.transform.translation; | |
| const rotation = transform.transform.rotation; | |
| const frameType = getFrameType(name); | |
| if (!frames[name]) { | |
| // Determine color and scale based on frame type | |
| let color = 0xffffff; | |
| let scale = 0.5; // Default scale | |
| if (frameType === 'static') { | |
| color = 0xff4444; | |
| scale = 1.0; // Bigger for static frame | |
| } else if (frameType === 'dynamic') { | |
| color = 0xff6600; // Bright orange color | |
| scale = 3.0; // MUCH bigger for dynamic frame - very easy to see! | |
| console.log('🟠 Creating DYNAMIC FRAME at:', position); | |
| } else if (frameType === 'fixed') { |
| const timer = node.createTimer(1000, () => { | ||
| // Wrap the async logic in a try-catch to handle promise rejections | ||
| (async () => { | ||
| try { | ||
| // Don't automatically spawn turtle2 - let user control this via UI | ||
| // This prevents service call errors if turtlesim is not running | ||
|
|
||
| // Simple following logic (in real implementation, this would use TF lookup) | ||
| // For demo purposes, we'll simulate the transform lookup behavior | ||
| if (turtleTf2Nodes.turtle2Spawned) { | ||
| // This is a simplified version - in real TF2, we'd lookup transforms | ||
| // For the demo, we'll let the renderer handle the following logic | ||
| if (mainWindow) { | ||
| mainWindow.webContents.send('request-turtle-follow'); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.error('Timer callback error:', error); | ||
| } | ||
| })(); |
Copilot
AI
Aug 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using an immediately invoked async function expression inside a timer callback can make error handling and debugging more difficult. Consider defining the async function separately and calling it, or restructuring to avoid the nested async pattern.
| const timer = node.createTimer(1000, () => { | |
| // Wrap the async logic in a try-catch to handle promise rejections | |
| (async () => { | |
| try { | |
| // Don't automatically spawn turtle2 - let user control this via UI | |
| // This prevents service call errors if turtlesim is not running | |
| // Simple following logic (in real implementation, this would use TF lookup) | |
| // For demo purposes, we'll simulate the transform lookup behavior | |
| if (turtleTf2Nodes.turtle2Spawned) { | |
| // This is a simplified version - in real TF2, we'd lookup transforms | |
| // For the demo, we'll let the renderer handle the following logic | |
| if (mainWindow) { | |
| mainWindow.webContents.send('request-turtle-follow'); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Timer callback error:', error); | |
| } | |
| })(); | |
| async function handleTurtleTf2Timer() { | |
| try { | |
| // Don't automatically spawn turtle2 - let user control this via UI | |
| // This prevents service call errors if turtlesim is not running | |
| // Simple following logic (in real implementation, this would use TF lookup) | |
| // For demo purposes, we'll simulate the transform lookup behavior | |
| if (turtleTf2Nodes.turtle2Spawned) { | |
| // This is a simplified version - in real TF2, we'd lookup transforms | |
| // For the demo, we'll let the renderer handle the following logic | |
| if (mainWindow) { | |
| mainWindow.webContents.send('request-turtle-follow'); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Timer callback error:', error); | |
| } | |
| } | |
| const timer = node.createTimer(1000, () => { | |
| handleTurtleTf2Timer(); |
| if (key in keyState) { | ||
| keyState[key] = true; | ||
| event.preventDefault(); | ||
| } | ||
| }); | ||
|
|
||
| document.addEventListener('keyup', (event) => { | ||
| // Only handle WASD keys (case-insensitive) | ||
| const key = event.key.toLowerCase(); | ||
| if (key in keyState) { | ||
| keyState[key] = false; | ||
| event.preventDefault(); | ||
| } | ||
| }); | ||
|
|
||
| // Send movement commands based on key state | ||
| setInterval(() => { | ||
| if (demoState.rosConnected) { | ||
| sendTurtleCommand(); | ||
| } | ||
| }, 100); // Send commands at 10Hz |
Copilot
AI
Aug 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using setInterval at 100ms (10Hz) for continuous command sending can be inefficient. Consider implementing a more responsive approach that only sends commands when keys are actually pressed/released, or use requestAnimationFrame for smoother updates.
| if (key in keyState) { | |
| keyState[key] = true; | |
| event.preventDefault(); | |
| } | |
| }); | |
| document.addEventListener('keyup', (event) => { | |
| // Only handle WASD keys (case-insensitive) | |
| const key = event.key.toLowerCase(); | |
| if (key in keyState) { | |
| keyState[key] = false; | |
| event.preventDefault(); | |
| } | |
| }); | |
| // Send movement commands based on key state | |
| setInterval(() => { | |
| if (demoState.rosConnected) { | |
| sendTurtleCommand(); | |
| } | |
| }, 100); // Send commands at 10Hz | |
| if (key in keyState && !keyState[key]) { | |
| keyState[key] = true; | |
| event.preventDefault(); | |
| if (demoState.rosConnected) { | |
| sendTurtleCommand(); | |
| } | |
| } | |
| }); | |
| document.addEventListener('keyup', (event) => { | |
| // Only handle WASD keys (case-insensitive) | |
| const key = event.key.toLowerCase(); | |
| if (key in keyState && keyState[key]) { | |
| keyState[key] = false; | |
| event.preventDefault(); | |
| if (demoState.rosConnected) { | |
| // If no movement keys are pressed, send stop command | |
| if (!keyState.w && !keyState.a && !keyState.s && !keyState.d) { | |
| sendTurtleStopCommand(); | |
| } else { | |
| sendTurtleCommand(); | |
| } | |
| } | |
| } | |
| }); |
This PR adds a comprehensive TF2 electron demo that showcases coordinate frame transformations, turtle simulation, and real-time 3D visualization using rclnodejs, Electron, and Three.js. The demo replicates the functionality of the ROS2 `turtle_tf2_py` package in a modern web-based interface. Key changes: - Adds a complete electron demonstration with TF2 coordinate frame visualization - Provides interactive turtle control with keyboard input (WASD keys) - Implements multiple TF2 broadcasters (static, dynamic, fixed frames) with real-time updates - Updates main README to highlight the new electron visualization capabilities Fix: #1230, #1231
This PR adds a comprehensive TF2 electron demo that showcases coordinate frame transformations, turtle simulation, and real-time 3D visualization using rclnodejs, Electron, and Three.js. The demo replicates the functionality of the ROS2
turtle_tf2_pypackage in a modern web-based interface.Key changes:
Fix: #1230, #1231