Just hooked up my blackbox and now diving into the Control software (very new to CNC). I have a project that requires a series of looping gcode commands. I'd like to explore using Javascript Macros for this. Can anyone point me to a resource that can help get me started with using javascript macros? I can't seem to find any documentation, videos, tutorials, or examples. Anything would be helpful at this point. Thanks.
1) Open CONTROL, and press ctrl+Shift+I (or cmd+shift+i i think its on Mac) Notice the little bit of Help thats provided for you to see some of the more popular pieces of data to access and some of the popular commands 2) But you are not limited to just that of course, you can use ANY JS, you can inject html into the DOM, you can manipulate CSS, you can use all the functions in the existing code (anything from OpenBuilds/OpenBuilds-CONTROL, lots of little functions that exist in the existing code, like, stupid example, if you want your Macro to print to the serial log, use the printLog function from here OpenBuilds/OpenBuilds-CONTROL - just depends on your skill level, and looking at the sources to discover more gems inside it. And of course talk to us, we'll be happy to help 3) So armed with some functions, and a goal To achieve what you want to do: You'll need a) the gcode to loop (I assume you loaded up our gcode into the Gcode editor? Code: editor.getValue(); b) the currently running state (so you can know when the job is complete to start it again?) Code: //0 = not connected, 1 = opening, 2 = connected, 3 = playing, 4 = paused, 5 = alarm, 6 = firmware upgrade laststatus.comms.connectionStatus == ? c) starting the job: Code: socket.emit("runJob", { data: gcode-commands, isJob: false, completedMsg: "message displayed upon completion ", }); 4) We recommend you use the Devtools console to test and develop your macro code before pasting it into a macro Code: // very simple and bad version of the macro just quickly put together to demonstrate the js macro functionality // you asked for, NB NOTE this will run the macro FOREVER (until you quit CONTROL) - should add some way to break it (: etc setInterval(function(){ if (laststatus.comms.connectionStatus == 2 ) { socket.emit("runJob", { data: editor.getValue(), isJob: false, completedMsg: false }); } else { printLog("job still running, or not connected, or some error/paused state"); } }, 100) I'll leave it up to you from here to expand it as you need it but hopefully this cover the basics of how it works NOTE: This thread is a showcase and introduction to Macros. Support posts here will be moved to Tech support for Javascript Macros in CONTROL - if you need help please post in Tech support for Javascript Macros in CONTROL (not in this thread)
More advanced version, with UI (; Stop and start buttons, counts the iterations, etc Code: window.macro1repeat = false; window.macro1interval; window.macro1interation = 0; window.runRepeatingGcode = function() { macro1repeat = true; // set flag window.macro1interval = setInterval(function() { if (macro1repeat) { if (laststatus.comms.connectionStatus == 2) { macro1interation++; $("#macro1log").html("running: iteration: " + macro1interation) socket.emit("runJob", { data: editor.getValue(), isJob: false, completedMsg: false }); } } else { $("#macro1log").html("Stopping...") socket.emit('stop', false) clearInterval(window.macro1interval); } }, 100) } // since we have metro, use Metro.dialog https://metroui.org.ua/dialog.html to make a UI Metro.dialog.create({ title: "My looping Macro", content: ` <button class="button info" onclick="runRepeatingGcode()">Run</button> <button class="button info" onclick="macro1repeat = false;">Stop</button> <hr> <span id="macro1log">pending run...</span> `, actions: [{ caption: "Stop and close this window", cls: "js-dialog-close alert", onclick: function() { macro1repeat = false; printLog("Repeating Macro Exited") } }] });
Inject a Z Probe button onto main toolbar: Code: var probezbtntpl = `<button id="grblProbeZMenu" class="ribbon-button" onclick="openProbeZDialog();"> <span class="icon"> <span class="fa-layers fa-fw"> <i class="fas fa-podcast fa-rotate-180 fg-blue"></i> </span> </span> <span class="caption grblmode">Probe Z</span> </button>` $( "#grblProbeMenu" ).after( probezbtntpl );
Show or hide Z jog buttons depending on step size. Help getting started with Javascript Macros in Control...
Updated hide Z buttons, now works with step size change via keyboard shortcuts. (though doesnt rebind if shortcuts are changed. needs a restart) Code: var macroRunResults = ''; if (!document.getElementById('macroRan')) { // Remove Z jog buttons when 100mm clicked var currentStepSize = "dist10label"; // should be dist10 on startup // incase stepsize was changed before macro was run if ($('#dist100').hasClass('bd-openbuilds') ) { hideZbtns(); } else if ($('#dist01').hasClass('bd-openbuilds')) { currentStepSize = "dist01label"; } else if ($('#dist1').hasClass('bd-openbuilds')) { currentStepSize = "dist1label"; } $(document).bind('keydown', keyboardShortcuts.stepP, function(e) { if ( currentStepSize == 'dist10label' ) { hideZbtns(); } else if (currentStepSize == 'dist01label') { currentStepSize = 'dist1label'; } else if (currentStepSize == 'dist1label') { currentStepSize = 'dist10label'; } }); $(document).bind('keydown', keyboardShortcuts.stepM, function(e) { showZbtns(); if ( currentStepSize == 'dist100label' ) { currentStepSize = 'dist10label'; } else if (currentStepSize == 'dist10label') { currentStepSize = 'dist1label'; } else if (currentStepSize == 'dist1label') { currentStepSize = 'dist01label'; } }); $('#dist100label').on('click', function() { hideZbtns(); }); $('#dist01label,#dist10label,#dist1label').on('click', function() { currentStepSize = event.srcElement.id; showZbtns(); }); function hideZbtns() { $('#zM,#zP').hide(); $('#dist100').css('width', '150%'); $('#dist100').closest('td').css('padding-right', '25px'); currentStepSize = 'dist100label'; }; function showZbtns() { $('#zM,#zP').show(); $('#dist100').closest('td').css('padding-right', '0px'); $('#dist100').css('width', '100%'); }; $('#jogTypeContinuous').on('click', function() { if ($(this).is(':checked')) { showZbtns(); } else { if ($('#dist100').hasClass('bd-openbuilds') ) { hideZbtns(); } } }); var macroHasRun = '<div id="macroRan" class="hidden"></div>' $('#grblProbeMenu').after(macroHasRun); macroRunResults = 'Macro ran.'; } else { printLog('Macro has already run.') macroRunResults = 'Macro already ran. Not going to run again.'; }; // since we have metro, use Metro.dialog https://metroui.org.ua/dialog.html to make a UI Metro.dialog.create({ title: "Macro Run Results", content: macroRunResults, actions: [{ caption: "OK", cls: "js-dialog-close alert" }] });
Want to know how big a piece of stock to grab from the offcuts bin? Code: if (typeof object !== 'undefined') { var string = "" if (object.userData.inch) { string += "Width (X): " + (object.userData.bbbox2.max.x - object.userData.bbbox2.min.x) + "inch" + " / " + "Height (Y): " + (object.userData.bbbox2.max.y - object.userData.bbbox2.min.y) + "inch"; } else { string += "Width (X): " + (object.userData.bbbox2.max.x - object.userData.bbbox2.min.x) + "mm" + " / " + "Height (Y): " + (object.userData.bbbox2.max.y - object.userData.bbbox2.min.y) + "mm"; } console.log(string) Metro.dialog.create({ title: "Stock Size needed", clsDialog: "dark", content: ` To run this job, you need a piece of stock:<hr> ` + string + ` `, actions: [{ caption: "Cancel", cls: "js-dialog-close alert", }] }); }
Move Stop button before Run button. Limited testing done but seems to work great. Code: $( "#stopBtn" ).remove(); var moveStopBtn = '<button id="stopBtn" class="ribbon-button" onclick="socket.emit(\'stop\', { stop: true, jog: false, abort: false});" disabled="disabled"> \ <span class="icon"> \ <i class="fas fa-stop"></i> \ </span> \ <span class="caption">Stop<br>Job</span> \ </button>' $( "#runBtn" ).before( moveStopBtn );
Saw stop Macro: See NickEng's Sawstop Grbl interface / coding for a more in depth discussion In CONTROL > Macros tab > Add Macro Enter a name (like SawStop Mode), and click the Javascript tab Paste the following: Code: // Add some CSS classes $("<style type='text/css'> .keypadBtn {width: 80px;} .sawStopKeypadBtn {width: 80px;} .sawStopActionBtn {width: 280px; } </style>").appendTo("head"); // variables placeholder window.sawStopValue = 0.0; window.sawStopFractionValue = 0; // Update the UI <span> fields to display value window.updateSawStopValueUI = setInterval(function() { $("#sawStopValue").html(sawStopValue.toFixed(0)); if (sawStopFractionValue.length > 1) { $("#sawStopFraction").html(" and " + sawStopFractionValue); } else { $("#sawStopFraction").html(""); } }, 50); // keypad button window.sawStopBtn = function(val) { var currentValue = sawStopValue.toString(); currentValue += val; sawStopValue = parseInt(currentValue); } // fraction button window.sawStopFraction = function(val) { sawStopFractionValue = val; } // backspace button window.sawStopBackSpace = function() { var sawStopcurrentValue = sawStopValue.toString(); var sawStopNewValue = sawStopcurrentValue.slice(0, -1); if (sawStopNewValue.length == 0) { sawStopNewValue = "0"; } sawStopValue = parseInt(sawStopNewValue); } window.sawStopMoveTo = function() { // Calculate Decimals from fractions if (sawStopFractionValue.length > 1) { var sawStopFractionSplit = sawStopFractionValue.split("/"); var sawStopDecimals = parseInt(sawStopFractionSplit[0], 10) / parseInt(sawStopFractionSplit[1], 10); var decimalSawStopFractionValue = eval(sawStopFractionValue); } else { var decimalSawStopFractionValue = 0; } // add decimals to integers var sawStopFinalPosition = sawStopValue + decimalSawStopFractionValue; // Send move command to Grbl sendGcode("$J=G90 G20 X" + sawStopFinalPosition + " F10000"); } // Create the User Interface Metro.dialog.create({ title: "Saw Stop Macro", width: '90%', content: ` <span class="display3" id="sawStopValue">0</span> <span class="display3" id="sawStopFraction"></span> <span class="display2"> inch</span> <div id="sawStopNumbersUI"> <table class="table striped compact"> <tr> <td> <table> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(7);">7</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(8);">8</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(9);">9</button></td> </tr> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(4);">4</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(5);">5</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(6);">6</button></td> </tr> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(1);">1</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(2);">2</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(3);">3</button></td> </tr> <tr> <td></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBtn(0);">0</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopBackSpace();"><i class="fas fa-arrow-left"></i></button></td> </tr> </table> </td> <td> <table> <tr> <td colspan="3"> <hr> </td> </tr> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('1/16');">1/16</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('3/16');">3/16</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('5/16');">5/16</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('7/16');">7/16</button></td> </tr> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('9/16');">9/16</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('11/16');">11/16</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('13/16');">13/16</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('15/16');">15/16</button></td> </tr> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('1/8');">1/8</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('3/8');">3/8</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('5/8');">5/8</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('7/8');">7/8</button></td> </tr> <tr> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('1/4');">1/4</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('1/2');">1/2</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFraction('3/4');">3/4</button></td> <td><button class="button outline sawStopKeypadBtn" onclick="sawStopFractionValue=0;">0/0</button></td> </tr> <tr> <td colspan="3"> <hr> </td> </tr> </table> </td> </tr> </table> </div> <hr> <button class="button outline sawStopActionBtn alert" onclick="sawStopValue=0; sawStopFractionValue=0;">CLEAR</button> <button class="button outline sawStopActionBtn success" onclick="sawStopMoveTo();">MOVE TO</button> <hr> <button class="button outline sawStopActionBtn warning" onclick="sendGcode('M8');">APPLY BRAKE</button> <button class="button outline sawStopActionBtn primary" onclick="sendGcode('M9');">RELEASE BRAKE</button> <hr> <button class="button outline sawStopActionBtn secondary" onclick="sendGcode('$H');">HOME</button> <button class="button outline sawStopActionBtn secondary" onclick="sendGcode('G10 P0 L20 X0');">SETZERO</button> `, actions: [{ caption: "Exit Macro", cls: "js-dialog-close alert", onclick: function() { printLog("Saw Stop Macro Exited") } }] });
Some of @sharmstr 's awesome Macros: Expand USB Port Selector in OpenBuilds Control ___________________________________________________________________________________________________________ Set Work Zero Via Keyboard Shortcuts in OpenBuilds Control ___________________________________________________________________________________________________________ Initiate Probing via Keyboard Shortcuts in OpenBuilds Control ___________________________________________________________________________________________________________ Change OpenBuilds Control Jog Step Size ___________________________________________________________________________________________________________ OpenBuilds Control Automatically Launch at Startup ___________________________________________________________________________________________________________ See more of her awesome Macros here: Blog
I have created a small macro I'm using for my builds and want to share it with the community, hoping it might be useful for one or the other: milling a linear cut or pocket into some stock. Intention The initial requirement was to easily perform an exact cut of some stock. As I do not have a good sawing equipment, I simply cut the stock by a milling operation. The g-code was generated by some CAD program and converted to GRBL g-code. Instead of changing the parameters all the time in the CAD program, I created this macro. Description The purpose of this macro is to cut some stock by milling. It starts at 0/0/0 (x/y/z) and mills to x/y/z. In fact, depending on the placement of the stock relative to 0/0 and the x/y/z length, the macro can be used to cut a stock mill a slot into a stock mill a long-hole into a stock mill a pocket into a stock The macro generates a sequence of G-Codes with linear milling movements in layers. The generated code will replace the one in the G-Code editor, and triggers the parsing and visualization of the code in the 3D viewer. Installation To install the macro, copy the code from below, or from https://raw.githubusercontent.com/poWer4aiX/ob-linear-cut-macro/master/src/ob-linear-cut-macro.js into a new macro in OpenBuilds CONTROL. Source You can find all the sources in github repository GitHub - poWer4aiX/ob-linear-cut-macro: Macro for OpenBuilds CONTROL to mill a linear horizontal cut. Code: function generateGCode(xMovement, yMovement, zMovement, stepDown, feedrate) { if (zMovement <= 0 || stepDown <= 0) return; var x0 = 0 var y0 = 0 var z0 = 0 var zSafe = 7 var x = x0 var y = y0 var z = z0 var xyFeed = feedrate var zFeed = feedrate / 2 var gCode = '' gCode += `; GCODE Generated by ???? on ?????\n` gCode += `; linear pocket movement from 0/0/0 to ${xMovement}/${yMovement}/${zMovement} in ${stepDown}mm steps\n` gCode += 'G21; mm-mode\n' gCode += 'G54; Work Coordinates\n' gCode += 'G21; mm-mode\n' gCode += 'G90; Absolute Positioning\n' gCode += 'M3 S1000; Spindle On\n' gCode += '\n' gCode += '; Begin of linear pocket / cut loop\n' gCode += '; Endmill Diameter: any\n' gCode += `G0 Z${z0 + zSafe}; move to z-safe height\n` gCode += `G0 F1000 X${x0} Y${y0}; move to x/y zeropoint\n` gCode += '\n' var forwardDir = 1 while (z >= -zMovement) { // step down z -= stepDown if (z < -zMovement) z = -zMovement gCode += `G1 F${zFeed} Z${z}; step down on current position\n` // x/y movement if (forwardDir) { x = x0 + xMovement y = y0 + yMovement } else { x = x0 y = y0 } gCode += `G1 F${xyFeed} X${x} Y${y}; linear x/y movement\n` // check for endCondition if (z <= -zMovement) break forwardDir = !forwardDir } // move tool back to save z gCode += '\n' gCode += '; End of linear pockt / cut loop\n' gCode += '; retracting back to z-safe\n' gCode += `G0 Z${z0 + zSafe}\n` gCode += '\n' gCode += 'M5 S0; Spindle Off\n' gCode += '; Job completed\n' // replace code in G-Code editor editor.session.setValue(gCode); // refresh 3D view parseGcodeInWebWorker(editor.getValue()) // required for testing return gCode; } function genInputHtml(label, id, value, icon, descr) { var html = '' html += '<div class="row mb-0">\n' html += ` <label class= "cell-sm-6" > ${label}</label >\n` html += ' <div class="cell-sm-6">\n' html += ` <input id="${id}" type="number" value="${value}" data-role="input" data-append="mm" data-prepend="<i class='fas ${icon}'></i>" data-clear-button="false">\n` html += ' </div>\n' html += '</div>\n' html += '<hr>\n' if (descr) html += `<small > ${descr}</small > ` return html } // Dialog creation Metro.dialog.create({ title: 'Linear Pocket / Cut', content: genInputHtml('X movement', 'xMovement', 100, 'fa-ruler-horizontal', '') + genInputHtml('Y movement', 'yMovement', 0, 'fa-ruler-vertical', '') + genInputHtml('Cutting deepth', 'zMovement', 10, 'fa-ruler', '') + genInputHtml('Step-down', 'stepDown', 1, 'fa-align-justify', '') + genInputHtml('Feedrate', 'feedrate', 100, 'fa-running', 'How fast to move the endmill in milling operation'), actions: [ { caption: "Generate G-Code", cls: "js-dialog-close success", onclick: function () { const xMovement = parseFloat($("#xMovement").val()) const yMovement = parseFloat($("#yMovement").val()) const zMovement = parseFloat($("#zMovement").val()) const stepDown = parseFloat($("#stepDown").val()) const feedrate = parseInt($("#feedrate").val()) generateGCode(xMovement, yMovement, zMovement, stepDown, feedrate) } }, { caption: "Cancel", cls: "js-dialog-close alert", onclick: function () { } } ] }); // required for jest test if (process.env.JEST_WORKER_ID !== undefined) module.exports = generateGCode;
I have created another small macro... Description This macro generates a sequence of G-Codes to mill a (vertical) hole into some stock at 0/0/0 (x/y/z). The generated code will replace the one in the G-Code editor, and triggers the parsing and visualization of the code in the 3D viewer. Usage When starting the macro it opens up a dialog to enter the milling parameters: Hole and Endmill diameter You need to specify the diameter of the endmill being used, as well as the hole diameter. Note: If the hole diameter is smaller than the endmill diameter, then the macro will not generate / update any G-Code! If the hole diameter is equal to the endmill diameter, then the operation is equal to a drilling operation (i.e. moving the endmill vertical in z direction only). Otherwise, the complete hole body will be milled. Note: Depending on your machine setup and material, the resulting hole might be smaller or even larger than the target. In this case adjust the target diameter by the messured difference. Receipe There are two base receipe implemented by which the area is being milled: circle: by milling in concentric growing circles. spiral: by milling in a (approximated) spiral. Each has two additional options which steers the milling direction: cw: clock-wise milling (usually against the rotation of the endmill), aka conventional milling ccw: counter-clock-wise milling (usually towards the endmill rotation), aha climb milling The milling direction can be defined for the rough cutting, as well for the finishing process if not disabled. As a result the receipes are following the following naming convention: <milling method>-<rough cut direction>-<finishing direction> e.g. spiral-ccw-cw Cutting depth Defines how deep to mill the pocket / hole into the material. Usually, this is being dome in multiple layers (see DOC). DOC (depth of cut) Milling is being done in layers. The DOC defines how deep the endmill mills in each layer (might be lower for the final layer). This depth is expressed as a percentage of the endmill diameter. U WOC (width of cut) The layers being milled will cut the material based on the receipe selected. The WOC defined how deep the endmill mills into the material on the current depth layer. Again, this value is given as a percentage to the endmill diameter. Finish WOC If you prefer to split the milling operation into a rough cutting and finishing part, you cen defined the WOC used for the very last circle being milled. A value of 0 disbles the finishing loop. Feedrate The maximum feedrate used during the milling operation. Installation The latest source can be found on gitbub: https://raw.githubusercontent.com/p...ing-macro/master/src/ob-hole-milling-macro.js Code: // mills a spiral starting at 0/0/${z}, which needs to be the current position function millSpiral(diam, ccw, stepWidth, g, xyFeed) { function f(x) { return Number(x).toFixed(6).replace(/\.?0+$/, '') } const d = stepWidth / 4 var step = 0 var pCurr = { x: 0, y: 0 } var pTarget = { x: 0, y: 0 } // target radius var rTarget = 0 g(`; diam=${diam} stepWidth=${stepWidth}`) var state = 0 var remainingClosingCount = 5 while (rTarget < diam || remainingClosingCount) { step++ rTarget = (step > 1 ? step - 0.5 : step) * d; if (rTarget > diam) rTarget = diam var xyTarget = step * d; if (xyTarget > diam) xyTarget = diam switch (state) { case 0: pTarget.x = xyTarget * -1; pTarget.y = 0; break; // arc top left case 1: pTarget.x = 0; pTarget.y = xyTarget * -1; break; // arc bottom left case 2: pTarget.x = xyTarget; pTarget.y = 0; break; // arc bottom right case 3: pTarget.x = 0; pTarget.y = xyTarget; break; // arc top right } // idea to determine the center point of the new circle catched from // https://math.stackexchange.com/questions/1781438/finding-the-center-of-a-circle-given-two-points-and-a-radius-algebraically // // distance to center of rhombus const xa = 1 / 2 * (pTarget.x - pCurr.x) const ya = 1 / 2 * (pTarget.y - pCurr.y) // center of rhombus const p0 = { x: pCurr.x + xa, y: pCurr.y + ya } // half lenth of diagonales of rhombus const a = Math.sqrt(xa * xa + ya * ya) const b = Math.sqrt(rTarget * rTarget - a * a) // center of circle const pCenter = { x: p0.x + (ccw ? -1 : 1) * ((b * ya) / a), y: p0.y + (ccw ? 1 : -1) * ((b * xa) / a) } g(`;--${pCurr}, ${pTarget}, ${a}, ${b}, ${pCenter}`) g(`G${ccw ? 3 : 2} F${xyFeed} X${f(pTarget.x)} Y${f(pTarget.y)} I${f(pCenter.x - pCurr.x)} J${f(pCenter.y - pCurr.y)}`) pCurr.x = pTarget.x pCurr.y = pTarget.y state += ccw ? 1 : -1 if (state < 0) state = 3 if (state > 3) state = 0 if (rTarget >= diam) remainingClosingCount-- } } // mills a set of circles starting at 0/0/${z}, which needs to be the current position function millCircles(diam, ccw, stepWidth, g, xyFeed) { var x = 0 while (x < diam) { x += stepWidth if (x > diam) x = diam g(`G1 F${xyFeed} X${x} Y0; mill right to circle radius`) if (x > 0) { if (ccw) { g(`G3 F${xyFeed} X-${x} Y0 I-${x} J0; 1st half circle`) g(`G3 F${xyFeed} X${x} Y0 I${x} J0; 2nd half circle`) } else { g(`G2 F${xyFeed} X-${x} Y0 I-${x} J0; 1st half circle`) g(`G2 F${xyFeed} X${x} Y0 I${x} J0; 2nd half circle`) } } if (x >= diam) break } } function generateGCode(holeDiam, endmillDiam, zMovement, doc, woc, wocFinish, feedrate, receipe) { if (holeDiam < endmillDiam) { console.log("holeDiam < endmillDiam"); return } if (zMovement <= 0) { console.log("zMovement <=0"); return } if (doc < 10) { console.log("doc < 10"); return } if (doc > 200) { console.log("doc > 200"); return } if (woc < 5) { console.log("woc < 5"); return } if (woc > 30) { console.log("woc > 30"); return } if (wocFinish < 0) wocFinish = 0 var x0 = 0 var y0 = 0 var z0 = 0 var zSafe = 7 var x = x0 var y = y0 var z = z0 var xyFeed = feedrate var zFeed = feedrate / 2 var xStep = endmillDiam * woc / 100; var finishOffset = endmillDiam * wocFinish / 100; var xMax = (holeDiam - endmillDiam - finishOffset) / 2 if (xMax < 0) xMax = 0 var xMax2 = (holeDiam - endmillDiam) / 2 var stepDown = endmillDiam * doc / 100; const docFinish = 200 const ccw = (() => { if (/^\w*-ccw/.test(receipe)) return 1 else if (/^\w*-cw/.test(receipe)) return 0 else return 1 })() var gCode = '' function g(str) { gCode += str + '\n' } g(`; GCODE Generated by ob-hole-milling-macro on ${new Date().toISOString()}`) g(`; ${holeDiam}mm hole milling at 0/0/0 downto ${zMovement}`) g(`; endmill diameter=${endmillDiam}mm, DOC=${doc}%/${stepDown}mm, WOC=${woc}%/${xStep}mm`) g('G21; mm-mode') g('G54; Work Coordinates') g('G90; Absolute Positioning') g('M3 S1000; Spindle On') g('') g('; Begin of hole milling loop') g(`; Endmill Diameter: ${endmillDiam}`) g(`G0 Z${z0 + zSafe}; move to z-safe height`) g(`G0 F1000 X${x} Y${y}; move to x/y startpoint`) g('') // rough cut while (z > -zMovement) { z -= stepDown if (z < -zMovement) z = -zMovement g(`; layer ${z}`) g(`G1 F${zFeed} Z${z}; step down on current position`) if (xMax > 0) { if (/^circle/.test(receipe)) { millCircles(xMax, ccw, xStep, g, xyFeed) } else if (/^spiral/.test(receipe)) { millSpiral(xMax, ccw, xStep, g, xyFeed) } else { console.log(`unknown receipe:${receipe}`) return } x = 0 g(`G1 F${xyFeed} X${x} Y0; move back to center`) } } // if we are not only drilling and not having a wocFinish of 0, then add a finishing cut if (xMax2 > xMax) { g('') g(`;--- finishing cut with DOC=${docFinish}%, WOC=${wocFinish}%`) g(`G0 Z${z0 + zSafe}; move to z-safe height`) g(`G0 F1000 X0 Y0 Z0; move up to zeropoint`) z = z0 stepDown = endmillDiam * docFinish / 100; const ccw = (() => { if (/^\w*-\w*-ccw/.test(receipe)) return 1 else if (/^\w*-\w*-cw/.test(receipe)) return 0 else if (/^\w*-ccw/.test(receipe)) return 1 else if (/^\w*-cw/.test(receipe)) return 0 else return 1 })() //g(`G1 F${zFeed} Z${z}; go back to z0`) while (z > -zMovement) { // step down z -= stepDown if (z < -zMovement) z = -zMovement g(`; layer ${z}`) g(`G1 F${zFeed} Z${z}; step down current position`) millCircles(xMax2, ccw, xMax2, g, xyFeed / 2) g(`G1 F${xyFeed} X${x} Y0; move to center`) } } // move tool back to save z g('') g('; End of hole milling loop') if (x != x0 || y != y0) g(`G1 F${xyFeed} X${x0} Y${y0}; move to center of hole`) g(`G0 F1000 Z${z0 + zSafe}; retracting back to z-safe`) g('') g('M5 S0; Spindle Off') g('; Job complete') // replace code in G-Code editor editor.session.setValue(gCode); // refresh 3D view parseGcodeInWebWorker(editor.getValue()) // not required for the macro but for testing return gCode; } function genInputHtml(label, id, value, icon, descr, append = "") { const descrHtml = descr ? ` <small><i>(${descr})</i></small>` : "" var html = '' html += '<div class="row mb-1">\n' html += ` <label class= "cell-sm-8" > ${label}${descrHtml}</label >\n` html += ' <div class="cell-sm-4">\n' html += ` <input id="${id}" type="number" value="${value}" data-role="input" data-append="${append}" data-prepend="<i class='fas ${icon}'></i>" data-clear-button="false">\n` html += ' </div>\n' html += '</div>\n' return html } function genSelectHtml(label, id, options, selected, descr = '', opt = '') { const descrHtml = descr ? ` <small><i>(${descr})</i></small>` : "" var html = '' html += '<div class="row mb-1">\n' html += ` <label class="cell-sm-8">${label}${descrHtml}</label>\n` html += ' <div class="cell-sm-4">\n' html += ` <select id="${id}" data-role="select" ${opt}>\n` html += options.map(o => ` <option value="${o}"${o == selected ? ' selected="selected"' : ''}>${o}</option>\n`).join('') html += ' </select>\n' html += ' </div>\n' html += '</div>\n' return html } var prefs = { holeDiam: 8, endmillDiam: 4, holeDepth: 1, doc: 100, woc: 20, wocFinish: 2, feedrate: 500, receipe: "spiral-ccw-ccw" } function loadPrefs() { if (window.tmp_prefs_macro_hole_milling) prefs = window.tmp_prefs_macro_hole_milling } function savePrefs() { window.tmp_prefs_macro_hole_milling = prefs } loadPrefs(); // Dialog creation Metro.dialog.create({ title: 'Hole Milling', content: genInputHtml('Hole diameter', 'holeDiam', prefs.holeDiam, 'fa-circle', '', 'mm') + genInputHtml('Endmill diameter', 'endmillDiam', prefs.endmillDiam, 'fa-circle', '', 'mm') + genSelectHtml('Receipe', 'receipe', ['circle-cw-cw', 'circle-cw-ccw', 'circle-ccw-cw', 'circle-ccw-ccw', 'spiral-cw-cw', 'spiral-cw-ccw', 'spiral-ccw-cw', 'spiral-ccw-ccw',], prefs.receipe, 'used to remove the material') + genInputHtml('Cutting depth', 'zMovement', prefs.holeDepth, 'fa-ruler', '', 'mm') + genInputHtml('DOC', 'doc', prefs.doc, 'fa-align-justify', 'depth of cut (10% - 200% of endmill diameter)', "%") + genInputHtml('WOC', 'woc', prefs.woc, 'fa-align-justify', 'width of cut (5% - 30% of endmill diameter)', "%") + genInputHtml('Finish WOC', 'wocFinish', prefs.wocFinish, 'fa-align-justify', 'width of cut for finish path (0 disables it)', "%") + genInputHtml('Feedrate', 'feedrate', prefs.feedrate, 'fa-running', 'How fast to move the endmill in milling operation', 'mm/min') + '', width: 700, actions: [ { caption: "Generate G-Code", cls: "js-dialog-close success", onclick: function () { prefs.holeDiam = parseFloat($("#holeDiam").val()) prefs.endmillDiam = parseFloat($("#endmillDiam").val()) prefs.holeDepth = parseFloat($("#zMovement").val()) prefs.doc = parseFloat($("#doc").val()) prefs.woc = parseFloat($("#woc").val()) prefs.wocFinish = parseFloat($("#wocFinish").val()) prefs.feedrate = parseInt($("#feedrate").val()) prefs.receipe = $('#receipe').val(); const gCode = generateGCode(prefs.holeDiam, prefs.endmillDiam, prefs.holeDepth, prefs.doc, prefs.woc, prefs.wocFinish, prefs.feedrate, prefs.receipe) if (gCode) savePrefs(); } }, { caption: "Cancel", cls: "js-dialog-close alert", onclick: function () { } } ] }); // required for jest test try { module.exports = generateGCode; } catch (e) { }
I've been looking at this for the last few days and have come to the conclusion that tool length offset is not viable for this use in grbl. It simply isn't persistent enough: It gets cancelled without warning for all sorts of reasons (e.g. grbl alarm conditions, or aborting a program - I *think* that this is a feature of grbl rather than OBControl because it seems to do the same in UGS). I have come up with the following macro that, I think, achieves pretty much the same thing by adjusting the WCS Z offset in response to the results of probing tools. The obvious limitation that comes with this method is that it is only valid in one WCS (whereas TLO would carry through to every WCS), so if you are machining at multiple stations using multiple WCS, then this isn't for you. The macro adds a 'Tool Length' button to the menu that opens the dialog below: This allows you to use one tool as a reference and then to set the Z offset of the WCS to maintain the same tool height through tool changes. It uses the GCODE preset locations G30 for the probe position and G28 for a preset tool change position (hence the machine needs to be homed) and these locations need to be set before using the macro (**IMPORTANT!**). Using these locations has the advantage that if you want to adjust (e.g.) your probe position, you can just jog the machine to where you want it to start and send 'G30.1' (or G28.1 for the tool change location) and it's done without needing to edit any code. If you already use G28 for something, then just ignore the 'Go To Tool Change' button. After use, it gives the option of returning the tool to the location it was in when the dialog was opened - use with caution! Any changes in Z zero between tool changes are carried through to the new tool. There are some customisable settings near the start of the file to save you from going digging for probe speeds, etc. I'd be interested to hear if anyone tests it - I've tested it on my little CNC router (running grbl_ESP32) and it **seems** to work well, but I would regard it as un-proven at the moment. It does fall over if the probe is in contact when it shouldn't be as GRBL throws an error, but if you close the dialog and open it again, it pulls its pants back up without any consequences, as far as I can tell. I'm not a programmer and definitely not a javascript programmer, so there may be gaffes in the code, but it seems to work OK. [Edit] I have updated the code below (V1.1) in response to the issue spotted by David the swarfer Posted in good faith, but use at your own risk! Andy [Edit 2] V1.3 -11th April 23: I've updated the macro to check the 'homedRecently' status and that the spindle isn't running, etc. The homedRecently status shows that the machine needs homing after every error (e.g. syntax error in gcode file, attempting to jog beyond the machine limits, etc.) even though the position is reliable. Because of this, I've given the option to carry on regardless, and also the option to disable the check in the macro (set tc_ignorehomestatus to "yes"). I've also corrected the reporting where the WCS has been changed but the tool offset hasn't, tidied up the formatting, and set the default values for the probing cycle to be 'lower and slower'. Also, the macro can be re-run now to update any changes to the settings without it trying to install another menu button. [Edit 3] V2.2 added 17th September 23: Revision to avoid causing 'error 9' under GRBLhal. Also changed the default to ignore the homed status (set 'tc_ignorehomestatus' to "no" to restore this) and added a check on Z height to avoid errors caused by trying to raise the Z axis above machine zero when attempting to return to the original location with a longer tool installed. Also added a zipped .JSON file that can be unpacked and imported directly into Control. [Edit 4] V2.3 added 10th August 24: Only change is that the toolbar button now works in dark mode (thanks to @sharmstr ). Discussion here: Tech support for Javascript Macros in CONTROL Known issue: If the probe isn't in the expected state before probing, or the requested probe travel is > machine travel these generate hard errors that my macro can't trap. If they happen, you will need to close the dialog and re-open it. Similarly if the requested probe travel exceeds the machine travel - take care not to set G30 too low. It isn't possible to pause a program to use this macro, as (I think) the comms port isn't released while the program is paused, so each tool will need its separate file.
LASER engraving test pattern generator I have recently acquired a diode LASER to fit to my CNC router. I couldn't find anything like this easily available (outside Lightburn). Running the macro generates the GCode to produce a matrix of test patches with a range of power and feed rate settings to determine the optimum parameters for LASER marking. Each row has a different power level, and each column has a different feed rate. The parameters can be changed by editing the first few lines of the macro: Resolution (number of lines per mm); Maximum and minimum feed rates and power levels; Number of rows / columns; The height and width of each patch, and the size of the gap between them; Machine acceleration. Power settings use splindle speed units (grbl default is 0-1000, I think). The value in $30 for the maximum speed is what you need to put into the macro to get 100% power. The labels are engraved at the half way point of both power and speed settings. The label text is fixed at 3mm high which will mean that the labels overwrite each other at patch sizes less than about 3mm. Acceleration is used to compute the over-travel needed to maintain a constant speed over the engraved patches. Set x_acceleration to zero to disable this, but this will make the speeds on the right hand columns inaccurate unless speeds are very slow / accelerations are very fast. Hopefully this will be self explanatory When the macro is run, any loaded GCode will be replaced by the output of the macro. The GCode can be run or saved as required. The code uses 'M4' to control the LASER which requires Laser Mode to be enabled in grbl ($32=1). I've tested this with my machine and my LASER, but please use with caution, and at your own risk. Any problems, please reply to my post on the support thread: Tech support for Javascript Macros in CONTROL (Feedback also appreciated ). Code: // LASER Power/Speed Raster Test Grid Macro For OpenBuilds Control // V 1.2 // Change these values to customise basic operation: var feed_Max = 3500; // Maximum feed rate (mm/min): Must be less than the max X speed - $110 value in GRBL var feed_Min = 500; // Minimum feed rate (mm/min) var feed_Columns = 7; // Number of feedrate columns to draw var power_Max = 1000; // Maximum power (spindle speed units): Check value of $30 in GRBL settings for maximum power var power_Min = 100; // Minimum power (spindle speed units) var power_Rows = 10; // Number of power rows to draw var patch_Width = 5; // Width of each test patch (mm) var patch_Height = 4; // Height of each test patch (mm) var patch_Border = 1; // Space between test patches (mm) var lines_mm = 8; // Resolution of test patch (lines / mm) var x_acceleration = 320; // Machine X acceleration value (mm/s^2): Used to calculate over-travel // Check $120 value in GRBL for machine settings. Set to zero to disable over-travel // Generates GCode to produce a raster pattern grid with varying speeds and powers // Running this macro will replace any existing GCode in Control // The GCode uses M4 to control the LASER and so needs LASER mode enabled ($32 = 1 in GRBL settings) // // Use at own risk! // DAG 19/4/23 // // V 1.1 20/4/23 - corrected label power & feed calculation // V 1.2 23/4/23 - added over-travel to avoid speed errors due to acceleration // // Macro continues below... var yStep = 1/lines_mm; // Calculate raster line spacing var patchLines = (patch_Height * lines_mm).toFixed(); // Calculate the number of lines needed for each patch var rowCount; // Row reference var colCount; // Column reference var lineCount; // Raster line within each patch var labelPower = ((power_Max + power_Min)/2); // Power level for labels var labelFeed = ((feed_Max + feed_Min)/2); // Feed rate for labels var margin = ((''+power_Max).length*3)+1; // Space at left for labels - text is ~3mm per character var over_right = 0; // Distance required to decelerate from max feed var over_left = 0; // Distance required to accelerate to min feed if (x_acceleration > 0){ // Calculate over-travel if X axis acceleration is set over_right = (feed_Max / x_acceleration); over_left = (feed_Min / x_acceleration); } // Start building the GCode string that will be the eventual output var gcode = "; Laser Raster Test Pattern\n;\n"; gcode += "; " + power_Rows + " Power levels from " + power_Min + " to " + power_Max + " (spindle speed units)\n"; gcode += "; " + feed_Columns + " Feed rates from " + feed_Min + " to " + feed_Max + " mm/min\n"; gcode += "; Patch size " + patch_Width + "mm x " + patch_Height + "mm\n"; gcode += "; Generated by Openbuilds Control macro V1.2 by Misterg\n;\n"; gcode += `G21; mm-mode G90; Absolute Positioning G0 X0 Y0; Move to origin position M04 S0; Laser on ` // Add column labels for (colCount =0; colCount <feed_Columns; colCount += 1) { var pSpeed = (feed_Min +(feed_Max - feed_Min)/(feed_Columns-1) * colCount).toFixed(); var gcXval = margin + (patch_Border + patch_Width) * colCount +1; var gcYval = (patch_Border + patch_Height) * (power_Rows); var speedString = "" + pSpeed; var strlen = speedString.length; gcode += "S" + labelPower.toFixed() + " F" + labelFeed.toFixed() + "\n"; for (var i=0; i<(strlen); i++){ gcode += "G0 X" + gcXval +" Y"+(gcYval+ i*3.5)+"\n"; gcode += "G91\n" + getGcode( speedString.substring(strlen-i-1, strlen-i)) +"G90\n"; } } // Generate the pattern for (rowCount = 0; rowCount < power_Rows; rowCount += 1) { // var pPower = (power_Max - (power_Max - power_Min)/(power_Rows-1) *rowCount).toFixed(); // Power level for this row (descending) var pPower = ((power_Max - power_Min)/(power_Rows-1) *rowCount + power_Min).toFixed(); // Power level for this row (ascending) var pwrString = "" + pPower; // Convert to string for label gcode += "S" + labelPower.toFixed() + " F" + labelFeed.toFixed() + "\n"; var row_Y = (patch_Border+ patch_Height)*rowCount; // Y coordinate of current row // Generate row labels for (var i=0; i < (pwrString.length); i++){ gcode += "G0 X" + (i*3) +" Y"+row_Y + "\n"; gcode += "G91\n" + getGcode( pwrString.substring(i, i+1)) +"G90\n"; } gcode += "G0 X" + (margin - over_left) +" Y" + row_Y +"\n"; // Move to starting point for pattern // Generate raster pattern for (lineCount =0; lineCount < patchLines; lineCount += 1){ var isOdd = (lineCount & 1); //Flag to run passes in alternate directions for (colCount =0; colCount <feed_Columns; colCount += 1) { var wkgCol = colCount; if (isOdd == 1) {wkgCol = feed_Columns - colCount-1}; var pSpeed = (feed_Min +(feed_Max - feed_Min)/(feed_Columns-1) *wkgCol).toFixed(); // Speed for this column var gcXval = margin + (patch_Border + patch_Width) * wkgCol; var gcYval = row_Y + (yStep * lineCount); gcode += `G0 X` + (gcXval + patch_Width * isOdd) + ` Y` + gcYval +`\n`; gcode += `G1 X` + (gcXval + patch_Width * (isOdd ^ 1)) + ` F` + pSpeed + ` S` + pPower + `\n`; } gcode += "G91\n G0 X"+ (isOdd * over_left * -1 )+((isOdd^1) * over_right) + "\nG90\n"; // Add over-travel } } // Tidy up the end of the GCode and pass to OB COntrol gcode += `M5 S0\n`; gcode += `M2\n`; editor.session.setValue(gcode); parseGcodeInWebWorker(gcode) printLog("<span class='fg-red'>[ Laser Test Pattern ] </span><span class='fg-green'>GCODE Loaded</span>") // The End function getGcode (numeral){ // Returns GCode string representing numbers 0 - 9 // No error checking! const gc_num = []; gc_num[0] = `;zero G0 X0.857 Y3 G1 X0.286 X0.428 Y-0.143 X0.286 Y-0.428 X0.143 Y-0.715 Y-0.428 X-0.143 Y-0.715 X-0.286 Y-0.428 X-0.428 Y-0.143 X-0.286 X-0.428 Y0.143 X-0.286 Y0.428 X-0.143 Y0.715 Y0.428 X0.143 Y0.715 X0.286 Y0.428 X0.428 Y0.143 `; gc_num[1] = `;one G0 Y2.429 G1 X0.286 Y0.142 X0.428 Y0.429 Y-3 `; gc_num[2] = `;two G0 X0.143 Y2.286 G1 Y0.143 X0.143 Y0.285 X0.143 Y0.143 X0.285 Y0.143 X0.572 X0.285 Y-0.143 X0.143 Y-0.143 X0.143 Y-0.285 Y-0.286 X-0.143 Y-0.286 X-0.285 Y-0.428 X-1.429 Y-1.429 X2 `; gc_num[3] = `;three G0 X0.286 Y3 G1 X1.571 X-0.857 Y-1.143 X0.429 X0.285 Y-0.143 X0.143 Y-0.143 X0.143 Y-0.428 Y-0.286 X-0.143 Y-0.428 X-0.286 Y-0.286 X-0.428 Y-0.143 X-0.429 X-0.428 Y0.143 X-0.143 Y0.143 X-0.143 Y0.285 `; gc_num[4] = `;four G0 X2.143 Y1 G1 X-2.143 X1.429 Y2 Y-3 `; gc_num[5] = `;five G0 X1.714 Y3 G1 X-1.428 X-0.143 Y-1.286 X0.143 Y0.143 X0.428 Y0.143 X0.429 X0.428 Y-0.143 X0.286 Y-0.286 X0.143 Y-0.428 Y-0.286 X-0.143 Y-0.428 X-0.286 Y-0.286 X-0.428 Y-0.143 X-0.429 X-0.428 Y0.143 X-0.143 Y0.143 X-0.143 Y0.285 `; gc_num[6] = `;six G0 Y1 G1 X0.143 Y0.429 X0.286 Y0.285 X0.428 Y0.143 X0.143 X0.429 Y-0.143 X0.285 Y-0.285 X0.143 Y-0.429 Y-0.143 X-0.143 Y-0.428 X-0.285 Y-0.286 X-0.429 Y-0.143 X-0.143 X-0.428 Y0.143 X-0.286 Y0.286 X-0.143 Y0.571 Y0.714 X0.143 Y0.715 X0.286 Y0.428 X0.428 Y0.143 X0.286 X0.428 Y-0.143 X0.143 Y-0.286 `; gc_num[7] = `;seven G0 Y3 G1 X2 X-1.429 Y-3 `; gc_num[8] = `;eight G0 X0.714 Y3 G1 X-0.428 Y-0.143 X-0.143 Y-0.286 Y-0.285 X0.143 Y-0.286 X0.285 Y-0.143 X0.572 Y-0.143 X0.428 Y-0.143 X0.286 Y-0.285 X0.143 Y-0.286 Y-0.429 X-0.143 Y-0.285 X-0.143 Y-0.143 X-0.428 Y-0.143 X-0.572 X-0.428 Y0.143 X-0.143 Y0.143 X-0.143 Y0.285 Y0.429 X0.143 Y0.286 X0.286 Y0.285 X0.428 Y0.143 X0.572 Y0.143 X0.285 Y0.143 X0.143 Y0.286 Y0.285 X-0.143 Y0.286 X-0.428 Y0.143 X-0.572 `; gc_num[9] = `;nine G0 X1.857 Y2 G1 X-0.143 Y-0.429 X-0.285 Y-0.285 X-0.429 Y-0.143 X-0.143 X-0.428 Y0.143 X-0.286 Y0.285 X-0.143 Y0.429 Y0.143 X0.143 Y0.428 X0.286 Y0.286 X0.428 Y0.143 X0.143 X0.429 Y-0.143 X0.285 Y-0.286 X0.143 Y-0.571 Y-0.714 X-0.143 Y-0.715 X-0.285 Y-0.428 X-0.429 Y-0.143 X-0.286 X-0.428 Y0.143 X-0.143 Y0.286 `; return gc_num[numeral]; } 20/4/23 - corrected the calculation for label power & speed calculations. 22/4/23 - added photo of the default test pattern burned on plywood with a 4W LASER 23/4/23 - Updated the code to add over-travel so that the end patches are cut at the indicated speeds
Focus / Depth Of Field Test Pattern Generator This macro generates the GCode for a test pattern that varies in Z height around the current Z0 to act as an aid in checking / setting LASER focus (For example, the best focus in the image above is at about Z= +0.5.) Parameters can be changed by altering the variables at the start of the macro: The range of movement from the current Z0 - note that this is +/- , so a value of 3(say) gives a range of Z-3 to Z+3; The power and speed for the pattern (power is a percentage of the spindle units); The number and spacing of the line pattern - playing with the spacing can really highlight the linewidth broadening when the beam is out of focus. The GCode generates travel that extends beyond the edge of the pattern to allow the machine to accelerate to the programmed speed. By default, this means that the head will move into negative X coordinates (to the left of X0) to start the pattern at X0. If this causes a problem, set 'xplus_Only' to "yes" - the pattern will be shifted to the right to allow enough travel for the machine to accelerate. Set 'x_acceleration' to the actual machine X acceleration ($120) for the best results, or to zero to disable the over-travel. As ever, use at your own risk. Queries / feedback: Tech support for Javascript Macros in CONTROL Code: // LASER Focus Pattern Generator Macro For OpenBuilds Control // V0.9 // Change these values to customise basic operation: const z_Range = 3; // Z movement each side of current position (mm) const power_Percent = 80; // Engraving power (0-100%) const engrave_Speed = 2000; // Engraving speed (mm/min) const num_Lines = 8; // Number of lines in test pattern (must be >0) const line_Spacing = 0.3; // Spacing between lines (mm) const power_Max = 1000; // Maximum power in spindle speed units - Check value of $30 in GRBL settings const power_Min = 0; // Minimum power in spindle speed units - Check value of $31 in GRBL settings const x_acceleration = 320; // Machine X acceleration value (mm/s^2)- Check value of $120 n GRBL settings // (Used to calculate over-travel - Set to zero to disable over-travel) const xplus_Only = "no"; // "no" = move to left of X0 to allow acceleration // "yes" = start acceleration from X0 - pattern will be shifted to the right const tilt = 0.12; // Slope of test pattern (mm/mm) // // Generates GCode to produce a pattern with varying Z height to assist with LASER focussing // Running this macro will replace any existing GCode in Control // The GCode uses M4 to control the LASER and so needs LASER mode enabled ($32 = 1 in GRBL settings) // // Use at own risk! // DAG 23/4/23 // // Macro continues below... var p_Power = ((power_Max - power_Min) * power_Percent / 100 + power_Min).toFixed();// Calculate engraving power in spindle speed units var over_Travel = 0; // Distance required to accelerate to engraving speed if (x_acceleration > 0){ over_Travel = (engrave_Speed / x_acceleration)}; // Calculate over-travel if X axis acceleration is set var line_Length = (z_Range * 2) / tilt; // Calculate pattern length var start_X = 0; if (xplus_Only != "yes" ) {start_X = -over_Travel}; // Starting point for X travel - NOT engraving var z_Min = z_Range * (-1); var z_Max = z_Range; // Start building the GCode string that will be the eventual output var gcode = "; Laser Focus Test Pattern\n;\n"; gcode += "; Z Range +/- " + z_Range + "mm\n"; gcode += "; Generated by Openbuilds Control macro V0.9 by Misterg\n;\n"; gcode += "G21; mm-mode\nG90; Absolute Positioning\n"; gcode += "F"+ engrave_Speed +" ; Set engraving speed\n"; gcode += "M04 S" + p_Power + " ; Laser on at set power\n"; // Draw lines for (var lineCount =0; lineCount < num_Lines; lineCount ++){ var gcYval = (line_Spacing * lineCount); // Current Y position gcode += "G0 X" + start_X +" Y" + gcYval +" Z"+ z_Min + "\n"; // Rapid to start of row gcode += "G91\nG0 X" + over_Travel + "\n"; // Rapid to start of line (relative) gcode += "G1 X" + line_Length + " Z" + (z_Range * 2) + "\n"; // Cut to end of line (relative) gcode += "G0 X" + over_Travel +"\nG90\n"; // Rapid to end of deceleration zone (relative) } // Add Tick Marks gcode += "G0 Z0; Add tick marks\n"; var major_Ticks = z_Range * 2; // 1 majortick per mm var minor_Ticks = 10; // 10 minor tick marks per mm var y_Pos = (line_Spacing * (num_Lines -1)); // Bottom of tick marks var gcXval = 0; // Calculated position var line_Start = start_X + over_Travel; // Start of labels var label_Z = z_Min; var z_MajorIncrement = (z_Max - z_Min)/(major_Ticks ); var z_MinorIncrement = z_MajorIncrement / minor_Ticks; var x_Increment = line_Length / (major_Ticks * minor_Ticks); for( var i =0 ;i<= major_Ticks;i++){ gcXval = line_Start + line_Length * i / major_Ticks; label_Z = z_Min + z_MajorIncrement * i; gcode += "G0 X" + gcXval +" Y" + y_Pos +" Z" +label_Z + "\n"; // move to start of major tick gcode += "G91\nG1 Y4\n"; // draw tick & stay in G91 // add labels if (i < major_Ticks / 2){ // Add +/- sign if needed gcode += ";minus\nG0 X2.571 Y1.5\nG1 X-2.571\nG0 X3 Y-1.5\n"; } else if (i > major_Ticks /2){ gcode += ";plus\nG0 X1.286 Y0.214\nG1 Y2.572\nG0 X1.285 Y-1.286\nG1 X-2.571\nG0 X3 Y-1.5\n"; } gcode += getGcode( ""+ Math.abs(i-major_Ticks/2)) +"G90\n"; // Get numeral & switch back to G90 gcode += "G0 X" + gcXval +" Y" + y_Pos +" Z" +label_Z + "\n"; // move back to start of major tick if (i < major_Ticks){ // Fill in minor ticks for( var j = 1; j <minor_Ticks; j++){ gcode += "G91\nG0 X" + x_Increment + " Z" + z_MinorIncrement + "\n"; // Move to start of tick if (j == (minor_Ticks /2)) { gcode += "\nG1 Y3\nG0 Y-3\n"; // Draw '5s' tick } else { gcode += "\nG1 Y2\nG0 Y-2\n"; // Draw minor tick }; } gcode += "G90\n"; } } // Tidy up the end of the GCode and pass to OB COntrol gcode += "G0 X0 Y0 Z0\n"; gcode += "M5 S0\nM2\n"; // LASER off & End of program editor.session.setValue(gcode); parseGcodeInWebWorker(gcode) printLog("<span class='fg-red'>[ Laser Test Pattern ] </span><span class='fg-green'>GCODE Loaded</span>") // The End function getGcode (numeral){ // Returns GCode string representing numbers 0 - 9 // No error checking! const gc_num = []; gc_num[0] = `;zero G0 X0.857 Y3 G1 X0.286 X0.428 Y-0.143 X0.286 Y-0.428 X0.143 Y-0.715 Y-0.428 X-0.143 Y-0.715 X-0.286 Y-0.428 X-0.428 Y-0.143 X-0.286 X-0.428 Y0.143 X-0.286 Y0.428 X-0.143 Y0.715 Y0.428 X0.143 Y0.715 X0.286 Y0.428 X0.428 Y0.143 `; gc_num[1] = `;one G0 Y2.429 G1 X0.286 Y0.142 X0.428 Y0.429 Y-3 `; gc_num[2] = `;two G0 X0.143 Y2.286 G1 Y0.143 X0.143 Y0.285 X0.143 Y0.143 X0.285 Y0.143 X0.572 X0.285 Y-0.143 X0.143 Y-0.143 X0.143 Y-0.285 Y-0.286 X-0.143 Y-0.286 X-0.285 Y-0.428 X-1.429 Y-1.429 X2 `; gc_num[3] = `;three G0 X0.286 Y3 G1 X1.571 X-0.857 Y-1.143 X0.429 X0.285 Y-0.143 X0.143 Y-0.143 X0.143 Y-0.428 Y-0.286 X-0.143 Y-0.428 X-0.286 Y-0.286 X-0.428 Y-0.143 X-0.429 X-0.428 Y0.143 X-0.143 Y0.143 X-0.143 Y0.285 `; gc_num[4] = `;four G0 X2.143 Y1 G1 X-2.143 X1.429 Y2 Y-3 `; gc_num[5] = `;five G0 X1.714 Y3 G1 X-1.428 X-0.143 Y-1.286 X0.143 Y0.143 X0.428 Y0.143 X0.429 X0.428 Y-0.143 X0.286 Y-0.286 X0.143 Y-0.428 Y-0.286 X-0.143 Y-0.428 X-0.286 Y-0.286 X-0.428 Y-0.143 X-0.429 X-0.428 Y0.143 X-0.143 Y0.143 X-0.143 Y0.285 `; gc_num[6] = `;six G0 Y1 G1 X0.143 Y0.429 X0.286 Y0.285 X0.428 Y0.143 X0.143 X0.429 Y-0.143 X0.285 Y-0.285 X0.143 Y-0.429 Y-0.143 X-0.143 Y-0.428 X-0.285 Y-0.286 X-0.429 Y-0.143 X-0.143 X-0.428 Y0.143 X-0.286 Y0.286 X-0.143 Y0.571 Y0.714 X0.143 Y0.715 X0.286 Y0.428 X0.428 Y0.143 X0.286 X0.428 Y-0.143 X0.143 Y-0.286 `; gc_num[7] = `;seven G0 Y3 G1 X2 X-1.429 Y-3 `; gc_num[8] = `;eight G0 X0.714 Y3 G1 X-0.428 Y-0.143 X-0.143 Y-0.286 Y-0.285 X0.143 Y-0.286 X0.285 Y-0.143 X0.572 Y-0.143 X0.428 Y-0.143 X0.286 Y-0.285 X0.143 Y-0.286 Y-0.429 X-0.143 Y-0.285 X-0.143 Y-0.143 X-0.428 Y-0.143 X-0.572 X-0.428 Y0.143 X-0.143 Y0.143 X-0.143 Y0.285 Y0.429 X0.143 Y0.286 X0.286 Y0.285 X0.428 Y0.143 X0.572 Y0.143 X0.285 Y0.143 X0.143 Y0.286 Y0.285 X-0.143 Y0.286 X-0.428 Y0.143 X-0.572 `; gc_num[9] = `;nine G0 X1.857 Y2 G1 X-0.143 Y-0.429 X-0.285 Y-0.285 X-0.429 Y-0.143 X-0.143 X-0.428 Y0.143 X-0.286 Y0.285 X-0.143 Y0.429 Y0.143 X0.143 Y0.428 X0.286 Y0.286 X0.428 Y0.143 X0.143 X0.429 Y-0.143 X0.285 Y-0.286 X0.143 Y-0.571 Y-0.714 X-0.143 Y-0.715 X-0.285 Y-0.428 X-0.429 Y-0.143 X-0.286 X-0.428 Y0.143 X-0.143 Y0.286 `; return gc_num[numeral]; }
I published a few useful macros on my github fork of the Control software OpenBuilds-CONTROL/UsefulMacros at master · ivomirb/OpenBuilds-CONTROL DisableZ.js – improvement of sharmstr’s macro that hides the Z buttons when large jog step is selected OpenFile.js – removes the dropdown from the Gcode button, and adds a new button to reload the last file. Requires the use of my fork, which includes the "reload" functionality StoreWcs/RestoreWcs.js – stores the work offset from the last job and can restore it in case it gets lost accidentally, most commonly due to user error SmartHome.js – prevents accidental homing in case the machine was recently homed. Works better with my fork, which doesn’t reset the homing status as aggressively as the official software SmartGCode.js – restores the rapid moves from the free version of Fusion 360, detects tool changes, and other useful features. It is intended to be used with my pendant software, however the code can be incorporated in other macros. Caution – modifying the Gcode is dangerous. I’ve had very little time to test it before I disassembled my machine for maintenance and upgrades. Use at your own risk, and keep an eye on the job until you are comfortable with the modified Gcode.
Disable the Z jog buttons when 100mm is selected or continuous jog is enabled This macro is based on the original script by sharmstr - OpenBuildsHacks/Macros/HideZBtns.js at main · sharmstr/OpenBuildsHacks But with a few improvements: Instead of hiding the buttons it replaces them with blank ones, which preserves the UI layout Intercepts and blocks the keyboard shortcuts for Z jogging Hooks more keyboard actions that might affect the step size The code is streamlined and less stateful Set it to run on startup Code: // Disable the Z jog buttons when 100mm is selected or continuous jog is enabled // Based on the original script by sharmstr - https://github.com/sharmstr/OpenBuildsHacks/blob/main/Macros/HideZBtns.js // But with a few improvements: // Instead of hiding the buttons it replaces them with blank ones, which preserves the UI layout // Intercepts and blocks the keyboard shortcuts for Z jogging // Hooks more keyboard actions that might affect the step size // The code is streamlined and less stateful $(document).ready(function() { $('#zM').before('<button class="button light square xlarge m-0 ml-2" id="zM2" />'); $('#zP').before('<button class="button light square xlarge m-0 ml-2" id="zP2" />'); $('#zM2,#zP2').hide(); var zHidden = false; function updateZBtns() { var jog100 = $('#dist100').hasClass('bd-openbuilds'); var contiguous = $('#jogTypeContinuous').is(":checked"); zHidden = jog100 || contiguous; if (zHidden) { $('#zM,#zP').hide(); $('#zM2,#zP2').show(); } else { $('#zM,#zP').show(); $('#zM2,#zP2').hide(); } } // list of shortcuts that might affect the step size var captureShortcuts = ["stepP", "stepM", "incJogMode", "conJogMode"]; for (var i = 0; i < captureShortcuts.length; i++) { var shortcut = keyboardShortcuts[captureShortcuts[i]]; if (shortcut.length) { $(document).bind('keydown', shortcut, function(e) { updateZBtns(); }); } } // intercept the Z+ and Z- shortcut keys if necessary document.addEventListener('keydown', (event) => { if (zHidden) { var keyName = event.key.toLowerCase(); if (keyName == keyboardShortcuts.zP || keyName == keyboardShortcuts.zM) { event.preventDefault(); event.stopImmediatePropagation(); } } }, {capture : true}); $('#dist01,#dist1,#dist10,#dist100,#jogTypeContinuous').on('click', updateZBtns); updateZBtns(); });
Store/Restore WCS offset This is a pair of macros that protect from accidental loss of the WCS offset. Imagine you run multiple programs that need to use the same offset. Then as you are changing tools you accidentally click Set Zero instead of Goto Zero. The WCS offset is lost. Can’t count how many times it has happened to me. The StoreWcs macro runs on startup and records the WCS offset after a job finishes. Code: // Stores the WCS offset after a job. It can later be restored by RestoreWCS.js $(document).ready(function() { window.LastWcs = undefined; socket.on('jobComplete', function(data) { if (data.jobStartTime) { window.LastWcs = { X : laststatus.machine.position.offset.x, Y : laststatus.machine.position.offset.y, Z : laststatus.machine.position.offset.z, }; printLog("Storing WCS: X=" + window.LastWcs.X.toFixed(3) + ", Y=" + window.LastWcs.Y.toFixed(3) + ", Z=" + window.LastWcs.Z.toFixed(3)); } }); }); The RestoreWcs macro restores the saved offset. Code: // Restores the WCS offset, previously stored by StoreWcs.js if (window.LastWcs != undefined) { var gcode = "G10 G90 L2 P0 X" + window.LastWcs.X.toFixed(3) + " Y" + window.LastWcs.Y.toFixed(3) + " Z" + + window.LastWcs.Z.toFixed(3); sendGcode(gcode); printLog("Restoring WCS: X=" + window.LastWcs.X.toFixed(3) + ", Y=" + window.LastWcs.Y.toFixed(3) + ", Z=" + window.LastWcs.Z.toFixed(3)); } else { printLog("No WCS has been previously stored."); }
Smart Home If you click the Home button by accident (like, you click Home on the keyboard, unaware that OpenBuilds has the focus), the machine starts moving, potentially hitting something on the way. This macro replaces the behavior of the Home button to ask for confirmation if the homedRecently flag is set. Set it to run on startup Code: // Prevents accidental homing if the machine was recently homed $(document).ready(function() { var homeButton = ` <button id="homeBtn" class="ribbon-button"> <span class="icon"> <i class="fas fa-home"></i> </span> <span class="caption grblmode">Home<br>All</span> </button>` $("#homeBtn").attr('id', "oldHomeBtn"); $("#oldHomeBtn").after(homeButton); $("#oldHomeBtn").remove(); $("#homeBtn").on('click',function() { if (laststatus.machine.modals.homedRecently) { Metro.dialog.create({ title: "Home All", content: "The machine was recently homed. Do you want to home again?", actions: [ { caption: "Proceed", cls: "js-dialog-close success", onclick: home }, { caption: "Cancel", cls: "js-dialog-close", onclick: function() { // do nothing } } ], closeButton: true }); } else { home(); } }); });
Centre Finding External Features This macro works with a 3D probe to locate the centre of external part features (i.e. raised bosses). Start with the probe roughly positioned above the centre location, run the macro, and enter the approximate size of the feature. It will work with rectangular / oval features provided that the length of the largest X/Y dimension is entered. Dimensions of the feature and the probe over travel entered in the dialog *must* be sufficient for the probe to clear the feature, taking into account the likely error in the initial position, or there will be a crash. Z travel entered must be sufficient for the probe to lower enough to detect the part when probing without unwanted contact with the part or machine bed. X and Y centres can be found independently by using the check boxes at the bottom of the dialog. After the macro runs, the probe will be positioned over the centre of the part at the original Z height and the X and/or Y coordinates of the active WCS will be set to zero. Z coordinates are preserved. By default, the routine uses a fast probe to determine the rough location, then slow probe to get a more accurate position. This can be changed to a single probing operation by clearing the checkbox at the bottom right. There are options to set Z travel rate and probe retract distance, but these are hidden by default. If you want to have them in the dialog box (as below) set simpleUI = "false" on line 24 of the macro. The macro is longer and more complicated than it needs to be as it includes embedded graphics, so I haven't included the text file, just the zipped .json file. There are signposts at the top of the macro for those that want to change the default values. Default UI: Alternative UI (simpleUI = "false") Quick demo: Let me know how it goes [Edit] V2.1 10th August 2024: Added the option to display the apparent size of the feature as measured during probing. Screenshots above updated to include the 'Show Result' checkbox. If checked, the routine will show the pop-up below: Bear in mind that this will only reflect the actual size of the feature if the probe was already centred on it. (Run the routine twice if you want to be sure of this.) In all cases, the actual measurements are available in the terminal window.
Another option is to display all the comments when you load the gcode file. In your other post regarding comments in the post processor, you have the tool information and the actual material size, not just the min / max movements that the macro above gives you. Based on the gcode file you posted in the other thread, here's what you see when you load the file. Obviously your post processor needs some work to improve the quality of the information (redundant "Toolname" line, etc...) Here's the code. Add it to a new javascript macro and set it to load at startup. Code: function alertMacro() { editor.session.on('change', function(e) { var alertText = ''; if (editor.curOp && editor.curOp.command.name) { return null; } let lines = editor.session.doc.getAllLines() for (var i = 0, l = lines.length; i < l; i++) { if (lines[i].indexOf('(') != -1) { alertText = `${alertText} ${lines[i].replace('(', '').replace(')','')}<br>`; } } if (alertText !== '') { var dialog = Metro.dialog.create({ clsDialog: 'dark', title: "<i class='fas fa-exclamation-triangle'></i> JOB SETUP", content: alertText, actions: [{ caption: "Close", cls: "js-dialog-close", onclick: function() { // } }] }); } }); } $(document).ready(() => setTimeout(alertMacro, 1000));
Hi @Misterg Super useful to compare/validate the dimension with the machining part !!! Thank you so much for this superb work