Your cart is currently empty!
Improved Duet3d Fusion 360 post processor for Ooznest Ox
I recently upgraded my ooznest OX cnc machine from the original xPro USB controller (GRBL based) to the Duet3d (marlin based?)
Ooznest typically use this on their newer workbee machines and they provide a post processor in the downloads for that machine. However, they also ship all of their machines for use with trim routers which you manually switch on/off and don’t have any RPM control. Whereas I run my OX with a chinese 2.2KW water cooled spindle run from a VFD.
So I took the liberty to create a new version of the post processor that outputs spindle start, RPM, and spindle stop commands in the relevant places. I also have my processor drive the machine home at the end of every file. Note the spindle doesn’t shutdown until after the homing operation. I thought about shutting it down first. But honestly in the past I’ve accidentally homed it through things like work holding clamps and at least with the spindle on it just cuts through them 😉
Obviously that will only work if you have also configured your duet to sent spindle rpm commands to your vfd, you can see how I set up mine in the wiring video here:
You milage may vary – and I make no guarantees as to suitability etc. basically don’t blame me if you use it and something weird happens. However I’m using it and figured I’d share my version
enjoy!
To use the below code – safe it into a file with a .cps extension (mine is called “Ox – Duet.cps” and save it into: C:\Users<youruser>\AppData\Roaming\Autodesk\Fusion 360 CAM\Posts
Then when you go to do post processing in fusion, select setup and choose “use personal post library”
you should then be able to select it from the drop down.
/** Copyright (C) 2012-2017 by Autodesk, Inc. All rights reserved. WorkBee - Duet post processor configuration. $Revision: 42115 bdeb2e221ae970b5318768fc88f8111865513bf5 $ $Date: 2018-09-06 14:16:13 $ FORKID */ description = "Ox - Duet"; vendor = "Ooznest"; vendorUrl = "https://ooznest.co.uk/"; legal = "Copyright (C) 2012-2017 by Autodesk, Inc."; certificationLevel = 2; minimumRevision = 24000; longDescription = "Generic milling post for Ox CNC Machine with Duet3d wifi."; extension = "nc"; setCodePage("ascii"); capabilities = CAPABILITY_MILLING; tolerance = spatial(0.002, MM); minimumChordLength = spatial(0.25, MM); minimumCircularRadius = spatial(0.01, MM); maximumCircularRadius = spatial(1000, MM); minimumCircularSweep = toRad(0.01); maximumCircularSweep = toRad(180); allowHelicalMoves = true; allowedCircularPlanes = undefined; // allow all circular planes // user-defined properties properties = { writeMachine: true, // write machine writeTools: true, // writes the tools useG28: true, // disable to avoid G28 output for safe machine retracts - when disabled you must manually ensure safe retracts showSequenceNumbers: false, // show sequence numbers sequenceNumberStart: 10, // first sequence number sequenceNumberIncrement: 1, // increment for sequence numbers separateWordsWithSpace: true // specifies that the words should be separated with a white space }; // user-defined property definitions propertyDefinitions = { writeMachine: {title:"Write machine", description:"Output the machine settings in the header of the code.", group:0, type:"boolean"}, writeTools: {title:"Write tool list", description:"Output a tool list in the header of the code.", group:0, type:"boolean"}, useG28: {title:"G28 Safe retracts", description:"Disable to avoid G28 output for safe machine retracts. When disabled, you must manually ensure safe retracts.", type:"boolean"}, showSequenceNumbers: {title:"Use sequence numbers", description:"Use sequence numbers for each block of outputted code.", group:1, type:"boolean"}, sequenceNumberStart: {title:"Start sequence number", description:"The number at which to start the sequence numbers.", group:1, type:"integer"}, sequenceNumberIncrement: {title:"Sequence number increment", description:"The amount by which the sequence number is incremented by in each block.", group:1, type:"integer"}, separateWordsWithSpace: {title:"Separate words with space", description:"Adds spaces between words if 'yes' is selected.", type:"boolean"} }; var gFormat = createFormat({prefix:"G", decimals:0}); var mFormat = createFormat({prefix:"M", decimals:0}); var xyzFormat = createFormat({decimals:(unit == MM ? 3 : 4), trim:false}); var feedFormat = createFormat({decimals:(unit == MM ? 1 : 2)}); var toolFormat = createFormat({decimals:0}); var rpmFormat = createFormat({decimals:0}); var secFormat = createFormat({decimals:3, forceDecimal:true}); // seconds - range 0.001-1000 var taperFormat = createFormat({decimals:1, scale:DEG}); var xOutput = createVariable({prefix:"X", force:true}, xyzFormat); var yOutput = createVariable({prefix:"Y", force:true}, xyzFormat); var zOutput = createVariable({prefix:"Z", force:true}, xyzFormat); var feedOutput = createVariable({prefix:"F", force:true}, feedFormat); var sOutput = createVariable({prefix:"S", force:true}, rpmFormat); // circular output var iOutput = createReferenceVariable({prefix:"I", force:true}, xyzFormat); var jOutput = createReferenceVariable({prefix:"J", force:true}, xyzFormat); var kOutput = createReferenceVariable({prefix:"K", force:true}, xyzFormat); var gMotionModal = createModal({force:true}, gFormat); // modal group 1 // G0-G3, ... var gPlaneModal = createModal({onchange:function () {gMotionModal.reset();}}, gFormat); // modal group 2 // G17-19 var gAbsIncModal = createModal({}, gFormat); // modal group 3 // G90-91 var gFeedModeModal = createModal({}, gFormat); // modal group 5 // G93-94 var gUnitModal = createModal({}, gFormat); // modal group 6 // G20-21 // collected state var sequenceNumber; var currentWorkOffset; /** Writes the specified block. */ function writeBlock() { if (properties.showSequenceNumbers) { writeWords2("N" + sequenceNumber, arguments); sequenceNumber += properties.sequenceNumberIncrement; } else { writeWords(arguments); } } function formatComment(text) { return "(" + String(text).replace(/[()]/g, "") + ")"; } /** Output a comment. */ function writeComment(text) { writeln(formatComment(text)); } function onOpen() { if (!properties.separateWordsWithSpace) { setWordSeparator(""); } sequenceNumber = properties.sequenceNumberStart; if (programName) { writeComment(programName); } if (programComment) { writeComment(programComment); } // dump machine configuration var vendor = machineConfiguration.getVendor(); var model = machineConfiguration.getModel(); var description = machineConfiguration.getDescription(); if (properties.writeMachine && (vendor || model || description)) { writeComment(localize("Machine")); if (vendor) { writeComment(" " + localize("vendor") + ": " + vendor); } if (model) { writeComment(" " + localize("model") + ": " + model); } if (description) { writeComment(" " + localize("description") + ": " + description); } } switch (unit) { case IN: error(localize("Please select millimeters as unit when post processing. Inch mode is not recommended by the BoXZY team.")); return; // writeBlock(gUnitModal.format(20)); // break; case MM: writeBlock(gUnitModal.format(21)); break; } // dump tool information if (properties.writeTools) { var zRanges = {}; if (is3D()) { var numberOfSections = getNumberOfSections(); for (var i = 0; i < numberOfSections; ++i) { var section = getSection(i); var zRange = section.getGlobalZRange(); var tool = section.getTool(); if (zRanges[tool.number]) { zRanges[tool.number].expandToRange(zRange); } else { zRanges[tool.number] = zRange; } } } var tools = getToolTable(); if (tools.getNumberOfTools() > 0) { for (var i = 0; i < tools.getNumberOfTools(); ++i) { var tool = tools.getTool(i); var comment = "T" + toolFormat.format(tool.number) + " " + "D=" + xyzFormat.format(tool.diameter) + " " + localize("CR") + "=" + xyzFormat.format(tool.cornerRadius); if ((tool.taperAngle > 0) && (tool.taperAngle < Math.PI)) { comment += " " + localize("TAPER") + "=" + taperFormat.format(tool.taperAngle) + localize("deg"); } if (zRanges[tool.number]) { comment += " - " + localize("ZMIN") + "=" + xyzFormat.format(zRanges[tool.number].getMinimum()); } comment += " - " + getToolTypeName(tool.type); writeComment(comment); } } } // absolute coordinates writeBlock(gAbsIncModal.format(90)); forceXYZ(); } function onComment(message) { writeComment(message); } /** Force output of X, Y, and Z. */ function forceXYZ() { xOutput.reset(); yOutput.reset(); zOutput.reset(); } /** Force output of X, Y, Z, and F on next output. */ function forceAny() { forceXYZ(); feedOutput.reset(); } var deviceOn = false; function setDeviceMode(enable) { if (enable != deviceOn) { deviceOn = enable; if (enable) { switch (tool.type) { default: writeComment("TURN ON CUTTING"); writeBlock("S1000"); // DEVICE ON // writeBlock(mFormat.format(15)); // CUT ON } } else { switch (tool.type) { default: writeComment("TURN OFF CUTTING"); writeBlock("S0"); // DEVICE OFF - could add delay here // writeBlock(mFormat.format(16)); // CUT OFF } } } } function onSection() { var insertToolCall = isFirstSection() || currentSection.getForceToolChange && currentSection.getForceToolChange() || (tool.number != getPreviousSection().getTool().number); var retracted = false; // specifies that the tool has been retracted to the safe plane writeln(""); if (hasParameter("operation-comment")) { var comment = getParameter("operation-comment"); if (comment) { writeComment(comment); } } // spindle speed if (insertToolCall || isFirstSection() || (rpmFormat.areDifferent(spindleSpeed, sOutput.getCurrent())) || (tool.clockwise != getPreviousSection().getTool().clockwise)) { if (spindleSpeed < 1) { error(localize("Spindle speed out of range.")); } if (spindleSpeed > 99999) { warning(localize("Spindle speed exceeds maximum value.")); } //switch on spindle via I/O ping writeBlock(mFormat.format(42), "P1 S1"); writeBlock( //set spindle rpm mFormat.format(tool.clockwise ? 3 : 4), sOutput.format(spindleSpeed) ); // dwell for 4 seconds for spindle ramp up writeBlock(gFormat.format(4),"P10000"); } forceXYZ(); { // pure 3D var remaining = currentSection.workPlane; if (!isSameDirection(remaining.forward, new Vector(0, 0, 1))) { error(localize("Tool orientation is not supported.")); return; } setRotation(remaining); } // coolant not supported forceAny(); var initialPosition = getFramePosition(currentSection.getInitialPosition()); if (!retracted) { if (getCurrentPosition().z < initialPosition.z) { writeBlock(gMotionModal.format(0), zOutput.format(initialPosition.z)); } } if (retracted || isFirstSection() || true) { // we pull up Z above if we start lower gMotionModal.reset(); writeBlock( gAbsIncModal.format(90), gMotionModal.format(0), xOutput.format(initialPosition.x), yOutput.format(initialPosition.y), zOutput.format(initialPosition.z) ); } else { writeBlock( gAbsIncModal.format(90), gMotionModal.format(0), xOutput.format(initialPosition.x), yOutput.format(initialPosition.y) ); } } function onDwell(seconds) { if (seconds > 99999.999) { warning(localize("Dwelling time is out of range.")); } seconds = clamp(0.001, seconds, 99999.999); writeBlock(gFormat.format(4), "P" + secFormat.format(seconds)); } function onSpindleSpeed(spindleSpeed) { writeBlock(mFormat.format(tool.clockwise ? 3 : 4),sOutput.format(spindleSpeed)); } var pendingRadiusCompensation = -1; function onRadiusCompensation() { pendingRadiusCompensation = radiusCompensation; } function onRapid(_x, _y, _z) { var x = xOutput.format(_x); var y = yOutput.format(_y); var z = zOutput.format(_z); if (x || y || z) { if (pendingRadiusCompensation >= 0) { error(localize("Radius compensation mode cannot be changed at rapid traversal.")); return; } writeBlock(gMotionModal.format(0), x, y, z); feedOutput.reset(); } } function onLinear(_x, _y, _z, feed) { // at least one axis is required if (pendingRadiusCompensation >= 0) { // ensure that we end at desired position when compensation is turned off xOutput.reset(); yOutput.reset(); } var x = xOutput.format(_x); var y = yOutput.format(_y); var z = zOutput.format(_z); var f = feedOutput.format(feed); if (x || y || z) { if (pendingRadiusCompensation >= 0) { error(localize("Radius compensation mode is not supported.")); return; } else { writeBlock(gMotionModal.format(1), x, y, z, f); } } else if (f) { if (getNextRecord().isMotion()) { // try not to output feed without motion feedOutput.reset(); // force feed on next line } else { writeBlock(gMotionModal.format(1), f); } } } function onRapid5D(_x, _y, _z, _a, _b, _c) { error(localize("Multi-axis motion is not supported.")); } function onLinear5D(_x, _y, _z, _a, _b, _c, feed) { error(localize("Multi-axis motion is not supported.")); } function onCircular(clockwise, cx, cy, cz, x, y, z, feed) { // one of X/Y and I/J are required and likewise if (pendingRadiusCompensation >= 0) { error(localize("Radius compensation cannot be activated/deactivated for a circular move.")); return; } var start = getCurrentPosition(); if (isFullCircle()) { if (isHelical()) { linearize(tolerance); return; } switch (getCircularPlane()) { case PLANE_XY: writeBlock(gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), feedOutput.format(feed)); break; /* case PLANE_ZX: writeBlock(gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), zOutput.format(z), iOutput.format(cx - start.x, 0), kOutput.format(cz - start.z, 0), feedOutput.format(feed)); break; case PLANE_YZ: writeBlock(gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), yOutput.format(y), jOutput.format(cy - start.y, 0), kOutput.format(cz - start.z, 0), feedOutput.format(feed)); break; */ default: linearize(tolerance); } } else { switch (getCircularPlane()) { case PLANE_XY: writeBlock(gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(cx - start.x, 0), jOutput.format(cy - start.y, 0), feedOutput.format(feed)); break; /* case PLANE_ZX: writeBlock(gPlaneModal.format(18), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), iOutput.format(cx - start.x, 0), kOutput.format(cz - start.z, 0), feedOutput.format(feed)); break; case PLANE_YZ: writeBlock(gPlaneModal.format(19), gMotionModal.format(clockwise ? 2 : 3), xOutput.format(x), yOutput.format(y), zOutput.format(z), jOutput.format(cy - start.y, 0), kOutput.format(cz - start.z, 0), feedOutput.format(feed)); break; */ default: linearize(tolerance); } } } var mapCommand = { COMMAND_STOP:0, COMMAND_END:2, COMMAND_SPINDLE_CLOCKWISE:3, COMMAND_SPINDLE_COUNTERCLOCKWISE:4, COMMAND_STOP_SPINDLE:5 }; function onCommand(command) { switch (command) { case COMMAND_START_SPINDLE: onCommand(tool.clockwise ? COMMAND_SPINDLE_CLOCKWISE : COMMAND_SPINDLE_COUNTERCLOCKWISE); return; case COMMAND_LOCK_MULTI_AXIS: return; case COMMAND_UNLOCK_MULTI_AXIS: return; case COMMAND_BREAK_CONTROL: return; case COMMAND_TOOL_MEASURE: return; } var stringId = getCommandStringId(command); var mcode = mapCommand[stringId]; if (mcode != undefined) { writeBlock(mFormat.format(mcode)); } else { onUnsupportedCommand(command); } } /** Output block to do safe retract and/or move to home position. */ function writeRetract() { // initialize routine var _xyzMoved = new Array(false, false, false); var _useG28 = properties.useG28; // can be either true or false // check syntax of call if (arguments.length == 0) { error(localize("No axis specified for writeRetract().")); return; } for (var i = 0; i < arguments.length; ++i) { if ((arguments[i] < 0) || (arguments[i] > 2)) { error(localize("Bad axis specified for writeRetract().")); return; } if (_xyzMoved[arguments[i]]) { error(localize("Cannot retract the same axis twice in one line")); return; } _xyzMoved[arguments[i]] = true; } // special conditions // none // define home positions var _xHome; var _yHome; var _zHome; if (_useG28) { _xHome = 0; _yHome = 0; _zHome = 0; } else { _xHome = machineConfiguration.hasHomePositionX() ? machineConfiguration.getHomePositionX() : 0; _yHome = machineConfiguration.hasHomePositionY() ? machineConfiguration.getHomePositionY() : 0; _zHome = machineConfiguration.getRetractPlane(); } // format home positions var words = []; // store all retracted axes in an array for (var i = 0; i < arguments.length; ++i) { // define the axes to move switch (arguments[i]) { case X: if (machineConfiguration.hasHomePositionX() || _useG28) { words.push("X" + xyzFormat.format(_xHome)); } break; case Y: if (machineConfiguration.hasHomePositionX() || _useG28) { words.push("Y" + xyzFormat.format(_yHome)); } break; case Z: if (_useG28) { words.push("Z" + xyzFormat.format(_zHome)); retracted = true; } break; } } // output move to home if (words.length > 0) { if (_useG28) { gAbsIncModal.reset(); writeBlock(gFormat.format(28), gAbsIncModal.format(91), words); writeBlock(gAbsIncModal.format(90)); } else { gMotionModal.reset(); writeBlock(gAbsIncModal.format(90), gFormat.format(53), gMotionModal.format(0), words); } // force any axes that move to home on next block if (_xyzMoved[0]) { xOutput.reset(); } if (_xyzMoved[1]) { yOutput.reset(); } if (_xyzMoved[2]) { zOutput.reset(); } } } function onSectionEnd() { forceAny(); } function onClose() { writeRetract(Z); writeRetract(X, Y); onImpliedCommand(COMMAND_END); onImpliedCommand(COMMAND_STOP_SPINDLE); writeBlock(mFormat.format(tool.clockwise ? 3 : 4),sOutput.format(0)); writeBlock(mFormat.format(5)); // stop program, spindle stop, coolant off writeBlock(mFormat.format(42), "P1 S0"); }