Spaces:
Sleeping
Sleeping
| # G-code 3D Preview | |
| ## Overview | |
| G-code preview renders CNC toolpaths as 3D lines/tubes in a WebGL viewport. The toolpath visualization shows rapid moves, cutting moves, and arcs with distinct colors, allowing the operator to verify the machining strategy before running on a real machine. | |
| ## Libraries Comparison | |
| ### gcode-preview (npm) | |
| Full-featured Three.js-based G-code renderer: | |
| - `npm install gcode-preview` | |
| - MIT license, active development (v2.18+) | |
| - Supports G0/G1/G2/G3, tool changes (T0-T7) | |
| - Tube rendering via BatchedMesh (Three.js r159+) | |
| - Layer controls (startLayer/endLayer), animation | |
| - Progressive loading for large files | |
| ```javascript | |
| const preview = new GCodePreview.WebGLPreview({ | |
| canvas: document.querySelector('canvas'), | |
| extrusionColor: ['#0088FF', '#00CC66', '#FF4444'], | |
| travelColor: '#FF000040', | |
| renderTubes: true, | |
| buildVolume: { x: 500, y: 500, z: 200 }, | |
| }); | |
| preview.processGCode(gcodeString); | |
| ``` | |
| **Limitation**: Designed for 3D printing. No spindle/coolant M-code handling, no per-speed coloring built in. | |
| ### Three.js GCodeLoader (built-in) | |
| Minimal loader included with Three.js: | |
| - Located at `three/examples/jsm/loaders/GCodeLoader.js` | |
| - Supports G0/G1 only (no G2/G3 arcs) | |
| - Returns `THREE.Group` with green (cutting) and red (rapid) `LineSegments` | |
| - Colors hardcoded, no tool changes, no animation | |
| - Simple to integrate into existing Three.js scene | |
| ### gcode-toolpath (npm, by cncjs) | |
| Parser library, not a renderer: | |
| - Parses full G-code grammar including G2/G3 arcs | |
| - Tracks modal state (motion, WCS, plane, units, feed, spindle, tool) | |
| - Callbacks: `addLine(modal, v1, v2)`, `addArcCurve(modal, v1, v2, v0)` | |
| - Pair with custom Three.js rendering for CNC-specific visualization | |
| ## Custom Implementation (NeuralCAD Approach) | |
| For CNC-specific needs, a custom parser + Three.js renderer provides the most control: | |
| ### G-code Parser | |
| ```javascript | |
| function parseGCode(gcodeString) { | |
| const segments = []; | |
| let pos = { x: 0, y: 0, z: 0 }; | |
| let mode = 'G0'; | |
| let feed = 0; | |
| let tool = 0; | |
| for (const line of gcodeString.split('\n')) { | |
| const cleaned = line.split(';')[0].split('(')[0].trim(); | |
| if (!cleaned) continue; | |
| // Extract commands | |
| const g = cleaned.match(/G(\d+)/)?.[1]; | |
| const x = cleaned.match(/X([-\d.]+)/)?.[1]; | |
| const y = cleaned.match(/Y([-\d.]+)/)?.[1]; | |
| const z = cleaned.match(/Z([-\d.]+)/)?.[1]; | |
| const f = cleaned.match(/F([-\d.]+)/)?.[1]; | |
| const t = cleaned.match(/T(\d+)/)?.[1]; | |
| const i = cleaned.match(/I([-\d.]+)/)?.[1]; | |
| const j = cleaned.match(/J([-\d.]+)/)?.[1]; | |
| if (g) mode = 'G' + g; | |
| if (f) feed = parseFloat(f); | |
| if (t) tool = parseInt(t); | |
| const newPos = { | |
| x: x ? parseFloat(x) : pos.x, | |
| y: y ? parseFloat(y) : pos.y, | |
| z: z ? parseFloat(z) : pos.z, | |
| }; | |
| if (mode === 'G0' || mode === 'G1') { | |
| if (x || y || z) { | |
| segments.push({ | |
| type: mode, start: {...pos}, end: {...newPos}, | |
| feed, tool | |
| }); | |
| } | |
| } else if (mode === 'G2' || mode === 'G3') { | |
| segments.push({ | |
| type: mode, start: {...pos}, end: {...newPos}, | |
| center: { x: pos.x + (i ? parseFloat(i) : 0), | |
| y: pos.y + (j ? parseFloat(j) : 0) }, | |
| feed, tool | |
| }); | |
| } | |
| pos = newPos; | |
| } | |
| return segments; | |
| } | |
| ``` | |
| ### Three.js Renderer | |
| ```javascript | |
| function renderToolpath(segments, scene) { | |
| const rapids = [], cuts = []; | |
| for (const seg of segments) { | |
| const arr = seg.type === 'G0' ? rapids : cuts; | |
| if (seg.type === 'G2' || seg.type === 'G3') { | |
| // Tessellate arc to line segments | |
| const points = tessellateArc(seg); | |
| for (let i = 0; i < points.length - 1; i++) { | |
| cuts.push(points[i].x, points[i].y, points[i].z, | |
| points[i+1].x, points[i+1].y, points[i+1].z); | |
| } | |
| } else { | |
| arr.push(seg.start.x, seg.start.y, seg.start.z, | |
| seg.end.x, seg.end.y, seg.end.z); | |
| } | |
| } | |
| // Rapid moves -- red, semi-transparent | |
| const rapidGeo = new THREE.BufferGeometry(); | |
| rapidGeo.setAttribute('position', | |
| new THREE.Float32BufferAttribute(rapids, 3)); | |
| scene.add(new THREE.LineSegments(rapidGeo, | |
| new THREE.LineBasicMaterial({ color: 0xFF0000, opacity: 0.3, | |
| transparent: true }))); | |
| // Cutting moves -- blue | |
| const cutGeo = new THREE.BufferGeometry(); | |
| cutGeo.setAttribute('position', | |
| new THREE.Float32BufferAttribute(cuts, 3)); | |
| scene.add(new THREE.LineSegments(cutGeo, | |
| new THREE.LineBasicMaterial({ color: 0x0088FF }))); | |
| } | |
| ``` | |
| ### View Toggle | |
| ```javascript | |
| const stlGroup = new THREE.Group(); | |
| const gcodeGroup = new THREE.Group(); | |
| scene.add(stlGroup); | |
| scene.add(gcodeGroup); | |
| function showPartView() { stlGroup.visible = true; gcodeGroup.visible = false; } | |
| function showToolpathView() { stlGroup.visible = false; gcodeGroup.visible = true; } | |
| function showOverlay() { | |
| stlGroup.visible = true; | |
| gcodeGroup.visible = true; | |
| stlGroup.traverse(child => { | |
| if (child.material) { | |
| child.material.transparent = true; | |
| child.material.opacity = 0.3; | |
| } | |
| }); | |
| } | |
| ``` | |
| ## Material Removal Simulation | |
| ### Heightmap/Dexel (Best for 3-axis) | |
| Workpiece represented as a 2D grid of height values. Tool carving lowers heights along path. O(n^2) memory, GPU-friendly, cannot represent undercuts. | |
| ### Voxel Grid (Multi-axis) | |
| 3D occupancy grid. Tool clears voxels along path. O(n^3) memory, handles undercuts. Marching cubes for mesh extraction. | |
| ### CSG (three-bvh-csg) | |
| Boolean subtraction of tool shape from stock mesh. 100x faster than BSP-based alternatives. Expensive per operation -- best for final result view, not animation. | |
| For NeuralCAD's 3-axis scope, the heightmap approach is optimal if material removal simulation is needed in the future. | |