diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 00000000..75132889 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,52 @@ +name: Test Electron App Production Build + +on: + # Runs on pushes targeting the default branch + push: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + +jobs: + build-ubuntu: + runs-on: "ubuntu-latest" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Build Electron app + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: npm run build + + build-windows: + runs-on: "windows-latest" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Build Electron app + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: npm run build + + + diff --git a/.github/workflows/prod-test.yml b/.github/workflows/prod-test.yml new file mode 100644 index 00000000..28093deb --- /dev/null +++ b/.github/workflows/prod-test.yml @@ -0,0 +1,27 @@ +name: Test NPM Production Build + +on: + # Runs on pushes targeting the default branch + push: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + +jobs: + build: + runs-on: "ubuntu-latest" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Setup And Install dependencies + run: npm run setup + + - name: dry run production build + run: npm run prod-dryrun \ No newline at end of file diff --git a/mods/bambu/bambu.js b/mods/bambu/bambu.js index 6ff97b21..5d182528 100644 --- a/mods/bambu/bambu.js +++ b/mods/bambu/bambu.js @@ -126,7 +126,12 @@ self.kiri.load(api => { }, drain() { while (socket.open && socket.q.length) { - socket.ws.send(JSON.stringify(socket.q.shift())); + let data = socket.q.shift(); + if (data instanceof ArrayBuffer) { + socket.ws.send(data); + } else { + socket.ws.send(JSON.stringify(data)); + } } }, send(msg) { @@ -614,6 +619,16 @@ self.kiri.load(api => { } } + function file_send(file, data) { + if (selected?.rec?.host) { + let { rec } = selected; + host = rec.host; + serial = rec.serial; + password = rec.code; + send(file.name, data, false); + } + } + function calcFG(bg) { let r = parseInt(bg.substring(0, 2), 16); let g = parseInt(bg.substring(2, 4), 16); @@ -1007,7 +1022,7 @@ self.kiri.load(api => { ]) ]), - h.div({ class: "t-body t-inset f-col gap3 pad4 grow" }, [ + h.div({ id: "bbl_drop", class: "t-body t-inset f-col gap3 pad4 grow" }, [ h.div({ class: "set-header", onclick() { file_list(); } }, h.a({ class: "flex f-row grow a-center" }, [ @@ -1050,7 +1065,10 @@ self.kiri.load(api => { file_print(selected.file.path); api.alerts.show(`printing: ${selected.file.path}`,2); }}), - ]) + ]), + h.div({ id: "bbl_drop_zone", class: "full hide allpe", style: "background-color: rgba(0,255,0,0.75); font-weight: bold" }, [ + h.label('drop to upload') + ]), ]), h.div({ class: "t-body t-inset f-col gap3 pad4" }, [ h.label({ class: "set-header dev-sel" }, [ @@ -1122,6 +1140,41 @@ self.kiri.load(api => { $('bbl_file_delete').disabled = $('bbl_file_print').disabled = false; }); + let drop = modal.bbl_drop; + let drop_zone = modal.bbl_drop_zone; + let drop_timer; + drop.ondragenter = (ev) => { + if (!selected?.rec?.serial) { + return; + } + drop.classList.add('nope'); + drop_zone.classList.remove('hide'); + clearTimeout(drop_timer); + }; + drop_zone.ondragleave = (ev) => { + clearTimeout(drop_timer); + drop_timer = setTimeout(() => { + drop.classList.remove('nope'); + drop_zone.classList.add('hide'); + }, 50); + }; + drop_zone.ondrop = (ev) => { + drop.classList.remove('nope'); + drop_zone.classList.add('hide'); + ev.preventDefault(); + ev.stopPropagation(); + let files = ev.dataTransfer.files; + console.log('filedrop', files); + for (let file of files) { + let reader = new FileReader(); + // reader.file = file; + reader.onloadend = (ev) => { + let data = ev.target.result; + file_send(file, data); + }; + reader.readAsArrayBuffer(file); + } + }; }); api.event.on("modal.show", which => { @@ -1251,6 +1304,7 @@ self.kiri.load(api => { console.log('Bambu Send', res); if (res.sent) { api.alerts.show('File Sent', 3); + file_list(); } else { api.alerts.show('File Send Error', 3); } diff --git a/mods/bambu/init.js b/mods/bambu/init.js index 25020120..370554c6 100644 --- a/mods/bambu/init.js +++ b/mods/bambu/init.js @@ -349,9 +349,13 @@ module.exports = async (server) => { wsopen.push(ws); debug && util.log('ws open', req.url, wsopen.length); wsend({ found }); - ws.on('message', msg => { - msg = JSON.parse(msg); - let { cmd, host, code, serial, path, amsmap, direct, frames } = msg; + let binaryData; + ws.on('message', (msg, isBinary) => { + if (isBinary) { + return binaryData = msg; + } + msg = isBinary ? msg : JSON.parse(msg.toString()); + let { cmd, host, code, serial, path, amsmap, direct, frames, file, data } = msg; switch (cmd) { case "monitor": get_mqtt(host, code, serial, message => { diff --git a/package.json b/package.json index 3dfb17e4..a09a9fab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.1.6", + "version": "4.1.7", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", @@ -36,7 +36,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", - "@gridspace/app-server": "^0.0.15", + "@gridspace/app-server": "^0.0.16", "@gridspace/net-level-client": "^0.2.3", "@tracespace/parser": "^5.0.0-next.0", "@tweenjs/tween.js": "^16.6.0", @@ -55,7 +55,7 @@ "three-mesh-bvh": "^0.7.6", "uglify-js": "3.14.5", "validator": ">=13.7.0", - "ws": "^7.5.5" + "ws": "^8.18.2" }, "devDependencies": { "@electron/notarize": "^3.0.0", @@ -72,6 +72,7 @@ "setup": "npm i && cd mods && npm i", "dev": "gs-app-server --debug", "prod": "gs-app-server", + "prod-dryrun": "gs-app-server --dryrun", "start": "npm run prebuild && electron .", "start-dev": "npm run prebuild && electron . --devel", "start-dbg": "npm run prebuild && electron . --debugg", diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index bf35233e..a73732e6 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1190,9 +1190,7 @@ CAM.init = function(kiri, api) { const {tool,mark} = poppedRec //TODO: display some visual difference if mark is selected let diam = new CAM.Tool(settings,tool).fluteDiameter() - api.hide.alert(alert); - alert = api.show.alert("[esc] cancels drill editing"); - kiri.api.widgets.opacity(0.8); + const widgets = kiri.api.widgets.all() /** * creates a mesh for a hole and adds it to a widget @@ -1242,10 +1240,16 @@ 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 ?? [] //flattened 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) @@ -1260,6 +1264,10 @@ CAM.init = function(kiri, api) { }) }); } + //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"); + kiri.api.widgets.opacity(0.8); } func.selectHolesHover = function(data) { diff --git a/src/kiri-mode/cam/driver.js b/src/kiri-mode/cam/driver.js index e5e415b0..f10b3783 100644 --- a/src/kiri-mode/cam/driver.js +++ b/src/kiri-mode/cam/driver.js @@ -168,6 +168,7 @@ kiri.load(api => { send.done(kiri.codec.encode(fresh.map(widget => { return { id: widget.id, holes: widget.holes, + shadowed:widget.shadowedDrills } } ))); } } diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index ec38dfcb..5624940f 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -460,14 +460,20 @@ CAM.holes = async function(settings, widget, diam) { // keeps the z bottom relative to the part when z align changes zBottom = isIndexed ? camZBottom : camZBottom - zbOff; - let slicer = new kiri.cam_slicer(widget); - let zees = Object.assign({}, slicer.zFlat); - let indices = [...new Set(Object.keys(zees) + let slicerOpts = {flatoff: 0.01} + let slicer = new kiri.cam_slicer(widget,slicerOpts); + let zFlats = Object.keys(slicer.zFlat).map(Number).map(z=>[z]).flat() + + let intervals = slicer.interval(1,{ + 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 centerDiff = diam * 0.1, area = (diam/2) * (diam/2) * Math.PI, areaDelta = area * 0.05, @@ -478,45 +484,52 @@ CAM.holes = async function(settings, widget, diam) { function onEach(slice) { slices.push(slice); } - - let opts = { each: onEach, over: false, flatoff: 0, edges: true, openok: false, lines: false }; + let opts = { each: onEach }; await slicer.slice(indices, opts); - const tslices = slices.map(s=>s.tops).flat() - // console.log("tslices",tslices) - for (let slice of tslices) { - slice.shadow = CAM.shadowAt(widget,slice.z, 0) - let inner = slice.inner; - if (!inner) { //no holes - continue; - } - for (let poly of inner) { - let center = poly.calcCircleCenter(); - center.selected = (!individual && poly.circularity() >= 0.985 && Math.abs(poly.area() - area) <= areaDelta ); - if (center.isInPolygon(slice.shadow)) { - //TODO: this does not work yet. needs to be implemented + let shadowedDrills = false; + // console.log("slices",slices) + for (let slice of slices) { + for(let top of slice.tops){ + // console.log("slicing",slice.z,top) + slice.shadow = CAM.shadowAt(widget,slice.z, 0) + let inner = top.inner; + if (!inner) { //no holes continue; } - let overlap = false; - for (let [i,drill] of drills.entries()) { - let dist = drill.distTo2D(center) - if (dist <= centerDiff) { // too close - // console.log("overlap",center,drill); - if(center.z > drill.z) { //if current is higher than old - drills[i] = center; //replace with top point - drills[i].depth = center.z-drill.z - }else if(center.z <= drill.z-drill.depth){// if current is lower or same as old - drills[i].depth = drill.z-center.z - } - // if overlapping, don't add and continue - overlap = true; + for (let poly of inner) { + let center = poly.calcCircleCenter(); + center.area = poly.area(); + // console.log("center",center) + center.selected = (!individual && poly.circularity() >= 0.985 && Math.abs(center.area - area) <= areaDelta ); + if (center.isInPolygon(slice.shadow)) { + // if shadowed, don't add, and inform client + shadowedDrills = true; continue; } + let overlap = false; + for (let [i,drill] of drills.entries()) { + let dist = drill.distTo2D(center) + //if on the same xy point, and both have the same selection value + if (dist <= centerDiff && drill.selected == center.selected) { + // console.log("overlap",center,drill); + if(center.z > drill.z) { //if current is higher than old + drills[i] = center; //replace with top point + drills[i].depth = center.z-drill.z + }else if(center.z <= drill.z-drill.depth){// if current is lower or same as old + drills[i].depth = drill.z-center.z + } + // if overlapping, don't add and continue + overlap = true; + continue; + } + } + center.depth = 0; + if(!overlap) drills.push(center); } - center.depth = 0; - if(!overlap) drills.push(center); } } drills = drills.filter(drill => drill.depth > 0) + widget.shadowedDrills = shadowedDrills widget.holes = drills; return drills; } diff --git a/src/moto/license.js b/src/moto/license.js index 9d24a228..05f7cb15 100644 --- a/src/moto/license.js +++ b/src/moto/license.js @@ -5,7 +5,7 @@ let terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: (is_self ? self : this).debug_version || "4.1.6" + VERSION: (is_self ? self : this).debug_version || "4.1.7" }; if (typeof(module) === 'object') { diff --git a/web/kiri/index.css b/web/kiri/index.css index 65c51cd2..a35f0b7f 100644 --- a/web/kiri/index.css +++ b/web/kiri/index.css @@ -646,6 +646,23 @@ details[open] summary::after { .contents { display: contents; } +.full { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.nope, .nope * { + pointer-events: none; +} +.allpe { + pointer-events: auto !important; +} .hide { display: none !important; } diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index 4c762d51..84bfb412 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.1.6", + "name": "Kiri:Moto 4.1.7", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/",