diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 46bab9d5..94b44438 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1184,7 +1184,7 @@ CAM.init = function(kiri, api) { return func.selectHolesDone(); } func.clearPops(); - alert = api.show.alert("analyzing parts...", 1000); + let alert = api.show.alert("analyzing parts..."); holeSelOn = hoveredOp; holeSelOn.classList.add("editing"); api.feature.hover = true; @@ -1192,9 +1192,7 @@ CAM.init = function(kiri, api) { func.hover = func.selectHolesHover; func.hoverUp = func.selectHolesHoverUp; - let settings = API.conf.get(); - const {tool,mark} = poppedRec //TODO: display some visual difference if mark is selected - let diam = new CAM.Tool(settings,tool).fluteDiameter() + const widgets = kiri.api.widgets.all() @@ -1205,7 +1203,7 @@ CAM.init = function(kiri, api) { * @returns {Mesh} the created mesh */ function createHoleMesh(widget,drill){ - let {depth,selected} = drill + let {depth,selected, diam} = drill let color = selected ? 0xFF0000:0x39e366 let geo = new THREE.CylinderGeometry(diam/2,diam/2,depth,20); const material = new THREE.MeshPhongMaterial( { color } ); @@ -1221,10 +1219,10 @@ CAM.init = function(kiri, api) { widget.adds.push(mesh); //for click detection return mesh } - let meshesCached = widgets.every(widget=>poppedRec.drills[widget.id] != undefined) if( individual && meshesCached ){ // if any widget already has cached holes // console.log("already has cached holes",poppedRec.drills) + api.hide.alert(alert) kiri.api.widgets.for(widget => { if(widget.adds){ let drills = poppedRec.drills[widget.id] @@ -1245,33 +1243,52 @@ CAM.init = function(kiri, api) { }) }else{ // if no widget has cached holes - await CAM.holes(individual?0:diam,(centers)=>{ - let shadow = centers.some(c=>c.shadowed) - if(shadow){ - alert = api.show.alert("Some holes are shadowed by part and are not shown."); - } - centers = centers ?? [] - // list of all hole centers and if they are selected - kiri.api.widgets.for(widget => { - const {holes} = centers.find(center=>center.id = widget.id) - // console.log(holes) - if (!holes.length) unselectHoles(holes); - holes.forEach(hole => { - createHoleMesh(widget, hole) + let alert = api.show.alert(""); + await CAM.holes( + individual, + poppedRec, + (progress,msg)=>{ + alert[0]=msg + api.show.progress(progress,msg) + api.alerts.update() + }, + async centers => { + api.show.progress(0) + if(! Array.isArray(centers) ){ + console.log("worker returned a malformed drills response") + return + } + let shadow = centers.some(c=>c.shadowed) + api.hide.alert(alert) + if(shadow){ + alert = api.show.alert("Some holes are shadowed by part and are not shown."); + } + centers = centers ?? [] + // list of all hole centers and if they are selected + kiri.api.widgets.for(widget => { + const {holes} = centers.find(center=>center.id = widget.id) + // console.log(holes) + if (!holes.length) unselectHoles(holes); + holes.forEach(hole => { + createHoleMesh(widget, hole) + }) + //add hole data to record + poppedRec.drills = poppedRec.drills ?? {} + poppedRec.drills[widget.id] = holes + //give widget access to an array of drill records that refrence it + //so that it can be cleared when widget is rotated or mirrored etc. + if(!widget.drills){widget.drills = []} + widget.drills.push(holes) }) - //add hole data to record - poppedRec.drills = poppedRec.drills ?? {} - poppedRec.drills[widget.id] = holes - //give widget access to an array of drill records that refrence it - //so that it can be cleared when widget is rotated or mirrored etc. - if(!widget.drills){widget.drills = []} - widget.drills.push(holes) - }) - }); + } + ); } //hide the alert once hole meshes are calculated on the worker, and then added to the scene - api.hide.alert(alert); - alert = api.show.alert("[esc] cancels drill editing"); + // api.hide.alert(alert); + let escAlert = api.show.alert("[esc] cancels drill editing",1000); + setTimeout(() => { + api.hide.alert(escAlert); + }, 5000); kiri.api.widgets.opacity(0.8); } @@ -1782,10 +1799,10 @@ CAM.init = function(kiri, api) { dwell: 'camDrillDwell', lift: 'camDrillLift', mark: 'camDrillMark', + precision:'camDrillPrecision', thru: 'camDrillThru', - }) - drillOp.inputs = { + }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), sep: UC.newBlank({class:"pop-sep"}), spindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, show:hasSpindle}), @@ -1795,8 +1812,9 @@ CAM.init = function(kiri, api) { lift: UC.newInput(LANG.cd_lift_s, {title:LANG.cd_lift_l, convert:UC.toFloat, units:true, show:() => !poppedRec.mark}), mark: UC.newBoolean(LANG.cd_mark_s, undefined, {title:LANG.cd_mark_l}), sep: UC.newBlank({class:"pop-sep"}), - thru: UC.newInput(LANG.cd_dtru_s, {title:LANG.cd_dtru_l, convert:UC.toFloat, units:true}), - sep: UC.newBlank({class:"pop-sep"}), + thru: UC.newInput(LANG.cd_dtru_s, {title:LANG.cd_dtru_l, convert:UC.toFloat, units:true,show:() => !poppedRec.mark}), + precision:UC.newInput(LANG.cd_prcn_s, {title:LANG.cd_prcn_l, convert:UC.toFloat, units:true,show:() => !poppedRec.mark}), + sep: UC.newBlank({class:"pop-sep", }), actions: UC.newRow([ UC.newButton(LANG.select, ()=>func.selectHoles(true), {title:LANG.cd_seli_l}), UC.newButton(LANG.cd_sela_s, ()=>func.selectHoles(false), {title:LANG.cd_sela_l}) diff --git a/src/kiri-mode/cam/driver.js b/src/kiri-mode/cam/driver.js index 12882077..44220e29 100644 --- a/src/kiri-mode/cam/driver.js +++ b/src/kiri-mode/cam/driver.js @@ -82,15 +82,23 @@ kiri.load(api => { }); }; - //0 or less diameter means select all holes - CAM.holes = function(diam,onDone) { + + CAM.holes = function(indiv,rec,onProgress,onDone) { kiri.client.sync(); const settings = api.conf.get(); - const widgets = api.widgets.map(); - kiri.client.send("cam_holes", { settings, diam }, output => { - let res = kiri.codec.decode(output) - onDone(res) - }); + return new Promise((res,rej)=>{ + kiri.client.send("cam_holes", { settings, rec, indiv }, output => { + let out = kiri.codec.decode(output) + if(out.progress != undefined){ + //if a progress message, + onProgress(out.progress,out.msg) + }else{ + api.hide.alert(alert) + onDone(out) + res(out) + } + }); + }) } } @@ -156,11 +164,14 @@ kiri.load(api => { }; kiri.worker.cam_holes = async function(data, send) { - const { settings, diam } = data; + const { settings, indiv, rec } = data; const widgets = Object.values(kiri.worker.cache); const fresh = []; - for (let widget of widgets) { - if (await CAM.holes(settings, widget, diam)) { + + for (let [i,widget] of widgets.entries() ) { + if (await CAM.holes(settings, widget, indiv, rec, + ( prog, msg )=>{ send.data({progress: (i/widgets.length)+(prog/widgets.length),msg})} + )){ fresh.push(widget); } } diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index 0d1a80e9..9f0db31a 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -441,10 +441,16 @@ CAM.traces = async function(settings, widget, single) { * * @param {Object} settings - settings object * @param {Object} widget - widget object - * @param {number} diam - diameter of the drill bit + * @param {boolean} individual - if true, drill holes individually + * @param {Object} rec - DrillOp record + * @param {Function} onProgress - callback function to report progress * @returns {Array} list of hole centers as objects with `x`, `y`, `z`, `depth`, and `selected` properties. */ -CAM.holes = async function(settings, widget, diam) { +CAM.holes = async function(settings, widget, individual, rec,onProgress) { + + let {tool,mark,precision} = rec //TODO: display some visual difference if mark is selected + let toolDiam = new CAM.Tool(settings,tool).fluteDiameter() + let diam = individual ? 1 : toolDiam; // sets default diameter when select individual used let proc = settings.process, stock = settings.stock || {}, @@ -462,23 +468,23 @@ CAM.holes = async function(settings, widget, diam) { zBottom = isIndexed ? camZBottom : camZBottom - zbOff; - let slicerOpts = {flatoff: 0.01} + let slicerOpts = {flatoff: 0.001} let slicer = new kiri.cam_slicer(widget,slicerOpts); - let zFlats = Object.keys(slicer.zFlat).map(Number).map(z=>[z]).flat() + let zFlats = Object.keys(slicer.zFlat).map(Number).map(z=>[z,z-0.002]).flat() - let intervals = slicer.interval(1,{ - fit: false, off: -0.01, flats: true - }) + precision = Math.max( 0, precision ) + let intervals = (precision == 0) ? [] : slicer.interval( + precision, + { + fit: false, off: -0.01, flats: true + } + ) let zees = [...zFlats,...intervals] let indices = [...new Set(zees .map(kv => parseFloat(kv).round(5)) .filter(z => z !== null) )] - let individual = (diam <= 0); - - diam = individual ? 1 : diam; // sets default diameter when select individual used - let centerDiff = diam * 0.1, area = (diam/2) * (diam/2) * Math.PI, circles = [], @@ -487,14 +493,14 @@ CAM.holes = async function(settings, widget, diam) { function onEach(slice) { slices.push(slice); } - let opts = { each: onEach }; + let opts = { each: onEach, progress: (num,total)=> onProgress(num/total*0.5,"slice") }; await slicer.slice(indices, opts); let shadowedDrills = false; // console.log("slices",slices) - for (let slice of slices) { + for (let [i,slice] of slices.entries()) { for(let top of slice.tops){ // console.log("slicing",slice.z,top) - slice.shadow = CAM.shadowAt(widget,slice.z, 0) + slice.shadow = CAM.shadowAt(widget, slice.z, 0) let inner = top.inner; if (!inner) { //no holes continue; @@ -532,13 +538,13 @@ CAM.holes = async function(settings, widget, diam) { if (!overlap) circles.push(center); } } + onProgress(0.5+(i/slices.length*0.25),"recognize circles") } let drills = [] - for (let c of circles) { + for (let [i,c] of circles.entries()) { let overlapping = c.overlapping - .sort((a,b) => b.z - a.z) let last = overlapping.shift(); while (overlapping.length) { @@ -555,10 +561,11 @@ CAM.holes = async function(settings, widget, diam) { } } if (last.depth != 0) drills.push(last) //add last circle + onProgress(0.75+(i/circles.length*0.25),"assemble holes") } - drills.forEach( h=>{ delete h.overlapping //for encoding + h.diam = toolDiam // for mesh generation h.selected = (!individual && Math.abs(h.area - area) <= area * 0.05 ); //for same size selection }) diff --git a/src/kiri-mode/cam/slicer.js b/src/kiri-mode/cam/slicer.js index 5bc28ade..ab4b4317 100644 --- a/src/kiri-mode/cam/slicer.js +++ b/src/kiri-mode/cam/slicer.js @@ -135,9 +135,21 @@ class Slicer { return this; } - // slice through points at given Z and return polygons + /** + * Given an array of z-slice positions, generate a list of polygons + * representing the intersection of the mesh with the given z-slice + * positions. + * + * @param {number[]} zs - array of z-slice positions + * @param {Object} options - slicing parameters + * @param {number} [options.flatoff=0.01] - z offset when slicing on a flat surface + * @param {boolean} [options.progress=false] - callback with slice progress + * @param {function} [options.each] - callback with each slice result + * @returns {Promise} - list of slice objects with z and tops (polygons) + */ async slice(zs, options) { const opt = this.setOptions(options); + // slice through points at given Z and return polygons if (!Array.isArray(zs)) { throw "slice zs parameter must be ab array"; diff --git a/src/kiri/alerts.js b/src/kiri/alerts.js index de674953..8ed32689 100644 --- a/src/kiri/alerts.js +++ b/src/kiri/alerts.js @@ -19,6 +19,7 @@ function show(message, time) { api.event.emit('alert', rec); } else { alerts.push(rec); + setTimeout( update, time+0.02); update(); } return rec; diff --git a/src/kiri/conf.js b/src/kiri/conf.js index ea61c40a..1830281f 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -521,6 +521,7 @@ const conf = exports({ camDrillLift: 2, camDrillMark: false, camDrillThru: 5, + camDrillPrecision: 1, camDrillingOn: false, camRegisterSpeed: 1000, camRegisterThru: 5, diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 9061c291..72a84134 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -599,6 +599,8 @@ self.kiri.lang['en-us'] = { cd_dtru_l: ["additional drill depth below part bottom","overrides global z thru","0 to disable"], cd_mark_s: "marking", cd_mark_l: ["only mark holes, do not drill out","the step down setting is used","to determine how deep to mark"], + cd_prcn_s: "precision", + cd_prcn_l: ["distance between slices","to search for holes","in workspace units","lower is slower and","uses more memory","0 = only flats"], cd_seli_l: ["individually select holes","to drill"], cd_sela_s: "Select Matching Diameter",