diff --git a/.gitignore b/.gitignore index 8a112de..8fc7ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ src/script/.DS_Store src/.DS_Store -bower_components/ \ No newline at end of file +bower_components/ +src/oldcode.js + +test.html diff --git a/build/d3d.js b/build/d3d.js index 6093345..f42af8b 100644 --- a/build/d3d.js +++ b/build/d3d.js @@ -89,61 +89,6 @@ Array.prototype.clone = function () { return array; }; - -function applyMouseControls (renderer, camera, center, 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.set( - Math.cos(rotY)*Math.sin(rotX)*distance, - Math.sin(rotY)*distance, - Math.cos(rotY)*Math.cos(rotX)*distance - ).add(center); - camera.lookAt(center); - } - - $(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 @@ -617,6 +562,159 @@ D3D.Printer.prototype.subsituteVariables = function (gcode) { }; /****************************************************** * +* Path +* +* Abstraction layer for annoying clipper js +* +******************************************************/ + +D3D.Path = function (path, closed) { + "use strict"; + + this.path = path || []; + this.closed = (closed !== undefined) ? closed : true; +}; +D3D.Path.prototype.setPath = function (path) { + "use strict"; + + this.path = path; + + return this; +}; +D3D.Path.prototype.union = function (path) { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctUnion, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.difference = function (path) { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctDifference, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.intersect = function (path) { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctIntersection, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.xor = function () { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctXor, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.offset = function (offset) { + "use strict"; + + var solution = new ClipperLib.Paths(); + var co = new ClipperLib.ClipperOffset(1, 1); + co.AddPaths(this.path, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon); + co.Execute(solution, offset); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.scaleUp = function (factor) { + "use strict"; + + var path = ClipperLib.JS.ScaleUpPaths(this.path, factor); + + return this; +}; +D3D.Path.prototype.scaleDown = function (factor) { + "use strict"; + + var path = ClipperLib.JS.ScaleDownPaths(this.path, factor); + + return this; +}; +D3D.Path.prototype.tresholdArea = function (minArea) { + "use strict"; + + for (var i = 0; i < this.path.length; i ++) { + var shape = this.path[i]; + + var area = ClipperLib.Clipper.Area(shape); + + if (area < minArea) { + this.path.splice(i, 1); + i --; + } + } + + return areas; +}; +D3D.Path.prototype.area = function () { + "use strict"; + + var areas = []; + + for (var i = 0; i < this.path.length; i ++) { + var shape = this.path[i]; + + areas.push(ClipperLib.Clipper.Area(shape)) + } + + return areas; +}; +D3D.Path.prototype.join = function (path) { + "use strict"; + + this.path = this.path.concat(path.path); + + return this; +} +D3D.Path.prototype.clone = function () { + "use strict"; + + var path = ClipperLib.JS.Clone(this.path); + + return new D3D.Path(path, this.closed); +} +D3D.Path.prototype.draw = function (context, color) { + "use strict"; + + context.strokeStyle = color; + for (var i = 0; i < this.path.length; i ++) { + var shape = this.path[i]; + + context.beginPath(); + var length = this.closed ? (shape.length + 1) : shape.length; + for (var j = 0; j < length; j ++) { + var point = shape[j % shape.length]; + + context.lineTo(point.X*2, point.Y*2); + } + context.stroke(); + } +}; +/****************************************************** +* * Slicer * * TODO (optimalisatie) @@ -732,8 +830,8 @@ D3D.Slicer.prototype.slice = function (height, step) { var line = this.lines[index].line; var alpha = (y - line.start.y) / (line.end.y - line.start.y); - var x = line.start.x * alpha + line.end.x * (1 - alpha); - var z = line.start.z * alpha + line.end.z * (1 - alpha); + var x = line.end.x * alpha + line.start.x * (1 - alpha); + var z = line.end.z * alpha + line.start.z * (1 - alpha); intersections[index] = new THREE.Vector2(x, z); } @@ -807,7 +905,8 @@ D3D.Slicer.prototype.slice = function (height, step) { //stop when ther are no intersects if (slice.length > 0) { - slices.push(slice); + slices.push(new D3D.Path(slice, true)); + //slices.push(slice); } else { break; @@ -816,20 +915,64 @@ D3D.Slicer.prototype.slice = function (height, step) { return slices; }; -D3D.Slicer.prototype.getInset = function (slice, offset) { +D3D.Slicer.prototype.slicesToData = function (slices, printer) { "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); + var scale = 100; - return solution; + 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 skinCount = Math.ceil(shellThickness/layerHeight); + + var data = []; + + var lowFillTemplate = this.getFillTemplate(dimensionsZ, fillSize, true, true); + + for (var layer = 0; layer < slices.length; layer ++) { + var slice = slices[layer]; + + var outerLayer = slice.clone(); + outerLayer.scaleUp(scale); + + var insets = new D3D.Path(); + for (var offset = wallThickness; offset <= shellThickness; offset += wallThickness) { + var inset = outerLayer.offset(-offset); + + insets.join(inset); + } + + var fillArea = (inset || outerLayer).offset(-wallThickness/2); + + var downFill = (layer - skinCount >= 0) ? slices[layer - skinCount] : new D3D.Path(); + var upFill = (layer + skinCount < slices.length) ? slices[layer + skinCount] : new D3D.Path(); + var highFillArea = fillArea.difference(downFill.intersect(upFill).scaleUp(scale)); + + var lowFillArea = fillArea.difference(highFillArea); + + var fill = new D3D.Path([], false); + fill.join(lowFillTemplate.intersect(lowFillArea)); + if (highFillArea.path.length > 0) { + var highFillTemplate = this.getFillTemplate(dimensionsZ, wallThickness, (layer % 2 === 0), (layer % 2 === 1)); + fill.join(highFillTemplate.intersect(highFillArea)); + } + + data.push({ + outerLayer: outerLayer.scaleDown(scale), + insets: insets.scaleDown(scale), + fill: fill.scaleDown(scale) + }); + } + + return data; }; D3D.Slicer.prototype.getFillTemplate = function (dimension, size, even, uneven) { "use strict"; - var paths = new ClipperLib.Paths(); + var paths = []; if (even) { for (var length = 0; length <= dimension; length += size) { @@ -842,125 +985,8 @@ D3D.Slicer.prototype.getFillTemplate = function (dimension, size, even, uneven) } } - 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); - } - - //moet fillArea wel kleiner? - //var fillArea = this.getInset((inset || outerLayer), wallThickness); - var fillArea = (inset || outerLayer); - - var fillAbove = false; - //for (var i = 1; i < shellThickness/layerHeight; i ++) { - for (var i = 1; i < shellThickness/layerHeight; 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 === false) { - fillAbove = newLayer; - } - else { - var c = new ClipperLib.Clipper(); - var solution = new ClipperLib.Paths(); - c.AddPaths(newLayer, 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; + //return paths; + return new D3D.Path(paths, false); }; D3D.Slicer.prototype.dataToGcode = function (data, printer) { "use strict"; @@ -982,8 +1008,8 @@ D3D.Slicer.prototype.dataToGcode = function (data, printer) { function sliceToGcode (slice) { var gcode = []; - for (var i = 0; i < slice.length; i ++) { - var shape = slice[i]; + for (var i = 0; i < slice.path.length; i ++) { + var shape = slice.path[i]; var previousPoint; @@ -1057,54 +1083,20 @@ D3D.Slicer.prototype.dataToGcode = function (data, printer) { var z = ((layer + 1) * layerHeight).toFixed(3); gcode = gcode.concat(sliceToGcode(slice.outerLayer)); - gcode = gcode.concat(sliceToGcode(slice.innerLayer)); + gcode = gcode.concat(sliceToGcode(slice.insets)); gcode = gcode.concat(sliceToGcode(slice.fill)); } gcode = gcode.concat(printer.getEndCode()); return gcode; }; +//only for debug purposes D3D.Slicer.prototype.drawPaths = function (printer, min, max) { "use strict"; var layerHeight = printer.config["printer.layerHeight"]; var dimensionsZ = printer.config["printer.dimensions.z"]; - function drawLines (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 * 2), (path[0].Y * 2)); - - for (var j = 0; j < path.length; j ++) { - var point = path[j]; - context.lineTo((point.X * 2), (point.Y * 2)); - } - //context.closePath(); - } - context.stroke(); - } - - function drawVertexes (paths, color) { - context.fillStyle = color; - context.strokeStyle = color; - - for (var i = 0; i < paths.length; i ++) { - var path = paths[i]; - - for (var j = 0; j < path.length; j ++) { - var point = path[j]; - context.beginPath(); - context.arc(point.X * 2, point.Y * 2, 1, 0, Math.PI*2, false); - context.stroke(); - } - } - } - var slices = this.slice(dimensionsZ, layerHeight); var data = this.slicesToData(slices, printer); @@ -1114,14 +1106,13 @@ D3D.Slicer.prototype.drawPaths = function (printer, min, max) { canvas.height = 400; var context = canvas.getContext("2d"); + for (var layer = min; layer < max; layer ++) { var slice = data[layer % data.length]; - drawLines(slice.outerLayer, "red"); - drawLines(slice.innerLayer, "green"); - drawLines(slice.fill, "blue"); - - drawVertexes(slice.outerLayer, "green"); + slice.insets.draw(context, "blue"); + slice.outerLayer.draw(context, "green"); + slice.fill.draw(context, "red"); } return canvas; diff --git a/build/d3d.min.js b/build/d3d.min.js index 580b948..6a49e7a 100644 --- a/build/d3d.min.js +++ b/build/d3d.min.js @@ -1 +1 @@ -function sendAPI(t,e,i){"use strict";$.ajax({url:t,type:"POST",data:e,dataType:"json",timeout:1e4,success:function(t){"success"===t.status?void 0!==i&&i(t.data):console.warn(t.msg)}}).fail(function(){console.warn("Failed connecting to "+t),sendAPI(t,e,i)})}function getAPI(t,e){"use strict";$.ajax({url:t,dataType:"json",timeout:5e3,success:function(t){"success"===t.status?void 0!==e&&e(t.data):console.warn(t.msg)}}).fail(function(){console.warn("Failed connecting to "+t),getAPI(t,e)})}function downloadFile(t,e){"use strict";$(document.createElement("a")).attr({download:t,href:"data:text/plain,"+e})[0].click()}function applyMouseControls(t,e,i,r){"use strict";function n(){e.position.set(Math.cos(a)*Math.sin(s)*o,Math.sin(a)*o,Math.cos(a)*Math.cos(s)*o).add(i),e.lookAt(i)}var o=20,s=0,a=0,p=!1;$(t.domElement).on("mousedown",function(t){p=!0}).on("wheel",function(t){var e=t.originalEvent;e.preventDefault(),o=THREE.Math.clamp(o-e.wheelDelta,1,r),n()}),$(window).on("mouseup",function(t){p=!1}).on("mousemove",function(t){var e=t.originalEvent;p===!0&&(s=(s-e.webkitMovementX/100)%(2*Math.PI),a=THREE.Math.clamp(a+e.webkitMovementY/100,-Math.PI/2,Math.PI/2),n())}),n()}var D3D={version:"0.1",website:"http://www.doodle3d.com/",contact:"develop@doodle3d.com"};THREE.Vector2.prototype.normal=function(){"use strict";var t=this.y,e=-this.x;return this.set(t,e)},Array.prototype.clone=function(){"use strict";for(var t=[],e=0;e0&&this.printer.status.buffered_lines+this.batchSize<=this.maxBufferedLines?this.printBatch():this.updateState()},D3D.Box.prototype.updateState=function(){"use strict";var t=this;this.getInfoStatus(function(e){t.printer.status=e,void 0!==t.onupdate&&t.onupdate(e),t.update()})},D3D.Box.prototype.print=function(t){"use strict";for(this.currentBatch=0,t=t.clone();t.length>0;){var e=t.splice(0,Math.min(this.batchSize,t.length));this.printBatches.push(e)}return this},D3D.Box.prototype.printBatch=function(){"use strict";var t=this,e=this.printBatches.shift();this.setPrinterPrint({start:0===this.currentBatch?!0:!1,first:0===this.currentBatch?!0:!1,gcode:e.join("\n")},function(e){console.log("batch sent: "+t.currentBatch,e),t.printBatches.length>0&&t.currentBatch++,t.updateState()})},D3D.Box.prototype.stopPrint=function(){"use strict";this.printBatches=[],this.currentBatch=0;var t=["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)"];return this.setPrinterStop({gcode:t.join("\n")},function(t){console.log("Printer stop command sent")}),this},D3D.Box.prototype.getConfig=function(t,e){"use strict";return getAPI(this.api+"config/?"+t.join("=&")+"=",e),this},D3D.Box.prototype.getConfigAll=function(t){"use strict";return getAPI(this.api+"config/all",t),this},D3D.Box.prototype.setConfig=function(t,e){"use strict";var i=this;return sendAPI(this.api+"config",t,function(r){for(var n in r.validation)"ok"!==r.validation[n]&&delete t[n];i.updateConfig(t),i.printer.updateConfig(t),void 0!==e&&e(r)}),this},D3D.Box.prototype.getInfo=function(t){"use strict";return getAPI(this.api+"info",t),this},D3D.Box.prototype.getInfoStatus=function(t){"use strict";return getAPI(this.api+"info/status",t),this},D3D.Box.prototype.downloadInfoLogFiles=function(){"use strict";window.location=this.api+"info/logfiles"},D3D.Box.prototype.getInfoAcces=function(t){"use strict";return getAPI(this.api+"info/access",t),this},D3D.Box.prototype.getNetworkScan=function(t){"use strict";return getAPI(this.api+"network/scan",t),this},D3D.Box.prototype.getNetworkKnown=function(t){"use strict";return getAPI(this.api+"network/known",t),this},D3D.Box.prototype.getNetworkStatus=function(t){"use strict";return getAPI(this.api+"network/status",t),this},D3D.Box.prototype.setNetworkAssosiate=function(t,e){"use strict";return sendAPI(this.api+"network/associate",t,e),this},D3D.Box.prototype.setNetworkDisassosiate=function(t){"use strict";return sendAPI(this.api+"network/disassociate",{},t),this},D3D.Box.prototype.setNetworkOpenAP=function(t){"use strict";return sendAPI(this.api+"network/openap",{},t),this},D3D.Box.prototype.setNetworkRemove=function(t,e){"use strict";return sendAPI(this.api+"network/remove",{ssid:t},e),this},D3D.Box.prototype.getNetworkSignin=function(t){"use strict";return getAPI(this.api+"network/signin",t),this},D3D.Box.prototype.getNetworkAlive=function(t){"use strict";return getAPI(this.api+"network/alive",t),this},D3D.Box.prototype.getPrinterTemperature=function(t){"use strict";return getAPI(this.api+"printer/temperature",t),this},D3D.Box.prototype.getPrinterProgress=function(t){"use strict";return getAPI(this.api+"printer/progress",t),this},D3D.Box.prototype.getPrinterState=function(t){"use strict";return getAPI(this.api+"printer/state",t),this},D3D.Box.prototype.getPrinterListAll=function(t){"use strict";return getAPI(this.api+"printer/listall",t),this},D3D.Box.prototype.setPrinterHeatup=function(t){"use strict";return sendAPI(this.api+"printer/heatup",{},t),this},D3D.Box.prototype.setPrinterPrint=function(t,e){"use strict";return sendAPI(this.api+"printer/print",t,e),this},D3D.Box.prototype.setPrinterStop=function(t,e){"use strict";return sendAPI(this.api+"printer/stop",t,e),this},D3D.Box.prototype.getSketch=function(t,e){"use strict";return getAPI(this.api+"sketch/?id="+t,e),this},D3D.Box.prototype.setSketch=function(t,e){"use strict";return sendAPI(this.api+"sketch",{data:t},e),this},D3D.Box.prototype.getSketchStatus=function(t){"use strict";return getAPI(this.api+"sketch/status",t),this},D3D.Box.prototype.setSketchClear=function(t){"use strict";return sendAPI(this.api+"sketch/clear",t),this},D3D.Box.prototype.getSystemVersions=function(t){"use strict";return getAPI(this.api+"system/fwversions",t),this},D3D.Box.prototype.getUpdateStatus=function(t){"use strict";return getAPI(this.api+"update/status",t),this},D3D.Box.prototype.setUpdateDownload=function(t){"use strict";return sendAPI(this.api+"update/download",{},t),this},D3D.Box.prototype.setUpdateInstall=function(t){"use strict";return sendAPI(this.api+"update/install",{},t),this},D3D.Box.prototype.setUpdateClear=function(t){"use strict";return sendAPI(this.api+"update/clear",{},t),this},D3D.Printer=function(t){"use strict";this.status={},this.config={},this.updateConfig(t)},D3D.Printer.prototype.updateConfig=function(t){"use strict";for(var e in t)0===e.indexOf("printer")&&(this.config[e]=t[e]);return this},D3D.Printer.prototype.getStartCode=function(){"use strict";var t=this.config["printer.startcode"];return t=this.subsituteVariables(t),t.split("\n")},D3D.Printer.prototype.getEndCode=function(){"use strict";var t=this.config["printer.endcode"];return t=this.subsituteVariables(t),t.split("\n")},D3D.Printer.prototype.subsituteVariables=function(t){"use strict";var e=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 t=t.replace(/{printingTemp}/gi,e),t=t.replace(/{printingBedTemp}/gi,i),t=t.replace(/{preheatTemp}/gi,r),t=t.replace(/{preheatBedTemp}/gi,n),t=t.replace(/{printerType}/gi,o),t=t.replace(/{if heatedBed}/gi,a)},D3D.Slicer=function(){"use strict";this.lines=[]},D3D.Slicer.prototype.setMesh=function(t){"use strict";t.updateMatrix();var e=t.geometry.clone();return e.mergeVertices(),e.applyMatrix(t.matrix),e instanceof THREE.BufferGeometry&&(e=(new THREE.Geometry).fromBufferGeometry(e)),this.geometry=e,this.createLines(),this},D3D.Slicer.prototype.createLines=function(){"use strict";function t(t,r){var n=e[t+"_"+r]||e[r+"_"+t];return void 0===n&&(n=i.lines.length,e[t+"_"+r]=n,i.lines.push({line:new THREE.Line3(i.geometry.vertices[t],i.geometry.vertices[r]),connects:[],normals:[]})),n}this.lines=[];for(var e={},i=this,r=0;r=a;a++)a>=0&&(void 0===i[a]&&(i[a]=[]),i[a].push(r));for(var p=[],c=1;c0)break;f=-1}else f=-1}v.length>0&&m.push(v)}}if(!(m.length>0))break;p.push(m)}return p},D3D.Slicer.prototype.getInset=function(t,e){"use strict";var i=new ClipperLib.Paths,r=new ClipperLib.ClipperOffset(1,1);return r.AddPaths(t,ClipperLib.JoinType.jtRound,ClipperLib.EndType.etClosedPolygon),r.Execute(i,-e),i},D3D.Slicer.prototype.getFillTemplate=function(t,e,i,r){"use strict";var n=new ClipperLib.Paths;if(i)for(var o=0;t>=o;o+=e)n.push([{X:o,Y:0},{X:o,Y:t}]);if(r)for(var o=0;t>=o;o+=e)n.push([{X:0,Y:o},{X:t,Y:o}]);return n},D3D.Slicer.prototype.slicesToData=function(t,e){"use strict";for(var i=100,r=e.config["printer.layerHeight"]*i,n=e.config["printer.dimensions.z"]*i,o=e.config["printer.wallThickness"]*i,s=e.config["printer.shellThickness"]*i,a=e.config["printer.fillSize"]*i,p=(e.config["printer.brimOffset"]*i,[]),c=this.getFillTemplate(n,a,!0,!0),u=0;ud;d+=o){var g=this.getInset(h,d);f=f.concat(g)}for(var D=g||h,y=!1,d=1;s/r>d;d++){var m=ClipperLib.JS.Clone(t[u+d]);if(ClipperLib.JS.ScaleUpPaths(m,i),0===m.length||y&&0===y.length){y=[];break}if(y===!1)y=m;else{var v=new ClipperLib.Clipper,P=new ClipperLib.Paths;v.AddPaths(m,ClipperLib.PolyType.ptSubject,!0),v.AddPaths(y,ClipperLib.PolyType.ptClip,!0),v.Execute(ClipperLib.ClipType.ctIntersection,P),y=P}}var b=new ClipperLib.Clipper,w=new ClipperLib.Paths;b.AddPaths(D,ClipperLib.PolyType.ptSubject,!0),b.AddPaths(y,ClipperLib.PolyType.ptClip,!0),b.Execute(ClipperLib.ClipType.ctDifference,w);var b=new ClipperLib.Clipper,x=new ClipperLib.Paths;b.AddPaths(D,ClipperLib.PolyType.ptSubject,!0),b.AddPaths(w,ClipperLib.PolyType.ptClip,!0),b.Execute(ClipperLib.ClipType.ctDifference,x);var C=[],b=new ClipperLib.Clipper,B=new ClipperLib.Paths;b.AddPaths(c,ClipperLib.PolyType.ptSubject,!1),b.AddPaths(x,ClipperLib.PolyType.ptClip,!0),b.Execute(ClipperLib.ClipType.ctIntersection,B),C=C.concat(B);var S=this.getFillTemplate(n,o,u%2===0,u%2===1),b=new ClipperLib.Clipper,T=new ClipperLib.Paths;b.AddPaths(S,ClipperLib.PolyType.ptSubject,!1),b.AddPaths(w,ClipperLib.PolyType.ptClip,!0),b.Execute(ClipperLib.ClipType.ctIntersection,T),C=C.concat(T),ClipperLib.JS.ScaleDownPaths(h,i),ClipperLib.JS.ScaleDownPaths(f,i),ClipperLib.JS.ScaleDownPaths(C,i),p.push({outerLayer:h,innerLayer:f,fill:C})}return p},D3D.Slicer.prototype.dataToGcode=function(t,e){"use strict";function i(t){for(var e=[],i=0;if&&l&&e.push(["G0","E"+(D-d).toFixed(3),"F"+(60*h).toFixed(3)].join(" ")),e.push(["G0","X"+a.X.toFixed(3)+" Y"+a.Y.toFixed(3)+" Z"+w,"F"+60*p].join(" ")),D>f&&l&&e.push(["G0","E"+D.toFixed(3),"F"+(60*h).toFixed(3)].join(" "));else{var c=(new THREE.Vector2).set(a.X,a.Y),g=(new THREE.Vector2).set(n.X,n.Y),P=c.distanceTo(g);D+=P*u*r/m*v,e.push(["G1","X"+a.X.toFixed(3)+" Y"+a.Y.toFixed(3)+" Z"+w,"F"+y,"E"+D.toFixed(3)].join(" "))}n=a}return e}for(var r=e.config["printer.layerHeight"],n=e.config["printer.speed"],o=e.config["printer.bottomLayerSpeed"],s=e.config["printer.firstLayerSlow"],a=e.config["printer.bottomFlowRate"],p=e.config["printer.travelSpeed"],c=e.config["printer.filamentThickness"],u=e.config["printer.wallThickness"],l=(e.config["printer.enableTraveling"],e.config["printer.retraction.enabled"]),h=e.config["printer.retraction.speed"],f=e.config["printer.retraction.minDistance"],d=e.config["printer.retraction.amount"],g=e.getStartCode(),D=0,y=s?(60*o).toFixed(3):(60*n).toFixed(3),m=Math.pow(c/2,2)*Math.PI,v=a,P=0;Pl;l++){var h=p[l%p.length];r(h.outerLayer,"red"),r(h.innerLayer,"green"),r(h.fill,"blue"),n(h.outerLayer,"green")}return c},D3D.Slicer.prototype.getGcode=function(t){"use strict";var e=t.config["printer.layerHeight"],i=t.config["printer.dimensions.z"],r=(new Date).getTime(),n=this.slice(i,e),o=(new Date).getTime();console.log("Slicing: "+(o-r)+"ms");var r=(new Date).getTime(),s=this.slicesToData(n,t),o=(new Date).getTime();console.log("Data: "+(o-r)+"ms");var r=(new Date).getTime(),a=this.dataToGcode(s,t),o=(new Date).getTime();return console.log("Gcode: "+(o-r)+"ms"),a}; \ No newline at end of file +function sendAPI(t,e,i){"use strict";$.ajax({url:t,type:"POST",data:e,dataType:"json",timeout:1e4,success:function(t){"success"===t.status?void 0!==i&&i(t.data):console.warn(t.msg)}}).fail(function(){console.warn("Failed connecting to "+t),sendAPI(t,e,i)})}function getAPI(t,e){"use strict";$.ajax({url:t,dataType:"json",timeout:5e3,success:function(t){"success"===t.status?void 0!==e&&e(t.data):console.warn(t.msg)}}).fail(function(){console.warn("Failed connecting to "+t),getAPI(t,e)})}function downloadFile(t,e){"use strict";$(document.createElement("a")).attr({download:t,href:"data:text/plain,"+e})[0].click()}var D3D={version:"0.1",website:"http://www.doodle3d.com/",contact:"develop@doodle3d.com"};THREE.Vector2.prototype.normal=function(){"use strict";var t=this.y,e=-this.x;return this.set(t,e)},Array.prototype.clone=function(){"use strict";for(var t=[],e=0;e0&&this.printer.status.buffered_lines+this.batchSize<=this.maxBufferedLines?this.printBatch():this.updateState()},D3D.Box.prototype.updateState=function(){"use strict";var t=this;this.getInfoStatus(function(e){t.printer.status=e,void 0!==t.onupdate&&t.onupdate(e),t.update()})},D3D.Box.prototype.print=function(t){"use strict";for(this.currentBatch=0,t=t.clone();t.length>0;){var e=t.splice(0,Math.min(this.batchSize,t.length));this.printBatches.push(e)}return this},D3D.Box.prototype.printBatch=function(){"use strict";var t=this,e=this.printBatches.shift();this.setPrinterPrint({start:0===this.currentBatch?!0:!1,first:0===this.currentBatch?!0:!1,gcode:e.join("\n")},function(e){console.log("batch sent: "+t.currentBatch,e),t.printBatches.length>0&&t.currentBatch++,t.updateState()})},D3D.Box.prototype.stopPrint=function(){"use strict";this.printBatches=[],this.currentBatch=0;var t=["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)"];return this.setPrinterStop({gcode:t.join("\n")},function(t){console.log("Printer stop command sent")}),this},D3D.Box.prototype.getConfig=function(t,e){"use strict";return getAPI(this.api+"config/?"+t.join("=&")+"=",e),this},D3D.Box.prototype.getConfigAll=function(t){"use strict";return getAPI(this.api+"config/all",t),this},D3D.Box.prototype.setConfig=function(t,e){"use strict";var i=this;return sendAPI(this.api+"config",t,function(r){for(var n in r.validation)"ok"!==r.validation[n]&&delete t[n];i.updateConfig(t),i.printer.updateConfig(t),void 0!==e&&e(r)}),this},D3D.Box.prototype.getInfo=function(t){"use strict";return getAPI(this.api+"info",t),this},D3D.Box.prototype.getInfoStatus=function(t){"use strict";return getAPI(this.api+"info/status",t),this},D3D.Box.prototype.downloadInfoLogFiles=function(){"use strict";window.location=this.api+"info/logfiles"},D3D.Box.prototype.getInfoAcces=function(t){"use strict";return getAPI(this.api+"info/access",t),this},D3D.Box.prototype.getNetworkScan=function(t){"use strict";return getAPI(this.api+"network/scan",t),this},D3D.Box.prototype.getNetworkKnown=function(t){"use strict";return getAPI(this.api+"network/known",t),this},D3D.Box.prototype.getNetworkStatus=function(t){"use strict";return getAPI(this.api+"network/status",t),this},D3D.Box.prototype.setNetworkAssosiate=function(t,e){"use strict";return sendAPI(this.api+"network/associate",t,e),this},D3D.Box.prototype.setNetworkDisassosiate=function(t){"use strict";return sendAPI(this.api+"network/disassociate",{},t),this},D3D.Box.prototype.setNetworkOpenAP=function(t){"use strict";return sendAPI(this.api+"network/openap",{},t),this},D3D.Box.prototype.setNetworkRemove=function(t,e){"use strict";return sendAPI(this.api+"network/remove",{ssid:t},e),this},D3D.Box.prototype.getNetworkSignin=function(t){"use strict";return getAPI(this.api+"network/signin",t),this},D3D.Box.prototype.getNetworkAlive=function(t){"use strict";return getAPI(this.api+"network/alive",t),this},D3D.Box.prototype.getPrinterTemperature=function(t){"use strict";return getAPI(this.api+"printer/temperature",t),this},D3D.Box.prototype.getPrinterProgress=function(t){"use strict";return getAPI(this.api+"printer/progress",t),this},D3D.Box.prototype.getPrinterState=function(t){"use strict";return getAPI(this.api+"printer/state",t),this},D3D.Box.prototype.getPrinterListAll=function(t){"use strict";return getAPI(this.api+"printer/listall",t),this},D3D.Box.prototype.setPrinterHeatup=function(t){"use strict";return sendAPI(this.api+"printer/heatup",{},t),this},D3D.Box.prototype.setPrinterPrint=function(t,e){"use strict";return sendAPI(this.api+"printer/print",t,e),this},D3D.Box.prototype.setPrinterStop=function(t,e){"use strict";return sendAPI(this.api+"printer/stop",t,e),this},D3D.Box.prototype.getSketch=function(t,e){"use strict";return getAPI(this.api+"sketch/?id="+t,e),this},D3D.Box.prototype.setSketch=function(t,e){"use strict";return sendAPI(this.api+"sketch",{data:t},e),this},D3D.Box.prototype.getSketchStatus=function(t){"use strict";return getAPI(this.api+"sketch/status",t),this},D3D.Box.prototype.setSketchClear=function(t){"use strict";return sendAPI(this.api+"sketch/clear",t),this},D3D.Box.prototype.getSystemVersions=function(t){"use strict";return getAPI(this.api+"system/fwversions",t),this},D3D.Box.prototype.getUpdateStatus=function(t){"use strict";return getAPI(this.api+"update/status",t),this},D3D.Box.prototype.setUpdateDownload=function(t){"use strict";return sendAPI(this.api+"update/download",{},t),this},D3D.Box.prototype.setUpdateInstall=function(t){"use strict";return sendAPI(this.api+"update/install",{},t),this},D3D.Box.prototype.setUpdateClear=function(t){"use strict";return sendAPI(this.api+"update/clear",{},t),this},D3D.Printer=function(t){"use strict";this.status={},this.config={},this.updateConfig(t)},D3D.Printer.prototype.updateConfig=function(t){"use strict";for(var e in t)0===e.indexOf("printer")&&(this.config[e]=t[e]);return this},D3D.Printer.prototype.getStartCode=function(){"use strict";var t=this.config["printer.startcode"];return t=this.subsituteVariables(t),t.split("\n")},D3D.Printer.prototype.getEndCode=function(){"use strict";var t=this.config["printer.endcode"];return t=this.subsituteVariables(t),t.split("\n")},D3D.Printer.prototype.subsituteVariables=function(t){"use strict";var e=this.config["printer.temperature"],i=this.config["printer.bed.temperature"],r=this.config["printer.heatup.temperature"],n=this.config["printer.heatup.bed.temperature"],s=this.config["printer.type"],o=this.config["printer.heatedbed"];switch(s){case"makerbot_replicator2":s="r2";break;case"makerbot_replicator2x":s="r2x";break;case"makerbot_thingomatic":s="t6";break;case"makerbot_generic":s="r2";break;case"_3Dison_plus":s="r2"}var a=o?"":";";return t=t.replace(/{printingTemp}/gi,e),t=t.replace(/{printingBedTemp}/gi,i),t=t.replace(/{preheatTemp}/gi,r),t=t.replace(/{preheatBedTemp}/gi,n),t=t.replace(/{printerType}/gi,s),t=t.replace(/{if heatedBed}/gi,a)},D3D.Path=function(t,e){"use strict";this.path=t||[],this.closed=void 0!==e?e:!0},D3D.Path.prototype.setPath=function(t){"use strict";return this.path=t,this},D3D.Path.prototype.union=function(t){"use strict";var e=new ClipperLib.Paths,i=new ClipperLib.Clipper;return i.AddPaths(this.path,ClipperLib.PolyType.ptSubject,this.closed),i.AddPaths(t.path,ClipperLib.PolyType.ptClip,t.closed),i.Execute(ClipperLib.ClipType.ctUnion,e),new D3D.Path(e,this.closed)},D3D.Path.prototype.difference=function(t){"use strict";var e=new ClipperLib.Paths,i=new ClipperLib.Clipper;return i.AddPaths(this.path,ClipperLib.PolyType.ptSubject,this.closed),i.AddPaths(t.path,ClipperLib.PolyType.ptClip,t.closed),i.Execute(ClipperLib.ClipType.ctDifference,e),new D3D.Path(e,this.closed)},D3D.Path.prototype.intersect=function(t){"use strict";var e=new ClipperLib.Paths,i=new ClipperLib.Clipper;return i.AddPaths(this.path,ClipperLib.PolyType.ptSubject,this.closed),i.AddPaths(t.path,ClipperLib.PolyType.ptClip,t.closed),i.Execute(ClipperLib.ClipType.ctIntersection,e),new D3D.Path(e,this.closed)},D3D.Path.prototype.xor=function(){"use strict";var t=new ClipperLib.Paths,e=new ClipperLib.Clipper;return e.AddPaths(this.path,ClipperLib.PolyType.ptSubject,this.closed),e.AddPaths(path.path,ClipperLib.PolyType.ptClip,path.closed),e.Execute(ClipperLib.ClipType.ctXor,t),new D3D.Path(t,this.closed)},D3D.Path.prototype.offset=function(t){"use strict";var e=new ClipperLib.Paths,i=new ClipperLib.ClipperOffset(1,1);return i.AddPaths(this.path,ClipperLib.JoinType.jtRound,ClipperLib.EndType.etClosedPolygon),i.Execute(e,t),new D3D.Path(e,this.closed)},D3D.Path.prototype.scaleUp=function(t){"use strict";ClipperLib.JS.ScaleUpPaths(this.path,t);return this},D3D.Path.prototype.scaleDown=function(t){"use strict";ClipperLib.JS.ScaleDownPaths(this.path,t);return this},D3D.Path.prototype.tresholdArea=function(t){"use strict";for(var e=0;er&&(this.path.splice(e,1),e--)}return areas},D3D.Path.prototype.area=function(){"use strict";for(var t=[],e=0;es;s++){var o=r[s%r.length];t.lineTo(2*o.X,2*o.Y)}t.stroke()}},D3D.Slicer=function(){"use strict";this.lines=[]},D3D.Slicer.prototype.setMesh=function(t){"use strict";t.updateMatrix();var e=t.geometry.clone();return e.mergeVertices(),e.applyMatrix(t.matrix),e instanceof THREE.BufferGeometry&&(e=(new THREE.Geometry).fromBufferGeometry(e)),this.geometry=e,this.createLines(),this},D3D.Slicer.prototype.createLines=function(){"use strict";function t(t,r){var n=e[t+"_"+r]||e[r+"_"+t];return void 0===n&&(n=i.lines.length,e[t+"_"+r]=n,i.lines.push({line:new THREE.Line3(i.geometry.vertices[t],i.geometry.vertices[r]),connects:[],normals:[]})),n}this.lines=[];for(var e={},i=this,r=0;r=a;a++)a>=0&&(void 0===i[a]&&(i[a]=[]),i[a].push(r));for(var p=[],c=1;c0)break;f=-1}else f=-1}v.length>0&&P.push(v)}}if(!(P.length>0))break;p.push(new D3D.Path(P,!0))}return p},D3D.Slicer.prototype.slicesToData=function(t,e){"use strict";for(var i=100,r=e.config["printer.layerHeight"]*i,n=e.config["printer.dimensions.z"]*i,s=e.config["printer.wallThickness"]*i,o=e.config["printer.shellThickness"]*i,a=e.config["printer.fillSize"]*i,p=(e.config["printer.brimOffset"]*i,Math.ceil(o/r)),c=[],h=this.getFillTemplate(n,a,!0,!0),u=0;u=D;D+=s){var g=f.offset(-D);d.join(g)}var y=(g||f).offset(-s/2),P=u-p>=0?t[u-p]:new D3D.Path,v=u+p0){var b=this.getFillTemplate(n,s,u%2===0,u%2===1);x.join(b.intersect(w))}c.push({outerLayer:f.scaleDown(i),insets:d.scaleDown(i),fill:x.scaleDown(i)})}return c},D3D.Slicer.prototype.getFillTemplate=function(t,e,i,r){"use strict";var n=[];if(i)for(var s=0;t>=s;s+=e)n.push([{X:s,Y:0},{X:s,Y:t}]);if(r)for(var s=0;t>=s;s+=e)n.push([{X:0,Y:s},{X:t,Y:s}]);return new D3D.Path(n,!1)},D3D.Slicer.prototype.dataToGcode=function(t,e){"use strict";function i(t){for(var e=[],i=0;if&&u&&e.push(["G0","E"+(g-d).toFixed(3),"F"+(60*l).toFixed(3)].join(" ")),e.push(["G0","X"+a.X.toFixed(3)+" Y"+a.Y.toFixed(3)+" Z"+x,"F"+60*p].join(" ")),g>f&&u&&e.push(["G0","E"+g.toFixed(3),"F"+(60*l).toFixed(3)].join(" "));else{var c=(new THREE.Vector2).set(a.X,a.Y),D=(new THREE.Vector2).set(n.X,n.Y),w=c.distanceTo(D);g+=w*h*r/P*v,e.push(["G1","X"+a.X.toFixed(3)+" Y"+a.Y.toFixed(3)+" Z"+x,"F"+y,"E"+g.toFixed(3)].join(" "))}n=a}return e}for(var r=e.config["printer.layerHeight"],n=e.config["printer.speed"],s=e.config["printer.bottomLayerSpeed"],o=e.config["printer.firstLayerSlow"],a=e.config["printer.bottomFlowRate"],p=e.config["printer.travelSpeed"],c=e.config["printer.filamentThickness"],h=e.config["printer.wallThickness"],u=(e.config["printer.enableTraveling"],e.config["printer.retraction.enabled"]),l=e.config["printer.retraction.speed"],f=e.config["printer.retraction.minDistance"],d=e.config["printer.retraction.amount"],D=e.getStartCode(),g=0,y=o?(60*s).toFixed(3):(60*n).toFixed(3),P=Math.pow(c/2,2)*Math.PI,v=a,w=0;wc;c++){var h=o[c%o.length];h.insets.draw(p,"blue"),h.outerLayer.draw(p,"green"),h.fill.draw(p,"red")}return a},D3D.Slicer.prototype.getGcode=function(t){"use strict";var e=t.config["printer.layerHeight"],i=t.config["printer.dimensions.z"],r=(new Date).getTime(),n=this.slice(i,e),s=(new Date).getTime();console.log("Slicing: "+(s-r)+"ms");var r=(new Date).getTime(),o=this.slicesToData(n,t),s=(new Date).getTime();console.log("Data: "+(s-r)+"ms");var r=(new Date).getTime(),a=this.dataToGcode(o,t),s=(new Date).getTime();return console.log("Gcode: "+(s-r)+"ms"),a}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 45ed37c..e9b67c5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,6 +10,7 @@ var files = [ "src/utils.js", "src/box.js", "src/printer.js", + "src/path.js", "src/slicer.js" ]; diff --git a/models/cubes.stl b/models/cubes.stl new file mode 100644 index 0000000..c90235c Binary files /dev/null and b/models/cubes.stl differ diff --git a/models/overhang_test.stl b/models/overhang_test.stl new file mode 100644 index 0000000..c6f60f3 Binary files /dev/null and b/models/overhang_test.stl differ diff --git a/slice_test.html b/slice_test.html index ea88dd7..3b09b8f 100644 --- a/slice_test.html +++ b/slice_test.html @@ -11,6 +11,7 @@ + @@ -43,9 +44,8 @@ var printerConfig = { "printer.heatup.bed.temperature": 70, "printer.heatup.enabled": true, "printer.heatup.temperature": 180, - "printer.layerHeight": 0.3, "printer.retraction.amount": 3, - "printer.retraction.enabled": true, + "printer.retraction.enabled": false, "printer.retraction.minDistance": 5, "printer.retraction.speed": 50, "printer.screenToMillimeterScale": 0.3, //???? @@ -55,13 +55,16 @@ var printerConfig = { "printer.travelSpeed": 200, "printer.type": "ultimaker", "printer.useSubLayers": true, //wat is dit? - "printer.wallThickness": 0.4, + + "printer.wallThickness": 0.4, //nozzle + "printer.layerHeight": 0.3, //variabele toevoegen; //-snelheid, retraction etc voor verschillende types (outerlayer, innerlayer, fill) - "printer.shellThickness": 0.8, + "printer.shellThickness": 0.4, "printer.fillSize": 5, //dit is het raster aan de binnen kant van de geometry - "printer.brimOffset": 5 + "printer.brimOffset": 5, + "printer.bottomTopThickness": 0.8 }; var printer = new D3D.Printer(printerConfig); @@ -104,53 +107,30 @@ var geometry = (function () { var material = new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}); //var geometry = new THREE.TorusGeometry(20, 10, 30, 30); -var geometry = new THREE.BoxGeometry(20, 20, 20, 1, 1, 1); +var geometry = new THREE.BoxGeometry(10, 10, 10, 1, 1, 1); //var geometry = new THREE.SphereGeometry(10, 10, 10); var mesh = new THREE.Mesh(geometry, material); mesh.position.x = 100; mesh.position.z = 100; -scene.add(mesh); +//scene.add(mesh); -var canvas = document.getElementById("canvas"); -var context = canvas.getContext("2d"); - -var slicer = new D3D.Slicer().setMesh(mesh); - -gcode = slicer.getGcode(printer); - -var canvas = document.getElementById("canvas"); -var context = canvas.getContext("2d"); - -var layer = 0; -var img = slicer.drawPaths(printer, layer, layer + 1); -context.drawImage(img, 0, 0); - -/* var loader = new THREE.STLLoader(); -loader.load('stl/overhang_test.stl', function (geometry) { - - var matrix = new THREE.Matrix4(); - matrix.makeRotationX(Math.PI*1.5); - - geometry.applyMatrix(matrix); - - var material = new THREE.MeshLambertMaterial({color: 0x000000, wireframe: true}); +loader.load("models/cubes.stl", function (geometry) { var mesh = new THREE.Mesh(geometry, material); + + mesh.position.x = 100; + mesh.position.z = 100; + mesh.rotation.x = -Math.PI/2; + mesh.scale.x = mesh.scale.y = mesh.scale.z = 3; + //mesh is lifted a little bit... + mesh.position.y = -0.6552429199218741; + scene.add(mesh); - var canvas = document.getElementById("canvas"); - var context = canvas.getContext("2d"); + var slicer = new D3D.Slicer().setMesh(mesh); - var slicer = new D3D.Slicer().setMesh(geometry); - - var layer = 149; - var img = slicer.drawPaths(printer, layer, layer + 1); - context.drawImage(img, 0, 0); - - var gcode = slicer.getGcode(printer); - console.log(gcode); + gcode = slicer.getGcode(printer); }); -*/ (function animate () { "use strict"; @@ -158,6 +138,53 @@ loader.load('stl/overhang_test.stl', function (geometry) { requestAnimationFrame(animate); renderer.render(scene, camera); })(); + +function applyMouseControls (renderer, camera, center, 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.set( + Math.cos(rotY)*Math.sin(rotX)*distance, + Math.sin(rotY)*distance, + Math.cos(rotY)*Math.cos(rotX)*distance + ).add(center); + camera.lookAt(center); + } + + $(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(); +} diff --git a/src/path.js b/src/path.js new file mode 100644 index 0000000..f0fbb59 --- /dev/null +++ b/src/path.js @@ -0,0 +1,153 @@ +/****************************************************** +* +* Path +* +* Abstraction layer for annoying clipper js +* +******************************************************/ + +D3D.Path = function (path, closed) { + "use strict"; + + this.path = path || []; + this.closed = (closed !== undefined) ? closed : true; +}; +D3D.Path.prototype.setPath = function (path) { + "use strict"; + + this.path = path; + + return this; +}; +D3D.Path.prototype.union = function (path) { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctUnion, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.difference = function (path) { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctDifference, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.intersect = function (path) { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctIntersection, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.xor = function () { + "use strict"; + + var solution = new ClipperLib.Paths(); + + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths(this.path, ClipperLib.PolyType.ptSubject, this.closed); + clipper.AddPaths(path.path, ClipperLib.PolyType.ptClip, path.closed); + clipper.Execute(ClipperLib.ClipType.ctXor, solution); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.offset = function (offset) { + "use strict"; + + var solution = new ClipperLib.Paths(); + var co = new ClipperLib.ClipperOffset(1, 1); + co.AddPaths(this.path, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon); + co.Execute(solution, offset); + + return new D3D.Path(solution, this.closed); +}; +D3D.Path.prototype.scaleUp = function (factor) { + "use strict"; + + var path = ClipperLib.JS.ScaleUpPaths(this.path, factor); + + return this; +}; +D3D.Path.prototype.scaleDown = function (factor) { + "use strict"; + + var path = ClipperLib.JS.ScaleDownPaths(this.path, factor); + + return this; +}; +D3D.Path.prototype.tresholdArea = function (minArea) { + "use strict"; + + for (var i = 0; i < this.path.length; i ++) { + var shape = this.path[i]; + + var area = ClipperLib.Clipper.Area(shape); + + if (area < minArea) { + this.path.splice(i, 1); + i --; + } + } + + return areas; +}; +D3D.Path.prototype.area = function () { + "use strict"; + + var areas = []; + + for (var i = 0; i < this.path.length; i ++) { + var shape = this.path[i]; + + areas.push(ClipperLib.Clipper.Area(shape)) + } + + return areas; +}; +D3D.Path.prototype.join = function (path) { + "use strict"; + + this.path = this.path.concat(path.path); + + return this; +} +D3D.Path.prototype.clone = function () { + "use strict"; + + var path = ClipperLib.JS.Clone(this.path); + + return new D3D.Path(path, this.closed); +} +D3D.Path.prototype.draw = function (context, color) { + "use strict"; + + context.strokeStyle = color; + for (var i = 0; i < this.path.length; i ++) { + var shape = this.path[i]; + + context.beginPath(); + var length = this.closed ? (shape.length + 1) : shape.length; + for (var j = 0; j < length; j ++) { + var point = shape[j % shape.length]; + + context.lineTo(point.X*2, point.Y*2); + } + context.stroke(); + } +}; \ No newline at end of file diff --git a/src/slicer.js b/src/slicer.js index 7a7ae77..fe8ccc9 100644 --- a/src/slicer.js +++ b/src/slicer.js @@ -23,12 +23,11 @@ D3D.Slicer.prototype.setMesh = function (mesh) { mesh.updateMatrix(); var geometry = mesh.geometry.clone(); - geometry.mergeVertices(); - geometry.applyMatrix(mesh.matrix); - if (geometry instanceof THREE.BufferGeometry) { geometry = new THREE.Geometry().fromBufferGeometry(geometry); } + geometry.mergeVertices(); + geometry.applyMatrix(mesh.matrix); this.geometry = geometry; @@ -55,7 +54,8 @@ D3D.Slicer.prototype.createLines = function () { self.lines.push({ line: new THREE.Line3(self.geometry.vertices[a], self.geometry.vertices[b]), connects: [], - normals: [] + normals: [], + ignore: 0 }); } @@ -80,6 +80,12 @@ D3D.Slicer.prototype.createLines = function () { this.lines[a].normals.push(normal); this.lines[b].normals.push(normal); this.lines[c].normals.push(normal); + + if (face.normal.y === 1 || face.normal.y === -1) { + this.lines[a].ignore ++; + this.lines[b].ignore ++; + this.lines[c].ignore ++; + } } }; D3D.Slicer.prototype.slice = function (height, step) { @@ -93,12 +99,14 @@ D3D.Slicer.prototype.slice = function (height, step) { var min = Math.ceil(Math.min(line.line.start.y, line.line.end.y) / step); var max = Math.floor(Math.max(line.line.start.y, line.line.end.y) / step); - for (var layerIndex = min; layerIndex <= max; layerIndex ++) { - if (layerIndex >= 0) { - if (layersIntersections[layerIndex] === undefined) { - layersIntersections[layerIndex] = []; + if (line.ignore < 2) { + for (var layerIndex = min; layerIndex <= max; layerIndex ++) { + if (layerIndex >= 0) { + if (layersIntersections[layerIndex] === undefined) { + layersIntersections[layerIndex] = []; + } + layersIntersections[layerIndex].push(i); } - layersIntersections[layerIndex].push(i); } } } @@ -115,8 +123,8 @@ D3D.Slicer.prototype.slice = function (height, step) { var line = this.lines[index].line; var alpha = (y - line.start.y) / (line.end.y - line.start.y); - var x = line.start.x * alpha + line.end.x * (1 - alpha); - var z = line.start.z * alpha + line.end.z * (1 - alpha); + var x = line.end.x * alpha + line.start.x * (1 - alpha); + var z = line.end.z * alpha + line.start.z * (1 - alpha); intersections[index] = new THREE.Vector2(x, z); } @@ -190,7 +198,8 @@ D3D.Slicer.prototype.slice = function (height, step) { //stop when ther are no intersects if (slice.length > 0) { - slices.push(slice); + slices.push(new D3D.Path(slice, true)); + //slices.push(slice); } else { break; @@ -199,20 +208,65 @@ D3D.Slicer.prototype.slice = function (height, step) { return slices; }; -D3D.Slicer.prototype.getInset = function (slice, offset) { +D3D.Slicer.prototype.slicesToData = function (slices, printer) { "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); + var scale = 100; - return solution; + 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 skinCount = Math.ceil(shellThickness/layerHeight); + + var data = []; + + var lowFillTemplate = this.getFillTemplate(dimensionsZ, fillSize, true, true); + + for (var layer = 0; layer < slices.length; layer ++) { + var slice = slices[layer]; + + var outerLayer = slice.clone(); + outerLayer.scaleUp(scale); + + var insets = new D3D.Path(); + for (var offset = wallThickness; offset <= shellThickness; offset += wallThickness) { + var inset = outerLayer.offset(-offset); + + insets.join(inset); + } + + var fillArea = (inset || outerLayer).offset(-wallThickness/2); + + var downFill = (layer - skinCount >= 0) ? slices[layer - skinCount] : new D3D.Path(); + var upFill = (layer + skinCount < slices.length) ? slices[layer + skinCount] : new D3D.Path(); + var highFillArea = fillArea.difference(downFill.intersect(upFill).scaleUp(scale)); + + var lowFillArea = fillArea.difference(highFillArea); + + var fill = new D3D.Path([], false); + fill.join(lowFillTemplate.intersect(lowFillArea)); + if (highFillArea.path.length > 0) { + var highFillTemplate = this.getFillTemplate(dimensionsZ, wallThickness, (layer % 2 === 0), (layer % 2 === 1)); + var highFillStrokes = highFillArea; + fill.join(highFillTemplate.intersect(highFillStrokes)); + } + + data.push({ + outerLayer: outerLayer.scaleDown(scale), + insets: insets.scaleDown(scale), + fill: fill.scaleDown(scale) + }); + } + + return data; }; D3D.Slicer.prototype.getFillTemplate = function (dimension, size, even, uneven) { "use strict"; - var paths = new ClipperLib.Paths(); + var paths = []; if (even) { for (var length = 0; length <= dimension; length += size) { @@ -225,125 +279,8 @@ D3D.Slicer.prototype.getFillTemplate = function (dimension, size, even, uneven) } } - 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); - } - - //moet fillArea wel kleiner? - //var fillArea = this.getInset((inset || outerLayer), wallThickness); - var fillArea = (inset || outerLayer); - - var fillAbove = false; - //for (var i = 1; i < shellThickness/layerHeight; i ++) { - for (var i = 1; i < shellThickness/layerHeight; 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 === false) { - fillAbove = newLayer; - } - else { - var c = new ClipperLib.Clipper(); - var solution = new ClipperLib.Paths(); - c.AddPaths(newLayer, 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; + //return paths; + return new D3D.Path(paths, false); }; D3D.Slicer.prototype.dataToGcode = function (data, printer) { "use strict"; @@ -365,8 +302,8 @@ D3D.Slicer.prototype.dataToGcode = function (data, printer) { function sliceToGcode (slice) { var gcode = []; - for (var i = 0; i < slice.length; i ++) { - var shape = slice[i]; + for (var i = 0; i < slice.path.length; i ++) { + var shape = slice.path[i]; var previousPoint; @@ -440,54 +377,20 @@ D3D.Slicer.prototype.dataToGcode = function (data, printer) { var z = ((layer + 1) * layerHeight).toFixed(3); gcode = gcode.concat(sliceToGcode(slice.outerLayer)); - gcode = gcode.concat(sliceToGcode(slice.innerLayer)); + gcode = gcode.concat(sliceToGcode(slice.insets)); gcode = gcode.concat(sliceToGcode(slice.fill)); } gcode = gcode.concat(printer.getEndCode()); return gcode; }; +//only for debug purposes D3D.Slicer.prototype.drawPaths = function (printer, min, max) { "use strict"; var layerHeight = printer.config["printer.layerHeight"]; var dimensionsZ = printer.config["printer.dimensions.z"]; - function drawLines (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 * 2), (path[0].Y * 2)); - - for (var j = 0; j < path.length; j ++) { - var point = path[j]; - context.lineTo((point.X * 2), (point.Y * 2)); - } - //context.closePath(); - } - context.stroke(); - } - - function drawVertexes (paths, color) { - context.fillStyle = color; - context.strokeStyle = color; - - for (var i = 0; i < paths.length; i ++) { - var path = paths[i]; - - for (var j = 0; j < path.length; j ++) { - var point = path[j]; - context.beginPath(); - context.arc(point.X * 2, point.Y * 2, 1, 0, Math.PI*2, false); - context.stroke(); - } - } - } - var slices = this.slice(dimensionsZ, layerHeight); var data = this.slicesToData(slices, printer); @@ -500,11 +403,9 @@ D3D.Slicer.prototype.drawPaths = function (printer, min, max) { for (var layer = min; layer < max; layer ++) { var slice = data[layer % data.length]; - drawLines(slice.outerLayer, "red"); - drawLines(slice.innerLayer, "green"); - drawLines(slice.fill, "blue"); - - drawVertexes(slice.outerLayer, "green"); + slice.insets.draw(context, "blue"); + slice.outerLayer.draw(context, "green"); + slice.fill.draw(context, "red"); } return canvas; diff --git a/src/utils.js b/src/utils.js index fa005e1..396fcc7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -88,59 +88,4 @@ Array.prototype.clone = function () { } return array; -}; - -function applyMouseControls (renderer, camera, center, 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.set( - Math.cos(rotY)*Math.sin(rotX)*distance, - Math.sin(rotY)*distance, - Math.cos(rotY)*Math.cos(rotX)*distance - ).add(center); - camera.lookAt(center); - } - - $(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); - }; -})(); \ No newline at end of file +}; \ No newline at end of file diff --git a/stl/overhang_test.stl b/stl/overhang_test.stl deleted file mode 100644 index 71987a6..0000000 Binary files a/stl/overhang_test.stl and /dev/null differ