neuralcad / docs /wiki /gcode-preview.md
CallMeDaniel's picture
docs: add CNC/CAM knowledge wiki and slicer/preview design spec
265686c
# 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.