//these settings are defined in the firmware (conf_defaults.lua) and will be initialized in loadSettings() var settings = { "network.ap.ssid": "d3d-ap-%%MAC_ADDR_TAIL%%", "network.ap.address": "192.168.10.1", "network.ap.netmask": "255.255.255.0", "printer.temperature": 220, "printer.maxObjectHeight": 150, "printer.layerHeight": 0.2, "printer.wallThickness": 0.7, "printer.screenToMillimeterScale": 0.3, "printer.speed": 50, "printer.travelSpeed": 200, "printer.filamentThickness": 2.85, "printer.enableTraveling": true, "printer.useSubLayers": true, "printer.firstLayerSlow": true, "printer.autoWarmUp": true, "printer.simplify.iterations": 10, "printer.simplify.minNumPoints": 15, "printer.simplify.minDistance": 3, "printer.retraction.enabled": true, "printer.retraction.speed": 50, "printer.retraction.minDistance": 1, "printer.retraction.amount": 5, "printer.autoWarmUpCommand": "M104 S220 (hardcoded temperature)" } function SettingsWindow() { this.wifiboxURL; this.wifiboxCGIBinURL this.window; this.form; this.timeoutTime = 3000; this.retryDelay = 2000; // retry setTimout delay this.retryRetrieveNetworkStatusDelayTime = 1000;// retry setTimout delay this.retryLoadSettingsDelay; // retry setTimout instance this.retrySaveSettingsDelay; // retry setTimout instance this.retryRetrieveNetworkStatusDelay;// retry setTimout instance this.apFieldSet; this.clientFieldSet; this.networks; this.currentNetwork; // the ssid of the network the box is on this.selectedNetwork; // the ssid of the selected network in the client mode settings this.currentLocalIP = ""; this.clientModeState = SettingsWindow.NOT_CONNECTED; this.currentAP; this.apModeState = SettingsWindow.NO_AP; // after switching wifi network or creating a access point we delay the status retrieval // because the webserver needs time to switch this.retrieveNetworkStatusDelay; // setTimout delay this.retrieveNetworkStatusDelayTime = 1000; // Events SettingsWindow.SETTINGS_LOADED = "settingsLoaded"; // network client mode states SettingsWindow.NOT_CONNECTED = "not connected"; // also used as first item in networks list SettingsWindow.CONNECTED = "connected"; SettingsWindow.CONNECTING = "connecting"; SettingsWindow.CONNECTING_FAILED = "connecting failed" // network access point mode states SettingsWindow.NO_AP = "no ap"; SettingsWindow.AP = "ap"; SettingsWindow.CREATING_AP = "creating ap"; SettingsWindow.API_CONNECTING_FAILED = -1 SettingsWindow.API_NOT_CONNECTED = 0 SettingsWindow.API_CONNECTING = 1 SettingsWindow.API_CONNECTED = 2 SettingsWindow.API_CREATING = 3 SettingsWindow.API_CREATED = 4 // network mode SettingsWindow.NETWORK_MODE_NEITHER = "neither"; SettingsWindow.NETWORK_MODE_CLIENT = "clientMode"; SettingsWindow.NETWORK_MODE_ACCESS_POINT = "accessPointMode"; this.networkMode = SettingsWindow.NETWORK_MODE_NEITHER; this.updatePanel = new UpdatePanel(); var self = this; this.init = function(wifiboxURL,wifiboxCGIBinURL) { this.wifiboxURL = wifiboxURL; this.wifiboxCGIBinURL = wifiboxCGIBinURL; this.window = $("#settings"); this.window.find(".btnOK").click(this.submitwindow); this.window.find(".settingsContainer").load("settings.html", function() { console.log("Settings:finished loading settings.html, now loading settings..."); self.form = self.window.find("form"); self.form.submit(function (e) { self.submitwindow(e) }); self.loadSettings(); var btnAP = self.form.find("label[for='ap']"); var btnClient = self.form.find("label[for='client']"); var btnRefresh = self.form.find("#refreshNetworks"); var btnConnect = self.form.find("#connectToNetwork"); var btnCreate = self.form.find("#createAP"); var networkSelector = self.form.find("#network"); self.apFieldSet = self.form.find("#apSettings"); self.clientFieldSet = self.form.find("#clientSettings"); btnAP.on('touchstart mousedown',self.showAPSettings); btnClient.on('touchstart mousedown',self.showClientSettings); btnRefresh.on('touchstart mousedown',self.refreshNetworks); btnConnect.on('touchstart mousedown',self.connectToNetwork); btnCreate.on('touchstart mousedown',self.createAP); networkSelector.change(self.networkSelectorChanged); // update panel var $updatePanelElement = self.form.find("#updatePanel"); self.updatePanel.init(wifiboxURL,$updatePanelElement); }); } this.submitwindow = function(e) { e.preventDefault(); e.stopPropagation(); self.saveSettings(self.readForm(),function(){ self.hideSettings(); }); clearTimeout(self.retryRetrieveNetworkStatusDelay); } this.showSettings = function() { console.log("f:showSettings()"); this.loadSettings(); // reload settings // this.window.css("display","table"); $("#contentOverlay").fadeIn(375, function() { document.body.removeEventListener('touchmove',prevent,false); }); } this.hideSettings = function() { $("#contentOverlay").fadeOut(375, function() { document.body.addEventListener('touchmove',prevent,false); // self.window.css("display","none"); }); } this.loadSettings = function() { if (!communicateWithWifibox) { console.log(" communicateWithWifibox is false: settings aren't being loaded from wifibox...") return; } console.log("Settings:loadSettings() >> getting new data..."); $.ajax({ url: this.wifiboxURL + "/config/all", dataType: 'json', timeout: this.timeoutTime, success: function(response){ console.log("Settings:loadSettings response: ",response); settings = response.data; console.log(" settings: ",settings); self.fillForm(); $(document).trigger(SettingsWindow.SETTINGS_LOADED); } }).fail(function() { console.log("Settings:loadSettings: failed"); clearTimeout(self.retryLoadSettingsDelay); self.retryLoadSettingsDelay = setTimeout(function() { self.loadSettings() },self.retryDelay); // retry after delay }); this.refreshNetworks(); this.retrieveNetworkStatus(false); } this.fillForm = function() { console.log("SettingsWindow:fillForm"); //fill form with loaded settings var selects = this.form.find("select"); selects.each( function(index,element) { var element = $(element); element.val(settings[element.attr('name')]); }); var inputs = this.form.find("input"); inputs.each( function(index,element) { var element = $(element); //console.log("printer setting input: ",index,element.attr("type"),element.attr('name')); //,element); switch(element.attr("type")) { case "text": case "number": element.val(settings[element.attr('name')]); break; case "checkbox": element.prop('checked', settings[element.attr('name')]); break; } }); var textareas = this.form.find("textarea"); textareas.each( function(index,element) { var element = $(element); var value = settings[element.attr('name')]; element.val(value); }); } this.saveSettings = function(newSettings,complete) { settings = newSettings; // store new settings in global settings if (communicateWithWifibox) { $.ajax({ url: this.wifiboxURL + "/config", type: "POST", data: newSettings, dataType: 'json', timeout: this.timeoutTime, success: function(response){ console.log("Settings:saveSettings response: ",response); if(response.status == "error") { clearTimeout(self.retrySaveSettingsDelay); self.retrySaveSettingsDelay = setTimeout(function() { self.saveSettings(settings) },self.retryDelay); // retry after delay } else { var data = response.data; var validation = data.validation; self.clearValidationErrors(); var validated = true; $.each(validation, function(key, val) { if (val != "ok") { console.log("ERROR: setting '" + key + "' not successfully set. Message: " + val); self.displayValidationError(key,val); validated = false; } }); settings.substituted_ssid = data.substituted_ssid; if(complete && validated) complete(); } } }).fail(function() { console.log("Settings:saveSettings: failed"); clearTimeout(self.retrySaveSettingsDelay); self.retrySaveSettingsDelay = setTimeout(function() { self.saveSettings(settings) },self.retryDelay); // retry after delay }); } } this.displayValidationError = function(key,msg) { var formElement = self.form.find("[name|='"+key+"']"); console.log("formElement: ",formElement); formElement.addClass("error"); var errorMsg = "

"+msg+"

