Folks, I don’t know if anyone has written this down before; if they have, I can’t find it, or what I did find was way complicated.
I did use AI to help me write the JavaScript. I had two developer friends review the code, and they said it was safe and good enough for our level of work. I am not a developer, I am a Network Architect and aspiring wood butcher. I hope this post helps someone. The code has made my workflow so much easier and the machine more enjoyable to use.
I am speaking and working from what I have learned using my machine for the last 3 years. I am by no means an expert or know everything about the device, but I do know I am making better workpieces each time I run the machine.
The Problem
Carbide Create’s built-in GRBL post processor outputs M0 ;T102 for tool changes. This is just a pause - it doesn’t trigger Onefinity’s tool-change macro.
The Onefinity controller only runs the code in the tool-change field when it sees an M6 command.
The Solution
A custom post-processor that outputs M6 T102 instead of M0 ;T102, plus a simple tool-change macro.
What Changes in the G-Code
Before (GRBL post-processor):
M05
M0 ;T102
M03S18000
After (Onefinity post-processor):
M05
(Move to safe Z to avoid workholding)
G53G0Z-5.000
(TOOL/MILL,3.17, 0.00, 0.00, 0.00)
M6T102
M03S18000
The key differences:
-
M6T102replacesM0 ;T102- This triggers the tool-change macro -
Adds a safe Z move before tool change using machine coordinates
-
Adds tool dimension comments for visualization software
Requirements
-
Carbide Create Pro (subscription or perpetual license)
-
Onefinity with Buildbotics controller (firmware 1.4.0+)
Installing the Post Processor
-
Download
onefinity.post(link to your file) -
Open Carbide Create Pro
-
Go to About → Open Data Directory
-
Open the Posts folder
-
Copy
onefinity.postinto the Posts folder -
Restart Carbide Create Pro
-
Go to Edit → Select Post Processor
-
Select “Onefinity (M6 Tool Change)”
Tool Change Macro
On your Onefinity controller:
-
Tap the hamburger menu (☰) → Settings
-
Scroll to the GCODE section
-
Find the tool-change field
-
Paste this code:
(Runs on M6, tool change)
M5
G4 P2
G53 G0 Z-1
M0 (MSG, Change Tool - Press Continue when ready)
- Tap Save
What this does:
-
M5- Stops the spindle -
G4 P2- Waits 2 seconds for the spindle to stop -
G53 G0 Z-1- Moves Z to 1mm from the machine top (maximum clearance) -
M0- Pauses and displays a message to change the tool
After you press Continue, the G-code takes over - it starts the spindle and moves to the first cut position.
The Post-Processor Code
javascript
description = "Onefinity (M6 Tool Change)"
notes = "Onefinity Buildbotics controller with M6 tool changes for proper tool change macro support"
vendor = "Onefinity"
vendorUrl = "https://www.onefinitycnc.com"
outputMetric = true
extension = "nc"
function createFormatter(name, numDigits, useUnits) {
var lastOutput = ""
return {
format: function(val) {
if(val == undefined) {
this.reset();
return "";
}
if(!outputMetric && useUnits) {
val = val / 25.4
}
var fmt = val.toFixed(numDigits);
var ret = ""
if(fmt != lastOutput) {
ret += name;
ret += fmt;
lastOutput = fmt;
}
return ret;
},
reset: function() {
lastOutput = "--"
}
}
}
function formatUnits(val) {
var un = "mm"
var digits = 2;
if(!outputMetric) {
val /= 25.4;
un = "in"
digits += 1;
}
return "" + val.toFixed(digits) + un
}
function formatDimension(val) {
var digits = 2;
if(!outputMetric) {
val /= 25.4;
digits += 1;
}
return "" + val.toFixed(digits)
}
function outputSafeZ() {
if(state.wasHomeZ) {
return
}
var distFromTop = -5
if(!outputMetric) {
distFromTop /= 25.4
}
onComment("Move to safe Z to avoid workholding")
writeLn("G53G0Z"+distFromTop.toFixed(3))
state.motionMode.reset()
state.zval.reset()
state.wasHomeZ = true
}
function writeBlock(){
var ln = ""
for (var i = 0; i < arguments.length; i++) {
ln += arguments[i];
}
writeLn(ln);
}
var state = {}
function onOpen() {
var coordDigits = outputMetric?3:4
state.xval = createFormatter("X", coordDigits, true)
state.yval = createFormatter("Y", coordDigits, true)
state.zval = createFormatter("Z", coordDigits, true)
state.fval = createFormatter("F", 1, true)
state.sval = createFormatter("S", 0)
state.tval = createFormatter("T", 0)
state.motionMode = createFormatter("G", 0)
state.rpm = 0
state.wasHomeZ = false
state.lastTool = -1
state.spindle = false
onComment("Design File: " + sourceFilename);
onComment("stockMin:"+formatUnits(stockMinX) + ", " + formatUnits(stockMinY) + ", " + formatUnits(stockMinZ))
onComment("stockMax:"+formatUnits(stockMaxX) + ", " + formatUnits(stockMaxY) + ", " + formatUnits(stockMaxZ))
var length = stockMaxX - stockMinX;
var width = stockMaxY - stockMinY;
var height = stockMaxZ - stockMinZ;
onComment("STOCK/BLOCK,"+formatDimension(length) + ", " + formatDimension(width) + ", " + formatDimension(height) + "," + formatDimension(-stockMinX) + ", " + formatDimension(-stockMinY) + ", " + formatDimension(-stockMinZ))
writeLn("G90")
if(outputMetric) {
writeLn("G21")
} else {
writeLn("G20")
}
outputSafeZ();
}
function onClose() {
if(state.spindle) {
writeLn("M05");
}
writeLn("M02");
}
function onTerminate() {
}
function onComment(text) {
writeLn("(" + text + ")");
}
function onLinear(x, y, z, feed) {
if(state.wasHomeZ) {
writeBlock(state.motionMode.format(1), state.xval.format(x), state.yval.format(y), state.fval.format(feed));
state.wasHomeZ = false
}
var xv = state.xval.format(x);
var yv = state.yval.format(y);
var zv = state.zval.format(z);
if(xv != "" || yv != "" || zv != "") {
writeBlock(state.motionMode.format(1), xv, yv, zv, state.fval.format(feed));
}
}
function onRapid(x, y, z) {
if(state.wasHomeZ) {
writeBlock(state.motionMode.format(0), state.xval.format(x), state.yval.format(y));
state.wasHomeZ = false
}
writeBlock(state.motionMode.format(0), state.xval.format(x), state.yval.format(y), state.zval.format(z));
}
function onSpindleSpeed(s) {
if(s <= 0) {
state.spindle = false
writeBlock("M05")
} else {
state.spindle = true
state.sval.reset()
writeBlock("M03", state.sval.format(s))
}
state.rpm = s
}
function onSection() {
var tnum = toolNumber
var force = forceToolChange
if(tnum != state.lastTool || force == true) {
state.lastTool = tnum
onSpindleSpeed(0)
outputSafeZ()
onComment("TOOL/MILL,"+formatDimension(toolDiameter) + ", "+ formatDimension(toolCornerRadius) + ", " + formatDimension(toolFluteLength) + ", " + Number(toolAngle).toFixed(2))
state.tval.reset()
writeBlock("M6", state.tval.format(tnum));
state.xval.reset()
state.yval.reset()
state.zval.reset()
state.fval.reset()
state.wasHomeZ = true
}
}
Notes
-
I use collared bits, so all tools are the same length - no re-probing needed after tool change
-
Output is always metric (G21)
-
Tested with 1/8" and 1/4" endmills, 10° to 90° vee bits, and engraving