diff --git a/.gitignore b/.gitignore index a17e2b0..8a112de 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ src/library/.DS_Store src/script/.DS_Store src/.DS_Store + +bower_components/ \ No newline at end of file diff --git a/build/d3d.js b/build/d3d.js new file mode 100644 index 0000000..41365e2 --- /dev/null +++ b/build/d3d.js @@ -0,0 +1,834 @@ +/****************************************************** +* +* Utils +* requires jQuery, Three.js +* +******************************************************/ + +var D3D = { + "version": "0.1", + "website": "http://www.doodle3d.com/", + "contact": "develop@doodle3d.com" +}; + +//add normal function to Three.js Vector class +THREE.Vector2.prototype.normal = function () { + "use strict"; + + var x = this.y; + var y = -this.x; + + return this.set(x, y); +}; + +function sendAPI (url, data, callback) { + "use strict"; + + $.ajax({ + url: url, + type: "POST", + data: data, + dataType: "json", + timeout: 10000, + success: function (response) { + if (response.status === "success") { + if (callback !== undefined) { + callback(response.data); + } + } + else { + console.warn(response.msg); + } + } + }).fail(function () { + console.warn("failed connecting to " + url); + sendAPI(url, data, callback); + }); +} + +function getAPI (url, callback) { + "use strict"; + + $.ajax({ + url: url, + dataType: "json", + timeout: 5000, + success: function (response) { + if (response.status === "success") { + if (callback !== undefined) { + callback(response.data); + } + } + else { + console.warn(response.msg); + } + } + }).fail(function () { + console.warn("failed connecting to " + url); + getAPI(url, callback); + }); +} + +function downloadFile (file, data) { + "use strict"; + + $(document.createElement("a")).attr({ + download: file, + href: "data:text/plain," + data + })[0].click(); +} + +Array.prototype.clone = function () { + "use strict"; + + var array = []; + + for (var i = 0; i < this.length; i ++) { + array[i] = this[i]; + } + + return array; +}; + +function applyMouseControls (renderer, camera, maxDistance) { + "use strict"; + //TODO + //impliment touch controls + //windows mouse wheel fix + + var distance = 20; + var rotX = 0; + var rotY = 0; + var moveCamera = false; + + function updateCamera () { + camera.position.x = Math.cos(rotY)*Math.sin(rotX)*distance; + camera.position.y = Math.sin(rotY)*distance; + camera.position.z = Math.cos(rotY)*Math.cos(rotX)*distance; + camera.lookAt(new THREE.Vector3(0, 0, 0)); + } + + $(renderer.domElement).on("mousedown", function (e) { + moveCamera = true; + }).on("wheel", function (e) { + var event = e.originalEvent; + + event.preventDefault(); + distance = THREE.Math.clamp(distance - event.wheelDelta, 1, maxDistance); + + updateCamera(); + }); + + $(window).on("mouseup", function (e) { + moveCamera = false; + }).on("mousemove", function (e) { + var event = e.originalEvent; + + if (moveCamera === true) { + rotX = (rotX - event.webkitMovementX/100) % (2*Math.PI); + rotY = THREE.Math.clamp(rotY + event.webkitMovementY/100, -Math.PI/2, Math.PI/2); + + updateCamera(); + } + }); + + updateCamera(); +} + +var requestAnimFrame = (function () { + "use strict"; + + return requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || function (callback) { + setTimeout(callback, 1000/60); + }; +})(); +/****************************************************** +* +* Box +* Representation of de Doodle3DBox +* Handles all communication with the doodle box +* +******************************************************/ + +//TODO +//Als meerdere clients met box zouden verbinden zal de api te veel requests krijgen waardoor hij crasht +//implimentatie van het veranderen van onder andere naam, netwerkverbinding etc + +D3D.Box = function (localIp) { + "use strict"; + var self = this; + + this.batchSize = 512; + this.maxBufferedLines = 4096; + + this.localIp = localIp; + this.api = "http://" + localIp + "/d3dapi/"; + + this.config = {}; + + this.printBatches = []; + this.currentBatch = 0; + + this.loaded = false; + this.onload; + + getAPI(self.api + "config/all", function (data) { + //self.config = data; + + for (var i in data) { + if (i.indexOf("doodle3d") === 0) { + self.config[i] = data[i]; + } + } + + self.printer = new D3D.Printer(data); + self.update(); + + self.loaded = true; + if (self.onload !== undefined) { + self.onload(); + } + }); +}; +D3D.Box.prototype.update = function () { + "use strict"; + //TODO + //Code is zo op gezet dat maar api call te gelijk is + //Bij error wordt gelijk zelfde data opnieuw gestuurd + //Als DoodleBox ontkoppeld wordt komt er een error in de loop waardoor pagina breekt en ververst moet worden + + //if (this.printBatches.length > 0 && (this.progress["buffered_lines"] + this.batchSize) <= this.maxBufferedLines) { + if (this.printBatches.length > 0 ) { + this.printBatch(); + } + else { + this.updateState(); + } +}; +D3D.Box.prototype.updateState = function () { + "use strict"; + var self = this; + + //que api calls so they don't overload the d3d box + getAPI(this.api + "info/status", function (data) { + self.printer.status = data; + + self.update(); + }); +}; +D3D.Box.prototype.print = function (gcode) { + "use strict"; + + this.currentBatch = 0; + + //clone gcode to remove array links + gcode = gcode.clone(); + + //gcode split in batches + while (gcode.length > 0) { + var gcodeBatch = gcode.splice(0, Math.min(this.batchSize, gcode.length)); + this.printBatches.push(gcodeBatch); + } +}; +D3D.Box.prototype.printBatch = function () { + "use strict"; + var self = this; + + var gcode = this.printBatches.shift(); + + sendAPI(this.api + "printer/print", { + "start": ((this.currentBatch === 0) ? "true" : "false"), + "first": ((this.currentBatch === 0) ? "true" : "false"), + "gcode": gcode.join("\n") + }, function (data) { + + console.log("batch sent: " + self.currentBatch, data); + if (self.printBatches.length > 0) { + //sent new batch + self.currentBatch ++; + } + else { + //finish printing + } + + self.updateState(); + }); +}; +D3D.Box.prototype.stop = function () { + "use strict"; + + this.printBatches = []; + this.currentBatch = 0; + + var finishMove = [ + "M107 ;fan off", + "G91 ;relative positioning", + "G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure", + "G1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more", + "G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way", + "M84 ;disable axes / steppers", + "G90 ;absolute positioning", + "M104 S180", + ";M140 S70", + "M117 Done ;display message (20 characters to clear whole screen)" + ]; + + sendAPI(this.api + "printer/stop", { + "gcode": finishMove.join("\n") + //"gcode": {} + }, function (data) { + console.log("Printer stop command sent"); + }); +}; +/****************************************************** +* +* Printer +* Representation of the connected printer +* +******************************************************/ + +D3D.Printer = function (config) { + "use strict"; + + this.status = {}; + this.config = {}; + + for (var i in config) { + if (i.indexOf("printer") === 0) { + this.config[i] = config[i]; + } + } +}; +D3D.Printer.prototype.getStartCode = function () { + "use strict"; + + var gcode = this.config["printer.startcode"]; + gcode = this.subsituteVariables(gcode); + + return gcode.split("\n"); +}; +D3D.Printer.prototype.getEndCode = function () { + "use strict"; + + var gcode = this.config["printer.endcode"]; + gcode = this.subsituteVariables(gcode); + + return gcode.split("\n"); +}; +D3D.Printer.prototype.subsituteVariables = function (gcode) { + "use strict"; + + var temperature = this.config["printer.temperature"]; + var bedTemperature = this.config["printer.bed.temperature"]; + var preheatTemperature = this.config["printer.heatup.temperature"]; + var preheatBedTemperature = this.config["printer.heatup.bed.temperature"]; + var printerType = this.config["printer.type"]; + var heatedbed = this.config["printer.heatedbed"]; + + switch (printerType) { + case "makerbot_replicator2": printerType = "r2"; break; + case "makerbot_replicator2x": printerType = "r2x"; break; + case "makerbot_thingomatic": printerType = "t6"; break; + case "makerbot_generic": printerType = "r2"; break; + case "_3Dison_plus": printerType = "r2"; break; + } + var heatedBedReplacement = heatedbed ? "" : ";"; + + gcode = gcode.replace(/{printingTemp}/gi, temperature); + gcode = gcode.replace(/{printingBedTemp}/gi, bedTemperature); + gcode = gcode.replace(/{preheatTemp}/gi, preheatTemperature); + gcode = gcode.replace(/{preheatBedTemp}/gi, preheatBedTemperature); + gcode = gcode.replace(/{printerType}/gi, printerType); + gcode = gcode.replace(/{if heatedBed}/gi, heatedBedReplacement); + + return gcode; +}; +/****************************************************** +* +* Slicer +* +* TODO (optimalisatie) +* sorteer lijnen op laagste hoogte -> stop loop wanneer hij een lijn zonder intersectie heeft gevonden +* verwijder lijnen die ooit interactie gehad hebben, maar nu niet meer +* helft van lijnen toevoegen omdat 4face altijd recht is, en 3face dus te veel data bevat +* +* omliggende lagen -> difference && sum omliggende lijnen +* voor laag 5 = 5 diff (3 && 4 && 6 && 7)) +* +******************************************************/ + +D3D.Slicer = function () { + "use strict"; + + this.geometry; + + this.lines = []; + this.lineLookup = {}; +}; +D3D.Slicer.prototype.setGeometry = function (geometry) { + "use strict"; + + this.geometry = geometry; + this.geometry.mergeVertices(); + + this.createLines(); + + return this; +}; +D3D.Slicer.prototype.addLine = function (a, b) { + "use strict"; + + //think lookup can only be b_a, a_b is only possible when face is flipped + var index = this.lineLookup[a + "_" + b] || this.lineLookup[b + "_" + a]; + + if (index === undefined) { + index = this.lines.length; + this.lineLookup[a + "_" + b] = index; + + this.lines.push({ + line: new THREE.Line3(this.geometry.vertices[a], this.geometry.vertices[b]), + connects: [], + normals: [] + }); + } + + return index; +}; +D3D.Slicer.prototype.createLines = function () { + "use strict"; + + this.lines = []; + this.lineLookup = {}; + + for (var i = 0; i < this.geometry.faces.length; i ++) { + var face = this.geometry.faces[i]; + var normal = new THREE.Vector2().set(face.normal.x, face.normal.z).normalize(); + + //check for only adding unique lines + //returns index of said line + var a = this.addLine(face.a, face.b); + var b = this.addLine(face.b, face.c); + var c = this.addLine(face.c, face.a); + + //set connecting lines (based on face) + + //something wrong here, 3 face can go in different direction + this.lines[a].connects.push(b, c); + this.lines[b].connects.push(c, a); + this.lines[c].connects.push(a, b); + + this.lines[a].normals.push(normal); + this.lines[b].normals.push(normal); + this.lines[c].normals.push(normal); + } + + //sort lines on min height + //this.lines.sort(function (a, b) { + // return Math.min() - Math.min(); + //}); +}; +D3D.Slicer.prototype.slice = function (height, step) { + "use strict"; + + var slices = []; + + var plane = new THREE.Plane(); + + for (var z = 0; z < height; z += step) { + plane.set(new THREE.Vector3(0, -1, 0), z); + + var slice = []; + + var intersections = []; + + for (var i = 0; i < this.lines.length; i ++) { + var line = this.lines[i].line; + + var intersection = plane.intersectLine(line); + + if (intersection !== undefined) { + //remove +100 when implimenting good structure for geometry is complete + var point = new THREE.Vector2(intersection.x + 100, intersection.z + 100); + + intersections.push(point); + } + else { + intersections.push(false); + } + } + + var done = []; + for (var i = 0; i < intersections.length; i ++) { + + if (intersections[i] && done.indexOf(i) === -1) { + var index = i; + + var shape = []; + + while (index !== -1) { + var intersection = intersections[index]; + shape.push({X: intersection.x, Y: intersection.y}); + + done.push(index); + + var connects = this.lines[index].connects; + var faceNormals = this.lines[index].normals; + for (var j = 0; j < connects.length; j ++) { + index = connects[j]; + + if (intersections[index] && done.indexOf(index) === -1) { + var a = new THREE.Vector2().set(intersection.x, intersection.y); + var b = intersections[index]; + var normal = a.sub(b).normal().normalize(); + var faceNormal = faceNormals[Math.floor(j/2)]; + + if (normal.dot(faceNormal) > 0) { + break; + } + else { + index = -1; + } + } + else { + index = -1; + } + } + } + + //think this check is not nescesary, always higher as 0 + if (shape.length > 0) { + slice.push(shape); + } + } + } + + //stop when ther are no intersects + if (slice.length > 0) { + slices.push(slice); + } + else { + break; + } + } + + return slices; +}; +D3D.Slicer.prototype.getInset = function (slice, offset) { + "use strict"; + + var solution = new ClipperLib.Paths(); + var co = new ClipperLib.ClipperOffset(1, 1); + co.AddPaths(slice, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon); + co.Execute(solution, -offset); + + return solution; +}; +D3D.Slicer.prototype.getFillTemplate = function (dimension, size, even, uneven) { + "use strict"; + + var paths = new ClipperLib.Paths(); + + if (even) { + for (var length = 0; length <= dimension; length += size) { + paths.push([{X: length, Y: 0}, {X: length, Y: dimension}]); + } + } + if (uneven) { + for (var length = 0; length <= dimension; length += size) { + paths.push([{X: 0, Y: length}, {X: dimension, Y: length}]); + } + } + + return paths; +}; +D3D.Slicer.prototype.slicesToData = function (slices, printer) { + "use strict"; + + //scale because of clipper crap + var scale = 100; + + var layerHeight = printer.config["printer.layerHeight"] * scale; + var dimensionsZ = printer.config["printer.dimensions.z"] * scale; + var wallThickness = printer.config["printer.wallThickness"] * scale; + var shellThickness = printer.config["printer.shellThickness"] * scale; + var fillSize = printer.config["printer.fillSize"] * scale; + var brimOffset = printer.config["printer.brimOffset"] * scale; + + var data = []; + + var lowFillTemplate = this.getFillTemplate(dimensionsZ, fillSize, true, true); + + for (var layer = 0; layer < slices.length; layer ++) { + var slice = slices[layer]; + + //var outerLayer = ClipperLib.JS.Clean(slice, 1.0); + var outerLayer = slice.clone(); + ClipperLib.JS.ScaleUpPaths(outerLayer, scale); + + var innerLayer = []; + + for (var i = wallThickness; i < shellThickness; i += wallThickness) { + var inset = this.getInset(outerLayer, i); + + innerLayer = innerLayer.concat(inset); + } + + var fillArea = this.getInset((inset || outerLayer), wallThickness); + + var highFill; + + var fillAbove = undefined; + //for (var i = 1; i < shellThickness/layerHeight; i ++) { + for (var i = 1; i < shellThickness/layerHeight + 4; i ++) { + var newLayer = ClipperLib.JS.Clone(slices[layer + i]); + ClipperLib.JS.ScaleUpPaths(newLayer, scale); + + if (newLayer.length === 0 || (fillAbove && fillAbove.length === 0)) { + fillAbove = []; + + break; + } + else if (fillAbove === undefined) { + fillAbove = newLayer; + } + else { + var c = new ClipperLib.Clipper(); + var solution = new ClipperLib.Paths(); + c.AddPaths(fillArea, ClipperLib.PolyType.ptSubject, true); + c.AddPaths(fillAbove, ClipperLib.PolyType.ptClip, true); + c.Execute(ClipperLib.ClipType.ctIntersection, solution); + + fillAbove = solution; + } + } + //kijkt alleen nog naar boven + //omliggende lagen hebben inhoud van lowFill; + //inset moet opgevult worden; + //verschill tussen lowFill en inset moet vol, rest is raster + + var clipper = new ClipperLib.Clipper(); + var highFillArea = new ClipperLib.Paths(); + clipper.AddPaths(fillArea, ClipperLib.PolyType.ptSubject, true); + clipper.AddPaths(fillAbove, ClipperLib.PolyType.ptClip, true); + clipper.Execute(ClipperLib.ClipType.ctDifference, highFillArea); + + var clipper = new ClipperLib.Clipper(); + var lowFillArea = new ClipperLib.Paths(); + clipper.AddPaths(fillArea, ClipperLib.PolyType.ptSubject, true); + clipper.AddPaths(highFillArea, ClipperLib.PolyType.ptClip, true); + clipper.Execute(ClipperLib.ClipType.ctDifference, lowFillArea); + + var fill = []; + + var clipper = new ClipperLib.Clipper(); + var lowFillStrokes = new ClipperLib.Paths(); + clipper.AddPaths(lowFillTemplate, ClipperLib.PolyType.ptSubject, false); + clipper.AddPaths(lowFillArea, ClipperLib.PolyType.ptClip, true); + clipper.Execute(ClipperLib.ClipType.ctIntersection, lowFillStrokes); + + fill = fill.concat(lowFillStrokes); + + //optimize + //make as big as bounding box of highFillArea + var highFillTemplate = this.getFillTemplate(dimensionsZ, wallThickness, (layer % 2 === 0), (layer % 2 === 1)); + + var clipper = new ClipperLib.Clipper(); + var highFillStrokes = new ClipperLib.Paths(); + clipper.AddPaths(highFillTemplate, ClipperLib.PolyType.ptSubject, false); + clipper.AddPaths(highFillArea, ClipperLib.PolyType.ptClip, true); + clipper.Execute(ClipperLib.ClipType.ctIntersection, highFillStrokes); + + fill = fill.concat(highFillStrokes); + + //create brim + /*if (layer === 0) { + var brim = this.getInset(outerLayer, -brimOffset); + outerLayer = brim.concat(outerLayer); + }*/ + + ClipperLib.JS.ScaleDownPaths(outerLayer, scale); + ClipperLib.JS.ScaleDownPaths(innerLayer, scale); + ClipperLib.JS.ScaleDownPaths(fill, scale); + + data.push({ + outerLayer: outerLayer, + innerLayer: innerLayer, + fill: fill + }); + } + + return data; +}; +D3D.Slicer.prototype.dataToGcode = function (data, printer) { + "use strict"; + + var layerHeight = printer.config["printer.layerHeight"]; + var normalSpeed = printer.config["printer.speed"]; + var bottomSpeed = printer.config["printer.bottomLayerSpeed"]; + var firstLayerSlow = printer.config["printer.firstLayerSlow"]; + var bottomFlowRate = printer.config["printer.bottomFlowRate"]; + var travelSpeed = printer.config["printer.travelSpeed"]; + var filamentThickness = printer.config["printer.filamentThickness"]; + var wallThickness = printer.config["printer.wallThickness"]; + var enableTraveling = printer.config["printer.enableTraveling"]; + var retractionEnabled = printer.config["printer.retraction.enabled"]; + var retractionSpeed = printer.config["printer.retraction.speed"]; + var retractionMinDistance = printer.config["printer.retraction.minDistance"]; + var retractionAmount = printer.config["printer.retraction.amount"]; + + function sliceToGcode (slice) { + var gcode = []; + + for (var i = 0; i < slice.length; i ++) { + var shape = slice[i]; + + var previousPoint; + + for (var j = 0; j <= shape.length; j ++) { + //Finish shape by going to first point + var point = shape[(j % shape.length)]; + + if (j === 0) { + //TODO + //add retraction + if (extruder > retractionMinDistance && retractionEnabled) { + gcode.push([ + "G0", + "E" + (extruder - retractionAmount).toFixed(3), + "F" + (retractionSpeed * 60).toFixed(3) + ].join(" ")); + } + + gcode.push([ + "G0", + "X" + point.X.toFixed(3) + " Y" + point.Y.toFixed(3) + " Z" + z, + "F" + (travelSpeed*60) + ].join(" ")); + + if (extruder > retractionMinDistance && retractionEnabled) { + gcode.push([ + "G0", + "E" + extruder.toFixed(3), + "F" + (retractionSpeed * 60).toFixed(3) + ].join(" ")); + } + } + else { + var a = new THREE.Vector2().set(point.X, point.Y); + var b = new THREE.Vector2().set(previousPoint.X, previousPoint.Y); + var lineLength = a.distanceTo(b); + + extruder += lineLength * wallThickness * layerHeight / filamentSurfaceArea * flowRate; + + gcode.push([ + "G1", + "X" + point.X.toFixed(3) + " Y" + point.Y.toFixed(3) + " Z" + z, + "F" + speed, + "E" + extruder.toFixed(3) + ].join(" ")); + } + + previousPoint = point; + } + } + + return gcode; + } + + var gcode = printer.getStartCode(); + + var extruder = 0.0; + var speed = firstLayerSlow ? (bottomSpeed*60).toFixed(3) : (normalSpeed*60).toFixed(3); + var filamentSurfaceArea = Math.pow((filamentThickness/2), 2) * Math.PI; + var flowRate = bottomFlowRate; + + for (var layer = 0; layer < data.length; layer ++) { + var slice = data[layer]; + + //turn on fan on layer 2 + if (layer === 2) { + gcode.push("M106"); + speed = (normalSpeed*60).toFixed(3); + flowRate = 1; + } + + var z = ((layer + 1) * layerHeight).toFixed(3); + + gcode = gcode.concat(sliceToGcode(slice.outerLayer)); + gcode = gcode.concat(sliceToGcode(slice.innerLayer)); + gcode = gcode.concat(sliceToGcode(slice.fill)); + } + + gcode = gcode.concat(printer.getEndCode()); + return gcode; +}; +D3D.Slicer.prototype.drawPaths = function (printer, min, max) { + "use strict"; + + var layerHeight = printer.config["printer.layerHeight"]; + var dimensionsZ = printer.config["printer.dimensions.z"]; + + function drawPolygons (paths, color) { + context.fillStyle = color; + context.strokeStyle = color; + context.beginPath(); + + for (var i = 0; i < paths.length; i ++) { + var path = paths[i]; + + context.moveTo((path[0].X- 100) * 6.0 + 200, (path[0].Y- 100) * 6.0 + 200); + + for (var j = 0; j < path.length; j ++) { + var point = path[j]; + context.lineTo((point.X- 100) * 6.0 + 200, (point.Y- 100) * 6.0 + 200); + } + context.closePath(); + } + context.stroke(); + } + + var slices = this.slice(dimensionsZ, layerHeight); + slices.shift(); + + var data = this.slicesToData(slices, printer); + + var canvas = document.createElement("canvas"); + canvas.width = 400; + canvas.height = 400; + var context = canvas.getContext("2d"); + + for (var layer = min; layer < max; layer ++) { + var layer = 0; + context.clearRect(0, 0, 400, 400); + var slice = data[layer % data.length]; + + drawPolygons(slice.outerLayer, "red"); + drawPolygons(slice.innerLayer, "green"); + drawPolygons(slice.fill, "blue"); + } + + return canvas; +}; +D3D.Slicer.prototype.getGcode = function (printer) { + "use strict"; + + var layerHeight = printer.config["printer.layerHeight"]; + var dimensionsZ = printer.config["printer.dimensions.z"]; + + var slices = this.slice(dimensionsZ, layerHeight); + + //still error in first layer, so remove first layer + //see https://github.com/Doodle3D/Doodle3D-Slicer/issues/1 + slices.shift(); + + var data = this.slicesToData(slices, printer); + //return data; + + //TODO + //make the path more optimized for 3d printers + //make the printer follow the shortest path from line to line + //see https://github.com/Ultimaker/CuraEngine#gcode-generation + + var gcode = this.dataToGcode(data, printer); + return gcode; +}; \ No newline at end of file diff --git a/build/d3d.min.js b/build/d3d.min.js new file mode 100644 index 0000000..93bbb4e --- /dev/null +++ b/build/d3d.min.js @@ -0,0 +1 @@ +function sendAPI(e,t,i){"use strict";$.ajax({url:e,type:"POST",data:t,dataType:"json",timeout:1e4,success:function(e){"success"===e.status?void 0!==i&&i(e.data):console.warn(e.msg)}}).fail(function(){console.warn("failed connecting to "+e),sendAPI(e,t,i)})}function getAPI(e,t){"use strict";$.ajax({url:e,dataType:"json",timeout:5e3,success:function(e){"success"===e.status?void 0!==t&&t(e.data):console.warn(e.msg)}}).fail(function(){console.warn("failed connecting to "+e),getAPI(e,t)})}function downloadFile(e,t){"use strict";$(document.createElement("a")).attr({download:e,href:"data:text/plain,"+t})[0].click()}function applyMouseControls(e,t,i){"use strict";function r(){t.position.x=Math.cos(s)*Math.sin(o)*n,t.position.y=Math.sin(s)*n,t.position.z=Math.cos(s)*Math.cos(o)*n,t.lookAt(new THREE.Vector3(0,0,0))}var n=20,o=0,s=0,a=!1;$(e.domElement).on("mousedown",function(e){a=!0}).on("wheel",function(e){var t=e.originalEvent;t.preventDefault(),n=THREE.Math.clamp(n-t.wheelDelta,1,i),r()}),$(window).on("mouseup",function(e){a=!1}).on("mousemove",function(e){var t=e.originalEvent;a===!0&&(o=(o-t.webkitMovementX/100)%(2*Math.PI),s=THREE.Math.clamp(s+t.webkitMovementY/100,-Math.PI/2,Math.PI/2),r())}),r()}var D3D={version:"0.1",website:"http://www.doodle3d.com/",contact:"develop@doodle3d.com"};THREE.Vector2.prototype.normal=function(){"use strict";var e=this.y,t=-this.x;return this.set(e,t)},Array.prototype.clone=function(){"use strict";for(var e=[],t=0;t0?this.printBatch():this.updateState()},D3D.Box.prototype.updateState=function(){"use strict";var e=this;getAPI(this.api+"info/status",function(t){e.printer.status=t,e.update()})},D3D.Box.prototype.print=function(e){"use strict";for(this.currentBatch=0,e=e.clone();e.length>0;){var t=e.splice(0,Math.min(this.batchSize,e.length));this.printBatches.push(t)}},D3D.Box.prototype.printBatch=function(){"use strict";var e=this,t=this.printBatches.shift();sendAPI(this.api+"printer/print",{start:0===this.currentBatch?"true":"false",first:0===this.currentBatch?"true":"false",gcode:t.join("\n")},function(t){console.log("batch sent: "+e.currentBatch,t),e.printBatches.length>0&&e.currentBatch++,e.updateState()})},D3D.Box.prototype.stop=function(){"use strict";this.printBatches=[],this.currentBatch=0;var e=["M107 ;fan off","G91 ;relative positioning","G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure","G1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more","G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way","M84 ;disable axes / steppers","G90 ;absolute positioning","M104 S180",";M140 S70","M117 Done ;display message (20 characters to clear whole screen)"];sendAPI(this.api+"printer/stop",{gcode:e.join("\n")},function(e){console.log("Printer stop command sent")})},D3D.Printer=function(e){"use strict";this.status={},this.config={};for(var t in e)0===t.indexOf("printer")&&(this.config[t]=e[t])},D3D.Printer.prototype.getStartCode=function(){"use strict";var e=this.config["printer.startcode"];return e=this.subsituteVariables(e),e.split("\n")},D3D.Printer.prototype.getEndCode=function(){"use strict";var e=this.config["printer.endcode"];return e=this.subsituteVariables(e),e.split("\n")},D3D.Printer.prototype.subsituteVariables=function(e){"use strict";var t=this.config["printer.temperature"],i=this.config["printer.bed.temperature"],r=this.config["printer.heatup.temperature"],n=this.config["printer.heatup.bed.temperature"],o=this.config["printer.type"],s=this.config["printer.heatedbed"];switch(o){case"makerbot_replicator2":o="r2";break;case"makerbot_replicator2x":o="r2x";break;case"makerbot_thingomatic":o="t6";break;case"makerbot_generic":o="r2";break;case"_3Dison_plus":o="r2"}var a=s?"":";";return e=e.replace(/{printingTemp}/gi,t),e=e.replace(/{printingBedTemp}/gi,i),e=e.replace(/{preheatTemp}/gi,r),e=e.replace(/{preheatBedTemp}/gi,n),e=e.replace(/{printerType}/gi,o),e=e.replace(/{if heatedBed}/gi,a)},D3D.Slicer=function(){"use strict";this.geometry,this.lines=[],this.lineLookup={}},D3D.Slicer.prototype.setGeometry=function(e){"use strict";return this.geometry=e,this.geometry.mergeVertices(),this.createLines(),this},D3D.Slicer.prototype.addLine=function(e,t){"use strict";var i=this.lineLookup[e+"_"+t]||this.lineLookup[t+"_"+e];return void 0===i&&(i=this.lines.length,this.lineLookup[e+"_"+t]=i,this.lines.push({line:new THREE.Line3(this.geometry.vertices[e],this.geometry.vertices[t]),connects:[],normals:[]})),i},D3D.Slicer.prototype.createLines=function(){"use strict";this.lines=[],this.lineLookup={};for(var e=0;en;n+=t){r.set(new THREE.Vector3(0,-1,0),n);for(var o=[],s=[],a=0;a0)break;u=-1}else u=-1}f.length>0&&o.push(f)}if(!(o.length>0))break;i.push(o)}return i},D3D.Slicer.prototype.getInset=function(e,t){"use strict";var i=new ClipperLib.Paths,r=new ClipperLib.ClipperOffset(1,1);return r.AddPaths(e,ClipperLib.JoinType.jtRound,ClipperLib.EndType.etClosedPolygon),r.Execute(i,-t),i},D3D.Slicer.prototype.getFillTemplate=function(e,t,i,r){"use strict";var n=new ClipperLib.Paths;if(i)for(var o=0;e>=o;o+=t)n.push([{X:o,Y:0},{X:o,Y:e}]);if(r)for(var o=0;e>=o;o+=t)n.push([{X:0,Y:o},{X:e,Y:o}]);return n},D3D.Slicer.prototype.slicesToData=function(e,t){"use strict";for(var i=100,r=t.config["printer.layerHeight"]*i,n=t.config["printer.dimensions.z"]*i,o=t.config["printer.wallThickness"]*i,s=t.config["printer.shellThickness"]*i,a=t.config["printer.fillSize"]*i,p=(t.config["printer.brimOffset"]*i,[]),c=this.getFillTemplate(n,a,!0,!0),l=0;ld;d+=o){var g=this.getInset(u,d);f=f.concat(g)}for(var m=this.getInset(g||u,o),v=void 0,d=1;s/r+4>d;d++){var b=ClipperLib.JS.Clone(e[l+d]);if(ClipperLib.JS.ScaleUpPaths(b,i),0===b.length||v&&0===v.length){v=[];break}if(void 0===v)v=b;else{var y=new ClipperLib.Clipper,C=new ClipperLib.Paths;y.AddPaths(m,ClipperLib.PolyType.ptSubject,!0),y.AddPaths(v,ClipperLib.PolyType.ptClip,!0),y.Execute(ClipperLib.ClipType.ctIntersection,C),v=C}}var D=new ClipperLib.Clipper,L=new ClipperLib.Paths;D.AddPaths(m,ClipperLib.PolyType.ptSubject,!0),D.AddPaths(v,ClipperLib.PolyType.ptClip,!0),D.Execute(ClipperLib.ClipType.ctDifference,L);var D=new ClipperLib.Clipper,P=new ClipperLib.Paths;D.AddPaths(m,ClipperLib.PolyType.ptSubject,!0),D.AddPaths(L,ClipperLib.PolyType.ptClip,!0),D.Execute(ClipperLib.ClipType.ctDifference,P);var w=[],D=new ClipperLib.Clipper,T=new ClipperLib.Paths;D.AddPaths(c,ClipperLib.PolyType.ptSubject,!1),D.AddPaths(P,ClipperLib.PolyType.ptClip,!0),D.Execute(ClipperLib.ClipType.ctIntersection,T),w=w.concat(T);var x=this.getFillTemplate(n,o,l%2===0,l%2===1),D=new ClipperLib.Clipper,E=new ClipperLib.Paths;D.AddPaths(x,ClipperLib.PolyType.ptSubject,!1),D.AddPaths(L,ClipperLib.PolyType.ptClip,!0),D.Execute(ClipperLib.ClipType.ctIntersection,E),w=w.concat(E),ClipperLib.JS.ScaleDownPaths(u,i),ClipperLib.JS.ScaleDownPaths(f,i),ClipperLib.JS.ScaleDownPaths(w,i),p.push({outerLayer:u,innerLayer:f,fill:w})}return p},D3D.Slicer.prototype.dataToGcode=function(e,t){"use strict";function i(e){for(var t=[],i=0;if&&h&&t.push(["G0","E"+(m-d).toFixed(3),"F"+(60*u).toFixed(3)].join(" ")),t.push(["G0","X"+a.X.toFixed(3)+" Y"+a.Y.toFixed(3)+" Z"+L,"F"+60*p].join(" ")),m>f&&h&&t.push(["G0","E"+m.toFixed(3),"F"+(60*u).toFixed(3)].join(" "));else{var c=(new THREE.Vector2).set(a.X,a.Y),g=(new THREE.Vector2).set(n.X,n.Y),C=c.distanceTo(g);m+=C*l*r/b*y,t.push(["G1","X"+a.X.toFixed(3)+" Y"+a.Y.toFixed(3)+" Z"+L,"F"+v,"E"+m.toFixed(3)].join(" "))}n=a}return t}for(var r=t.config["printer.layerHeight"],n=t.config["printer.speed"],o=t.config["printer.bottomLayerSpeed"],s=t.config["printer.firstLayerSlow"],a=t.config["printer.bottomFlowRate"],p=t.config["printer.travelSpeed"],c=t.config["printer.filamentThickness"],l=t.config["printer.wallThickness"],h=(t.config["printer.enableTraveling"],t.config["printer.retraction.enabled"]),u=t.config["printer.retraction.speed"],f=t.config["printer.retraction.minDistance"],d=t.config["printer.retraction.amount"],g=t.getStartCode(),m=0,v=s?(60*o).toFixed(3):(60*n).toFixed(3),b=Math.pow(c/2,2)*Math.PI,y=a,C=0;Cl;l++){var l=0;c.clearRect(0,0,400,400);var h=a[l%a.length];r(h.outerLayer,"red"),r(h.innerLayer,"green"),r(h.fill,"blue")}return p},D3D.Slicer.prototype.getGcode=function(e){"use strict";var t=e.config["printer.layerHeight"],i=e.config["printer.dimensions.z"],r=this.slice(i,t);r.shift();var n=this.slicesToData(r,e),o=this.dataToGcode(n,e);return o}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..a53d0c5 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,25 @@ +'use strict'; + +var gulp = require('gulp'); +var rename = require('gulp-rename'); +var uglify = require('gulp-uglify'); +var concat = require('gulp-concat'); +var watch = require('gulp-watch'); + +var files = [ + "src/utils.js", + "src/box.js", + "src/printer.js", + "src/slicer.js" +]; + +var DEST = 'build/'; + +gulp.task('default', function () { + return gulp.src(files) + .pipe(concat('d3d.js')) + .pipe(gulp.dest(DEST)) + .pipe(uglify()) + .pipe(rename({extname: '.min.js'})) + .pipe(gulp.dest(DEST)); +}); \ No newline at end of file diff --git a/src/slicer.js b/src/slicer.js index 95d7ff1..72aaa96 100644 --- a/src/slicer.js +++ b/src/slicer.js @@ -31,7 +31,7 @@ D3D.Slicer.prototype.setGeometry = function (geometry) { return this; }; D3D.Slicer.prototype.addLine = function (a, b) { - "use stict"; + "use strict"; //think lookup can only be b_a, a_b is only possible when face is flipped var index = this.lineLookup[a + "_" + b] || this.lineLookup[b + "_" + a]; @@ -309,7 +309,7 @@ D3D.Slicer.prototype.slicesToData = function (slices, printer) { outerLayer: outerLayer, innerLayer: innerLayer, fill: fill - }) + }); } return data; @@ -332,8 +332,6 @@ D3D.Slicer.prototype.dataToGcode = function (data, printer) { var retractionAmount = printer.config["printer.retraction.amount"]; function sliceToGcode (slice) { - "use strict"; - var gcode = []; for (var i = 0; i < slice.length; i ++) { @@ -422,17 +420,10 @@ D3D.Slicer.prototype.dataToGcode = function (data, printer) { D3D.Slicer.prototype.drawPaths = function (printer, min, max) { "use strict"; - var canvas = document.createElement("canvas"); - canvas.width = 400; - canvas.height = 400; - var context = canvas.getContext("2d"); - var layerHeight = printer.config["printer.layerHeight"]; var dimensionsZ = printer.config["printer.dimensions.z"]; function drawPolygons (paths, color) { - "use strict"; - context.fillStyle = color; context.strokeStyle = color; context.beginPath(); @@ -456,6 +447,11 @@ D3D.Slicer.prototype.drawPaths = function (printer, min, max) { var data = this.slicesToData(slices, printer); + var canvas = document.createElement("canvas"); + canvas.width = 400; + canvas.height = 400; + var context = canvas.getContext("2d"); + for (var layer = min; layer < max; layer ++) { var layer = 0; context.clearRect(0, 0, 400, 400); @@ -486,6 +482,7 @@ D3D.Slicer.prototype.getGcode = function (printer) { //TODO //make the path more optimized for 3d printers //make the printer follow the shortest path from line to line + //see https://github.com/Ultimaker/CuraEngine#gcode-generation var gcode = this.dataToGcode(data, printer); return gcode; diff --git a/src/utils.js b/src/utils.js index 2f782bc..cbe6bb3 100644 --- a/src/utils.js +++ b/src/utils.js @@ -31,8 +31,6 @@ function sendAPI (url, data, callback) { dataType: "json", timeout: 10000, success: function (response) { - "use strict"; - if (response.status === "success") { if (callback !== undefined) { callback(response.data); @@ -43,8 +41,6 @@ function sendAPI (url, data, callback) { } } }).fail(function () { - "use strict"; - console.warn("failed connecting to " + url); sendAPI(url, data, callback); }); @@ -58,8 +54,6 @@ function getAPI (url, callback) { dataType: "json", timeout: 5000, success: function (response) { - "use strict"; - if (response.status === "success") { if (callback !== undefined) { callback(response.data); @@ -70,8 +64,6 @@ function getAPI (url, callback) { } } }).fail(function () { - "use strict"; - console.warn("failed connecting to " + url); getAPI(url, callback); }); @@ -110,8 +102,6 @@ function applyMouseControls (renderer, camera, maxDistance) { var moveCamera = false; function updateCamera () { - "use strict"; - camera.position.x = Math.cos(rotY)*Math.sin(rotX)*distance; camera.position.y = Math.sin(rotY)*distance; camera.position.z = Math.cos(rotY)*Math.cos(rotX)*distance; @@ -119,11 +109,8 @@ function applyMouseControls (renderer, camera, maxDistance) { } $(renderer.domElement).on("mousedown", function (e) { - "use strict"; - moveCamera = true; }).on("wheel", function (e) { - "use strict"; var event = e.originalEvent; event.preventDefault(); @@ -133,11 +120,8 @@ function applyMouseControls (renderer, camera, maxDistance) { }); $(window).on("mouseup", function (e) { - "use strict"; - moveCamera = false; }).on("mousemove", function (e) { - "use strict"; var event = e.originalEvent; if (moveCamera === true) { @@ -155,8 +139,6 @@ var requestAnimFrame = (function () { "use strict"; return requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || function (callback) { - "use strict"; - setTimeout(callback, 1000/60); }; })(); \ No newline at end of file