diff --git a/src/geo/base.js b/src/geo/base.js index 50a5bd46..89ba5eb4 100644 --- a/src/geo/base.js +++ b/src/geo/base.js @@ -356,7 +356,7 @@ * return circle center given three points * from https://stackoverflow.com/questions/4103405/what-is-the-algorithm-for-finding-the-center-of-a-circle-from-three-points */ - function center2d(A,B,C) { + function center2d(A,B,C,rad) { let yDelta_a = B.y - A.y, xDelta_a = B.x - A.x, yDelta_b = C.y - B.y, @@ -366,7 +366,11 @@ center = {x:0, y:0, z:0}; center.x = (aSlope*bSlope*(A.y - C.y) + bSlope*(A.x + B.x) - aSlope*(B.x+C.x) )/(2* (bSlope-aSlope) ); center.y = -1*(center.x - (A.x+B.x)/2)/aSlope + (A.y+B.y)/2; - + if (rad) { + let dx = center.x - A.x; + let dy = center.y - A.y; + center.r = Math.sqrt(dx*dx + dy*dy) + } return center; } diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 402ad5bd..3235eb34 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -666,6 +666,16 @@ return this; }; + PRO.addObj = function(obj) { + if (Array.isArray(obj)) { + for (let o of obj) { + this.addObj(o); + } + return this; + } + return this.add(obj.x, obj.y, obj.z); + } + /** * append array of points to polygon and return polygon * diff --git a/src/kiri/conf.js b/src/kiri/conf.js index 6f42544c..66e62b76 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -343,6 +343,7 @@ sliceMinHeight: 0, sliceAdaptive: false, zHopDistance: 0.2, + arcTolerance: 0, antiBacklash: 1, gcodePause: "" } diff --git a/src/kiri/init.js b/src/kiri/init.js index da7b66db..31fa8c3b 100644 --- a/src/kiri/init.js +++ b/src/kiri/init.js @@ -1783,6 +1783,7 @@ outputCoastDist: UC.newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), zHopDistance: UC.newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, bound:UC.bound(0,3.0), convert:UC.toFloat, modes:FDM}), + arcTolerance: UC.newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, bound:UC.bound(0,1.0), convert:UC.toFloat, modes:FDM, show:isNotBelt}), antiBacklash: UC.newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, bound:UC.bound(0,3), convert:UC.toInt, modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), gcodePauseLayers: UC.newInput(LANG.ag_paws_s, {title:LANG.ag_paws_l, modes:FDM, comma:true}), diff --git a/src/kiri/print.js b/src/kiri/print.js index 5f98da55..64ce8bf8 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -139,6 +139,20 @@ const beltaxis = { X: "X", Y: "Z", Z: "Y", E: "E", F: "F" }; const beltfact = Math.cos(Math.PI/4); + function G2G3(g2, line) { + const rec = {}; + + line.forEach(function(tok) { + rec[tok.charAt(0)] = parseFloat(tok.substring(1)); + }); + + console.log({g2, arc: line, pos}); + + pos.X = rec.X; + pos.Y = rec.Y; + pos.Z = rec.Z; + } + function G0G1(g0, line) { const mov = {}; @@ -284,6 +298,14 @@ case 'G1': G0G1(false, line); break; + case 'G2': + // turn arc into a series of points + G2G3(true, line) + break; + case 'G3': + // turn arc into a series of points + G2G3(false, line); + break; case 'M6': tool = parseInt(line[0].substring(1)); break; diff --git a/src/mode/fdm/export.js b/src/mode/fdm/export.js index 761cda6b..e49ce98e 100644 --- a/src/mode/fdm/export.js +++ b/src/mode/fdm/export.js @@ -33,7 +33,7 @@ pause = [], pauseCmd = device.gcodePause, output = [], - outputLength = 0, + outputLength = 0, // absolute extruder position lastProgress = 0, decimals = BASE.config.gcode_decimals || 4, progress = 0, @@ -50,6 +50,7 @@ retSpeed = process.outputRetractSpeed * 60, retDwell = process.outputRetractDwell || 0, timeDwell = retDwell / 1000, + arcDist = isBelt ? 0 : (process.arcTolerance || 0), originCenter = process.outputOriginCenter, offset = originCenter ? null : { x: device.bedWidth/2, @@ -80,7 +81,9 @@ bcos = Math.cos(Math.PI/4), icos = 1 / bcos, loops = process.outputLoopLayers, - inLoop; + inLoop, + arcAt, + arcQ = []; if (isBelt && loops) { loops = loops.split(',').map(range => { @@ -392,6 +395,7 @@ // re-engage post-retraction before new extrusion if (out.emit && retracted) { + drainQ(); // console.log({engage:zhop}); // when enabled, resume previous Z if (zhop && pos.z != zpos) moveTo({z:zpos}, seekMMM, "z-hop end"); @@ -404,10 +408,58 @@ } if (lastp && out.emit) { - emitMM = emitPerMM * out.emit * dist; - moveTo({x:x, y:y, e:emitMM}, speedMMM); - emitted += emitMM; + if (arcDist) { + let rec = {e:out.emit, x, y, z, dist, emitPerMM, speedMMM}; + arcQ.push(rec); + let deem = false; + let depm = false; + let desp = false; + if (arcQ.length > 1) { + deem = arcQ[0].e !== rec.e; + depm = arcQ[0].emitPerMM !== rec.emitPerMM; + desp = arcQ[0].speedMMM !== rec.speedMMM; + } + if (arcQ.length > 2) { + let el = arcQ.length; + let e1 = arcQ[el-3]; + let e2 = arcQ[el-2]; + let e3 = arcQ[el-1]; + let cc = BASE.util.center2d(e1, e2, e3, 1); + let dc = 0; + if (arcQ.length === 3) { + arcQ.center = [ cc ]; + } else { + // check center point delta + let dx = cc.x - arcQ.center[0].x; + let dy = cc.y - arcQ.center[0].y; + dc = Math.sqrt(dx * dx + dy * dy); + } + // if new point is off the arc + if (deem || depm || desp || dc > arcDist) { + // console.log({dc, depm, desp}); + if (arcQ.length === 4) { + // not enough points for an arc, drop first point and recalc center + emitQrec(arcQ.shift()); + arcQ.center = [ BASE.util.center2d(arcQ[0], arcQ[1], arcQ[2], 1) ]; + } else { + // enough to consider an arc, emit and start new arc + let defer = arcQ.pop(); + drainQ(); + // re-add point that was off the last arc + arcQ.push(defer); + } + } else { + // new point is on the arc + arcQ.center.push(cc); + } + } + } else { + emitMM = emitPerMM * out.emit * dist; + moveTo({x:x, y:y, e:emitMM}, speedMMM); + emitted += emitMM; + } } else { + drainQ(); moveTo({x:x, y:y}, seekMMM); // TODO disabling out of plane z moves until a better mechanism // can be built that doesn't rely on computed zpos from layer heights... @@ -418,6 +470,7 @@ // retract filament if point retract flag set if (out.retract) { + drainQ(); retract(zhop); } @@ -437,6 +490,61 @@ } layer++; } + drainQ(); + + function emitQrec(rec) { + let {e, x, y, dist, emitPerMM, speedMMM} = rec; + emitMM = emitPerMM * e * dist; + moveTo({x:x, y:y, e:emitMM}, speedMMM); + emitted += emitMM; + } + + function drainQ() { + if (!arcDist) { + return; + } + if (arcQ.length > 4) { + let area = BASE.newPolygon().addObj(arcQ).area(); + let from = arcQ[0]; + let to = arcQ.peek(); + let cc = {x:0, y:0, z:0, r:0}; + let cl = arcQ.length; + for (let center of arcQ.center) { + cc.x += center.x; + cc.y += center.y; + cc.z += center.z; + cc.r += center.r; + } + cc.x /= cl; + cc.y /= cl; + cc.z /= cl; + cc.r /= cl; + // first arc point + emitQrec(from); + // rest of arc to final point + let dist = arcQ.slice(1).map(v => v.dist).reduce((a,v) => a+v); + let emit = from.e;//arcQ.slice(1).map(v => v.e).reduce((a,v) => a+v); + emit = (from.emitPerMM * emit * dist); + outputLength += emit; + emitted += emit; + if (extrudeAbs) { + emit = outputLength; + } + let gc = area > 0 ? 'G2' : 'G3'; + let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} E${emit.toFixed(decimals)}`; + let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; + append(`${pre}${add} ; merged=${cl-1} dist=${dist.toFixed(decimals)}`); + pos.x = to.x; + pos.y = to.y; + pos.z = to.z; + } else { + for (let rec of arcQ) { + emitQrec(rec); + } + } + arcQ.length = 0; + arcQ.center = undefined; + } if (inLoop) { append(`M808`); diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 4f8b06aa..6422e69b 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -483,10 +483,12 @@ kiri.lang['en-us'] = { ad_mins_l: "minimum speed\nfor short segments", ad_spol_s: "short path", ad_spol_l: "polygons shorter than this\nwill have their print speed\nscaled down to min speed\nin millimeters", + ad_arct_s: "arc tolerance", + ad_arct_l: "convert faceted lines to arcs\ncenter point drift tolerance\nwhen matching arc points\nconsider values around 0.15\nin millimeters\n0 to disable", ad_zhop_s: "z hop dist", ad_zhop_l: "amount to raise z\non retraction moves\nin millimeters\n0 to disable", ad_abkl_s: "anti-backlash", - ad_abkl_l: "use micro-movements to cancel\nbacklash during fills\nin millimeters", + ad_abkl_l: "for better flat surface finish\nuse micro-movements to cancel\nbacklash in solid layer output\nin millimeters\n0 to disable", ad_lret_s: "layer retract", ad_lret_l: "force filament retraction\nbetween layers", ad_play_s: "polish layers",