# 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.