From 39cc3db3d8b1607405219c74a309251e4371aa92 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 26 Sep 2025 09:44:56 -0400 Subject: [PATCH 01/13] add codec optional extended point data encoding --- src/kiri/core/codec.js | 28 +++++++++++++++++++++++++++- src/kiri/mode/fdm/post.js | 6 ++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/kiri/core/codec.js b/src/kiri/core/codec.js index 06c9f896..3547fb6f 100644 --- a/src/kiri/core/codec.js +++ b/src/kiri/core/codec.js @@ -312,7 +312,7 @@ Polygon.prototype.encode = function(state) { state.poly[this.id] = this; - return { + let enc = { type: TYPE.POLY, id: this.id, array: encodePointArray(this.points, state, this.z), @@ -322,6 +322,20 @@ Polygon.prototype.encode = function(state) { color: this.color, open: this.open }; + + // encode extended point info for tagged polys + if (this._epk) { + let epk = enc.epk = {}; + for (let key of this._epk) { + let arr = epk[key] = []; + let pts = this.points; + for (let i=0, l=pts.length; i { let keep = (last_seg !== p.segment || p.moved || p.skip); last_seg = p.segment; From 35b48273bd65b9901c1415562bbab02cc966aa12 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 26 Sep 2025 19:48:56 -0400 Subject: [PATCH 02/13] move output layer menu items to layer menu. relabel for clarity --- src/kiri/mode/fdm/init-menu.js | 4 ++-- web/kiri/lang/en.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index 5320c367..7eee5e85 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -74,6 +74,8 @@ export function menu() { sliceBottomLayers: newInput(LANG.sl_lbot_s, { title:LANG.sl_lbot_l, convert:toInt }), separator: newBlank({ class:"set-sep", driven }), sliceAdaptive: newBoolean(LANG.ad_adap_s, onBooleanClick, { title: LANG.ad_adap_l }), + outputLayerRetract: newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l}), + sliceLayerStart: newSelect(LANG.sl_strt_s, {title:LANG.sl_strt_l}, "start"), _____: newGroup(LANG.sw_menu, $('fdm-walls'), { modes:FDM, driven, hideable, separator, group:"fdm-walls" }), sliceShells: newInput(LANG.sl_shel_s, { title:LANG.sl_shel_l, convert:toFloat }), sliceLineWidth: newInput(LANG.sl_line_s, { title:LANG.sl_line_l, convert:toFloat, bound:bound(0,5) }), @@ -165,8 +167,6 @@ export function menu() { outputRetractSpeed: newInput(LANG.ad_rrat_s, {title:LANG.ad_rrat_l, convert:toInt}), outputRetractWipe: newInput(LANG.ad_wpln_s, {title:LANG.ad_wpln_l, convert:toFloat, bound:bound(0.0,10)}), separator: newBlank({ class:"set-sep", driven }), - sliceLayerStart: newSelect(LANG.sl_strt_s, {title:LANG.sl_strt_l}, "start"), - outputLayerRetract: newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l}), outputAvoidGaps: newBoolean(LANG.ad_agap_s, onBooleanClick, {title:LANG.ad_agap_l}), separator: newBlank({ class:"set-sep", driven, show:isBelt }), outputBeltFirst: newBoolean(LANG.ad_lbir_s, onBooleanClick, {title:LANG.ad_lbir_l, show:isBelt}), diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index a9b61cf5..baa8b7eb 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -360,8 +360,8 @@ self.lang['en-us'] = { sl_line_l: ["extrusion width in millimeters","0 = nozzle diameter"], sl_ordr_s: "shell order", sl_ordr_l: ["output shell order","inside to outside","or outside to inside","affects surface quality"], - sl_strt_s: "layer start", - sl_strt_l: ["layer starting point","last = last layer end","center = part center","origin = device origin"], + sl_strt_s: "start point", + sl_strt_l: ["layer starting point","last = where last layer ended","center = part center","origin = device origin","random = yolo"], // FDM SOLID fs_menu: "solid fill", @@ -876,8 +876,8 @@ self.lang['en-us'] = { ad_altr_l: ["alternate shell winding order","clockwise / counter-clockwise","may reduce warping in thin areas","and help with belt edge adhesion"], ad_zint_s: "interleave z", ad_zint_l: ["interleave Z heights with odd number shells"], - ad_lret_s: "layer retract", - ad_lret_l: ["force filament retraction","between layers"], + ad_lret_s: "force retract", + ad_lret_l: ["force filament retraction between layers"], ad_agap_s: "avoid gaps", ad_agap_l: ["route around gaps rather","than perform a retract","when possible"], ad_play_s: "polish layers", From ac956a3cac8d591d7a2b22a452de25d49640ee84 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 26 Sep 2025 22:04:27 -0400 Subject: [PATCH 03/13] add spiral layer start point option --- src/kiri/core/consts.js | 1 + src/kiri/mode/fdm/prepare.js | 11 +++++++++++ web/kiri/lang/en.js | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/kiri/core/consts.js b/src/kiri/core/consts.js index fa73787f..4efe0ba5 100644 --- a/src/kiri/core/consts.js +++ b/src/kiri/core/consts.js @@ -27,6 +27,7 @@ const LISTS = { { name: "center" }, { name: "origin" }, { name: "random" }, + { name: "spiral" }, ], infill: [ { name: "none" }, diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index bd676ec2..d3d2e601 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -41,6 +41,7 @@ export async function fdm_prepare(widgets, settings, update) { zneg = 0, zoff = 0, zmin = 0, + zmax = 0, shield, output = [], layerout = []; @@ -224,6 +225,7 @@ export async function fdm_prepare(widgets, settings, update) { } for (let slice of widget.slices) { zmin = Math.min(zmin, slice.z); + zmax = Math.max(zmax, slice.z); if (!slice.supports) { continue; } @@ -469,6 +471,8 @@ export async function fdm_prepare(widgets, settings, update) { let lastExt; let lastOut; + print.zmax = zmax; + // walk cake layers bottom up for (let layer of cake) { // track purge blocks generated for each layer @@ -1474,6 +1478,13 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { // control of layer start point switch (process.sliceLayerStart) { + case "spiral": { + let center = top.poly.bounds.center(); + let angle = (Math.PI * 2) * (slice.z / print.zmax); + let x = center.x + Math.cos(angle)*10000; + let y = center.y + Math.sin(angle)*10000; + startPoint = newPoint(x,y,startPoint.z); + } break; case "last": break; case "random": diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index baa8b7eb..35819ccc 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -848,7 +848,7 @@ self.lang['en-us'] = { // FDM ADVANCED ad_menu: "expert", - ad_rdst_s: "retract dist", + ad_rdst_s: "retract distance", ad_rdst_l: ["amount to retract filament","for long moves. in millimeters"], ad_rrat_s: "retract rate", ad_rrat_l: ["speed of filament","retraction in mm/s"], @@ -866,7 +866,7 @@ self.lang['en-us'] = { ad_spol_l: ["polygons shorter than this","will have their print speed","scaled down to min speed","in millimeters"], ad_arct_s: "arc tolerance", ad_arct_l: ["convert faceted lines to arcs","center point drift tolerance","when matching arc points","consider values around 0.15","in millimeters","0 to disable"], - ad_zhop_s: "z hop dist", + ad_zhop_s: "z hop distance", ad_zhop_l: ["amount to raise z","on retraction moves","in millimeters","0 to disable"], ad_abkl_s: "anti-backlash", ad_abkl_l: ["for better flat surface finish","use micro-movements to cancel","backlash in solid layer output","in millimeters","0 to disable","if your firmware has M425","put that in the gcode header","and leave this as 0"], From 7660570f3a0a5b5dfaa3f29dcbe1346029cfa926 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 27 Sep 2025 00:19:22 -0400 Subject: [PATCH 04/13] add oname and oid params for macro conditions and substitutions --- src/kiri/core/print.js | 11 +++++++++-- src/kiri/mode/fdm/export.js | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index c343540b..27c7cd32 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -176,6 +176,14 @@ class Print { } constReplace(str, consts, start, pad, short) { + function tryeval(str) { + try { + return eval(`{ ${str} }`) + } catch (e) { + console.log({ eval_error: e, str }); + return str; + } + } let cs = str.indexOf("{", start || 0), ce = str.indexOf("}", cs), tok, nutok, nustr; @@ -199,8 +207,7 @@ class Print { } eva.push(`function range(a,b) { return (a + (layer / layers) * (b-a)).round(4) }`); eva.push(`try {( ${tok} )} catch (e) {console.log(e);0}`); - let scr = eva.join(''); - let evl = eval(`{ ${scr} }`); + let evl = tryeval(eva.join('')); nutok = evl; if (pad === 666) { return evl; diff --git a/src/kiri/mode/fdm/export.js b/src/kiri/mode/fdm/export.js index 8481bdc0..2f693f08 100644 --- a/src/kiri/mode/fdm/export.js +++ b/src/kiri/mode/fdm/export.js @@ -111,6 +111,8 @@ export function fdm_export(print, online, ondone, ondebug) { progress: 0, nozzle: tool, tool: tool, + oid: '', + oname: '', model_labels: model_labels.sort().join(',') }, pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist, @@ -620,6 +622,8 @@ export function fdm_export(print, online, ondone, ondebug) { let b64 = encodeBitOffset(off); append(`; start object id: ${out.widget.track.grid_id}`); isBambu && append(`M624 ${b64}`); + subst.oid = out.widget.track.grid_id; + subst.oname = out.widget.meta.file; } cwidget = out.widget; } From 79a227d7411fd0612bf2a0247706f3a2aba396e8 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 27 Sep 2025 21:15:21 -0400 Subject: [PATCH 05/13] add fdm scarf seams and min layer time --- notes.md | 16 +++----- src/geo/polygon.js | 15 +++++-- src/kiri/core/conf.js | 2 + src/kiri/core/print.js | 71 ++++++++++++++++++++++++++++++++-- src/kiri/core/render.js | 7 +--- src/kiri/mode/fdm/init-menu.js | 2 + src/kiri/mode/fdm/prepare.js | 42 ++++++++++++++++---- web/kiri/lang/en.js | 6 ++- 8 files changed, 129 insertions(+), 32 deletions(-) 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", From 77b4cce97ad20ec459b641d7fc3174ed6c2e741d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 28 Sep 2025 02:08:32 -0400 Subject: [PATCH 06/13] fix scarf/arc conflict and scarf z on export. add print remain time macro var --- notes.md | 7 ++++- src/kiri/core/print.js | 20 +++++++------- src/kiri/mode/fdm/export.js | 27 ++++++++++++++----- src/kiri/mode/fdm/prepare.js | 51 ++++++++++++++++++++---------------- web/kiri/lang/en.js | 4 +-- 5 files changed, 66 insertions(+), 43 deletions(-) diff --git a/notes.md b/notes.md index d45c1523..b7e7a9a1 100644 --- a/notes.md +++ b/notes.md @@ -100,11 +100,12 @@ * subfolder parameter for dropped files * auto-kick check box in preferences - # Mesh:Tool * add undo/redo * add TextGeometry +* https://threejs.org/docs/#examples/en/geometries/TextGeometry +* https://dustinpfister.github.io/2023/07/05/threejs-text-geometry/ * font to path with https://github.com/paulzi/svg-text-to-path * click to repair normals based on a known good (selected) face * send to Kiri:Moto workspace (or update model vertices in place) @@ -116,3 +117,7 @@ * fix mirror to work with groups (just models currently) * add analyze results dialog * remove/hide auto-repair function + +# Other + +* preferred icons https://icons.getbootstrap.com/ diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index d4e973b9..5fcfb198 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -80,11 +80,9 @@ class Print { return lastOut; } - addPrintPoints(input, output, startPoint, tool) { + addPrintPoints(input, output) { if (this.startPoint && input.length > 0) { this.lastPoint = this.startPoint; - // TODO: revisit seek to origin as the first move - // addOutput(output, startPoint, 0, undefined, tool); } output.appendAll(input); } @@ -120,11 +118,11 @@ class Print { const { settings } = scope; const { process } = settings; - let shortDist = process.outputShortDistance, - shellMult = numOrDefault(options.extrude, process.outputShellMult), + let shellMult = numOrDefault(options.extrude, process.outputShellMult), printSpeed = options.rate || process.outputFeedrate, moveSpeed = process.outputSeekrate, minSpeed = process.outputMinSpeed, + nozzleSize = options.nozzleSize, coastDist = options.coast || 0, closest = options.simple ? poly.first() : poly.findClosestPointTo(startPoint), perimeter = poly.perimeter(), @@ -177,20 +175,20 @@ class Print { 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 fcs = 1.0; // flow compensation seam + let fco = (1 / sp.length) * 0.0; // flow compensation offset (- 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; + p.moved = (((zi++) / sp.length) * fcs) - 1 - fco; } 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; + p.moved = (((--zi) / esp.length) * fcs) - 1 - fco; } - esp.pop(); // stop short to prevent overlap bulge + // esp.pop(); // stop short to prevent overlap bulge pp.push(...esp); // console.log({ poly, spz, epz, zd, sp, esp }); scarf = true; @@ -208,7 +206,7 @@ class Print { } else if (first) { // if (point.skip) console.log({ skip: point }); if (options.onfirst) { - options.onfirst(point); + options.onfirst(point, output); } // move to first output point on poly let out = scope.addOutput(output, point, 0, moveSpeed, tool); diff --git a/src/kiri/mode/fdm/export.js b/src/kiri/mode/fdm/export.js index 2f693f08..74328cfb 100644 --- a/src/kiri/mode/fdm/export.js +++ b/src/kiri/mode/fdm/export.js @@ -59,7 +59,8 @@ export function fdm_export(print, online, ondone, ondebug) { retSpeed = process.outputRetractSpeed * 60 || 1, // range retDwell = process.outputRetractDwell || 0, // range timeDwell = retDwell / 1000, - arcDist = isBelt ? 0 : (process.arcTolerance || 0), + scarfZ = process.outputScarfLength ? true : false, + arcDist = isBelt || scarfZ ? 0 : (process.arcTolerance || 0), arcMin = 1, arcRes = 20, arcDev = 0.5, @@ -113,7 +114,9 @@ export function fdm_export(print, online, ondone, ondebug) { tool: tool, oid: '', oname: '', - model_labels: model_labels.sort().join(',') + model_labels: model_labels.sort().join(','), + total_time: Math.ceil(print.total_time / 60), + remain_time: Math.ceil(print.total_time / 60), }, pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist, lines = 0, @@ -527,6 +530,7 @@ export function fdm_export(print, online, ondone, ondebug) { // retract before first move retract(); + subst.remain_time = Math.ceil(print.total_time / 60); while (layer < layers.length) { path = layers[layer]; layerno = path.slice.index; @@ -600,7 +604,7 @@ export function fdm_export(print, online, ondone, ondebug) { } // move Z to layer height - if (layer > 0 || !isBelt) { + if (layer === 0 || ((layer > 0 || !isBelt) && !scarfZ)) { moveTo({z:zpos}, seekMMM); } @@ -783,12 +787,20 @@ export function fdm_export(print, online, ondone, ondebug) { } else { // emitMM = emitPerMM * out.emit * dist; emitMM = extrudeMM(dist, emitPerMM, out.emit); - moveTo({x:x, y:y, e:emitMM}, speedMMM); + if (scarfZ) { + moveTo({x, y, z:z + path.height/2, e:emitMM}, speedMMM); + } else { + moveTo({x, y, e:emitMM}, speedMMM); + } emitted += emitMM; } } else { drainQ(); - moveTo({x:x, y:y}, seekMMM); + if (scarfZ) { + moveTo({x, y, z:z + path.height/2, e:emitMM}, speedMMM); + } else { + moveTo({x, 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 @@ -834,11 +846,13 @@ export function fdm_export(print, online, ondone, ondebug) { append(`M808`); } drainQ(); + + print.total_time -= path.print_time; + subst.remain_time = Math.ceil(print.total_time / 60); } function emitQrec(rec) { let {e, x, y, dist, emitPerMM, speedMMM} = rec; - // emitMM = emitPerMM * e * dist; emitMM = extrudeMM(dist, emitPerMM, e); moveTo({x:x, y:y, e:emitMM}, speedMMM); emitted += emitMM; @@ -962,7 +976,6 @@ export function fdm_export(print, online, ondone, ondebug) { } return true; - } if (inloops) { diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index c4b207b3..5d18cba7 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -142,6 +142,7 @@ export async function fdm_prepare(widgets, settings, update) { }); layerout.z = zoff + height; layerout.height = height; + layerout.print_time = printPoint.layer_time; output.append(layerout); layerout = []; @@ -196,7 +197,7 @@ export async function fdm_prepare(widgets, settings, update) { }); }); - print.addPrintPoints(preout, layerout, null); + print.addPrintPoints(preout, layerout); if (preout.length) { // retract between brims and print @@ -472,6 +473,7 @@ export async function fdm_prepare(widgets, settings, update) { let lastOut; print.zmax = zmax; + print.total_time = 0; // walk cake layers bottom up for (let layer of cake) { @@ -574,6 +576,8 @@ export async function fdm_prepare(widgets, settings, update) { } } ); + layerout.print_time = printPoint.layer_time; + print.total_time += printPoint.layer_time; print.setWidget(null); lastOut = slice; @@ -1084,6 +1088,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } } else { let finishShell = poly.depth === 0 && !firstLayer; + let scarf = (slice.index > 0 && traceNo === 0 && z - startPoint?.z > 1e-3) ? scarfLength : 0; startPoint = print.polyPrintPath(poly, startPoint, preout, { accel: finishShell, ccw: opt.shell && process.outputAlternating && slice.index % 2, @@ -1091,10 +1096,9 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { extrude: numOrDefault(opt.extrude, shellMult), nozzleSize, rate: finishShell ? finishSpeed : printSpeed, - scarf: (traceNo === 0 && z - startPoint?.z > 1e-3) ? scarfLength : 0, + scarf, tool: extruder, - wipe: process.outputWipeDistance || 0, - onfirst: function(firstPoint) { + onfirst: (firstPoint, preout) => { let from = seedPoint || startPoint; if (from.distTo2D(firstPoint) > retractDist) { if (intersectsTop(from, firstPoint)) { @@ -1576,23 +1580,21 @@ 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; + 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; } - if (tt < minLayerTime) { - let fact = tt / minLayerTime; - for (let p of preout) { - if (p.emit) p.speed *= fact; - } + lp = p.point; + } + if (minLayerTime && tt < minLayerTime) { + let fact = tt / minLayerTime; + for (let p of preout) { + if (p.emit) p.speed *= fact; } } @@ -1602,7 +1604,12 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } // add offset points to total print - print.addPrintPoints(preout, output, origin, extruder); + print.addPrintPoints(preout, output); - return startPoint.add(offset); + // create next layer start point and annotate + // with print time for previous layer (hack to + // avoid changing return signature rn) + let newStart = startPoint.add(offset); + newStart.layer_time = Math.max(tt, minLayerTime); + return newStart; } diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index b0eb9348..451bcadb 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -865,11 +865,11 @@ self.lang['en-us'] = { 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_scar_l: ["ease up length for scarf joint. disables arc detection","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", - ad_arct_l: ["convert faceted lines to arcs","center point drift tolerance","when matching arc points","consider values around 0.15","in millimeters","0 to disable"], + ad_arct_l: ["convert faceted lines to arcs","center point drift tolerance","when matching arc points","consider values around 0.15","value in millimeters","0 or scarf disables this"], ad_zhop_s: "z hop distance", ad_zhop_l: ["amount to raise z","on retraction moves","in millimeters","0 to disable"], ad_abkl_s: "anti-backlash", From 5aa5456f37c6974823dfa7d8b4a14ee74c022bcf Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 28 Sep 2025 12:41:08 -0400 Subject: [PATCH 07/13] fix move extrusion with scarfz --- src/kiri/core/print.js | 2 -- src/kiri/mode/fdm/export.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index 5fcfb198..86736811 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -188,9 +188,7 @@ class Print { p.z = epz; p.moved = (((--zi) / esp.length) * fcs) - 1 - fco; } - // esp.pop(); // stop short to prevent overlap bulge pp.push(...esp); - // console.log({ poly, spz, epz, zd, sp, esp }); scarf = true; } diff --git a/src/kiri/mode/fdm/export.js b/src/kiri/mode/fdm/export.js index 74328cfb..b6768e4b 100644 --- a/src/kiri/mode/fdm/export.js +++ b/src/kiri/mode/fdm/export.js @@ -797,7 +797,7 @@ export function fdm_export(print, online, ondone, ondebug) { } else { drainQ(); if (scarfZ) { - moveTo({x, y, z:z + path.height/2, e:emitMM}, speedMMM); + moveTo({x, y, z:z + path.height/2}, speedMMM); } else { moveTo({x, y}, seekMMM); } From 6c26dd0d8174e8b0872f80c71ac5bbbf3484790a Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 28 Sep 2025 21:22:19 -0400 Subject: [PATCH 08/13] only apply new starting point on first move of new layer --- src/kiri/mode/fdm/prepare.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 5d18cba7..d3a0bfd9 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1485,6 +1485,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } // control of layer start point + if (!lastTop) switch (process.sliceLayerStart) { case "spiral": { let center = top.poly.bounds.center(); From f53d6da12bbf24d685fd38482d85195ccad6c0bb Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 28 Sep 2025 22:39:56 -0400 Subject: [PATCH 09/13] improve gap routing. add adaptive shell fallback on failure --- src/geo/polygon.js | 23 ++++++++++++++++++++--- src/kiri/mode/fdm/post.js | 15 +++++++++++---- src/kiri/mode/fdm/prepare.js | 17 +++++++++++------ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 2ef4d2f6..ddce7094 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1323,15 +1323,15 @@ export class Polygon { pp2.pos = ip2; } }); - list.sort(function(p1, p2) { - return util.distSq(lp1, p1) - util.distSq(lp1, p2); - }); if (deep && this.inner) { this.inner.forEach(p => { let ints = p.intersections(lp1, lp2); if (ints) list.appendAll(ints); }); } + list.sort(function(p1, p2) { + return util.distSq(lp1, p1) - util.distSq(lp1, p2); + }); return list; } @@ -1392,6 +1392,23 @@ export class Polygon { return poly; } + /** + * emit the shortest poly that connects two points on a closed CW poly + * because traveling around the 'end point' may yield a shorter route. + * if the poly is open, use emitSegment() + */ + emitShortestSegment(i1, i2) { + if (this.open) { + return this.emitSegment(i1, i2); + } + let start = i1.p2.pos, + end = i2.p1.pos, + points = this.points, + seg0 = new Polygon([ i1, ...points.slice(start, end + 1), i2 ]).setOpen(), + seg1 = new Polygon([ i2, ...points.slice(end +1 ), ...points.slice(0, start), i1 ]).setOpen(); + return seg0.perimeter() < seg1.perimeter() ? seg0 : seg1; + } + /** * @param {Polygon} poly * @param {number} [tolerance] diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 55b335fd..58e4ca65 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -487,6 +487,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { // construct medial axis lines. this appears to produce duplicate // reverse segments in a subset of cases let ma = JSPoly.construct_medial_axis(out, inr); + if (ma.length === 0) throw('cannot construct medial axis'); // dedup the points so we can track them uniquely for (let { point0, point1 } of ma) { pointsToLine(point0, point1); @@ -696,7 +697,8 @@ export function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = { if (dist < 1) p.open = false; } }); let ret = { last, gaps }; - switch (opt.thinType) { + let ttype = opt.thinType; + switch (ttype) { case "legacy 1": ret = thin_type_1({ z, top, count, top_poly, offsetN, fillOffset }); break; @@ -704,13 +706,18 @@ export function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = { ret = thin_type_2({ z, top, count, top_poly, offset1, offsetN, fillOffset, gaps, wasm }); break; case "adaptive": - ret = thin_type_3({ z, top, count, offsetN }); - break; + try { + ret = thin_type_3({ z, top, count, offsetN }); + break; + } catch (e) { + console.log('adaptive layer failed. falling back to basic.', e); + ttype = "basic"; + } case "basic": default: ret = offset_default({ z, top, count, top_poly, offset1, offsetN, wasm, - thin: opt.thinType === 'basic' + thin: ttype === 'basic' }); break; } diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index d3a0bfd9..8d560b43 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -894,7 +894,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { // return true if move path from p1 to p2 intersects a // top and a path around the top (inside print) was not found - function intersectsTop(p1, p2) { + function requiresRetract(p1, p2) { if (slice.index < 0) { return false; } @@ -919,15 +919,20 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { if (dbug === slice.index) console.log(slice.index, {p1, p2, d: p1.distTo2D(p2)}); let ints = []; + let intk = new Set(); let tops = slice.topRouteFlat(); for (let poly of tops) { poly.forEachSegment((s1, s2) => { let ip = util.intersect(p1,p2,s1,s2,base.key.SEGINT); - if (ip) { + if (ip) ip._key = undefined; + if (ip && !intk.has(ip.key)) { ints.push({ip, poly}); + intk.add(ip.key); + ip.p1.poly.hits = (ip.p1.poly.hits ?? 0) + 1; } }); } + ints = ints.filter(i => i.poly.hits === 2); // no intersections if (ints.length === 0) { @@ -1101,7 +1106,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { onfirst: (firstPoint, preout) => { let from = seedPoint || startPoint; if (from.distTo2D(firstPoint) > retractDist) { - if (intersectsTop(from, firstPoint)) { + if (requiresRetract(from, firstPoint)) { retract(); } } @@ -1183,7 +1188,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { poly.forEachPoint((p, i) => { let dist = lp.distTo2D(p); let rdst = dist > retractDist; - let itop = rdst && intersectsTop(lp,p); + let itop = rdst && requiresRetract(lp,p); let emit = extrude; // retract if dist trigger and crosses a slice top polygon if (i === 0) { @@ -1227,7 +1232,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } let first = points[0]; let last = first; - if (startPoint && startPoint.distTo2D(first) > thinWall && intersectsTop(startPoint, first)) { + if (startPoint && startPoint.distTo2D(first) > thinWall && requiresRetract(startPoint, first)) { retract(); } print.addOutput(preout, first, 0, moveSpeed, extruder); @@ -1376,7 +1381,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { print.addOutput(preout, p2, fill, fillSpeed, extruder); } else { // retract if dist trigger or crosses a slice top polygon - if (!fast && dist > retractDist && (zhop || intersectsTop(startPoint, p1))) { + if (!fast && dist > retractDist && (zhop || requiresRetract(startPoint, p1))) { retract(); } From 8e75ad718f53b3fc24aa1c34bc14ecc2b5065061 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 29 Sep 2025 11:56:07 -0400 Subject: [PATCH 10/13] add test slicing poly pumping (rounding) --- src/kiri/mode/fdm/post.js | 21 ++++++++++++++++++++- src/kiri/mode/fdm/slice.js | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 58e4ca65..5a44e57d 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -21,7 +21,26 @@ export function slicePost(data, options) { const { useAssembly, post_args, zIndexes } = options; const { process, isSynth, vaseMode } = post_args; const { shellOffset, fillOffset, clipOffset } = post_args; - data.tops = POLY.nest(groups); + + if (options.pump) { + let nugroups = []; + let pump = shellOffset / 10; + for (let p of groups) { + let retop = POLY.offset([ p ], -pump, { + join: ClipperLib.JoinType.jtRound, + arc: (1/pump) * 4 + }); + retop = POLY.offset(retop, pump, { + join: ClipperLib.JoinType.jtRound, + arc: (1/pump) * 4 + }); + nugroups.push(...retop); + } + data.tops = POLY.nest(nugroups); + } else { + data.tops = POLY.nest(groups); + } + if (isSynth) { const process = post_args.process; if (process.sliceSupportGrow > 0) { diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index f0bc89e2..bc91bb0e 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -345,7 +345,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { } } if (process.xray) { - slice.index = process.xrayi.shift(); + slice.index = process.xray.shift(); slice.lines = lines; slice.groups = groups; slice.xray = process.xray; From 8eaa0a33a837e266208338e357cdada70ea810d8 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 29 Sep 2025 12:28:51 -0400 Subject: [PATCH 11/13] fix angle diff calc for basic thin wall merge test --- src/kiri/mode/fdm/post.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 5a44e57d..25cd368e 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -165,9 +165,8 @@ function offset_default(params) { if (p0 === p1 || p0.segment === p1.segment) continue; let dist = p0.distTo2D(p1); if (dist < offset1) { - let diff = p0.segment.angle.angleDiff(p1.segment.angle,false); - if (p0.segment === p1.segment && diff < deltaBig) continue; - if (p0.segment !== p1.segment && diff > deltSmall && diff < deltaBig) continue; + let diff = p0.segment.angle.angleDiff(p1.segment.angle,true); + if (diff < deltaBig) continue; // merge points marking point offset and skip let mid = p0.midPointTo(p1); let inc = p0.distTo2D(mid) / offset1; From 1cca29349525122d7effb7b5c9815ce6d22f9bae Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 29 Sep 2025 19:11:24 -0400 Subject: [PATCH 12/13] add hole and perimeter offset compensation as expert settings --- notes.md | 2 -- src/kiri/core/api.js | 6 ++++++ src/kiri/core/conf.js | 2 ++ src/kiri/core/widget.js | 4 +++- src/kiri/mode/fdm/init-menu.js | 2 ++ src/kiri/mode/fdm/post.js | 29 +++++++++++++++++++++++------ src/kiri/mode/fdm/slice.js | 5 ++++- web/kiri/lang/en.js | 4 ++++ 8 files changed, 44 insertions(+), 10 deletions(-) diff --git a/notes.md b/notes.md index b7e7a9a1..7cf2186c 100644 --- a/notes.md +++ b/notes.md @@ -33,8 +33,6 @@ * `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 diff --git a/src/kiri/core/api.js b/src/kiri/core/api.js index c66292a3..42ec035c 100644 --- a/src/kiri/core/api.js +++ b/src/kiri/core/api.js @@ -96,6 +96,7 @@ let UC = UI.prefix('kiri').inputAction(settings.conf.update), put: (key, val) => localSet(key, val), set: (key, val) => localSet(key, val), setItem: (key, val) => localSet(key, val), + removeItem: (key) => localRemove(key) }, tweak = { line_precision(v) { api.work.config({ base: { clipperClean: v } }) }, @@ -119,6 +120,11 @@ function localSet(key, val) { return val; } +function localRemove(key) { + let sloc = api.conf.get().local; + return delete sloc[key]; +} + export const api = { ajax, beta, diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index 4daf5556..71908b54 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -267,6 +267,8 @@ export const conf = { sliceLayerStartX: 0, sliceLayerStartY: 0, sliceLineWidth: 0, + sliceCompInner: 0, + sliceCompOuter: 0, sliceFillGrow: 0, sliceFillAngle: 45, sliceFillWidth: 1, diff --git a/src/kiri/core/widget.js b/src/kiri/core/widget.js index 783598ca..47222eb7 100644 --- a/src/kiri/core/widget.js +++ b/src/kiri/core/widget.js @@ -782,9 +782,11 @@ class Widget { } if (set) { let dark = this.api.space.is_dark(); + let cam = this.api.mode.is_cam(); + let color = dark && !cam ? 0xffffff : 0; let angle = this.api.conf.get().controller.edgeangle || 20; let edges = new THREE.EdgesGeometry(mesh.geometry, angle); - let material = new THREE.LineBasicMaterial({ color: 0 }); + let material = new THREE.LineBasicMaterial({ color }); this.outline = new THREE.LineSegments(edges, material); this.outline.renderOrder = -20; mesh.add(this.outline); diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index fc0506c1..07571f0b 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -181,6 +181,8 @@ export function menu() { 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)}), + sliceCompInner: newInput(LANG.sl_ofin_s, {title:LANG.sl_ofin_l, convert:toFloat, bound:bound(-10.0,10.0)}), + sliceCompOuter: newInput(LANG.sl_ofot_s, {title:LANG.sl_ofot_l, convert:toFloat, bound:bound(-10.0,10.0)}), zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}), outputLoops: newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, convert:toInt, bound:bound(-1,1000), show:isBelt}), diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 25cd368e..b910958a 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -17,12 +17,13 @@ slicer.slicePost.FDM = slicePost; * data object. return is ignored. */ export function slicePost(data, options) { - const { z, lines, groups } = data; - const { useAssembly, post_args, zIndexes } = options; - const { process, isSynth, vaseMode } = post_args; - const { shellOffset, fillOffset, clipOffset } = post_args; + const { groups, z } = data; + const { post_args, useAssembly, zIndexes } = options; + const { isSynth, process, vaseMode } = post_args; + const { compInner, compOuter, pump } = post_args; + const { clipOffset, fillOffset, shellOffset } = post_args; - if (options.pump) { + if (pump) { let nugroups = []; let pump = shellOffset / 10; for (let p of groups) { @@ -41,6 +42,19 @@ export function slicePost(data, options) { data.tops = POLY.nest(groups); } + // perimeter and inner hole compensation offsets + if (compInner || compOuter) { + let inner = POLY.inner(data.tops).flat(); + let outer = POLY.outer(data.tops); + if (compInner) { + inner = POLY.flatten(POLY.offset(inner, compInner/2)); + } + if (compOuter) { + outer = POLY.flatten(POLY.offset(outer, -compOuter/2)); + } + data.tops = POLY.nest([...inner, ...outer]); + } + if (isSynth) { const process = post_args.process; if (process.sliceSupportGrow > 0) { @@ -54,6 +68,7 @@ export function slicePost(data, options) { delete data.groups; return; } + const index = zIndexes.indexOf(z); const range = getRangeParameters(process, index); // calculate fractional shells @@ -129,7 +144,7 @@ function offset_default(params) { } const deltaBig = 160; - const deltSmall = 20; + const deltaSmall = 20; // look for close points on adjacent segmented polys and merge let test_polys = first !== last ? [ ...first, ...last ] : last; @@ -139,6 +154,8 @@ function offset_default(params) { for (let poly of test_flats) poly.forEachSegment((p0, p1) => { p0.angle = new Slope(p0, p1); + // todo calc vertex angle + // exclude segment vertex from merge when angle < deltaSmall }); // segment each poly to be tested diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index bc91bb0e..9bda3df8 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -140,7 +140,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) { fillOffset = lineWidth * fillOffsetMult, clipOffset = process.sliceSupportOffset, sliceFillAngle = process.sliceFillAngle, - supportDensity = process.sliceSupportDensity; + supportDensity = process.sliceSupportDensity, + { sliceCompInner, sliceCompOuter } = process; // override globals used by vopt() isFlat = controller.lineType === "flat"; @@ -230,6 +231,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) { useAssembly, post: 'FDM', post_args: { + compInner: sliceCompInner, + compOuter: sliceCompOuter, shellOffset, fillOffset, clipOffset, diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 451bcadb..39eb200d 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -362,6 +362,10 @@ self.lang['en-us'] = { sl_ordr_l: ["output shell order","inside to outside","or outside to inside","affects surface quality"], sl_strt_s: "start point", sl_strt_l: ["layer starting point","last = where last layer ended","center = part center","origin = device origin","random = yolo"], + sl_ofin_s: "shell inner offset", + sl_ofin_l: ["inner hole perimeter offset","in millimeters"], + sl_ofot_s: "shell outer offset", + sl_ofot_l: ["outer wall perimeter offset","in millimeters"], // FDM SOLID fs_menu: "solid fill", From 70f02f4c8253e4142480a0723772b910a0f7a86c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 29 Sep 2025 21:19:57 -0400 Subject: [PATCH 13/13] bump rev 4.3.2 --- package.json | 2 +- src/moto/license.js | 2 +- web/kiri/manifest.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 82e76bb8..d8d10734 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.3.1", + "version": "4.3.2", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", diff --git a/src/moto/license.js b/src/moto/license.js index 21ca7569..62c0e58c 100644 --- a/src/moto/license.js +++ b/src/moto/license.js @@ -3,7 +3,7 @@ const terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: "4.3.1" + VERSION: "4.3.2" }; export const beta = 0; diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index 051f5f4c..80b2d2f8 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.3.1", + "name": "Kiri:Moto 4.3.2", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/",