" formElement.after(errorMsg); } this.clearValidationErrors = function() { var formElements = self.form.find(".error"); formElements.each( function(index,element) { $(element).removeClass("error"); }); } this.readForm = function() { //console.log("SettingsWindow:readForm"); var settings = {}; var selects = self.form.find("select"); selects.each( function(index,element) { var element = $(element); if(element.attr('name') != "network.client.network") { settings[element.attr('name')] = element.val(); } }); var inputs = self.form.find("input"); inputs.each( function(index,element) { var element = $(element); switch(element.attr("type")) { case "text": case "number": settings[element.attr('name')] = element.val(); break; case "checkbox": settings[element.attr('name')] = element.prop('checked') break; } }); var textareas = self.form.find("textarea"); textareas.each( function(index,element) { var element = $(element); settings[element.attr('name')] = element.val(); }); //console.log(settings); return settings; } /* * Networks ui */ this.showAPSettings = function() { self.apFieldSet.show(); self.clientFieldSet.hide(); } this.showClientSettings = function() { self.clientFieldSet.show(); self.apFieldSet.hide(); } this.refreshNetworks = function() { console.log("Settings:refreshNetworks"); if (communicateWithWifibox) { $.ajax({ url: self.wifiboxURL + "/network/scan", type: "GET", dataType: 'json', timeout: self.timeoutTime, success: function(response){ console.log("Settings:refreshNetworks response: ",response); if(response.status == "error") { //clearTimeout(self.retrySaveSettingsDelay); //self.retrySaveSettingsDelay = setTimeout(function() { self.saveSettings() },self.retryDelay); // retry after delay } else { var networks = response.data.networks self.networks = {}; var foundCurrentNetwork = false; var networkSelector = self.form.find("#network"); networkSelector.empty(); networkSelector.append( $("").val(SettingsWindow.NOT_CONNECTED).html("not connected") ); $.each(networks, function(index,element) { if(element.ssid == self.currentNetwork) { foundCurrentNetwork = true; } networkSelector.append( $("").val(element.ssid).html(element.ssid) ); self.networks[element.ssid] = element; }); if(foundCurrentNetwork) { networkSelector.val(self.currentNetwork); self.selectNetwork(self.currentNetwork); } } } }).fail(function() { }); } } this.retrieveNetworkStatus = function(connecting) { //console.log("Settings:retrieveNetworkStatus"); if (communicateWithWifibox) { $.ajax({ url: self.wifiboxURL + "/network/status", type: "GET", dataType: 'json', timeout: self.timeoutTime, success: function(response){ console.log("Settings:retrieveNetworkStatus response: ",response); if(response.status == "error") { } else { var data = response.data; if(typeof data.status === 'string') { data.status = parseInt(data.status); } //console.log(" data.status: ",data.status,data.statusMessage); // Determine which network settings to show switch(data.status) { case SettingsWindow.API_NOT_CONNECTED: //console.log(" not connected & not a access point"); self.apFieldSet.show(); self.clientFieldSet.show(); self.networkMode = SettingsWindow.NETWORK_MODE_NEITHER; break; case SettingsWindow.API_CONNECTING_FAILED: case SettingsWindow.API_CONNECTING: case SettingsWindow.API_CONNECTED: //console.log(" client mode"); self.form.find("#client").prop('checked',true); self.apFieldSet.hide(); self.clientFieldSet.show(); if(data.status == SettingsWindow.API_CONNECTED) { var networkSelector = self.form.find("#network"); networkSelector.val(data.ssid); self.currentNetwork = data.ssid; self.currentLocalIP = data.localip; self.selectNetwork(data.ssid); } else { self.currentLocalIP = "" } self.networkMode = SettingsWindow.NETWORK_MODE_CLIENT; break; case SettingsWindow.API_CREATING: case SettingsWindow.API_CREATED: //console.log(" access point mode"); self.form.find("#ap").prop('checked',true); self.apFieldSet.show(); self.clientFieldSet.hide(); self.currentNetwork = undefined; self.selectNetwork(SettingsWindow.NOT_CONNECTED); var networkSelector = self.form.find("#network"); networkSelector.val(SettingsWindow.NOT_CONNECTED); if(data.ssid && data.status == SettingsWindow.API_CREATED) { self.currentAP = data.ssid; } self.networkMode = SettingsWindow.NETWORK_MODE_ACCESS_POINT; break; } self.updatePanel.setNetworkMode(self.networkMode); // update status message switch(data.status) { case SettingsWindow.API_CONNECTING_FAILED: self.setClientModeState(SettingsWindow.CONNECTING_FAILED,data.statusMessage); self.setAPModeState(SettingsWindow.NO_AP,""); break; case SettingsWindow.API_NOT_CONNECTED: self.setClientModeState(SettingsWindow.NOT_CONNECTED,""); self.setAPModeState(SettingsWindow.NO_AP,""); break; case SettingsWindow.API_CONNECTING: self.setClientModeState(SettingsWindow.CONNECTING,""); self.setAPModeState(SettingsWindow.NO_AP,""); break; case SettingsWindow.API_CONNECTED: self.setClientModeState(SettingsWindow.CONNECTED,""); self.setAPModeState(SettingsWindow.NO_AP,""); break; case SettingsWindow.API_CREATING: self.setClientModeState(SettingsWindow.NOT_CONNECTED,""); self.setAPModeState(SettingsWindow.CREATING_AP,""); break; case SettingsWindow.API_CREATED: self.setClientModeState(SettingsWindow.NOT_CONNECTED,""); self.setAPModeState(SettingsWindow.AP,""); break; } // Keep checking for updates? if(connecting) { switch(data.status) { case SettingsWindow.API_CONNECTING: case SettingsWindow.API_CREATING: clearTimeout(self.retryRetrieveNetworkStatusDelay); self.retryRetrieveNetworkStatusDelay = setTimeout(function() { self.retrieveNetworkStatus(connecting) },self.retryRetrieveNetworkStatusDelayTime); // retry after delay break; } } } } }).fail(function() { console.log("Settings:retrieveNetworkStatus: failed"); clearTimeout(self.retryRetrieveNetworkStatusDelay); self.retryRetrieveNetworkStatusDelay = setTimeout(function() { self.retrieveNetworkStatus(connecting) },self.retryDelay); // retry after delay }); } } this.networkSelectorChanged = function(e) { var selectedOption = $(this).find("option:selected"); self.selectNetwork(selectedOption.val()); } this.selectNetwork = function(ssid) { console.log("select network: ",ssid); if(ssid == "") return; console.log(" checked"); this.selectedNetwork = ssid; if(this.networks == undefined || ssid == SettingsWindow.NOT_CONNECTED) { this.hideWiFiPassword(); } else { var network = this.networks[ssid]; if(network.encryption == "none") { this.hideWiFiPassword(); } else { this.showWiFiPassword(); } this.form.find("#password").val(""); } } this.showWiFiPassword = function() { this.form.find("#passwordLabel").show(); this.form.find("#password").show(); } this.hideWiFiPassword = function() { this.form.find("#passwordLabel").hide(); this.form.find("#password").hide(); } this.setClientModeState = function(state,msg) { var field = this.form.find("#clientModeState"); var btnConnect = self.form.find("#connectToNetwork"); switch(state) { case SettingsWindow.NOT_CONNECTED: btnConnect.removeAttr("disabled"); field.html("Not connected"); break; case SettingsWindow.CONNECTED: btnConnect.removeAttr("disabled"); var fieldText = "Connected to: "+this.currentNetwork+"."; if(this.currentLocalIP != undefined && this.currentLocalIP != "") { var a = ""+this.currentLocalIP+""; fieldText += " (IP: "+a+")"; } field.html(fieldText); break; case SettingsWindow.CONNECTING: btnConnect.attr("disabled", true); field.html("Connecting... Reconnect by connecting your device to "+this.selectedNetwork+" and going to connect.doodle3d.com"); break; case SettingsWindow.CONNECTING_FAILED: btnConnect.removeAttr("disabled"); field.html(msg); break; } this.clientModeState = state; } this.setAPModeState = function(state,msg) { var field = this.form.find("#apModeState"); var btnCreate = this.form.find("#createAP"); switch(state) { case SettingsWindow.NO_AP: btnCreate.removeAttr("disabled"); field.html("Not currently a access point"); break; case SettingsWindow.AP: btnCreate.removeAttr("disabled"); field.html("Is access point: "+this.currentAP+""); break; case SettingsWindow.CREATING_AP: btnCreate.attr("disabled", true); field.html("Creating access point... Reconnect by connecting your device to "+settings.substituted_ssid+" and going to draw.doodle3d.com"); break; } this.apModeState = state; } this.connectToNetwork = function() { console.log("connectToNetwork"); if(self.selectedNetwork == undefined) return; var postData = { ssid:self.selectedNetwork, phrase:self.form.find("#password").val(), recreate:true } console.log(" postData: ",postData); if (communicateWithWifibox) { // save network related settings and on complete, connect to network self.saveSettings(self.readForm(),function() { $.ajax({ url: self.wifiboxCGIBinURL + "/network/associate", type: "POST", data: postData, dataType: 'json', timeout: self.timeoutTime, success: function(response){ console.log("Settings:connectToNetwork response: ",response); } }).fail(function() { console.log("Settings:connectToNetwork: timeout (normal behavior)"); //clearTimeout(self.retrySaveSettingsDelay); //self.retrySaveSettingsDelay = setTimeout(function() { self.saveSettings() },self.retryDelay); // retry after delay }); }); } self.setClientModeState(SettingsWindow.CONNECTING,""); // after switching wifi network or creating a access point we delay the status retrieval // because the webserver needs time to switch clearTimeout(self.retrieveNetworkStatusDelay); self.retrieveNetworkStatusDelay = setTimeout(function() { self.retrieveNetworkStatus(true) },self.retrieveNetworkStatusDelayTime); } this.createAP = function() { console.log("createAP"); if (communicateWithWifibox) { // save network related settings and on complete, create access point self.saveSettings(self.readForm(),function() { self.setAPModeState(SettingsWindow.CREATING_AP); // get latest substituted ssid $.ajax({ url: self.wifiboxCGIBinURL + "/network/openap", type: "POST", dataType: 'json', timeout: self.timeoutTime, success: function(response){ console.log("Settings:createAP response: ",response); } }).fail(function() { console.log("Settings:createAP: timeout (normal behavior)"); //clearTimeout(self.retrySaveSettingsDelay); //self.retrySaveSettingsDelay = setTimeout(function() { self.saveSettings() },self.retryDelay); // retry after delay }); self.setAPModeState(SettingsWindow.CREATING_AP,""); // after switching wifi network or creating a access point we delay the status retrieval // because the webserver needs time to switch clearTimeout(self.retrieveNetworkStatusDelay); self.retrieveNetworkStatusDelay = setTimeout(function() { self.retrieveNetworkStatus(true) },self.retrieveNetworkStatusDelayTime); }); } } } /************************* * * * FROM DOODLE3D.INI * */ //TODO: find all references to these variables, replace them and finally remove these. var objectHeight = 20; var layerHeight = .2; //var wallThickness = .5; //var hop = 0; //var speed = 70; //var travelSpeed = 200; var enableTraveling = true; //var filamentThickness = 2.89; var minScale = .3; var maxScale = 1; var shape = "%"; var twists = 0; //var useSubLayers = true; //var debug = false; // debug moved to main.js var loglevel = 2; //var zOffset = 0; var serverport = 8888; var autoLoadImage = "hand.txt"; var loadOffset = [0, 0]; // x en y ? var showWarmUp = true; var loopAlways = false; var firstLayerSlow = true; var useSubpathColors = false; var autoWarmUp = true; //var maxObjectHeight = 150; var maxScaleDifference = .1; var frameRate = 60; var quitOnEscape = true; var screenToMillimeterScale = .3; // 0.3 //var targetTemperature = 220; //var simplifyiterations = 10; //var simplifyminNumPoints = 15; //var simplifyminDistance = 3; //var retractionspeed = 50; //var retractionminDistance = 5; //var retractionamount = 3; var sideis3D = true; var sidevisible = true; var sidebounds = [900, 210, 131, 390]; var sideborder = [880, 169, 2, 471]; var windowbounds = [0, 0, 800, 500]; var windowcenter = true; var windowfullscreen = false; var autoWarmUpCommand = "M104 S230"; //var checkTemperatureInterval = 3; var autoWarmUpDelay = 3; function UpdatePanel() { this.wifiboxURL; this.element; this.statusCheckInterval = 1000; this.statusCheckDelayer; // setTimout instance this.installedDelay = 60*1000; // Since we can't retrieve status during installation we show the installed text after a fixed delay this.installedDelayer; // setTimout instance this.retryDelay = 1000; this.retryDelayer; // setTimout instance //this.timeoutTime = 3000; this.canUpdate = false; this.currentVersion = ""; this.newestVersion; this.progress; this.imageSize; // states from api, see Doodle3D firmware src/script/d3d-updater.lua UpdatePanel.NONE = 1; // default state UpdatePanel.DOWNLOADING = 2; UpdatePanel.DOWNLOAD_FAILED = 3; UpdatePanel.IMAGE_READY = 4; // download successfull and checked UpdatePanel.INSTALLING = 5; UpdatePanel.INSTALLED = 6; UpdatePanel.INSTALL_FAILED = 7; this.state; // update state from api this.stateText = ""; // update state text from api this.networkMode; // network modes from SettingsWindow var self = this; this.init = function(wifiboxURL,updatePanelElement) { this.wifiboxURL = wifiboxURL; this.element = updatePanelElement; this.btnUpdate = this.element.find("#update"); this.statusDisplay = this.element.find("#updateState"); this.infoDisplay = this.element.find("#updateInfo"); this.btnUpdate.click(this.update); this.checkStatus(false); } this.update = function() { console.log("UpdatePanel:update"); self.downloadUpdate(); } this.downloadUpdate = function() { console.log("UpdatePanel:downloadUpdate"); $.ajax({ url: self.wifiboxURL + "/update/download", type: "POST", dataType: 'json', success: function(response){ console.log("UpdatePanel:downloadUpdate response: ",response); } }).fail(function() { console.log("UpdatePanel:downloadUpdate: failed"); }); self.setState(UpdatePanel.DOWNLOADING); self.startCheckingStatus(); } this.installUpdate = function() { console.log("UpdatePanel:installUpdate"); self.stopCheckingStatus(); $.ajax({ url: self.wifiboxURL + "/update/install", type: "POST", dataType: 'json', success: function(response){ console.log("UpdatePanel:installUpdate response: ",response); } }).fail(function() { console.log("UpdatePanel:installUpdate: no respons (there shouldn't be)"); }); self.setState(UpdatePanel.INSTALLING); clearTimeout(self.installedDelayer); self.installedDelayer = setTimeout(function() { self.setState(UpdatePanel.INSTALLED) },self.installedDelay); } this.startCheckingStatus = function() { clearTimeout(self.statusCheckDelayer); clearTimeout(self.retryDelayer); self.statusCheckDelayer = setTimeout(function() { self.checkStatus(true) },self.statusCheckInterval); } this.stopCheckingStatus = function() { clearTimeout(self.statusCheckDelayer); clearTimeout(self.retryDelayer); } this.checkStatus = function(keepChecking) { if (!communicateWithWifibox) return; $.ajax({ url: self.wifiboxURL + "/update/status", type: "GET", dataType: 'json', //timeout: self.timeoutTime, success: function(response){ console.log("UpdatePanel:checkStatus response: ",response); // Keep checking ? if(keepChecking) { switch(self.state){ case UpdatePanel.DOWNLOADING: case UpdatePanel.INSTALLING: clearTimeout(self.statusCheckDelayer); self.statusCheckDelayer = setTimeout(function() { self.checkStatus(keepChecking) },self.statusCheckInterval); break; } } if(response.status != "error") { var data = response.data; self.handleStatusData(data); } } }).fail(function() { //console.log("UpdatePanel:checkStatus: failed"); if(keepChecking) { clearTimeout(self.retryDelayer); self.retryDelayer = setTimeout(function() { self.checkStatus(keepChecking) },self.retryDelay); // retry after delay } }); } this.handleStatusData = function(data) { //console.log("UpdatePanel:handleStatusData"); self.canUpdate = data.can_update; if(self.currentVersion != data.current_version || self.newestVersion != data.newest_version) { self.currentVersion = data.current_version; self.newestVersion = data.newest_version; self.updateInfoDisplay(); } self.stateText = data.state_text; self.progress = data.progress; // not always available self.imageSize = data.image_size; // not always available self.setState(data.state_code); switch(this.state){ case UpdatePanel.IMAGE_READY: self.installUpdate(); break; } } this.setState = function(newState) { if(this.state == newState) return; console.log("UpdatePanel:setState: ",this.state," > ",newState,"(",this.stateText,") (networkMode: ",self.networkMode,") (newestVersion: ",self.newestVersion,")"); this.state = newState; // download button // if there isn't newestVersion data something went wrong, // probably accessing the internet if(self.newestVersion != undefined) { switch(this.state){ case UpdatePanel.NONE: case UpdatePanel.DOWNLOAD_FAILED: case UpdatePanel.INSTALL_FAILED: if(self.canUpdate) { self.btnUpdate.removeAttr("disabled"); } else { self.btnUpdate.attr("disabled", true); } break; default: self.btnUpdate.attr("disabled", true); break; } } else { self.btnUpdate.attr("disabled", true); } this.updateStatusDisplay(); } this.updateStatusDisplay = function() { var text = ""; if(self.newestVersion != undefined) { switch(this.state){ case UpdatePanel.NONE: if(self.canUpdate) { text = "Update(s) available."; } else { text = "You're up to date."; } break; case UpdatePanel.DOWNLOADING: text = "Downloading update..."; break; case UpdatePanel.DOWNLOAD_FAILED: text = "Downloading update failed."; break; case UpdatePanel.IMAGE_READY: text = "Update downloaded."; break; case UpdatePanel.INSTALLING: text = "Installing update... (will take a minute)"; break; case UpdatePanel.INSTALLED: text = "Update complete, please refresh Page."; break; case UpdatePanel.INSTALL_FAILED: text = "Installing update failed."; break; } } else { if(self.networkMode == SettingsWindow.NETWORK_MODE_ACCESS_POINT) { text = "Can't access internet in access point mode."; } else { text = "Can't access internet."; } } this.statusDisplay.html(text); } this.updateInfoDisplay = function() { var text = "Current version: "+self.currentVersion+". "; if(self.canUpdate) { text += "Latest version: "+self.newestVersion+"."; } self.infoDisplay.text(text); } this.setNetworkMode = function(networkMode) { self.networkMode = networkMode; } } function setTemperature(callback) { if (callback != undefined) callback(); } function setTemperature(callback) { if (callback != undefined) callback(); } var VERTICALSHAPE; var verticalShapes = { "NONE": 'none', "DIVERGING": 'diverging', "CONVERGING": 'converging', "SINUS": 'sinus' }; function initVerticalShapes() { // TODO give these vertical shapes a better spot VERTICALSHAPE = verticalShapes.NONE; $(".verticalShapes, .straight").on('mouseup touchend', function(e) { e.preventDefault(); console.log("diverging"); VERTICALSHAPE = verticalShapes.NONE; redrawRenderedPreview(); }) $(".verticalShapes, .diverging").on('mouseup touchend', function(e) { e.preventDefault(); console.log("diverging"); VERTICALSHAPE = verticalShapes.DIVERGING; redrawRenderedPreview(); }) $(".verticalShapes, .converging").on('mouseup touchend', function(e) { e.preventDefault(); console.log("converging"); VERTICALSHAPE = verticalShapes.CONVERGING; redrawRenderedPreview(); }) $(".verticalShapes, .sinus").on('mouseup touchend', function(e) { e.preventDefault(); console.log("sinus"); VERTICALSHAPE = verticalShapes.SINUS; redrawRenderedPreview(); }) } function resetVerticalShapes() { VERTICALSHAPE = verticalShapes.NONE; } var btnMoveUpInterval; var btnMoveDownInterval; var btnTwistLeftInterval; var btnTwistRightInterval; var twistIncrement = Math.PI/1800; var btnOopsInterval; var btnNew, btnPrevious, btnNext; var btnOops, btnStop, btnClear; var btnMoveUp, btnMoveDown, btnTwistLeft, btnTwistRight; var btnInfo, btnSettings; //var btnDebug; // debug var state; var prevState; var hasControl; var gcodeGenerateDelayer; var gcodeGenerateDelay = 50; function initButtonBehavior() { console.log("f:initButtonBehavior"); // btnClear= $(".btnClear"); btnOops = $(".btnOops"); btnMoveUp = $("#btnMoveUp"); btnMoveDown = $("#btnMoveDown"); btnTwistLeft = $("#btnTwistLeft"); btnTwistRight = $("#btnTwistRight"); btnInfo = $(".btnInfo"); btnSettings = $(".btnSettings"); btnNew = $(".btnNew"); btnPrint= $(".btnPrint"); btnStop = $(".btnStop"); btnPrevious = $(".btnPrevious"); btnNext = $(".btnNext"); //debug //btnDebug = $(".debugBtn"); btnNew.on('touchstart mousedown', clearDoodle); btnPrint.on('touchstart mousedown', print); // not using these at the moment $("#btnPrevious").css("opacity", "0.3"); btnNext.css("opacity", "0.3"); $("#btnSave").css("opacity", "0.3"); btnInfo.css("opacity", "0.3"); // btnClear.click(function(e) { // e.preventDefault(); // // console.log("clear"); // // clearDoodle(); // }); function startOops(e) { // console.log("btnOops mouse down"); e.preventDefault(); btnOopsInterval = setInterval( function() { oopsUndo(); }, 1000/50); } function stopOops(e) { // console.log("btnOops mouse up"); e.preventDefault(); clearInterval(btnOopsInterval); } btnOops.on('touchstart', function(e) { startOops(e); }); btnOops.on('touchend', function(e) { stopOops(e); }); btnOops.mousedown(function(e) { startOops(e); }); btnOops.mouseup(function(e) { stopOops(e); }); function startMoveUp(e) { e.preventDefault(); // console.log("btnMoveUp mouse down"); previewUp(true); clearInterval(btnMoveUpInterval); btnMoveUpInterval = setInterval( function() { previewUp(true); }, 1000/30); } function stopMoveUp(e) { e.preventDefault(); console.log("btnMoveUp mouse up"); clearInterval(btnMoveUpInterval); previewUp(); } btnMoveUp.mousedown(function(e) { startMoveUp(e) }); btnMoveUp.mouseup(function(e) { stopMoveUp(e) }); btnMoveUp.on('touchstart', function(e) { startMoveUp(e) }); btnMoveUp.on('touchend', function(e) { stopMoveUp(e) }); function startMoveDown(e) { e.preventDefault(); // console.log("btnMoveDown mouse down"); previewDown(true); clearInterval(btnMoveDownInterval); btnMoveDownInterval = setInterval( function() { previewDown(true); }, 1000/30); } function stopMoveDown(e) { e.preventDefault(); console.log("btnMoveDown mouse up"); clearInterval(btnMoveDownInterval); previewDown(); } btnMoveDown.mousedown(function(e) { startMoveDown(e) }); btnMoveDown.mouseup(function(e) { stopMoveDown(e) }); btnMoveDown.on('touchstart', function(e) { startMoveDown(e) }); btnMoveDown.on('touchend', function(e) { stopMoveDown(e) }); function startTwistLeft(e) { e.preventDefault(); // console.log("btnTwistLeft mouse down"); previewTwistLeft(true); clearInterval(btnTwistLeftInterval); btnTwistLeftInterval = setInterval( function() { previewTwistLeft(true); }, 1000/30); } function stopTwistLeft(e) { e.preventDefault(); // console.log("btnTwistLeft mouse up"); clearInterval(btnTwistLeftInterval); previewTwistLeft(); } btnTwistLeft.mousedown(function(e) { startTwistLeft(e) }); btnTwistLeft.mouseup(function(e) { stopTwistLeft(e) }); btnTwistLeft.on('touchstart', function(e) { startTwistLeft(e) }); btnTwistLeft.on('touchend', function(e) { stopTwistLeft(e) }); function startTwistRight(e) { e.preventDefault(); // console.log("btnTwistRight mouse down"); previewTwistRight(true); clearInterval(btnTwistRightInterval); btnTwistRightInterval = setInterval( function() { previewTwistRight(true); }, 1000/30); } function stopTwistRight(e) { e.preventDefault(); // console.log("btnTwistRight mouse up"); clearInterval(btnTwistRightInterval); previewTwistRight(); } btnTwistRight.mousedown(function(e) { startTwistRight(e) }); btnTwistRight.mouseup(function(e) { stopTwistRight(e) }); btnTwistRight.on('touchstart', function(e) { startTwistRight(e) }); btnTwistRight.on('touchend', function(e) { stopTwistRight(e) }); /*function openSettings() { console.log("f:openSettings()"); $("#contentOverlay").fadeIn(1000, function() { loadSettings(); }); }*/ btnSettings.bind('touchstart mousedown',function () { //e.preventDefault(); //console.log("btnSettings clicked"); settingsWindow.showSettings(); }); // btnSettings.on('touchend', function(e) { // e.preventDefault(); // console.log("btnSettings touchend"); // }); btnInfo.mouseup(function(e) { e.preventDefault(); console.log("btnInfo mouse up"); }); // DEBUG /* // $(".agentInfo").css("display", "none"); btnDebug.click(function(e) { console.log("debugClick"); $(".agentInfo").toggleClass("agentInfoToggle"); e.preventDefault(); }) //*/ //btnStop.on('touchstart mousedown',stopPrint); } function stopPrint() { console.log("f:stopPrint() >> sendPrintCommands = " + sendPrintCommands); //if (!confirm("Weet je zeker dat je huidige print wilt stoppen?")) return; if (sendPrintCommands) printer.stop(); //setState(Printer.STOPPING_STATE,printer.hasControl); printer.overruleState(Printer.STOPPING_STATE); } function prevDoodle(e) { console.log("f:prevDoodle()"); console.log("f:prevDoodle()"); } function nextDoodle(e) { console.log("f:nextDoodle()"); } function print(e) { console.log("f:print() >> sendPrintCommands = " + sendPrintCommands); //$(".btnPrint").css("display","none"); if (_points.length > 2) { //setState(Printer.BUFFERING_STATE,printer.hasControl); printer.overruleState(Printer.BUFFERING_STATE); btnStop.css("display","none"); // hack // we put the gcode generation in a little delay // so that for example the print button is disabled right away clearTimeout(gcodeGenerateDelayer); gcodeGenerateDelayer = setTimeout(function() { var gcode = generate_gcode(); if (sendPrintCommands) { if(gcode.length > 0) { printer.print(gcode); } else { printer.overruleState(Printer.IDLE_STATE); printer.startStatusCheckInterval(); } } else { console.log("sendPrintCommands is false: not sending print command to 3dprinter"); } if (debugMode) { $("#textdump").text(""); $("#textdump").text(gcode.join("\n")); } }, gcodeGenerateDelay); } else { console.log("f:print >> not enough points!"); } //alert("Je tekening zal nu geprint worden"); //$(".btnPrint").css("display","block"); // $.post("/doodle3d.of", { data:output }, function(data) { // btnPrint.disabled = false; // }); } function clearMainView() { // console.log("f:clearMainView()"); ctx.save(); ctx.clearRect(0,0,canvas.width, canvas.height); ctx.restore(); } function resetPreview() { // console.log("f:resetPreview()"); // clear preview canvas previewCtx.save(); previewCtx.clearRect(0,0,canvas.width, canvas.height); previewCtx.restore(); // reset height and rotation to default values numLayers = previewDefaults.numLayers; // current number of preview layers rStep = previewDefaults.rotation; // Math.PI/180; //Math.PI/40; // } function oopsUndo() { // console.log("f:oopsUndo()"); _points.pop(); redrawDoodle(); redrawPreview(); } function previewUp(redrawLess) { // console.log("f:previewUp()"); if (numLayers < maxNumLayers) { numLayers++; } // redrawPreview(redrawLess); redrawRenderedPreview(redrawLess); } function previewDown(redrawLess) { // console.log("f:previewDown()"); if (numLayers > minNumLayers) { numLayers--; } // redrawPreview(redrawLess); redrawRenderedPreview(redrawLess); } function previewTwistLeft(redrawLess) { if (redrawLess == undefined) redrawLess = false; // console.log("f:previewTwistLeft()"); if (rStep > -previewRotationLimit) rStep -= twistIncrement; // redrawPreview(redrawLess); redrawRenderedPreview(redrawLess); } function previewTwistRight(redrawLess) { // console.log("f:previewTwistRight()"); if (rStep < previewRotationLimit) rStep += twistIncrement; // redrawPreview(redrawLess); redrawRenderedPreview(redrawLess); } function update() { setState(printer.state,printer.hasControl); thermometer.update(printer.temperature, printer.targetTemperature); progressbar.update(printer.currentLine, printer.totalLines); } function setState(newState,newHasControl) { if(newState == state && newHasControl == hasControl) return; prevState = state; console.log("setState: ",prevState," > ",newState," ( ",newHasControl,")"); setDebugText("State: "+newState); // print button var printEnabled = (newState == Printer.IDLE_STATE && newHasControl); if(printEnabled) { btnPrint.removeClass("disabled"); // enable print button btnPrint.unbind('touchstart mousedown'); btnPrint.bind('touchstart mousedown',print); } else { btnPrint.addClass("disabled"); // disable print button btnPrint.unbind('touchstart mousedown'); } // stop button var stopEnabled = ((newState == Printer.PRINTING_STATE || newState == Printer.BUFFERING_STATE) && newHasControl); if(stopEnabled) { btnStop.removeClass("disabled"); btnStop.unbind('touchstart mousedown'); btnStop.bind('touchstart mousedown',stopPrint); } else { btnStop.addClass("disabled"); btnStop.unbind('touchstart mousedown'); } // thermometer switch(newState) { case Printer.IDLE_STATE: case Printer.BUFFERING_STATE: case Printer.PRINTING_STATE: case Printer.STOPPING_STATE: thermometer.show(); break; default: thermometer.hide(); break; } // progress indicator switch(newState) { case Printer.PRINTING_STATE: progressbar.show(); break; default: progressbar.hide(); break; } if(newState == Printer.WIFIBOX_DISCONNECTED_STATE) { message.set("Lost connection to WiFi box",Message.ERROR); } else if(prevState == Printer.WIFIBOX_DISCONNECTED_STATE) { message.set("Connected to WiFi box",Message.INFO,true); } else if(newState == Printer.DISCONNECTED_STATE) { message.set("Printer disconnected",Message.WARNING,true); } else if(prevState == Printer.DISCONNECTED_STATE && newState == Printer.IDLE_STATE || prevState == Printer.UNKNOWN_STATE && newState == Printer.IDLE_STATE) { message.set("Printer connected",Message.INFO,true); } state = newState; hasControl = newHasControl; } /* * * * * * * * * * * * VARS * * * * * * * * * * */ var preview; var previewCtx; var svgPathRegExp = /[LM]\d* \d*/ig; var svgPathParamsRegExp = /([LM])(\d*) (\d*)/; var dragging = false; var $canvas, canvas, ctx; var canvasWidth, canvasHeight; var drawCanvas; var drawCanvasTopLeftCoords = [0, 0]; var doodleBounds = [-1, -1, -1, -1]; // left, top, right, bottom // var doodleScaleVals = [[0, 0], [1.0, 1.0]]; // [ [x, y], [scaleX, scaleY] ] var doodleTransform = [0, 0, 1.0, 1.0]; // [ x, y, scaleX, scaleY ] var _points = []; var prevCountingTime = 0; var movementCounter = 0; var drawVariableLineWeight = false; // set to true to have the momentum of the mouse/touch movement result in larger/smaller strokes var lineweight = 2; /* * * * * * * * * * * * INIT * * * * * * * * * * */ function initDoodleDrawing() { console.log("f:initDoodleDrawing()"); $canvas = $("#mycanvas"); canvas = $canvas[0]; ctx = canvas.getContext('2d'); canvasWidth = canvas.width; canvasHeight = canvas.height; //* //TODO make these jquery eventhandlers (works for all) if (!canvas.addEventListener) { canvas.attachEvent('onmousedown',onCanvasMouseDown); canvas.attachEvent('onmousemove',onCanvasMouseMove); canvas.attachEvent('onmouseup',onCanvasMouseUp); canvas.attachEvent('ontouchstart',onCanvasTouchDown); canvas.attachEvent('ontouchmove',onCanvasTouchMove); canvas.attachEvent('ontouchend',onCanvasTouchEnd); document.body.attachEvent('ontouchmove',prevent); } else { canvas.addEventListener('mousedown',onCanvasMouseDown,false); canvas.addEventListener('mousemove',onCanvasMouseMove,false); canvas.addEventListener('mouseup',onCanvasMouseUp,false); canvas.addEventListener('touchstart',onCanvasTouchDown,false); canvas.addEventListener('touchmove',onCanvasTouchMove,false); canvas.addEventListener('touchend',onCanvasTouchEnd,false); if (!debugMode) document.body.addEventListener('touchmove',prevent,false); } //*/ // drawCanvas = $(".drawareacontainer"); drawCanvas = $("#mycanvasContainer"); // $("#drawAreaContainer") console.log("drawCanvasTopLeftCoords: " + drawCanvasTopLeftCoords); // drawCanvasTopLeftCoords[0] = drawCanvas.css("left").match(/[0-9]/g).join(""); // drawCanvasTopLeftCoords[1] = drawCanvas.css("top").match(/[0-9]/g).join(""); drawCanvasTopLeftCoords[0] = drawCanvas.offset().left; drawCanvasTopLeftCoords[1] = drawCanvas.offset().top; // drawCanvasTopLeftCoords[0] = drawCanvas[0].offsetParent.offsetLeft; // drawCanvasTopLeftCoords[1] = drawCanvas[0].offsetParent.offsetTop; console.log("f:initDoodleDrawing() >> canvasWidth: " + canvasWidth); console.log("f:initDoodleDrawing() >> canvasHeight: " + canvasHeight); } /* * * * * * * * * * * * CANVAS DRAWING FUNCTION * * * * * * * * * * */ function draw(_x, _y, _width) { // console.log("f:draw() >> _width: " + _width); if (prevX == 0 && prevY ==0) { prevX = _x; prevY = _y; } ctx.beginPath(); ctx.moveTo(prevX, prevY); ctx.lineTo(_x, _y); if (_width != undefined) { ctx.lineWidth = _width; } else { if (drawVariableLineWeight) { var dist = Math.sqrt(Math.pow((prevX - _x), 2) + Math.pow((prevY - _y), 2)); if (dist < 10) { lineweight += .25; } else if (dist < 20) { lineweight += .5; } else if (dist < 30) { lineweight += .75; } else if (dist < 50) { lineweight += 1; } else if (dist < 80) { lineweight += 1.5; } else if (dist < 120) { lineweight += 2.25; } else if (dist < 170) { lineweight += 3.5; } else { lineweight += 2; } lineweight = Math.min(lineweight, 30); lineweight *= 0.90; lineweight = Math.max(lineweight, 1.0); } else { lineweight = 2; } ctx.lineWidth = lineweight; } ctx.lineCap = 'round'; ctx.stroke(); prevX = _x; prevY = _y; } /* * * * * * * * * * * * SUPPORTING FUNCTIONS * * * * * * * * * * */ function clearDoodle() { console.log("f:clearDoodle"); _points = []; prevX = 0; prevY = 0; updatePrevX = -1; updatePrevY = -1; doodleBounds = [-1, -1, -1, -1]; // left, top, right, bottom doodleTransform = [0, 0, 1.0, 1.0]; // [ x, y, scaleX, scaleY ] dragging = false; clearMainView(); resetPreview(); resetVerticalShapes(); } function redrawDoodle() { console.log("f:redrawDoodle()"); clearMainView(); prevX = 0; prevY = 0; for (var i = 0; i < _points.length; i++) { // console.log(" drawing points " + _points[i]); if (_points[i][2] == true) { draw(_points[i][0], _points[i][1], 0.5); } else { draw(_points[i][0], _points[i][1]); } } } function adjustBounds(x, y) { var newPointsOutsideOfCurrentBounds = false; console.log("f:adjustBounds("+x+","+y+")"); if (doodleBounds[0] == -1) { // if doodleBounds[0] is -1 then it isn't initted yet, so x and y are both the min and max vals doodleBounds[0] = x; doodleBounds[1] = y; doodleBounds[2] = x; doodleBounds[3] = y; return; } if (x < doodleBounds[0]) { doodleBounds[0] = x; newPointsOutsideOfCurrentBounds = true; } if (x > doodleBounds[2]) { doodleBounds[2] = x; newPointsOutsideOfCurrentBounds = true; } if (y < doodleBounds[1]) { doodleBounds[1] = y; newPointsOutsideOfCurrentBounds = true; } if (y > doodleBounds[3]) { doodleBounds[3] = y; newPointsOutsideOfCurrentBounds = true; } // doodleBounds[0] = Math.min(doodleBounds[0], x); // left // doodleBounds[2] = Math.max(doodleBounds[2], x); // right // // doodleBounds[1] = Math.min(doodleBounds[1], y); // top // doodleBounds[3] = Math.max(doodleBounds[3], y); // bottom // draw the bounding rect (DEBUG) /* ctx.beginPath(); ctx.rect(doodleBounds[0],doodleBounds[1], doodleBounds[2] - doodleBounds[0], doodleBounds[3] - doodleBounds[1]); ctx.lineWidth = .2; ctx.strokeStyle = "#333" ctx.stroke(); ctx.closePath(); //*/ // console.log(" new bounds: " + doodleBounds); return newPointsOutsideOfCurrentBounds; } // does what exactly? function adjustPreviewTransformation() { // console.log("f:adjustPreviewTransformation()"); // doodleTransform[0] = doodleBounds[0] - (doodleBounds[2] - doodleBounds[0]) / 2; // doodleTransform[1] = doodleBounds[1] - (doodleBounds[3] - doodleBounds[1]) / 2; // doodleTransform[0] = doodleBounds[0] - ((doodleBounds[2] - doodleBounds[0]) / 2); // doodleTransform[1] = doodleBounds[1] - ((doodleBounds[3] - doodleBounds[1]) / 2); doodleTransform[0] = doodleBounds[0]; doodleTransform[1] = doodleBounds[1]; var sclX, sclY, finalScl; if (_points.length < 2) { // console.log(_points); sclX = 1.0; sclY = 1.0; finalScl = Math.min(sclX, sclY); } else { sclX = canvasWidth / (doodleBounds[2] - doodleBounds[0]); sclY = canvasHeight / (doodleBounds[3] - doodleBounds[1]); // TODO this shouldn't be a matter if choosing the smallest but should probably involve maintaining aspect ratio?? finalScl = Math.min(sclX, sclY); } doodleTransform[2] = finalScl; doodleTransform[3] = finalScl; } /* * * * * * * * * * * * MOUSE/TOUCH EVENTHANDLERS * * * * * * * * * * */ function onCanvasMouseDown(e) { // console.log("onmousedown >> e.offsetX,e.offsetY = " + e.offsetX+","+e.offsetY); // console.log("onmousedown >> e.layerX,e.layerY= " + e.layerX+","+e.layerY); // console.log("onmousedown >> e: " + e); // console.log(e); // console.log("f:onCanvasMouseDown()"); dragging = true; prevCountingTime = new Date().getTime(); movementCounter = 0 // _points.push([e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop, true]); // adjustBounds(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); // adjustPreviewTransformation(); // draw(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop, 0.5); var x, y; if (e.offsetX != undefined) { x = e.offsetX; y = e.offsetY; } else { x = e.layerX; y = e.layerY; } // console.log(" x: " + x + ", y: " + y); _points.push([x, y, true]); adjustBounds(x, y); adjustPreviewTransformation(); draw(x, y, 0.5); } var prevPoint = {x:-1, y:-1}; function onCanvasMouseMove(e) { // console.log("f:onCanvasMouseMove()"); if (!dragging) return; // console.log("onmousemove"); var x, y; if (e.offsetX != undefined) { x = e.offsetX; y = e.offsetY; } else { x = e.layerX; y = e.layerY; } if (prevPoint.x != -1 || prevPoint.y != -1) { var dist = Math.sqrt(Math.pow((prevPoint.x - x), 2) + Math.pow((prevPoint.y - y), 2)); if (dist > 5) { // replace by setting: doodle3d.simplify.minDistance _points.push([x, y, false]); adjustBounds(x, y) adjustPreviewTransformation(); draw(x, y); prevPoint.x = x; prevPoint.y = y; } } else { _points.push([x, y, false]); adjustBounds(x, y) adjustPreviewTransformation(); draw(x, y); prevPoint.x = x; prevPoint.y = y; } // DEBUG // $("#textdump").text(""); // $("#textdump").append("doodlebounds:" + doodleBounds + "\n"); // $("#textdump").append("doodletransform:" + doodleTransform + "\n"); if (new Date().getTime() - prevRedrawTime > redrawInterval) { // redrawing the whole preview the first X points ensures that the doodleBounds is set well prevRedrawTime = new Date().getTime(); if (_points.length < 50) { redrawPreview(); } else { updatePreview(x, y, true); /* if (_points.length - prevUpdateFullPreview > prevUpdateFullPreviewInterval) { console.log("f:onTouchMove >> passed prevUpdateFullPreviewInterval, updating full preview"); redrawPreview(); prevUpdateFullPreview = _points.length; } else { updatePreview(x, y, true); } //*/ // redrawPreview(); } } } prevUpdateFullPreview = 0; // 0 is not a timeframe but refers to the _points array prevUpdateFullPreviewInterval = 25; // refers to number of points, not a timeframe function onCanvasMouseUp(e) { // console.log("f:onCanvasMouseUp()"); // console.log("onmouseup"); dragging = false; console.log("doodleBounds: " + doodleBounds); console.log("doodleTransform: " + doodleTransform); // ctx.stroke(); console.log("_points.length :" + _points.length); // console.log(_points); // DEBUG // $("#textdump").text(""); // $("#textdump").append("doodlebounds:" + doodleBounds + "\n"); // $("#textdump").append("doodletransform:" + doodleTransform + "\n"); // redrawPreview(); renderToImageDataPreview(); } function onCanvasTouchDown(e) { e.preventDefault(); console.log("f:onCanvasTouchDown >> e: " , e); // var x = e.touches[0].pageX - e.touches[0].target.offsetLeft; // var y = e.touches[0].pageY - e.touches[0].target.offsetTop; var x = e.touches[0].pageX - drawCanvasTopLeftCoords[0]; var y = e.touches[0].pageY - drawCanvasTopLeftCoords[1]; // var x = e.touches[0].pageX; // var y = e.touches[0].pageY; // var x = e.touches[0].layerX; // var y = e.touches[0].layerY; _points.push([x, y, true]); adjustBounds(x, y); adjustPreviewTransformation(); draw(x, y, .5); movementCounter = 0; prevRedrawTime = new Date().getTime(); } function onCanvasTouchMove(e) { e.preventDefault(); // var x = e.touches[0].pageX - e.touches[0].target.offsetLeft; // var y = e.touches[0].pageY - e.touches[0].target.offsetTop; var x = e.touches[0].pageX - drawCanvasTopLeftCoords[0]; var y = e.touches[0].pageY - drawCanvasTopLeftCoords[1]; // var x = e.touches[0].layerX; // var y = e.touches[0].layerY; // var x = e.touches[0].layerX; // var y = e.touches[0].layerY; console.log("f:onCanvasTouchMove >> x,y = "+x+","+y+" , e: " , e); if (prevPoint.x != -1 || prevPoint.y != -1) { var dist = Math.sqrt(Math.pow((prevPoint.x - x), 2) + Math.pow((prevPoint.y - y), 2)); if (dist > 5) { _points.push([x, y, false]); adjustBounds(x, y) adjustPreviewTransformation(); draw(x, y); prevPoint.x = x; prevPoint.y = y; } } else { _points.push([x, y, false]); adjustBounds(x, y) adjustPreviewTransformation(); draw(x, y); prevPoint.x = x; prevPoint.y = y; } // update counter -> this was for getting a handle on how often the Canvas fires a move-event /* movementCounter++; if (new Date().getTime() - prevCountingTime > 1000) { // console.log("number of moves in 1sec: " + movementCounter) prevCountingTime= new Date().getTime(); $("#numtimes").text(movementCounter + " times"); movementCounter = 0; } //*/ if (new Date().getTime() - prevRedrawTime > redrawInterval) { // redrawing the whole preview the first X points ensures that the doodleBounds is set well if (_points.length < 50) { redrawPreview(); } else { updatePreview(x, y, true); /* if (_points.length - prevUpdateFullPreview > prevUpdateFullPreviewInterval) { console.log("f:onTouchMove >> passed prevUpdateFullPreviewInterval, updating full preview"); redrawPreview(); prevUpdateFullPreview = _points.length; } else { updatePreview(x, y, true); } //*/ } prevRedrawTime = new Date().getTime(); } } function onCanvasTouchEnd(e) { console.log("f:onCanvasTouchEnd()"); console.log("doodleBounds: " + doodleBounds); console.log("doodleTransform: " + doodleTransform); // ctx.stroke(); console.log("_points.length :" + _points.length); // redrawPreview(); renderToImageDataPreview(); } function prevent(e) { e.preventDefault(); } //* var $preview; var preview; var previewCtx; var preview_tmp; var previewCtx_tmp; var previewDefaults = { rotation: Math.PI/90, numLayers: 10 } var svgPathRegExp = /[LM]\d* \d*/ig; var svgPathParamsRegExp = /([LM])(\d*) (\d*)/; var prevRedrawTime = new Date().getTime(); var redrawInterval = 1000 / 30; // ms function initPreviewRendering() { console.log("f:initPreviewRendering()"); $preview = $("#preview"); preview = $preview[0]; previewCtx = preview.getContext('2d'); // DEBUG --> mbt preview_tmp (voor de toImageData truc) var _ratio = preview.width / canvas.width; preview_tmp = document.getElementById('preview_tmp'); preview_tmp.width = preview.width; preview_tmp.height = canvas.height * _ratio; $("#preview_tmp").css("top", -preview_tmp.height); previewCtx_tmp = preview_tmp.getContext('2d'); calcPreviewCanvasProperties(); redrawPreview(); } function calcPreviewCanvasProperties() { console.log("f:calcPreviewCanvasProperties()"); globalScale = preview.width / canvasWidth; layerCX = (canvasWidth / 2) * globalScale; // defined in canvasDrawing_v01.js layerCY = (canvasHeight / 2) * globalScale; // defined in canvasDrawing_v01.js // layerOffsetY = preview.height - 1.75 * layerCY; layerOffsetY = preview.height * (1 - previewVerticalPadding.bottom); yStep = (preview.height - (preview.height * (previewVerticalPadding.top + previewVerticalPadding.bottom))) / maxNumLayers; } // TODO (perhaps) : make the twist limit dynamic, depending on what's printable (w.r.t. overlapping) var previewRotationLimit = Math.PI / 30; // rough estimate var numLayers = previewDefaults.numLayers; // current number of preview layers var maxNumLayers= 100; // maximum number of preview layers var minNumLayers= 2; // minimum number of preview layers var globalScale = 0.3; // global scale of preview (width preview / width canvas) var globalAlpha = 0.20; // global alpha of preview var scaleY = 0.4; // additional vertical scale per path for 3d effect var viewerScale = 0.65; // additional scale to fit into preview nicely (otherwise is fills out totally) var previewVerticalPadding = { "top" : .15, "bottom" : 0.12 }; // % var strokeWidth = 2; //4; //var rStep = Math.PI/40; //Math.PI/40; // var rStep = previewDefaults.rotation; // Math.PI/180; //Math.PI/40; // var yStep;// = preview.height / 150; // 3; //6; //var svgWidth = 500; // 650 //parseInt($(svg).css("width")); //var svgHeight = 450; //450; //parseInt($(svg).css("height")); var layerCX, layerCY; //var layerCX = (canvasWidth / 2) * globalScale; // defined in canvasDrawing_v01.js //var layerCY = (canvasHeight / 2) * globalScale; // defined in canvasDrawing_v01.js var layerOffsetY; //= preview.height - 1.75 * layerCY; // 330; // previewHeight - 120 var prevX = 0; var prevY = 0; var highlight = true; //highlight bottom, middle and top layers var linesRaw = ""; var debug_redrawSimplification = 6; function redrawPreview(redrawLess) { if (redrawLess == undefined) redrawLess = false; if (_points.length < 2) return; if (!redrawLess) { //debug_redrawSimplification = Math.round(_points.length / 65); //* if (_points.length < 100) { debug_redrawSimplification = 6; } else if (_points.length < 250) { debug_redrawSimplification = 7; } else if (_points.length < 400) { debug_redrawSimplification = 8; } else if (_points.length < 550) { debug_redrawSimplification = 9; } else if (_points.length < 700) { debug_redrawSimplification = 10; } else { debug_redrawSimplification = 11; } //*/ // console.log("debug_redrawSimplification: " + debug_redrawSimplification); } var y = 0; var r = 0; //preview.width = preview.width; previewCtx.clearRect(0, 0, preview.width, preview.height); previewCtx.lineWidth = strokeWidth; previewCtx.strokeStyle = '#f00'; //"rgba(255,255,0,0)"; for(var i = 0; i < numLayers; i++) { var verticalScaleFactor = scaleFunction(i / maxNumLayers); if(i == 0 || i == Math.floor(numLayers/2) || i == numLayers-1) { previewCtx.globalAlpha = 1; } else { previewCtx.globalAlpha = globalAlpha; } if (redrawLess && i%debug_redrawSimplification != 0 && !(i == 0 || i == Math.floor(numLayers/2) || i == numLayers-1) ) { y -= yStep; r += rStep; continue; } previewCtx.save(); // previewCtx.translate(layerCX, layerOffsetY + layerCY + y); previewCtx.translate(layerCX, layerOffsetY + y); // previewCtx.setTransform(1, 0, 0, scaleY, layerCX, layerOffsetY+layerCY+y); previewCtx.scale(viewerScale * verticalScaleFactor, scaleY * viewerScale * verticalScaleFactor); previewCtx.rotate(r); previewCtx.translate((-doodleTransform[0]) * (globalScale * doodleTransform[2]), (-doodleTransform[1]) * (globalScale * doodleTransform[3])); var adjustedDoodlePoint = centeredAndScaledDoodlePoint(_points[0]); previewCtx.beginPath(); previewCtx.moveTo(adjustedDoodlePoint.x, adjustedDoodlePoint.y); for(var j = 1; j < _points.length; j++) { adjustedDoodlePoint = centeredAndScaledDoodlePoint(_points[j]) if (redrawLess && j%debug_redrawSimplification != 0 ) continue; previewCtx.lineTo(adjustedDoodlePoint.x, adjustedDoodlePoint.y); } previewCtx.stroke(); y -= yStep; r += rStep; previewCtx.restore(); } previewCtx.globalAlpha = globalAlpha; } function renderToImageDataPreview() { console.log("f:renderToImageDataPreview()"); if (_points.length < 2) return; //* // the first step previewCtx_tmp.clearRect(0, 0, preview.width, preview.height); previewCtx_tmp.lineWidth = strokeWidth; previewCtx_tmp.strokeStyle = '#f00'; //"rgba(255,255,0,0)"; previewCtx_tmp.save(); previewCtx_tmp.translate(layerCX, layerCY); previewCtx_tmp.scale(viewerScale, viewerScale); previewCtx_tmp.translate((-doodleTransform[0]) * (globalScale * doodleTransform[2]), (-doodleTransform[1]) * (globalScale * doodleTransform[3])); var adjustedDoodlePt = centeredAndScaledDoodlePoint(_points[0]); previewCtx_tmp.beginPath(); previewCtx_tmp.moveTo(adjustedDoodlePt.x, adjustedDoodlePt.y); for(var j = 1; j < _points.length; j++) { adjustedDoodlePt = centeredAndScaledDoodlePoint(_points[j]) previewCtx_tmp.lineTo(adjustedDoodlePt.x, adjustedDoodlePt.y); } previewCtx_tmp.stroke(); previewCtx_tmp.closePath(); previewCtx_tmp.restore(); //*/ // var saved_rect = previewCtx_tmp.getImageData(0, 0, layerCX*2, layerCY*2); var saved_rect_todataurl = preview_tmp.toDataURL(); doodleImageCapture = new Image(); doodleImageCapture.onload = function() { previewCtx.clearRect(0, 0, preview.width, preview.height); previewCtx.lineWidth = strokeWidth; previewCtx.strokeStyle = '#f00'; //"rgba(255,255,0,0)"; var y = 0; var r = 0; for(var i=0;i> EFFE CHECKEN:"); console.log(" speed: " + speed); console.log(" travelSpeed: " + travelSpeed); console.log(" filamentThickness: " + filamentThickness); console.log(" wallThickness: " + wallThickness); console.log(" screenToMillimeterScale: " + screenToMillimeterScale); console.log(" layerHeight: " + layerHeight); console.log(" objectHeight: " + objectHeight); console.log(" maxObjectHeight: " + maxObjectHeight); console.log(" temperature: " + temperature); console.log(" maxObjectHeight: " + maxObjectHeight); console.log(" useSubLayers: " + useSubLayers); console.log(" enableTraveling: " + enableTraveling); console.log(" retractionspeed: " + retractionspeed); console.log(" retractionminDistance: " + retractionminDistance); console.log(" retractionamount: " + retractionamount); console.log(""); //*/ // max amount of real world layers var layers = maxObjectHeight / layerHeight; //maxObjectHeight instead of objectHeight // translate numLayers in preview to objectHeight in real world //objectHeight = Math.ceil(numLayers / 5); // in settings objectHeight = 20, in previewRendering_v01.js numLayers is 100, hence the / 5 //objectHeight = numLayers; // in settings objectHeight = 20, in previewRendering_v01.js numLayers is 100, hence the / 5 objectHeight = Math.round(numLayers/maxNumLayers*maxObjectHeight); // translate preview rotation (per layer) to real world rotation var rStepGCode = rStep * maxNumLayers/layers; ///maxNumLayers*maxObjectHeight; // correct direction rStepGCode = -rStepGCode; // todo hier een array van PATHS maken wat de losse paths zijn // copy array without reference -> http://stackoverflow.com/questions/9885821/copying-of-an-array-of-objects-to-another-array-without-object-reference-in-java var points = JSON.parse(JSON.stringify(_points)); // console.log("f:generategcode() >> paths: " + paths.toString()); // console.log("paths.toString(): " + paths.toString()); // return; //gcode.push("M104 S" + temperature); // set target temperature and do not wait for the extruder to reach it //gcode.push("M109 S" + temperature); // set target temperature and wait for the extruder to reach it // add gcode begin commands gcode = gcode.concat(startGcode); //gcode.push("M109 S" + temperature); // set target temperature and wait for the extruder to reach it var layers = maxObjectHeight / layerHeight; //maxObjectHeight instead of objectHeight var extruder = 0.0; var prev = new Point(); prev.set(0, 0); // replacement (and improvement) for ofxGetCenterofMass var centerOfDoodle = { x: doodleBounds[0] + (doodleBounds[2]- doodleBounds[0])/2, y: doodleBounds[1] + (doodleBounds[3] - doodleBounds[1])/2 // x: doodleBounds[0], // y: doodleBounds[1] } console.log("f:generategcode() >> layers: " + layers); if (layers == Infinity) return; // check feasibility of design var pointsToPrint = points.length * layers*(objectHeight/maxObjectHeight) //console.log(" points.length: ",points.length); //console.log(" numLayers: ",(layers*(objectHeight/maxObjectHeight))); //console.log(" pointsToPrint: ",pointsToPrint); //console.log(" MAX_POINTS_TO_PRINT: ",MAX_POINTS_TO_PRINT); if(pointsToPrint > MAX_POINTS_TO_PRINT) { alert("Sorry, your doodle to to complex and / or to high"); console.log("WARNING: to many points to convert to gcode"); return []; } for (var layer = 0; layer < layers; layer++) { var p = JSON.parse(JSON.stringify(points)); // [].concat(points); if (p.length < 2) return; var even = (layer % 2 == 0); var progress = layer / layers; // float layerScale = scaleFunction(float(layer)/layers); // scaleFactor van de layer -> lookup naar vfunc[] voor die scaleVals // var layerScale = 1.0; var layerScale = scaleFunction(progress); // if begin point this row and end point last row are close enough, isLoop is true var isLoop = lineLength(points[0][0], points[0][1], points[points.length-1][0], points[points.length-1][1]) < 3; // set center of doodle as middle (ie subtract to that) pointsTranslate(p, -centerOfDoodle.x, -centerOfDoodle.y); pointsScale(p, screenToMillimeterScale,-screenToMillimeterScale); pointsScale(p, layerScale, layerScale); // sort-of in de buurt van (360/2.5) // // -> aight.. er zijn 750 lines vs 1000 in de d3d app. 135 = .75 * 180... dit kan je nog rechttrekken als je NET wat slimmer nadenkt :) // update: NEE, het is niet .75 * 180 want 135 was niet de beste value. //pointsRotate(p, rStep * progress * 139); pointsRotate(p, rStepGCode * layer); if (layer == 0) { //gcode.push("M107"); //fan off if (firstLayerSlow) { //gcode.push("M220 S20"); //slow speed speed = bottomSpeed; //console.log("> speed: ",speed); } } else if (layer == 2) { ////////LET OP, pas bij layer 2 weer op normale snelheid ipv layer 1 gcode.push("M106"); //fan on //gcode.push("M220 S100"); //normal speed speed = normalSpeed; //console.log("> speed: ",speed); } var curLayerCommand = 0; var totalLayerCommands = p.length; var layerProgress = 0; var paths = []; var pathCounter = -1; // var points = []; for (var i = 0; i < p.length; i++) { if (p[i][2] == true) { pathCounter++; paths.push([]); paths[pathCounter].push([p[i][0], p[i][1]]); } else { paths[pathCounter].push([p[i][0], p[i][1]]); } } // console.log("f:generategcode() >> paths.length: " + paths.length); // loop over the subpaths (the separately drawn lines) for (var j = 0; j < paths.length; j++) { // TODO paths > subpaths // this line is probably for drawing efficiency, alternating going from 0->end and end->0 (i.e. to and fro) // vector &commands = subpaths[even ? j : subpaths.size()-1-j].getCommands(); var commands = paths[j]; //commands zijn alle points uit subpath j // TODO commands > subpathPoints // loop over the coordinates of the subpath for (var i = 0; i < commands.length; i++) { var last = commands.length - 1; // this line is probably for drawing efficiency, alternating going from 0->end and end->0 (i.e. to and fro) // ofPoint to = commands[(even || isLoop || loopAlways) ? i : last-i].to; var to = new Point(); to.set(commands[i][0], commands[i][1]); // TODO 2013-09-18 evaluate if this should stay.. // this was added when Rick mailed us wrt the Ultimaker delivery of Doodle3D to.x += gCodeOffsetX; to.y += gCodeOffsetY; var sublayer = (layer == 0) ? 0.0 : layer + (useSubLayers ? (curLayerCommand/totalLayerCommands) : 0); var z = (sublayer + 1) * layerHeight; // 2013-09-06 removed zOffset (seemed to be useless) var isTraveling = !isLoop && i==0; var doRetract = retractionEnabled && prev.distance(to) > retractionminDistance; if (enableTraveling && isTraveling) { // console.log("enableTraveling && isTraveling >> doRetract: " + doRetract + ", retractionspeed: " + retractionspeed); if (doRetract) gcode.push("G0 E" + (extruder - retractionamount).toFixed(3) + " F" + (retractionspeed * 60).toFixed(3)); //retract gcode.push("G0 X" + to.x.toFixed(3) + " Y" + to.y.toFixed(3) + " Z" + z.toFixed(3) + " F" + (travelSpeed * 60).toFixed(3)); if (doRetract) gcode.push("G0 E" + extruder.toFixed(3) + " F" + (retractionspeed * 60).toFixed(3)); // return to normal } else { // console.log(" else"); //extruder += prev.distance(to) * wallThickness * layerHeight / filamentThickness; extruder += prev.distance(to) * wallThickness * layerHeight / (Math.pow((filamentThickness/2), 2) * Math.PI); gcode.push("G1 X" + to.x.toFixed(3) + " Y" + to.y.toFixed(3) + " Z" + z.toFixed(3) + " F" + (speed * 60).toFixed(3) + " E" + extruder.toFixed(3)); } curLayerCommand++; layerProgress = curLayerCommand/totalLayerCommands; prev = to; } } if ((layer/layers) > (objectHeight/maxObjectHeight)) { console.log("f:generategcode() >> (layer/layers) > (objectHeight/maxObjectHeight) is true -> breaking at layer " + (layer + 1)); break; } } // add gcode end commands gcode = gcode.concat(endGcode); return gcode; } function scaleFunction(percent) { var r = 1.0; switch (VERTICALSHAPE) { case verticalShapes.NONE: r = 1.0; break; case verticalShapes.DIVERGING: r = .5 + (percent * .5); break; case verticalShapes.CONVERGING: r = 1.0 - (percent * .8); break; case verticalShapes.SINUS: r = (Math.cos(percent * Math.PI * 4) * .25) + .75; break; } // return 1.0 - (percent *.8); return r; } pointsTranslate = function(p, x, y) { for (var i = 0; i < p.length; i++) { p[i][0] += x; p[i][1] += y; } } pointsScale = function(p, sx, sy) { for (var i = 0; i < p.length; i++) { p[i][0] *= sx; p[i][1] *= sy; } } // rotates around point 0,0 (origin). // Not the prettiest kind of rotation solution but in our case we're assuming that the points have just been translated to origin pointsRotate = function(p, ang) { var _ang, dist; for (var i = 0; i < p.length; i++) { dist = Math.sqrt(p[i][0] * p[i][0] + p[i][1] * p[i][1]); _ang = Math.atan2(p[i][1], p[i][0]); p[i][0] = Math.cos(_ang + ang) * dist; p[i][1] = Math.sin(_ang + ang) * dist; } } //+ Jonas Raoni Soares Silva //@ http://jsfromhell.com/math/line-length [rev. #1] lineLength = function(x, y, x0, y0){ return Math.sqrt((x -= x0) * x + (y -= y0) * y); }; var Point = function() {}; Point.prototype = { x: 0, y: 0, set: function(_x, _y) { this.x = _x; this.y = _y; }, distance: function(p) { var d = -1; if (p instanceof Point) { d = Math.sqrt((p.x - this.x) * (p.x - this.x) + (p.y - this.y) * (p.y - this.y)); } return d; }, toString: function() { console.log("x:" + this.x + ", y:" + this.y); } } // TODO refactor this stuff, there's much to wipe var drawAreaContainerMinHeight = 300; var drawAreaContainerMaxHeight = 450; function doOnResize() { // console.log("doOnResize() >> " + new Date().getTime()); canvas.width = $canvas.width(); canvas.height = $canvas.height(); // canvas.clientHeight; preview.width = $preview.width(); preview.height = $drawAreaContainer.height(); canvasWidth = canvas.width; canvasHeight = canvas.height; console.log(" preview.width: " + preview.width + ", $preview.width(): " + $preview.width()); calcPreviewCanvasProperties(); drawCanvasTopLeftCoords[0] = drawCanvas.offset().left; drawCanvasTopLeftCoords[1] = drawCanvas.offset().top; redrawDoodle(); redrawPreview(); } function initLayouting() { console.log("f:initLayouting()"); $drawAreaContainer = $(".drawareacontainer"); canvas.width = $canvas.width(); canvas.height = $canvas.height(); // canvas.clientHeight; preview.width = $preview.width(); preview.height = $drawAreaContainer.height(); canvasWidth = canvas.width; canvasHeight = canvas.height; $drawAreaContainer.show(); // window.innerHeight console.log("window.innerHeight: " + window.innerHeight); console.log("window.innerWidth: " + window.innerWidth); console.log("$drawAreaContainer.innerHeight(): " + $drawAreaContainer.innerHeight()); console.log("$drawAreaContainer.offset().top: " + $drawAreaContainer.offset().top); // timeout because it SEEMS to be beneficial for initting the layout // 2013-09-18 seems beneficial since when? setTimeout(_startOrientationAndChangeEventListening, 1000); } function _startOrientationAndChangeEventListening() { // Initial execution if needed $(window).on('resize', doOnResize); // is it necessary to call these? Aren't they called by the above eventhandlers? doOnResize(); } /* not using this now var $printProgressContainer = $("#printProgressContainer"); var $progressbar = $("#progressbar"); var $progressAmount = $(".progressAmount"); function setPrintprogress(val) { if (isNaN(val)) return; // console.log("f:setPrintprogress() >> val " + val); $progressbar.css("width", val*100 + "%"); $progressAmount.text(Math.floor(val*100) + "%"); } //*/ function Printer() { Printer.WIFIBOX_DISCONNECTED_STATE = "wifibox disconnected"; Printer.UNKNOWN_STATE = "unknown"; // happens when a printer is connection but there isn't communication yet Printer.DISCONNECTED_STATE = "disconnected"; // printer disconnected Printer.IDLE_STATE = "idle"; // printer found, but idle Printer.BUFFERING_STATE = "buffering"; // printer is buffering (recieving) data, but not yet printing Printer.PRINTING_STATE = "printing"; Printer.STOPPING_STATE = "stopping"; // when you stop (abort) a print it prints the endcode Printer.ON_BEFORE_UNLOAD_MESSAGE = "You're doodle is still being send to the printer, leaving will result in a incomplete 3D print"; this.temperature = 0; this.targetTemperature = 0; this.currentLine = 0; this.totalLines = 0; this.bufferedLines = 0; this.state = Printer.UNKNOWN_STATE; this.hasControl = true; // whether this client has control access this.wifiboxURL; this.checkStatusInterval = 3000; this.checkStatusDelay; this.timeoutTime = 3000; this.sendPrintPartTimeoutTime = 5000; this.gcode; // gcode to be printed this.sendLength = 1500; // max amount of gcode lines per post (limited because WiFi box can't handle to much) this.retryDelay = 2000; // retry setTimout delay this.retrySendPrintPartDelay; // retry setTimout instance this.retryCheckStatusDelay; // retry setTimout instance this.retryStopDelay; // retry setTimout instance this.retryPreheatDelay; // retry setTimout instance this.maxGCodeSize = 10; // max size of gcode in MB's (estimation) this.stateOverruled = false; // Events Printer.UPDATE = "update"; var self = this; this.init = function() { console.log("Printer:init"); //this.wifiboxURL = "http://" + window.location.host + "/cgi-bin/d3dapi"; //this.wifiboxURL = "http://192.168.5.1/cgi-bin/d3dapi"; this.wifiboxURL = wifiboxURL; //this.wifiboxURL = "proxy5.php"; console.log(" wifiboxURL: ",this.wifiboxURL); if(autoUpdate) { this.startStatusCheckInterval(); } } this.preheat = function() { console.log("Printer:preheat"); var self = this; if (communicateWithWifibox) { $.ajax({ url: this.wifiboxURL + "/printer/heatup", type: "POST", dataType: 'json', timeout: this.timeoutTime, success: function(data){ console.log("Printer:preheat response: ",data); if(data.status == "error") { clearTimeout(self.retryPreheatDelay); self.retryPreheatDelay = setTimeout(function() { self.preheat() },self.retryDelay); // retry after delay } } }).fail(function() { console.log("Printer:preheat: failed"); clearTimeout(self.retryPreheatDelay); self.retryPreheatDelay = setTimeout(function() { self.preheat() },self.retryDelay); // retry after delay }); } else { console.log ("Printer >> f:preheat() >> communicateWithWifibox is false, so not executing this function"); } } this.print = function(gcode) { console.log("Printer:print"); console.log(" gcode total # of lines: " + gcode.length); message.set("Sending doodle to printer...",Message.NOTICE); self.addLeaveWarning(); /*for (i = 0; i < gcode.length; i++) { gcode[i] += " (" + i + ")"; }*/ this.sendIndex = 0; this.gcode = gcode; //console.log(" gcode[20]: ",gcode[20]); var gcodeLineSize = this.byteSize(gcode[20]); //console.log(" gcodeLineSize: ",gcodeLineSize); var gcodeSize = gcodeLineSize*gcode.length/1024/1024; // estimate gcode size in MB's console.log(" gcodeSize: ",gcodeSize); if(gcodeSize > this.maxGCodeSize) { console.log("Error: Printer:print: gcode file is probably to big ("+gcodeSize+"MB) (max: "+this.maxGCodeSize+"MB)"); return; } //this.targetTemperature = settings["printer.temperature"]; // slight hack this.sendPrintPart(this.sendIndex, this.sendLength); } this.byteSize = function(s){ return~-encodeURI(s).split(/%..|./).length; } this.sendPrintPart = function(sendIndex,sendLength) { console.log("Printer:sendPrintPart sendIndex: " + sendIndex + "/" + this.gcode.length + ", sendLength: " + sendLength); var firstOne = (sendIndex == 0)? true : false; var start = firstOne; // start printing right away var completed = false; if (this.gcode.length < (sendIndex + sendLength)) { console.log(" sending less than max sendLength (and last)"); sendLength = this.gcode.length - sendIndex; //lastOne = true; completed = true; } var gcodePart = this.gcode.slice(sendIndex, sendIndex+sendLength); var postData = { gcode: gcodePart.join("\n"), first: firstOne, start: start}; var self = this; if (communicateWithWifibox) { $.ajax({ url: this.wifiboxURL + "/printer/print", type: "POST", data: postData, dataType: 'json', timeout: this.sendPrintPartTimeoutTime, success: function(data){ console.log("Printer:sendPrintPart response: ",data); if(data.status == "success") { if (completed) { console.log("Printer:sendPrintPart:gcode sending completed"); this.gcode = []; btnStop.css("display","block"); // hack self.removeLeaveWarning(); message.set("Doodle is send to printer...",Message.INFO,true); //self.targetTemperature = settings["printer.temperature"]; // slight hack } else { // only if the state hasn't bin changed (by for example pressing stop) we send more gcode console.log("Printer:sendPrintPart:gcode part received (state: ",self.state,")"); if(self.state == Printer.PRINTING_STATE || self.state == Printer.BUFFERING_STATE) { console.log("Printer:sendPrintPart:sending next part"); self.sendPrintPart(sendIndex + sendLength, sendLength); } } } // after we know the first gcode packed has bin received or failed // (and the driver had time to update the printer.state) // we start checking the status again if(sendIndex == 0) { self.startStatusCheckInterval(); } } }).fail(function() { console.log("Printer:sendPrintPart: failed"); clearTimeout(self.retrySendPrintPartDelay); self.retrySendPrintPartDelay = setTimeout(function() { console.log("request printer:sendPrintPart failed retry"); self.sendPrintPart(sendIndex, sendLength) },self.retryDelay); // retry after delay // after we know the gcode packed has bin received or failed // (and the driver had time to update the printer.state) // we start checking the status again self.startStatusCheckInterval(); }); } else { console.log ("Printer >> f:sendPrintPart() >> communicateWithWifibox is false, so not executing this function"); } } this.stop = function() { console.log("Printer:stop"); var self = this; if (communicateWithWifibox) { $.ajax({ url: this.wifiboxURL + "/printer/stop", type: "POST", dataType: 'json', timeout: this.timeoutTime, success: function(data){ console.log("Printer:stop response: ", data); // after we know the stop has bin received or failed // (and the driver had time to update the printer.state) // we start checking the status again self.startStatusCheckInterval(); } }).fail(function() { console.log("Printer:stop: failed"); clearTimeout(self.retryStopDelay); self.retryStopDelay = setTimeout(function() { self.stop() },self.retryDelay); // retry after delay // after we know the stop has bin received or failed // (and the driver had time to update the printer.state) // we start checking the status again self.startStatusCheckInterval(); }); } else { console.log ("Printer >> f:communicateWithWifibox() >> communicateWithWifibox is false, so not executing this function"); } } this.startStatusCheckInterval = function() { console.log("Printer:startStatusCheckInterval"); self.checkStatus(); clearTimeout(self.checkStatusDelay); clearTimeout(self.retryCheckStatusDelay); self.checkStatusDelay = setTimeout(function() { self.checkStatus() }, self.checkStatusInterval); } this.stopStatusCheckInterval = function() { console.log("Printer:stopStatusCheckInterval"); clearTimeout(self.checkStatusDelay); clearTimeout(self.retryCheckStatusDelay); } this.checkStatus = function() { console.log("Printer:checkStatus"); this.stateOverruled = false; //console.log(" stateOverruled: ",this.stateOverruled); var self = this; if (communicateWithWifibox) { $.ajax({ url: this.wifiboxURL + "/info/status", dataType: 'json', timeout: this.timeoutTime, success: function(response){ //console.log(" Printer:status: ",response.data.state); //," response: ",response); self.handleStatusUpdate(response); clearTimeout(self.checkStatusDelay); clearTimeout(self.retryCheckStatusDelay); self.checkStatusDelay = setTimeout(function() { self.checkStatus() }, self.checkStatusInterval); } }).fail(function() { console.log("Printer:checkStatus: failed"); self.state = Printer.WIFIBOX_DISCONNECTED_STATE; clearTimeout(self.checkStatusDelay); clearTimeout(self.retryCheckStatusDelay); self.retryCheckStatusDelay = setTimeout(function() { self.checkStatus() },self.retryDelay); // retry after delay $(document).trigger(Printer.UPDATE); }); } else { console.log ("Printer >> f:checkStatus() >> communicateWithWifibox is false, so not executing this function"); } } this.handleStatusUpdate = function(response) { console.log("Printer:handleStatusUpdate response: ",response); var data = response.data; if(response.status != "success") { self.state = Printer.UNKNOWN_STATE; } else { // state //console.log(" stateOverruled: ",this.stateOverruled); if(!this.stateOverruled) { self.state = data.state; //console.log(" state > ",self.state); } // temperature self.temperature = data.hotend; self.targetTemperature = data.hotend_target; // progress self.currentLine = data.current_line; self.totalLines = data.total_lines; self.bufferedLines = data.buffered_lines // access self.hasControl = data.has_control; if(self.state == Printer.PRINTING_STATE || self.state == Printer.STOPPING_STATE) { console.log("progress: ",self.currentLine+"/"+self.totalLines+" ("+self.bufferedLines+") ("+self.state+")"); } } $(document).trigger(Printer.UPDATE); } this.overruleState = function(newState) { this.stateOverruled = true; console.log(" stateOverruled: ",this.stateOverruled); self.state = newState; $(document).trigger(Printer.UPDATE); this.stopStatusCheckInterval(); } this.removeLeaveWarning = function() { window.onbeforeunload = null; } this.addLeaveWarning = function() { window.onbeforeunload = function() { return Printer.ON_BEFORE_UNLOAD_MESSAGE; }; } } function Progressbar() { this.currProgress = 0; // default val this.progressbarFGImg = new Image(); this.progressbarFGImgSrc = "img/progress_fg.png"; this.progressbarBGImg = new Image(); this.progressbarBGImgSrc = "img/progress_bg.png"; this.progressWidth= 93; this.progressHeight = 82; this.quartPI = .5 * Math.PI; this.twoPI = 2 * Math.PI; this.$canvas; this.canvas; this.context; this.$container; this.isInitted = false; this.enabled = true; this.init = function(targCanvas, targCanvasContainer) { console.log("Thermometer.init()"); this.$container = targCanvasContainer; this.$canvas = targCanvas; this.canvas = this.$canvas[0]; this.context = this.canvas.getContext('2d'); var self = this; this.progressbarBGImg.onload = function() { console.log("progressbarBGImg img loaded"); // self.isInitted = true; // self.update(self.currentTemperature, self.targetTemperature); self.progressbarFGImg.onload = function() { console.log("progressbarFGImg img loaded"); self.isInitted = true; self.update(0, 100); }; self.progressbarFGImg.src = self.progressbarFGImgSrc; }; this.progressbarBGImg.src = this.progressbarBGImgSrc; } this.update = function(part, total) { //console.log("Progressbar.update(" + part + "," + total + ")"); var pct = part / total; if (this.isInitted) { if (part == undefined) part = 0; if (total== undefined) total = 100; // prevent divide by zero var progress = part / total; progress = Math.min(progress, 1.0); progress = Math.max(progress, 0); //console.log("progressbar >> f:update() >> progress: " + progress); // clear this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); this.context.drawImage(this.progressbarBGImg, 0, 0); this.context.font = "7pt sans-serif"; // draw the progressbar foreground's clipping path this.context.save(); this.context.beginPath(); this.context.moveTo(45, 45); this.context.lineTo(45, 0); this.context.arc(45, 45, 45, -this.quartPI, -this.quartPI + (progress * (this.twoPI)), false); // circle bottom of thermometer this.context.lineTo(45, 45); this.context.clip(); this.context.drawImage(this.progressbarFGImg, 0, 0); this.context.restore(); if (debugMode) { this.context.fillStyle = '#222'; this.context.strokeStyle = '#fff'; this.context.lineWidth = 3; this.context.textAlign="center"; this.context.strokeText(part + " / " + total, 45, 45, 90); this.context.fillText(part + " / " + total, 45, 45, 90); } } else { console.log("Progressbar.setTemperature() -> thermometer not initialized!"); } } this.show = function() { this.$container.addClass("progressbarAppear"); // this.$container.show(); this.enabled = true; } this.hide = function() { this.$container.removeClass("progressbarAppear"); // this.$container.hide(); this.enabled = false; } } // TODO assess if this var is still necessary var $displayThermometer = $("#thermometerContainer"); //TODO 2013-09-18 allow displaying temperatures HIGHER than the targTemp (it's now being capped at targTemp). function Thermometer() { this.currentTemperature = 0; // default val this.targetTemperature = 0; // default val this.thermoOverlayImg = new Image(); this.thermoOverlayImgSrc = "img/thermometer_fg_overlay.png"; // ../img/thermometer_fg_overlay.png this.thermoWidth= 40; this.thermoHeight = 100; this.$canvas; this.canvas; this.context; this.$container; this.isInitted = false; this.enabled = true; this.thermoColors = [ [50, 200, 244], // 'cold' [244, 190, 10], // 'warming up' [244, 50, 50] // 'ready / hot' ]; this.init = function(targCanvas, targCanvasContainer) { console.log("Thermometer.init()"); this.$container = targCanvasContainer; this.$canvas = targCanvas; this.canvas = this.$canvas[0]; this.context = this.canvas.getContext('2d'); var self = this; this.thermoOverlayImg.onload = function() { console.log("canvasThermoOverlay img loaded"); self.isInitted = true; self.update(self.currentTemperature, self.targetTemperature); }; this.thermoOverlayImg.src = this.thermoOverlayImgSrc; } this.update = function(curr, targ) { // console.log("Thermometer.update(" + curr + "," + targ + ")"); if (this.isInitted) { if(!this.enabled) return; if (curr == undefined) curr = 0; if (targ== undefined) targ = 180; // prevent divide by zero var progress = curr / targ; progress = Math.min(progress, 1.0); progress = Math.max(progress, 0); var h = this.thermoHeight; // 94 // px var paddingUnder = 15; // how far is beginpoint from bottom of thermometer var paddingAbove = 25; // how far is endpoint from top of thermometer var endPoint = h * .8; var p = Math.floor((h - paddingUnder - paddingAbove) * progress); // % // var tempHeight = var currColor = this.thermoColors[0]; if (progress > 0.98) { currColor = this.thermoColors[2]; } else if (progress > 0.25) { currColor = this.thermoColors[1]; } // clear this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); this.context.font = "10pt sans-serif"; // draw the thermometer clipping path this.context.save(); this.context.beginPath(); this.context.arc(40, 80, 16, 0, 2 * Math.PI, false); // circle bottom of thermometer this.context.arc(40, 10, 4, 0, 2 * Math.PI, false); // circle at top of thermometer tube this.context.rect(36, 11, 8, 70); // thermometer tube this.context.fillStyle = '#fff'; this.context.fill(); this.context.clip(); // draw rectangle which represents temperature // rect will be clipped by the thermometer outlines this.context.beginPath(); this.context.rect(20, h - paddingUnder - p, 60, p + paddingUnder); //console.log(" currColor: " + currColor); //todo Math.floor?? this.context.fillStyle = "rgb(" + currColor[0] + "," + currColor[1] + "," + currColor[2] + ")"; this.context.fill(); this.context.restore(); // additional text labels this.context.save(); this.context.beginPath(); this.context.moveTo(32, paddingAbove); this.context.lineTo(52, paddingAbove); this.context.lineWidth = 2; this.context.strokeStyle = '#000'; this.context.stroke(); this.context.fillStyle = '#000'; this.context.textAlign = "left"; this.context.textBaseline = "middle"; this.context.fillText(targ + "°", 55, paddingAbove); this.context.restore(); // the thermometer outline png this.context.drawImage(this.thermoOverlayImg, 20, 0); // text this.context.fillStyle = '#000'; this.context.textAlign="center"; this.context.fillText(curr + "°", 40, h + paddingUnder); } else { console.log("Thermometer.setTemperature() -> thermometer not initialized!"); } } this.show = function() { this.$container.addClass("thermometerAppear"); // $("#progressbarCanvasContainer").addClass("thermometerAppear"); // this.$container.show(); this.enabled = true; } this.hide = function() { this.$container.removeClass("thermometerAppear"); // $("#progressbarCanvasContainer").removeClass("thermometerAppear"); // this.$container.hide(); this.enabled = false; } } // http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery function getURLParameter(name) { return decodeURI( (new RegExp('[&?]'+name + '=' + '(.+?)(&|$)').exec(location.search)||[,null])[1] ); } var sidebarLeft; var sidebarRight; function initSidebars() { console.log("f:initSidebars()"); sidebarLeft = new SideBar(); sidebarLeft.init(".leftpanel", "hideleft", function() { $(".leftpanel").show(); }); sidebarRight = new SideBar(); sidebarRight.init(".rightpanel", "hideright", function() { $(".rightpanel").show(); }); } function SideBar() { this.initted = false; this.$contentTarg = undefined; this.$sideBtn = undefined; this.contentHidden = false; this.hideClass = ""; this.init = function(targ, hideClass, callback) { console.log("SideBar >> f:init >> targ: " , $(targ) , ", hideClass: " + hideClass); this.$contentTarg = $(targ); this.hideClass = hideClass; this.$contentTarg.addClass(this.hideClass); this.contentHidden = true; this.$contentTarg.append("
"); this.$sideBtn = $(targ +" .sidebutton"); var self = this; this.$sideBtn.on('click', function(e) { console.log("sidebutton"); self.toggleShowHide(); }); this.initted = true; callback(); } this.toggleShowHide = function() { if (this.contentHidden) { this.contentHidden = false; this.$contentTarg.removeClass(this.hideClass); // self.$sideBtn.addClass("sidebuttonin"); this.$sideBtn.addClass("sidebuttonin"); } else { this.contentHidden = true; this.$contentTarg.addClass(this.hideClass); // self.$sideBtn.removeClass("sidebuttonin"); this.$sideBtn.removeClass("sidebuttonin"); } } } function Message() { Message.ERROR = "error"; Message.WARNING = "warning"; Message.NOTICE = "notice"; Message.INFO = "info"; this.mode = ""; this.$element; var self = this; var autoHideDelay = 2000; var autohideTimeout; this.init = function($element) { console.log("Message:init"); this.$element = $element; console.log("$element: ",$element); } this.set = function(text,mode,autoHide) { console.log("Message:set: ",text,mode,autoHide); self.hide(function() { self.show(); self.clear(); self.$element.text(text); self.$element.addClass(mode); self.show(); self.mode = mode; clearTimeout(autohideTimeout); if(autoHide) { autohideTimeout = setTimeout(function(){ self.hide()},autoHideDelay); } }); } this.clear = function($element) { this.$element.text(""); this.$element.removeClass(this.mode); } this.show = function() { this.$element.fadeIn(200); } this.hide = function(complete) { this.$element.fadeOut(200,complete); } } var debugMode = false; // debug mode var sendPrintCommands = true; // if Doodle3d should send print commands to the 3d printer var communicateWithWifibox = true; // if Doodle3d should try interfacing with the wifibox (in case one is not connected) var wifiboxIsRemote = false; // when you want to run the client on a computer and have it remotely connect to the wifibox var autoUpdate = true; // auto retrieve updates about temperature and progress from printer var printer = new Printer(); var progressbar = new Progressbar(); var thermometer = new Thermometer(); var settingsWindow = new SettingsWindow(); var message = new Message(); var firstTimeSettingsLoaded = true; var wifiboxURL; // Using the uhttpd lua handler as default, because of better performance var wifiboxCGIBinURL; // CGI-bin, for some network stuff, where it needs to restart the webserver for example var $drawAreaContainer, $doodleCanvas, doodleCanvas, doodleCanvasContext, $previewContainer; var showhideInterval; var showOrHide = false; $(function() { console.log("ready"); //TODO give this a more logical place in code if (getURLParameter("d") != "null") debugMode = (getURLParameter("d") == "1"); if (getURLParameter("p") != "null") sendPrintCommands = (getURLParameter("p") == "1"); if (getURLParameter("c") != "null") communicateWithWifibox = (getURLParameter("c") == "1"); if (getURLParameter("r") != "null") wifiboxIsRemote = (getURLParameter("r") == "1"); if (getURLParameter("u") != "null") autoUpdate = (getURLParameter("u") == "1"); if (wifiboxIsRemote) { wifiboxURL = "http://192.168.5.1/d3dapi"; wifiboxCGIBinURL = "http://192.168.5.1/cgi-bin/d3dapi"; } else { wifiboxURL = "http://" + window.location.host + "/d3dapi"; wifiboxCGIBinURL = "http://" + window.location.host + "/cgi-bin/d3dapi"; } if (!communicateWithWifibox) { sendPrintCommands = false; // 'communicateWithWifibox = false' implies this } console.log("debugMode: " + debugMode); console.log("sendPrintCommands: " + sendPrintCommands); console.log("communicateWithWifibox: " + communicateWithWifibox); console.log("wifiboxIsRemote: " + wifiboxIsRemote); console.log("wifibox URL: " + wifiboxURL); initDoodleDrawing(); initPreviewRendering(); initLayouting(); initSidebars(); initButtonBehavior(); initVerticalShapes(); thermometer.init($("#thermometerCanvas"), $("#thermometerContainer")); progressbar.init($("#progressbarCanvas"), $("#progressbarCanvasContainer")); message.init($("#message")); printer.init(); $(document).on(Printer.UPDATE,update); settingsWindow.init(wifiboxURL,wifiboxCGIBinURL); $(document).on(SettingsWindow.SETTINGS_LOADED, settingsLoaded); if(debugMode) { console.log("debug mode is true"); $("body").css("overflow", "auto"); $("#debug_textArea").css("display", "block"); $("#preview_tmp").css("display", "block"); $("#debug_display").css("display", "block"); // show and hide the progressguage and thermometer showhideInterval = setInterval(showOrHideThermo, 2500); // $("#debugContainer").css("display", "block"); /* TEMP CODE!! -> artificially populates the startgcode and endgcode textareas in the settings window */ // todo remove this temporary code... /* setTimeout(function() { $("#startgcode").text(""); $("#startgcode").append("G21 (mm) \n"); $("#startgcode").append("G91 (relative) \n"); $("#startgcode").append("G28 X0 Y0 Z0 (physical home) \n"); $("#startgcode").append("M104 S230 (temperature) \n"); $("#startgcode").append("G1 E10 F250 (flow) \n"); $("#startgcode").append("G92 X-100 Y-100 Z0 E10 \n"); $("#startgcode").append("G1 Z3 F5000 (prevent diagonal line) \n"); $("#startgcode").append("G90 (absolute) \n"); $("#startgcode").append("M106 (fan on)"); console.log("$('#startgcode'): " + $("#startgcode").val()); $("#endgcode").text(""); $("#endgcode").append("G1 X-100 Y-100 F15000 (fast homing) \n"); $("#endgcode").append("M107 \n"); $("#endgcode").append("M84 (disable axes) \n"); console.log("$('#endgcode'): " + $("#endgcode").val()); }, 1000); //*/ } }); function showOrHideThermo() { console.log("f:showOrHideThermo()"); if (showOrHide) { thermometer.hide(); progressbar.hide(); } else { thermometer.show(); progressbar.show(); } showOrHide = !showOrHide; } function settingsLoaded() { console.log("settingsLoaded"); console.log("autoHeatup: ",settings["printer.heatup.enabled"]); if(settings["printer.heatup.enabled"]) { if(firstTimeSettingsLoaded) { printer.preheat(); firstTimeSettingsLoaded = false; } } } function setDebugText(text) { $("#debug_display").text(text); }