var gcode; var loadedFileName = ""; var editor; var isJogWidget = false; var lastJobStartTime = false; function setWindowTitle(status) { var string = "OpenBuilds CONTROL" if (status) { string += " v" + status.driver.version } else if (laststatus) { string += " v" + laststatus.driver.version } if (loadedFileName.length > 0) { string += " / " + loadedFileName } if (!nostatusyet && laststatus.comms.interfaces.activePort) { string += " / connected to " + laststatus.comms.interfaces.activePort } $('#windowtitle').html(string) document.title = string } function getReleaseStats() { var url = "https://api.github.com/repos/OpenBuilds/OpenBuilds-CONTROL/releases/latest"; $.getJSON(url, function(data) { console.log(data) var assets = data.assets; var downloadCount = 0 for (i = 0; i < assets.length; i++) { if (assets[i].name.indexOf("exe") != -1) { downloadCount = downloadCount + assets[i].download_count; } if (assets[i].name.indexOf("dmg") != -1) { downloadCount = downloadCount + assets[i].download_count; } if (assets[i].name.indexOf("zip") != -1) { downloadCount = downloadCount + assets[i].download_count; } if (assets[i].name.indexOf("AppImage") != -1) { downloadCount = downloadCount + assets[i].download_count; } } console.log("Latest version has already been installed " + downloadCount + " times") $("#releaseStats").html(downloadCount) $("#releaseDate").html(data.published_at.split("T")[0]) }); } function getChangelog() { // Splash Screen Begin $("#changelog").empty() var template2 = `` $("#changelog").html(template2); // Update Dialog var template3 = `
Changelog:

` $("#changelogupdate").html(template3); }); } $(document).ready(function() { initDiagnostics(); // run second time to ensure checkboxes are ticked if (!isJogWidget) { init3D(); } // File Open Button compatible with Node 19+ dialogs if (!disableElectron19FileOpen) { console.log("Native Dialog not disabled in Troubleshooting") if (navigator.userAgent.indexOf('Electron') >= 0) { console.log("Native Dialog Button Enabled") $("#openGcodeBtn").hide() $("#openGcodeBtnElectron19").show() } else { console.log("Native Dialog Button Disabled") $("#openGcodeBtn").show() $("#openGcodeBtnElectron19").hide() } } else { console.log("Native Dialog is Disabled in Troubleshooting") $("#openGcodeBtn").show() $("#openGcodeBtnElectron19").hide() } if (typeof ace !== 'undefined') { editor = ace.edit("editor"); editor.$blockScrolling = Infinity; editor.session.setMode("ace/mode/cncpro"); editor.setTheme('ace/theme/sqlserver') // editor.setOption('printMarginColumn', 0) editor.setAutoScrollEditorIntoView(true); editor.session.setValue('; No GCODE yet - please Load a GCODE file from the Open GCODE button'); // from samplefile.js editor.setShowPrintMargin(false); editor.getSession().on('change', function() { // parseGcodeInWebWorker(editor.getValue()) }); } function setposition(e) { var bodyOffsets = document.body.getBoundingClientRect(); tempX = e.pageX //- bodyOffsets.left; tempY = e.pageY; // console.log(tempX); var offset = $("#editorContextMenu").offset(); $("#editorContextMenu").css({ display: 'block', left: e.pageX, top: e.pageY }); } if (editor) { editor.container.addEventListener("contextmenu", function(e) { setposition(e); e.preventDefault(); $('.linenumber').html((editor.getSelectionRange().start.row + 1)); // alert('success! - rightclicked line ' + (editor.getSelectionRange().start.row + 1)); }, false); } var fileOpen = document.getElementById('file'); if (fileOpen) { fileOpen.addEventListener('change', readFile, false); } $.get("/gcode").done(function(data) { // console.log(data.length) if (data.length > 2) { if (data.length > 10000000) { gcode = this.result editor.session.setValue("GCODE is too large (" + (data.length / 1024).toFixed(0) + "kB) to load into the GCODE Editor. \nIf you need to edit it inside CONTROL, please use a standalone text editing application and reload it "); } else { editor.session.setValue(data); gcode = false; } parseGcodeInWebWorker(data) $('#controlTab').click() if (!webgl) { $('#gcodeviewertab').click(); } else { $('#gcodeeditortab').click() } jobNeedsHoming(); } }); getChangelog() setInterval(function() { setWindowTitle(); }, 1000) const element = new Image(); Object.defineProperty(element, 'id', { get: function() { /* Call callback function here */ socket.emit("maximize", true) console.log("%c ", "background-image: url('https://openbuilds.com/styles/uix/uix/OpenBuildsHeader_logo.png'); font-size: 41px; background-repeat: no-repeat; background-size: 183px 41px; "); console.log('%cOpenBuilds CONTROL Devtools', 'font-weight: bold; font-size: 20px;color: rgb(50,80,188); text-shadow: 1px 1px 0 rgb(0,00,39)'); console.log('%c', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%cGeneral: Check for any errors, messages as requested by our support team', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%c', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%cConsole Commands:', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%cAccess the last received feedback data (positions', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%coffsets, probes, comms, queues, etc )', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%claststatus', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%cAccess the Grbl Settings on the controller', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%cgrblParams', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c; Clears the console screen', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%cconsole.clear()', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c; Print a log entry/message to the Serial Log', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%cprintLog("string")', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%cAccess the running/last ran gcode via API', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%c$.get("/gcode", function(data) { //do something with gcode data });', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c; Send a job, ideal for macros, jobs. Can display a message when complete.', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%c; Set isJob to store for access via GET /gcode if needed', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%csocket.emit("runJob", {', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c data: gcode-commands,', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c isJob: false,', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c completedMsg: "message displayed upon completion ",', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c});', 'font-weight: regular; font-size: 12px;color: black; '); console.log('%c; Send the GCODE string to the controller, ideal for single commands', 'font-weight: bold; font-size: 12px;color: black; '); console.log('%csendGcode("gcode-string")', 'font-weight: regular; font-size: 12px;color: black; '); } }); console.log('%c', element); }); function runJobFile() { if (gcode) { var formData = new FormData(); var blob = new Blob([gcode], { type: 'text/plain' }); var fileOfBlob = new File([blob], 'upload.gcode'); formData.append("file", fileOfBlob); var xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { console.log(xhr.response) } }; // Add any event handlers here... xhr.open('POST', '/runjob', true); xhr.send(formData); printLog(`[ GCODE Parser ] GCODE File (from memory) sent to backend `); } else { // v1.0.329 Removed as a test for random issue with Websocket Disconnects on some files, using http post for both // socket.emit('runJob', { // data: editor.getValue(), // isJob: true, // fileName: loadedFileName // }); var formData = new FormData(); var blob = new Blob([editor.getValue()], { type: 'text/plain' }); var fileOfBlob = new File([blob], 'upload.gcode'); formData.append("file", fileOfBlob); var xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { console.log(xhr.response) } }; // Add any event handlers here... xhr.open('POST', '/runjob', true); xhr.send(formData); printLog(`[ GCODE Parser ] GCODE File (from gcode editor) sent to backend `); } lastJobStartTime = new Date().getTime() } function readFile(evt) { console.group("New FileOpen Event:"); console.log(evt); console.groupEnd(); // Close the menu $("#drop1").dropdown("toggle"); // Files var files = evt.target.files || evt.dataTransfer.files; for (var i = 0; i < files.length; i++) { loadFile(files[i]); } document.getElementById('file').value = ''; } // load file function loadFile(f) { // Filereader if (f) { var r = new FileReader(); // if (f.name.match(/.gcode$/i)) { r.readAsText(f); r.onload = function(event) { if (this.result.length > (20 * 1024 * 1024)) { gcode = this.result editor.session.setValue("File " + f.name + " is too large (" + (this.result.length / 1024).toFixed(0) + "kB) to load into the GCODE Editor. \nIf you need to edit it inside CONTROL, please use a standalone text editing application and reload it "); } else { editor.session.setValue(this.result); gcode = false; } loadedFileName = f.name; setWindowTitle() if (webgl) { printLog(`[ GCODE Parser ] GCODE File Loaded, please wait while we render a preview... `); } else { printLog(`[ GCODE Parser ] GCODE File Loaded `); } parseGcodeInWebWorker(this.result) jobNeedsHoming(); }; // } } } function jobNeedsHoming() { if (editor.getValue().lastIndexOf("G53") != -1 || editor.getValue().lastIndexOf("g53") != -1) { if (laststatus !== undefined) { if (laststatus.machine.modals.homedRecently == false) { var dialog = Metro.dialog.create({ clsDialog: 'dark', title: " Job uses Machine Coordinates:", content: " Tip: The GCODE file you loaded contains G53 commands. Please make sure to HOME the machine to establish the Machine Coordinate (G53) System properly to prevent crashes.", actions: [{ caption: "Close", cls: "js-dialog-close", onclick: function() { // } }] }); } } } } function versionCompare(v1, v2, options) { var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, v1parts = v1.split('.'), v2parts = v2.split('.'); function isValidPart(x) { return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); } if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { return NaN; } if (zeroExtend) { while (v1parts.length < v2parts.length) v1parts.push("0"); while (v2parts.length < v1parts.length) v2parts.push("0"); } if (!lexicographical) { v1parts = v1parts.map(Number); v2parts = v2parts.map(Number); } for (var i = 0; i < v1parts.length; ++i) { if (v2parts.length == i) { return 1; } if (v1parts[i] == v2parts[i]) { continue; } else if (v1parts[i] > v2parts[i]) { return 1; } else { return -1; } } if (v1parts.length != v2parts.length) { return -1; } return 0; } function isWebGLAvailable() { try { const canvas = document.createElement('canvas'); return !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))); } catch (e) { return false; } } function isWebGL2Available() { try { const canvas = document.createElement('canvas'); return !!(window.WebGL2RenderingContext && canvas.getContext('webgl2')); } catch (e) { return false; } } function getWebGLErrorMessage() { return getErrorMessage(1); } function getWebGL2ErrorMessage() { return getErrorMessage(2); } function getErrorMessage(version) { const names = { 1: 'WebGL', 2: 'WebGL 2' }; const contexts = { 1: window.WebGLRenderingContext, 2: window.WebGL2RenderingContext }; let message = 'Your $0 does not seem to support $1: See http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation to learn more'; if (contexts[version]) { message = message.replace('$0', 'graphics card'); } else { message = message.replace('$0', 'browser'); } message = message.replace('$1', names[version]); return message; } var webgl = (function() { if (disable3Dviewer) { return false; } else if (screen.availHeight < 650) { // On screens thats not tall enough, disable 3D view - it just doesn't fit return false; } else { // console.log("Testing WebGL") try { if (isWebGLAvailable() || isWebGL2Available()) { return true }; } catch (e) { return false; } } })(); function saveGcode() { var blob = new Blob([editor.getValue()], { type: "plain/text" }); invokeSaveAsDialog(blob, 'edited-gcode.gcode'); } function invokeSaveAsDialog(file, fileName) { if (!file) { throw 'Blob object is required.'; } if (!file.type) { file.type = 'text/plain'; } var fileExtension = file.type.split('/')[1]; if (fileName && fileName.indexOf('.') !== -1) { var splitted = fileName.split('.'); fileName = splitted[0]; fileExtension = splitted[1]; } var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { return navigator.msSaveOrOpenBlob(file, fileFullName); } else if (typeof navigator.msSaveBlob !== 'undefined') { return navigator.msSaveBlob(file, fileFullName); } var hyperlink = document.createElement('a'); hyperlink.href = URL.createObjectURL(file); // hyperlink.target = '_blank'; hyperlink.download = fileFullName; if (!!navigator.mozGetUserMedia) { hyperlink.onclick = function() { (document.body || document.documentElement).removeChild(hyperlink); }; (document.body || document.documentElement).appendChild(hyperlink); } var evt = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); hyperlink.dispatchEvent(evt); if (!navigator.mozGetUserMedia) { URL.revokeObjectURL(hyperlink.href); } } Date.prototype.yyyymmdd = function() { var mm = this.getMonth() + 1; // getMonth() is zero-based var dd = this.getDate(); return [this.getFullYear(), (mm > 9 ? '' : '0') + mm, (dd > 9 ? '' : '0') + dd ].join('-'); }; function timeConvert(n) { var num = n; var hours = (num / 60); var rhours = Math.floor(hours); var minutes = (hours - rhours) * 60; var rminutes = Math.round(minutes); //return num + " minutes = " + rhours + " hour(s) and " + rminutes + " minute(s)."; if (rhours < 10) { rhours = "0" + rhours } if (rminutes < 10) { rminutes = "0" + rminutes } return rhours + "h:" + rminutes + "m"; } function toTitleCase(str) { return str.replace(/(?:^|\s)\w/g, function(match) { return match.toUpperCase(); }); }