/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; (function() { let KIRI = self.kiri, BASE = self.base, UTIL = BASE.util, FDM = KIRI.driver.FDM, debug = false; /** * @returns {Array} gcode lines */ FDM.export = function(print, online) { let layers = print.output, settings = FDM.fixExtruders(print.settings), getRangeParameters = FDM.getRangeParameters, device = settings.device, extruders = device.extruders, gcodeFan = device.gcodeFan, gcodeLayer = device.gcodeLayer, gcodeTrack = device.gcodeTrack, gcodeExt = device.gcodeExt, gcodeInt = device.gcodeInt, tool = 0, fwRetract = device.fwRetract, isDanger = settings.controller.danger, isBelt = device.bedBelt, bedType = isBelt ? "belt" : "fixed", extruder = extruders[tool], offset_x = extruder.extOffsetX, offset_y = extruder.extOffsetY, extrudeAbs = device.extrudeAbs || false, extrudeSet = false, time = 0, layer = 0, layerno = 0, pause = [], pauseCmd = device.gcodePause, output = [], outputLength = 0, // absolute extruder position lastProgress = 0, decimals = BASE.config.gcode_decimals || 4, progress = 0, distance = 0, emitted = 0, retracted = 0, pos = {x:0, y:0, z:0, f:0}, lout = null, last = null, zpos = 0, bmax = 0, blast = 0, blastz = 0, process = settings.process, belt_add_y = (process.firstLayerYOffset || 0) - (print.belty || 0), loops = process.outputLoops || 0, zhop = process.zHopDistance || 0, // range seekMMM = process.outputSeekrate * 60, retDist = process.outputRetractDist || 0, // range retSpeed = process.outputRetractSpeed * 60, // range retDwell = process.outputRetractDwell || 0, // range timeDwell = retDwell / 1000, peelGuard = process.outputPeelGuard || 0, arcDist = isBelt || !isDanger ? 0 : (process.arcTolerance || 0), arcMin = 1, arcMax = 200, arcSegMax = 1, originCenter = process.outputOriginCenter, offset = originCenter ? null : { x: device.bedWidth/2, y: isBelt ? 0 : device.bedDepth/2 }, nozzleTemp = process.firstLayerNozzleTemp || process.outputTemp, bedTemp = process.firstLayerBedTemp || process.outputBedTemp, fanSpeed = undefined, lastType = undefined, lastNozzleTemp = nozzleTemp, lastBedTemp = bedTemp, lastFanSpeed = fanSpeed, subst = { travel_speed: seekMMM, retract_speed: retSpeed, retract_distance: retDist, temp: nozzleTemp, temp_bed: bedTemp, bed_temp: bedTemp, fan_speed: fanSpeed, speed: fanSpeed, // legacy top: offset ? device.bedDepth : device.bedDepth/2, left: offset ? 0 : -device.bedWidth/2, right: offset ? device.bedWidth : device.bedWidth/2, bottom: offset ? 0 : -device.bedDepth/2, z_max: device.maxHeight, layers: layers.length, progress: 0, nozzle: 0, tool: 0 }, pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist, append, lines = 0, bytes = 0, bcos = Math.cos(Math.PI/4), icos = 1 / bcos, inLoop, arcQ = [], minz = { x: Infinity, y: Infinity, z: Infinity }; // smallish band-aid. refactor above to remove redundancy function updateParams(layer) { let process = getRangeParameters(settings, layer); zhop = process.zHopDistance || 0; // range retDist = process.outputRetractDist || 0; // range retSpeed = process.outputRetractSpeed * 60; // range retDwell = process.outputRetractDwell || 0; // range timeDwell = retDwell / 1000; nozzleTemp = layer === 0 ? process.firstLayerNozzleTemp || process.outputTemp : process.outputTemp || process.firstLayerNozzleTemp; bedTemp = layer === 0 ? process.firstLayerBedTemp || process.outputBedTemp : process.outputBedTemp || process.firstLayerBedTemp; fanSpeed = layer === 0 ? process.firstLayerFanSpeed || 0 : process.outputFanSpeed || 0; Object.assign(subst, { temp_bed: bedTemp, bed_temp: bedTemp, fan_speed: fanSpeed, speed: fanSpeed, // legacy retract_speed: retSpeed, retract_distance: retDist, temp: process.outputTemp, // range temp_bed: process.outputBedTemp, // range bed_temp: process.outputBedTemp, // range }); } // collect loops from ranges and synth range array let rloops = []; if (loops > 0) { rloops.push({ start: layers[0].slice.index, end: layers.last().slice.index, iter: loops - 1 }); } for (let range of process.ranges) { if (range.fields.outputLoops) { rloops.push({ start: range.lo, end: range.hi, iter: range.fields.outputLoops - 1 }); } } loops = isBelt && rloops.length ? rloops : undefined; (process.gcodePauseLayers || "").split(",").forEach(function(lv) { let v = parseInt(lv); if (v >= 0) pause.push(v); }); append = function(line) { if (line) { lines++; bytes += line.length; output.append(line); } if (!line || output.length > 1000) { online(output.join("\n")); output = []; } }; function appendSubPad(line, pad) { appendSub(line, true); } function appendSub(line, pad) { append(print.constReplace(line, subst, 0, pad)); } function appendAll(arr) { if (!arr) return; if (!Array.isArray(arr)) arr = [ arr ]; arr.forEach(function(line) { append(line) }); } function appendAllSub(arr, pad) { if (!arr || arr.length === 0) return; if (!Array.isArray(arr)) arr = [ arr ]; arr.forEach(function(line) { appendSub(line, pad) }); } append(`; Generated by Kiri:Moto ${KIRI.version}`); append(`; ${new Date().toString()}`); appendSub("; Bed left:{left} right:{right} top:{top} bottom:{bottom}"); append(`; Bed type: ${bedType}`); append(`; Target: ${settings.filter[settings.mode]}`); append("; --- process ---"); for (let pk in process) { append("; " + pk + " = " + process[pk]); } append("; --- startup ---"); let t0 = false; let t1 = false; for (let i=0; i 0) { line.split(";")[0].split(' ').forEach(function (tok) { // use max E position from gcode-preamble if (tok[0] == 'E') { outputLength = Math.max(outputLength, parseFloat(tok.substring(1)) || 0); } }); } if (line.indexOf("{tool}") > 0 && extruders.length > 1) { for (let i=0; i= 0) { updateParams(path.layer); } emitPerMM = print.extrudePerMM( extruder.extNozzle, extruder.extFilament, path.layer === 0 ? (process.firstSliceHeight || process.sliceHeight) : path.height); zpos = path.z || zpos; bmax = Math.max(bmax, pos.z * icos); subst.z = subst.Z = zpos.round(3); subst.e = subst.E = outputLength; subst.layer = layer; subst.height = path.height.toFixed(3); if (isBelt) { pos.z = zpos; if (peelGuard && bmax > peelGuard && blast < peelGuard) { peelGuard += 50; append(`G0 Z${(blast + 55).round(decimals)} F200 ; peel guard`); append(`G0 Z${blastz.round(decimals)} F200 ; unpeel`); } blast = bmax; } if (pauseCmd && pause.indexOf(layer) >= 0) { appendAllSub(pauseCmd) } let endloop = false; if (loops) { if (inLoop) { if (layerno === inLoop.end) { endloop = true; // append(`M808`); // inLoop = undefined; } } else { for (let loop of loops) { if (layerno === loop.start) { append(`M808 L${loop.iter}`); if (extrudeAbs) { append(`G92 Z${lout.z.round(decimals)} E${outputLength.round(decimals)}`); } else { append(`G92 Z${lout.z.round(decimals)}`); } inLoop = loop; break; } } } } if (gcodeLayer && gcodeLayer.length) { appendAllSub(gcodeLayer); } else { append(`;; --- layer ${layer} (${subst.height} @ ${subst.z.round(3)}) ---`); } // layer temp and fan overrides at layer changes if (fanSpeed !== lastFanSpeed) { appendAllSub(gcodeFan); lastFanSpeed = fanSpeed; } if (bedTemp !== lastBedTemp) { append(`M140 S${bedTemp} T0`); lastBedTemp = bedTemp; } if (nozzleTemp !== lastNozzleTemp) { if (t0) append(`M104 S${nozzleTemp} T0`); if (t1) append(`M104 S${nozzleTemp} T1`); if (!(t0 || t1)) append(`M104 S${nozzleTemp} T${tool}`); lastNozzleTemp = nozzleTemp; } // move Z to layer height if (layer > 0 || !isBelt) { moveTo({z:zpos}, seekMMM); } // iterate through layer outputs for (pidx=0; pidx 1) { let el = arcQ.length; deem = arcQ[0].e !== rec.e; depm = arcQ[0].emitPerMM !== rec.emitPerMM; desp = arcQ[0].speedMMM !== rec.speedMMM; arcSeg = Math.sqrt(Math.pow(arcQ[el - 1].x - arcQ[el-2].x, 2) + Math.pow(arcQ[el - 1].y - arcQ[el-2].y, 2)); } if (arcQ.length > 2) { let el = arcQ.length; let e1 = arcQ[0]; // first in arcQ let e2 = arcQ[Math.floor(el/2)]; // mid in arcQ let e3 = arcQ[el-1]; // last in arcQ let cc = BASE.util.center2d(e1, e2, e3, 1); // find center let dc = 0; if (cc) { if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { console.log({cc, e1, e2, e3}); } 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 || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { if (deem || depm || desp || dc > arcDist || dist > cc.r || cc.r > arcMax || arcSeg > arcSegMax) { // console.log({dc, depm, desp}); if (arcQ.length === 4) { // not enough points for an arc, drop first point and recalc center emitQrec(arcQ.shift()); let tc = BASE.util.center2d(arcQ[0], arcQ[1], arcQ[2], 1); // the new center is invalid as well. drop the first point if (!tc) { emitQrec(arcQ.shift()); } else { arcQ.center = [ tc ]; } } 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 { // drainQ on invalid center drainQ(); } } } 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... // when making z moves (like polishing) allow slowdown vs fast seek // let moveSpeed = (lastp && lastp.z !== z) ? speedMMM : seekMMM; // moveTo({x:x, y:y, z:z}, moveSpeed); } // retract filament if point retract flag set if (out.retract) { drainQ(); retract(zhop); } // update time and distance (should calc in moveTo() instead) time += (dist / speedMMM) * 60 * 1.5; distance += dist; subst.progress = progress = Math.round((distance / totaldistance) * 100); // emit tracked progress if (gcodeTrack && progress != lastProgress) { appendAllSub(gcodeTrack); lastProgress = progress; } lastp = out.point; laste = out.emit; } layer++; // end open loop when detected if (endloop) { append(`M808`); inLoop = undefined; } 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 vec1 = new THREE.Vector2(arcQ[1].x - arcQ[0].x, arcQ[1].y - arcQ[0].y); let vec2 = new THREE.Vector2(arcQ.center[0].x - arcQ[0].x, arcQ.center[0].y - arcQ[0].y); 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 = 0; for (let center of arcQ.center) { cc.x += center.x; cc.y += center.y; cc.z += center.z; cc.r += center.r; cl++; } cc.x /= cl; cc.y /= cl; cc.z /= cl; cc.r /= cl; // first arc point emitQrec(from); // console.log(arcQ.slice(), arcQ.center); // console.log({first: from, last: arcQ.peek(), center: cc}); // 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 = vec1.cross(vec2) < 0 ? 'G2' : 'G3'; // XYR form // let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} E${emit.toFixed(decimals)}`; // XYIJ form let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`; let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`); 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`); } subst.material = UTIL.round(emitted,2); subst.time = UTIL.round(time,2); subst['print_time'] = subst.time; append("; --- shutdown ---"); appendAllSub(device.gcodePost); append(`; --- filament used: ${subst.material} mm ---`); append(`; --- print time: ${time.toFixed(0)}s ---`); // force emit of buffer append(); print.distance = emitted; print.lines = lines; print.bytes = bytes + lines - 1; print.time = time; if (debug) { console.log('minz', minz); } }; })();