diff --git a/notes.md b/notes.md index 5efbac1a..d45c1523 100644 --- a/notes.md +++ b/notes.md @@ -31,27 +31,21 @@ * `B` multi-extruder rendering of raft fails to offset the rest of the print * `B` multi-extruder purge blocks fail to generate properly for rafts -* `F` min layer time -* `F` separate bridging speed / fan control -* `F` elephant foot compensation -* `F` inner / outer perimeter compensation -* `F` better thin-wall (like arachne) -* `F` add scarf seams -* `F` add 'random' and 'seam' start point options +* `F` new parameter to cap output flow rate (on printer device) +* `F` new parameters for bridging speed / bridge fan control +* `F` new base paremeter for hole and perimeter compensation +* `F` new parameters for inner / outer perimeter compensation * `F` convert ranges to z offsets while continuing to show layer # * `F` support pillar top/bottom should conform to part * `F` more explicit line width control with ranges and min/max adaptive * `F` test outlining solid projected areas (internally) * `F` gradient infill https://www.youtube.com/watch?v=hq53gsYREHU&feature=emb_logo -* `F` feather sharp tips by reducing extrusion in area of overlap * `F` segment large flat areas on first layer to mitigate peeling * `F` option to support interior bridges when 0% infill * `F` calculate filament use per extruder per print * `F` expand internal supporting flats / solids before projection (threshold) -* `P` reduce fan speed, feed rate, and extrusion factor for bridges * `P` auto purge pillars when quick layers are detected for extra cooling -* `P` extruder + filament max flow rate cap in planner * `P` solid fill the tops of supports for down facing flats # FDM - BELT @@ -91,8 +85,8 @@ * `F` ease-in and ease-out especially on tab cut-out start/stop * `F` maintain several part orientations + op chains in a single profile +* `P` outer outside corners as arc moves * `P` improve parser - do not require spaced tokens and support implied G0 / G1 -* `P` outer corner arc moves * `P` log Z interpolation for contour XYZ moves * `P` option to start with the smallest poly by area on layer change * `P` redo all path route / planning in prepare to account for terrain before camOut diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 806fbd9b..2ef4d2f6 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -2324,9 +2324,12 @@ export class Polygon { return obj; } - // split long straight lines into segments no longer than max - // and return a new polygon - segment(max = 1, mark, wrap) { + // split long straight lines into segments no longer than `max` + // and return a new polygon. optionally `mark` points with their + // derived segment start point (hinting for thin walls), `wrap` + // the poly first point by appending to the end (adaptive walls), + // or stopping segmentation at `maxoff` + segment(max = 1, mark = false, wrap = false, maxoff = Infinity) { const newp = []; const points = this.points; const length = points.length; @@ -2339,9 +2342,13 @@ export class Polygon { const dl = Math.sqrt(dx * dx + dy * dy); newp.push(p1); if (mark) p1.segment = p1; - if (dl < max) { + // if segment shorter than max (delta) or + // maxoff is exhausted (only segmenting up to some length) + // then skip sub-segmenting the current segment + if (dl < max || maxoff < 0) { continue; } + maxoff -= dl; const div = dl / max; const fit = div | 0; const add = fit - 1; diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index 3153c01f..4daf5556 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -327,8 +327,10 @@ export const conf = { outputRetractDwell: 20, outputBrimCount: 2, outputBrimOffset: 2, + outputScarfLength: 0, outputShortPoly: 100.0, outputMinSpeed: 10.0, + outputMinLayerTime: 0, outputCoastDist: 0, outputPurgeTower: 0, outputBeltFirst: false, diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index 27c7cd32..d4e973b9 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -98,6 +98,7 @@ class Print { * @param {Array} output - the array to print to * @param {Object} [options] - optional parameters * @param {boolean} [options.ccw] - set the polygon to be counter-clockwise + * @param {boolean} [options.scarf] - scarf seam permitted * @param {number} [options.extrude] - extrude factor for the polygon * @param {number} [options.rate] - print speed in mm/s * @param {number} [options.coast] - distance to coast at the end of the polygon @@ -128,6 +129,7 @@ class Print { closest = options.simple ? poly.first() : poly.findClosestPointTo(startPoint), perimeter = poly.perimeter(), close = !options.open, + scarf = !poly.open ? (options.scarf ?? 0) : false, tool = options.tool, last = startPoint, first = true; @@ -137,8 +139,70 @@ class Print { printSpeed = minSpeed + (printSpeed - minSpeed) * (perimeter / process.outputShortPoly); } + // if not starting at first point in poly, rotate to move start to index = 0 + let pp = poly.points; + if (closest.index > 0) { + let cio = pp.indexOf(closest.point); + pp = poly.points = [ ...pp.slice(cio), ...pp.slice(0, cio) ]; + } + + // scarf sanity checks + if (scarf) { + // cancel scarf for thin wall polys + if (pp.filter(p => p.skip || p.moved).length) { + scarf = 0; + } else { + // cancel scarf if any point.z differs + let z0 = pp[0].z; + let zd = 0; + for (let p of pp) zd += Math.abs(p.z - z0); + if (zd) scarf = 0; + } + // console.log({ scarf }); + } + + // when creating scarf seams, segment poly up to seam length + // create array of step up points at start of poly with increasing z + // and increasing shellMult and then append the same points on the back + // end of the poly with fixed z and decreasing shellMult + if (scarf) { + let epz = Math.max(...poly.points.map(p => p.z)); + let spz = startPoint.z; + poly = poly.segment(options.nozzleSize ?? 0.4, false, false, scarf * 2); + pp = poly.points; + let lp, sp = []; + for (let p of pp) { + let d = lp?.distTo2D(p) ?? 0; + sp.push(lp = p); + scarf -= d; + if (scarf <= 0) break; + } + let scale = 0.25; // flow compensation on overlap + let mid = (1 / sp.length) * 0.75; // flow compensation (- half step) + let zd = (epz - spz) / sp.length; + let zi = 1; + for (let p of sp) { + p.z -= zd * (sp.length - zi); + p.moved = (((zi++) / sp.length) * scale) - 1 - mid; + } + let esp = sp.map(p => p.clone()); // ending scarf points + for (let p of esp) { + p.z = epz; + p.moved = (((--zi) / esp.length) * scale) - 1 - mid; + } + esp.pop(); // stop short to prevent overlap bulge + pp.push(...esp); + // console.log({ poly, spz, epz, zd, sp, esp }); + scarf = true; + } + + // scarf manages its own close point + if (close && !scarf) { + pp.push(pp[0]); + } + let lpo; - poly.forEachPoint((point, pos, points, count) => { + for (let point of pp) { if (point.skip && lpo?.skip) { scope.addOutput(output, point, 0, moveSpeed, tool); } else if (first) { @@ -154,7 +218,8 @@ class Print { first = false; } else { let seglen = last.distTo2D(point); - if (coastDist && shellMult && perimeter - seglen <= coastDist) { + // cancel coast when using scarf seam + if (!scarf && coastDist && shellMult && perimeter - seglen <= coastDist) { let delta = perimeter - coastDist; let offset = seglen - delta; let offPoint = last.offsetPointFrom(point, offset) @@ -168,7 +233,7 @@ class Print { scope.addOutput(output, point, multOut, printSpeed, tool); } last = lpo = point; - }, close, closest.index); + } this.lastPoly = poly; diff --git a/src/kiri/core/render.js b/src/kiri/core/render.js index a322958d..1c92f077 100644 --- a/src/kiri/core/render.js +++ b/src/kiri/core/render.js @@ -181,8 +181,8 @@ async function path(levels, update, opts = {}) { return; } - if (lastOut) { // if last outpoint is defined - + // if last outpoint is defined + if (lastOut) { if (arrowAll || lastOut.emit !== out.emit) { heads.push({p1: lastOutPoint, p2: outPoint}); } @@ -200,13 +200,10 @@ async function path(levels, update, opts = {}) { current.color = color(out); pushPrint(out.tool, current); } - out.arcPoints.forEach(p => { current.push(p); }) - current.push(outPoint); - } else if (out.emit) { // explicity G1 in CAM mode // just a non-move in other modes diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index 7eee5e85..fc0506c1 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -102,6 +102,7 @@ export function menu() { _____: newGroup(LANG.fc_menu, $('fdm-cool'), { modes:FDM, driven, hideable, separator, group:"fdm-cool" }), outputFanLayer: newInput(LANG.ou_fanl_s, { title:LANG.ou_fanl_l, convert:toInt, bound:bound(0,255) }), outputFanSpeed: newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:toInt, bound:bound(0,255)}), + outputMinLayerTime: newInput(LANG.ou_layt_s, { title:LANG.ou_layt_l, convert:toInt, bound:bound(0,200) }), _____: newGroup(LANG.sp_menu, $('fdm-support'), { modes:FDM, driven, hideable, separator, group:"fdm-supp" }), sliceSupportNozzle: newSelect(LANG.sp_nozl_s, {title:LANG.sp_nozl_l, show:isMultiHead}, "extruders"), sliceSupportDensity: newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:toFloat, bound:bound(0.0,1.0)}), @@ -177,6 +178,7 @@ export function menu() { outputRetractDwell: newInput(LANG.ad_rdwl_s, {title:LANG.ad_rdwl_l, convert:toInt}), outputMinSpeed: newInput(LANG.ad_mins_s, {title:LANG.ad_mins_l, convert:toFloat, bound:bound(1,200)}), outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}), + outputScarfLength: newInput(LANG.ad_scar_s, {title:LANG.ad_scar_l, convert:toFloat, bound:bound(0,1000)}), outputShortPoly: newInput(LANG.ad_spol_s, {title:LANG.ad_spol_l, convert:toFloat, bound:bound(0,10000)}), outputCoastDist: newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, convert:toFloat, bound:bound(0.0,10)}), zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}), diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index d3d2e601..c4b207b3 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -800,6 +800,8 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { process = opt.params || settings.process, originCenter = device.originCenter || bedRound, extruder = parseInt(slice.extruder || 0), + scarfLength = process.outputScarfLength || 0, + minLayerTime = process.outputMinLayerTime || 0, nozzleSize = process.sliceLineWidth || device.extruders[extruder].extNozzle, firstLayer = (opt.first || false) && !opt.support, thinWall = nozzleSize * (opt.thinWall || 1.75), @@ -1036,8 +1038,8 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { return false; } - // solid infill - function outputTraces(poly, opt = {}) { + // output shell wall + function outputTraces(poly, opt = {}, traceNo = 0) { if (!poly) return; if (Array.isArray(poly)) { if (opt.sort) { @@ -1070,26 +1072,28 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { if (next) { last = next; polys.remove(next); - outputTraces(next, opt); + outputTraces(next, opt, traceNo++); } else { last = null; } } } else { outputOrderClosest(poly, function(next) { - outputTraces(next, opt); + outputTraces(next, opt, traceNo++); }, null); } } else { let finishShell = poly.depth === 0 && !firstLayer; startPoint = print.polyPrintPath(poly, startPoint, preout, { - ccw: opt.shell && process.outputAlternating && slice.index % 2, - tool: extruder, - rate: finishShell ? finishSpeed : printSpeed, accel: finishShell, - wipe: process.outputWipeDistance || 0, + ccw: opt.shell && process.outputAlternating && slice.index % 2, coast: firstLayer ? 0 : coastDist, extrude: numOrDefault(opt.extrude, shellMult), + nozzleSize, + rate: finishShell ? finishSpeed : printSpeed, + scarf: (traceNo === 0 && z - startPoint?.z > 1e-3) ? scarfLength : 0, + tool: extruder, + wipe: process.outputWipeDistance || 0, onfirst: function(firstPoint) { let from = seedPoint || startPoint; if (from.distTo2D(firstPoint) > retractDist) { @@ -1570,6 +1574,28 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } } + // adjust layer output speeds (by slowing them down) + // if layer print time is less than minimum (required for cooling) + if (minLayerTime) { + let lp, td = 0, tt = 0; + for (let p of preout) { + if (!p.emit) continue; + let mms = p.speed ?? 1; + let dst = lp?.distTo2D(p.point) ?? 0; + if (dst) { + tt += (dst/mms); + td += dst; + } + lp = p.point; + } + if (tt < minLayerTime) { + let fact = tt / minLayerTime; + for (let p of preout) { + if (p.emit) p.speed *= fact; + } + } + } + // offset print points for (let i=0; i 0.1"], ad_mins_s: "min speed", ad_mins_l: ["minimum speed","for short segments"], + ad_scar_s: "scarf length", + ad_scar_l: ["ease up length for scarf joint","0 = disabled"], ad_spol_s: "short path", ad_spol_l: ["polygons shorter than this","will have their print speed","scaled down to min speed","in millimeters"], ad_arct_s: "arc tolerance",