From 25b55383a3c329475e72a621f97191c6dbd801be Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 12 Oct 2025 19:31:14 -0400 Subject: [PATCH 01/24] add experimental boot --- web/boot/index.html | 56 ++++++++++++++ web/boot/service.js | 177 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 web/boot/index.html create mode 100644 web/boot/service.js diff --git a/web/boot/index.html b/web/boot/index.html new file mode 100644 index 00000000..9cc47226 --- /dev/null +++ b/web/boot/index.html @@ -0,0 +1,56 @@ + + + + + + Kiri:Moto (loading) + + + + + + Kiri:Moto is Loading + + + \ No newline at end of file diff --git a/web/boot/service.js b/web/boot/service.js new file mode 100644 index 00000000..be51a7ae --- /dev/null +++ b/web/boot/service.js @@ -0,0 +1,177 @@ +// --- configurable --- +const CACHE_VERSION = 'kiri-v001'; +const BUNDLE_URL = '/kiri/bundle.bin'; +const VERSION_KEY = '/__version__'; + +let mode = 'cached'; +let currentVersion = null; + +// --- logging --- +const log = (...a) => console.log('[SW]', ...a); +self.addEventListener('install', e => log('install')); +self.addEventListener('activate', e => log('activate')); +// self.addEventListener('fetch', e => log('fetch', e.request.url)); + +// --- lifecycle --- +self.skipWaiting(); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +// --- mode control --- +self.addEventListener('message', e => { + log({ message: e.data }); + const data = e.data || {}; + if (data.mode) mode = data.mode; + if (data.mode === 'cached' && data.version !== undefined) { + ensureVersion(data.version); + } +}); + +// --- version check and preload --- +async function ensureVersion(incomingVersion) { + const cache = await caches.open(CACHE_VERSION); + let needPreload = false; + + const existing = await cache.match(VERSION_KEY); + if (!existing) { + log('no version stored → preload'); + needPreload = true; + } else { + const text = await existing.text(); + const stored = parseInt(text, 10); + if (stored !== incomingVersion) { + log(`version mismatch ${stored} ≠ ${incomingVersion} → preload`); + needPreload = true; + } else { + log(`version OK (${stored})`); + } + } + + if (needPreload) { + await preloadBundle(); + await cache.put( + VERSION_KEY, + new Response(String(incomingVersion), { headers: { 'Content-Type': 'text/plain' } }) + ); + broadcast('preload-complete'); + } else { + broadcast('preload-cached'); + } +} + +// --- fetch with redirect handler --- +async function fetch_safe(req) { + const res = await fetch(req); + if (res.redirected) { + log({ was_redirected_cloning: res.url }); + // clone into a fresh non-redirect response + const clone = res.clone(); + return new Response(await clone.blob(), { + headers: clone.headers, + status: 200, + statusText: 'OK' + }); + } + return res; +} + +// --- fetch handler --- +self.addEventListener('fetch', e => { + const { request } = e; + const url = new URL(request.url); + + if (request.method !== 'GET') return; + + if (mode === 'transparent') { + e.respondWith(fetch_safe(e.request)); + return; + } + + e.respondWith(fromCacheOrNetwork(request)); +}); + +// --- helpers --- +async function fromCacheOrNetwork(req) { + const cache = await caches.open(CACHE_VERSION); + const hit = await cache.match(req); + if (hit) { + // log({ hit: hit.url || req.url }); + return hit; + } + const net = await fetch_safe(req); + cache.put(req, net.clone()); + log({ put: net.url }); + return net; +} + +async function serveIndex() { + const cache = await caches.open(CACHE_VERSION); + const hit = await cache.match('/kiri/index.html'); + log({ serve_index: hit, cache }); + if (hit) return hit; + return fetch_safe('/kiri/index.html'); +} + +async function broadcast(message) { + const clientsList = await clients.matchAll({ type: 'window' }); + for (const client of clientsList) { + client.postMessage(message); + } +} + +const sec_headers = { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp' +}; + +// --- preload & unpack bundle --- +async function preloadBundle() { + // return; + const cache = await caches.open(CACHE_VERSION); + const res = await fetch(BUNDLE_URL, { cache: 'no-store' }); + const buf = await res.arrayBuffer(); + const files = await unpackBundle(buf); + log({ files }); + await Promise.all( + Object.entries(files).map(([path, blob]) => { + const ext = path.split('.').pop(); + const type = + ext === 'html' ? 'text/html' : + ext === 'js' ? 'application/javascript' : + ext === 'css' ? 'text/css' : + ext === 'json' ? 'application/json' : + ext === 'wasm' ? 'application/wasm' : + ext === 'svg' ? 'image/svg+xml' : + 'application/octet-stream'; + const headers = { 'Content-Type': type, ...sec_headers }; + // Object.assign(headers, sec_headers); + const resp = new Response(blob, { headers }); + return cache.put('/' + path, resp); + }) + ); +} + +// --- simple bundle format --- +// [count:uint32][entries...][file data...] +// entry: [nlen:uint16][name][offset:uint32][length:uint32] +async function unpackBundle(buf) { + const view = new DataView(buf); + let pos = 0; + const count = view.getUint32(pos, true); pos += 4; + const decoder = new TextDecoder(); + const table = []; + + for (let i = 0; i < count; i++) { + const nlen = view.getUint16(pos, true); pos += 2; + const name = decoder.decode(new Uint8Array(buf, pos, nlen)); pos += nlen; + const offset = view.getUint32(pos, true); pos += 4; + const len = view.getUint32(pos, true); pos += 4; + table.push({ name, offset, len }); + } + log({ table }); + + const files = {}; + for (const { name, offset, len } of table) + files[name] = buf.slice(offset, offset + len); + + return files; +} From 38a6ef8f038143ecb750915cef623e35cf732fcf Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 12 Oct 2025 20:22:53 -0400 Subject: [PATCH 02/24] exclude bundle from itself. add ability to force SW cache update --- .gitignore | 3 ++- bin/bundle.config.json | 2 +- bin/bundler.mjs | 5 ++++- web/boot/index.html | 6 ++++-- web/boot/service.js | 7 +++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index c66e12fd..3132d9dc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,8 @@ jszip-esm.js logs mod node_modules +pack package-lock.json three.js tmp -pack +web/boot/bundle.bin diff --git a/bin/bundle.config.json b/bin/bundle.config.json index 655fa67e..f2e3a8e7 100644 --- a/bin/bundle.config.json +++ b/bin/bundle.config.json @@ -1,6 +1,6 @@ { "outputs": { - "bundle": "./web/kiri/bundle.bin" + "bundle": "./web/boot/bundle.bin" }, "inputs": [ { "root": "./web", "prefix": "" }, diff --git a/bin/bundler.mjs b/bin/bundler.mjs index 21cec2bd..e55e9821 100644 --- a/bin/bundler.mjs +++ b/bin/bundler.mjs @@ -3,7 +3,7 @@ import path from 'path'; import uglify from 'uglify-js'; import { Buffer } from 'buffer'; -const cfg = JSON.parse(fs.readFileSync(process.argv[2] || './bundle.config.json', 'utf8')); +const cfg = JSON.parse(fs.readFileSync(process.argv[2] || './bin/bundle.config.json', 'utf8')); const compress = process.env.UGLIFY ? true : false // --- recursive directory walker (follows symlinks safely) --- @@ -42,6 +42,9 @@ let offset = 0; let cache = new Map(); for (const { file, virt } of entries) { + if (file.endsWith('/bundle.bin')) { + continue; + } let record = cache.get(file); if (record) { // console.log({ dup: file }); diff --git a/web/boot/index.html b/web/boot/index.html index 9cc47226..d34a84e2 100644 --- a/web/boot/index.html +++ b/web/boot/index.html @@ -19,8 +19,10 @@ font-weight: bold; } - diff --git a/web/boot/service.js b/web/boot/service.js index b1491641..b28524c8 100644 --- a/web/boot/service.js +++ b/web/boot/service.js @@ -1,8 +1,9 @@ // --- configurable --- -const CACHE_VERSION = 'boot-v001'; +const CACHE_VERSION = 'boot-001'; const BUNDLE_URL = '/boot/bundle.bin'; const VERSION_KEY = '/__version__'; +let loaded = 0; let mode = 'cached'; let currentVersion = null; @@ -18,14 +19,33 @@ self.addEventListener('activate', e => e.waitUntil(clients.claim())); // --- mode control --- self.addEventListener('message', e => { - log({ message: e.data }); const data = e.data || {}; - if (data.mode) mode = data.mode; - if (data.mode === 'cached' && data.version !== undefined) { - ensureVersion(data.version); + const { clear, disable, version } = data; + if (clear) { + clearCache(); + } + if (disable) { + mode = 'transparent'; + broadcast('transparent mode'); + unregister(); + } else if (version !== undefined) { + mode = 'cached'; + log('version', version); + ensureVersion(version); } }); +// --- unregister current service worker --- +async function unregister() { + await self.registration.unregister(); +} + +// --- erase existing cache --- +async function clearCache() { + log('clearing cache'); + await caches.delete(CACHE_VERSION); +} + // --- version check and preload --- async function ensureVersion(incomingVersion) { const cache = await caches.open(CACHE_VERSION); @@ -38,10 +58,8 @@ async function ensureVersion(incomingVersion) { } else { const stored = await existing.text(); if (stored !== incomingVersion) { - log(`version mismatch ${stored} ≠ ${incomingVersion} → preload`); + log(`mismatch ${stored} ≠ ${incomingVersion} → preload`); needPreload = true; - } else { - log(`version OK (${stored})`); } } @@ -51,9 +69,9 @@ async function ensureVersion(incomingVersion) { VERSION_KEY, new Response(String(incomingVersion), { headers: { 'Content-Type': 'text/plain' } }) ); - broadcast('preload-complete'); + broadcast(`preload added ${loaded} files`); } else { - broadcast('preload-cached'); + broadcast('preload cached'); } } @@ -80,7 +98,7 @@ self.addEventListener('fetch', e => { if (request.method !== 'GET') return; - if (mode === 'transparent') { + if (mode == 'transparent') { e.respondWith(fetch_safe(e.request)); return; } @@ -93,23 +111,15 @@ async function fromCacheOrNetwork(req) { const cache = await caches.open(CACHE_VERSION); const hit = await cache.match(req); if (hit) { - // log({ hit: hit.url || req.url }); + // log(`hit: ${req.url}`); return hit; } const net = await fetch_safe(req); cache.put(req, net.clone()); - log({ put: net.url }); + log(`put: ${net.url}`); return net; } -async function serveIndex() { - const cache = await caches.open(CACHE_VERSION); - const hit = await cache.match('/kiri/index.html'); - log({ serve_index: hit, cache }); - if (hit) return hit; - return fetch_safe('/kiri/index.html'); -} - async function broadcast(message) { const clientsList = await clients.matchAll({ type: 'window' }); for (const client of clientsList) { @@ -124,12 +134,11 @@ const sec_headers = { // --- preload & unpack bundle --- async function preloadBundle() { - // return; + loaded = 0; const cache = await caches.open(CACHE_VERSION); const res = await fetch(BUNDLE_URL, { cache: 'no-store' }); const buf = await res.arrayBuffer(); const files = await unpackBundle(buf); - log({ files }); await Promise.all( Object.entries(files).map(([path, blob]) => { const ext = path.split('.').pop(); @@ -142,8 +151,8 @@ async function preloadBundle() { ext === 'svg' ? 'image/svg+xml' : 'application/octet-stream'; const headers = { 'Content-Type': type, ...sec_headers }; - // Object.assign(headers, sec_headers); const resp = new Response(blob, { headers }); + loaded++; return cache.put('/' + path, resp); }) ); @@ -166,7 +175,6 @@ async function unpackBundle(buf) { const len = view.getUint32(pos, true); pos += 4; table.push({ name, offset, len }); } - log({ table }); const files = {}; for (const { name, offset, len } of table) From 5894dcc61c0b28bc14a6f41e2500752df995f685 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 13 Oct 2025 12:24:23 -0400 Subject: [PATCH 06/24] add bundler load order priority, module bundling with --dryrun --- app.js | 18 ++++++++++++++++-- bin/bundle.config.json | 3 ++- bin/bundler.mjs | 8 +++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 54b8d3f4..0bcf5072 100644 --- a/app.js +++ b/app.js @@ -37,6 +37,7 @@ let util; let dir; let log; let pre; +let alt; const EventEmitter = require('events'); class AppEmitter extends EventEmitter {} @@ -64,6 +65,7 @@ function init(mod) { startTime = time(); lastmod = mod.util.lastmod; logger = mod.log; + alt = ENV.alt; pre = ENV.pre || mod.meta.pre; debug = ENV.debug || mod.meta.debug; oversion = ENV.over || mod.meta.over; @@ -191,6 +193,14 @@ function init(mod) { append[key] = minify(append[key]); } } + + if (alt) { + for (let [ key, val ] of Object.entries(append)) { + fs.mkdirSync(`alt/main`, { recursive: true }); + let body = fs.readFileSync(`src/main/${key}.js`); + fs.writeFileSync(`alt/main/${key}.js`, body + val); + } + } }; // either add module assets to path or require(init.js) @@ -288,14 +298,14 @@ function initModule(mod, file, dir) { const path = mod.dir + '/' + dir + '/' + file; try { const body = fs.readFileSync(path); - // console.log({ inject: code, file, opt }); + logger.log({ inject: code, file, opt }); if (opt.first) { append[code] = body.toString() + '\n' + append[code]; } else { append[code] += body.toString() + '\n'; } } catch (e) { - console.log({ missing_file: path, dir, mod }); + logger.log({ missing_file: path, dir, mod }); } }, onload(fn) { @@ -430,6 +440,10 @@ function generateDevices() { let dstr = JSON.stringify(devs); util.mkdir(PATH.join(dir,"src","pack")); fs.writeFileSync(PATH.join(dir,"src","pack","kiri-devs.js"), `export const devices = ${dstr};`); + if (alt) { + fs.mkdirSync('alt/pack', { recursive: true }); + fs.writeFileSync(PATH.join(dir,"alt","pack","kiri-devs.js"), `export const devices = ${dstr};`); + } } function minify(code) { diff --git a/bin/bundle.config.json b/bin/bundle.config.json index 93994e27..e7bd3566 100644 --- a/bin/bundle.config.json +++ b/bin/bundle.config.json @@ -5,7 +5,8 @@ }, "inputs": [ { "root": "web", "prefix": "" }, - { "root": "src", "prefix": "lib" }, + { "root": "alt", "prefix": "lib"}, + { "root": "src", "prefix": "lib"}, { "root": "src/wasm", "prefix": "wasm" }, { "root": "src/wasm", "prefix": "lib/kiri/wasm" } ], diff --git a/bin/bundler.mjs b/bin/bundler.mjs index 3a96efb4..36d66e9e 100644 --- a/bin/bundler.mjs +++ b/bin/bundler.mjs @@ -32,13 +32,19 @@ function* walk(dir, seen = new Set()) { // --- collect all files according to config --- let entries = []; +let virtMap = new Set(); for (const input of inputs) { const root = input.root; const prefix = input.prefix || ''; for (const file of walk(root)) { const rel = path.relative(root, file).replace(/\\/g, '/'); const virt = prefix ? `${prefix}/${rel}` : rel; - entries.push({ file, virt }); + if (virtMap.has(virt)) { + console.log('pre-existing', virt, rel, root); + } else { + entries.push({ file, virt }); + virtMap.add(virt); + } } } From a205e9710dd2102a09d42ac85ae0e0fc5c3bf634 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 13 Oct 2025 16:42:32 -0400 Subject: [PATCH 07/24] fix custom op naming --- src/kiri/mode/cam/cl-ops.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kiri/mode/cam/cl-ops.js b/src/kiri/mode/cam/cl-ops.js index 944f8d2e..eb9232dd 100644 --- a/src/kiri/mode/cam/cl-ops.js +++ b/src/kiri/mode/cam/cl-ops.js @@ -9,6 +9,7 @@ import { selectHoles } from './cl-hole.js'; import { selectHelical } from './cl-helical.js'; import { surfaceAdd } from './cl-surface.js'; import { traceAdd } from './cl-trace.js'; +import { opRender } from './client.js'; const { MODES } = api.const; const { uc: UC } = api; From 998603efe215e9184427c1a7e8ae303bdef36f79 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 13 Oct 2025 16:43:03 -0400 Subject: [PATCH 08/24] fix rapid plunge in Z --- src/kiri/mode/cam/export.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/kiri/mode/cam/export.js b/src/kiri/mode/cam/export.js index 84afee08..09571532 100644 --- a/src/kiri/mode/cam/export.js +++ b/src/kiri/mode/cam/export.js @@ -257,17 +257,18 @@ export function cam_export(print, online) { points--; return; } - let gn; - if (out.emit >= 0 && out.emit <= 3) gn = `G${out.emit}`; - let speed = out.speed, - arc = out.emit == 2 || out.emit == 3, - center = out.center, - nl = (compact_output && lastGn === gn) ? [] : [gn], - dx = opt.dx || newpos.x - pos.x, + + let dx = opt.dx || newpos.x - pos.x, dy = opt.dy || newpos.y - pos.y, dz = opt.dz || newpos.z - pos.z, da = newpos.a != pos.a, - maxf = dz ? maxZd : maxXYd, + gv = (dz >= 0 && out.emit >= 0 && out.emit <= 3) ? out.emit : 1, + gn = `G${gv}`, + speed = out.speed, + arc = out.emit == 2 || out.emit == 3, + center = out.center, + nl = (compact_output && lastGn === gn) ? [] : [gn], + maxf = dz < 0 ? maxZd : maxXYd, feed = Math.min(speed || maxf, maxf), dist = Math.sqrt(dx * dx + dy * dy + dz * dz), newFeed = feed && feed !== pos.f; @@ -357,6 +358,7 @@ export function cam_export(print, online) { // } append(nl.join('')); points++; + if (dz < 0) console.log(nl); } // look for SCALE header directive From fb3420566d08bfa5044d741c984b338b7d65b4a0 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 13 Oct 2025 16:43:34 -0400 Subject: [PATCH 09/24] fix lathe thru stock depth --- src/kiri/mode/cam/topo4.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/cam/topo4.js b/src/kiri/mode/cam/topo4.js index 5bd4f528..95053024 100644 --- a/src/kiri/mode/cam/topo4.js +++ b/src/kiri/mode/cam/topo4.js @@ -52,7 +52,7 @@ export class Topo { console.log(widget.id, 'topo4 auto tolerance', resolution.round(4)); } - this.zBottom = state.zBottom; + this.zBottom = state.zBottom ?? 0; this.resolution = resolution; this.vertices = widget.getGeoVertices({ unroll: true, translate: true }); this.tabverts = widget.getTabVertices(); @@ -285,8 +285,8 @@ export class Topo { } } if (mz === -Infinity) { - mz = zBottom; // Math.min(0, lz) - } else if (zBottom && mz < zBottom) { + mz = zBottom; + } else if (mz < zBottom) { mz = zBottom; } heights.push(rx, 0, lz = mz); From 4d9250e6620608e198e7aa920be838d36443694b Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 13 Oct 2025 20:45:23 -0400 Subject: [PATCH 10/24] refactor cam indexed animations to show cut path only --- src/kiri/mode/cam/anim-3d-be.js | 116 +++++++++++++++++--------------- src/kiri/mode/cam/anim-3d-fe.js | 84 ++++++++++++++++++++--- src/kiri/mode/cam/client.js | 4 +- src/kiri/mode/cam/export.js | 1 - web/kiri/index.css | 2 +- 5 files changed, 139 insertions(+), 68 deletions(-) diff --git a/src/kiri/mode/cam/anim-3d-be.js b/src/kiri/mode/cam/anim-3d-be.js index 204e5b2a..13df6084 100644 --- a/src/kiri/mode/cam/anim-3d-be.js +++ b/src/kiri/mode/cam/anim-3d-be.js @@ -5,31 +5,31 @@ import { CSG } from '../../../geo/csg.js'; import { Tool } from './tool.js'; -let nextMeshID = 1; -let stock, center, rez; -let path, pathIndex, tool, tools, last; - -let stockZ; -let stockIndexMsg = false; -let stockSlices; -let stockIndex; -let startTime; - -let toolID = -1; -let toolMesh; -let toolRadius; -let toolUpdateMsg; - -let animateClear = false; -let animating = false; -let moveDist = 0; -let renderDist = 0; -let renderDone = false; -let renderPause = 10; -let renderSteps = 0; -let renderSpeed = 0; -let indexCount = 0; -let updates = 0; +let nextMeshID = 1, + stock, + path, + pathIndex, + tool, + tools, + last, + stockZ, + stockIndexMsg = false, + stockSlices, + stockIndex, + startTime, + toolID = -1, + toolMesh, + toolRadius, + toolUpdateMsg, + showStock = false, + animateClear = false, + animating = false, + renderDone = false, + renderSteps = 0, + renderSpeed = 0, + indexCount = 0, + updates = 0, + maxR; export function init(worker) { const { dispatch } = worker; @@ -45,8 +45,6 @@ export function init(worker) { path = print.output.flat(); tools = settings.tools; stock = settings.stock; - rez = 1 / Math.sqrt(density / (stock.x * stock.y)); - tool = null; last = null; animating = false; @@ -56,11 +54,14 @@ export function init(worker) { startTime = 0; updates = 0; stockZ = isIndexed ? 0 : stock.z; + maxR = Math.hypot(stock.y, stock.z); stockSlices = []; const { x, y, z } = stock; const sliceCount = parseInt(settings.controller.animesh || 2000) / 100; const sliceWidth = stock.x / sliceCount; + + if (showStock) for (let i = 0; i < sliceCount; i++) { let xmin = -(x / 2) + (i * sliceWidth) + sliceWidth / 2; let slice = new Stock(sliceWidth, y, z).translate(xmin, 0, 0); @@ -74,7 +75,6 @@ export function init(worker) { }; dispatch.animate2 = function (data, send) { - renderPause = data.pause || renderPause; renderSpeed = data.speed || 0; if (animating) { return send.done(); @@ -126,7 +126,6 @@ function renderPath(send) { return; } pathIndex++; - // console.log(next.point.z); if (next.tool && (!tool || tool.getID() !== next.tool.getID())) { // on real tool change, go to safe Z first @@ -143,44 +142,50 @@ function renderPath(send) { } const id = toolID; - const rezstep = rez * 2; + const rezstep = Math.min(tool.maxDiameter(), 1) / 4; + // console.log({ rezstep }); + if (last) { + // console.log(last.point, next.point); const lp = last.point, np = next.point; last = next; + + // render move line on client + send.data({ line: [ lp, np ]}); + // dwell ops have no point if (!np || !lp) { return renderPath(send); } + let dx = np.x - lp.x, dy = np.y - lp.y, dz = np.z - lp.z, - da = Math.abs((np.a || 0) - (lp.a || 0)), - dr = (da / 360) * (2 * Math.PI * Math.max(np.z, lp.z)), - dist = Math.sqrt(dx * dx + dy * dy + dz * dz + dr * dr); - - moveDist += dist; - - // skip moves that are less than grid resolution - if (moveDist < rezstep) { - // console.log('skip', moveDist, rezstep, next); - renderPath(send); - return; - } + da = (np.a ?? 0) - (lp.a ?? 0), // delta rotation degrees + AD = maxR * 2, // arc diameter @ max z + dr = (Math.abs(da) / 360) * AD, // delta rotation arc + dist = Math.max(Math.hypot(dx, dy, dz), dr); // motion distance + // console.log({ dz, da, AD, dr, dist }); + // sub-divide long moves for animation and better boolean ops const md = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz), dr); - const st = Math.ceil(md / rezstep); - const mx = dx / st, my = dy / st, mz = dz / st; - // const sd = Math.sqrt(mx*mx + my*my + mz*mz + dr*dr); - const sd = Math.sqrt(mx * mx + my * my + Math.min(1, mz * mz) + dr * dr); + const st = Math.ceil(md / rezstep); // steps + const mx = dx / st, my = dy / st, mz = dz / st, ma = da / st; + const sd = Math.sqrt(mx * mx + my * my + mz * mz + ma * ma); + const sp = next.speed; const moves = []; - for (let i = 0, x = lp.x, y = lp.y, z = lp.z; i < st; i++) { - moves.push({ x, y, z, a: lp.a, md: sd, dx, dy, dz }); + for (let i = 0, x = lp.x, y = lp.y, z = lp.z, a = lp.a; i < st; i++) { + moves.push({ x, y, z, a, md: sd, dx, dy, dz, sp }); x += mx; y += my; z += mz; + a += ma; } - moveDist = 0; - moves.push({ ...next.point, md: sd }); + // console.log({ dist: md, steps: st, speed: next.speed, moves }); + + // push last point + moves.push({ ...next.point, md: sd, sp }); + // console.log(...moves); renderMoves(id, moves, send); } else { last = next; @@ -197,7 +202,7 @@ function renderMoves(id, moves, send, seed = 0) { if (!pos) { throw `no pos @ ${index} of ${moves.length}`; } - const { dx, dy, dz } = pos; + const { dx, dy, dz, sp, md } = pos; toolMove(pos); // console.log('renderMoves', {id, moves, seed}); let subs = 0; @@ -208,15 +213,14 @@ function renderMoves(id, moves, send, seed = 0) { subs++; } } - // console.log({ index, subs }); - renderDist += pos.md; // pause renderer at specified offsets - if (renderSpeed && renderDist >= renderSpeed) { - renderDist = 0; + if (renderSpeed) { + let time = sp ? (md / sp) * 60000 : 10; + // console.log({ time, ...pos }); renderUpdate(send); setTimeout(() => { renderMoves(id, moves, send, index + 1); - }, renderPause); + }, time / renderSpeed); return; } } diff --git a/src/kiri/mode/cam/anim-3d-fe.js b/src/kiri/mode/cam/anim-3d-fe.js index 3f31d97f..2300fdab 100644 --- a/src/kiri/mode/cam/anim-3d-fe.js +++ b/src/kiri/mode/cam/anim-3d-fe.js @@ -10,24 +10,26 @@ const { client } = api; let meshes = {}, button = {}, label = {}, + lineTracker, material, - speedPauses = [0, 0, 0, 0, 0], speedValues = [1, 2, 4, 8, 32], speedNames = ["1x", "2x", "4x", "8x", "!!"], speedMax = speedValues.length - 1, speedIndex = 0, speed, origin, - color = 0; + color = 0, + A2R = Math.PI / 180; export function animate_clear2(api) { let { anim } = api.ui; - client.animate_cleanup2(); + lineTracker?.clear(); Object.keys(meshes).forEach(id => deleteMesh(id)); api.widgets.setAxisIndex(0); api.uc.setVisible(anim.laba, true); api.uc.setVisible(anim.vala, true); anim.vala.value = "0.0"; + client.animate_cleanup2(); } export function animate2(api, delay) { @@ -86,8 +88,9 @@ Object.assign(client, { }, animate_setup2(settings, ondone) { + initPathMesh(); color = settings.controller.dark ? 0x888888 : 0; - material = new THREE.MeshPhongMaterial({ + material = material ?? new THREE.MeshPhongMaterial({ flatShading: true, transparent: false, opacity: 0.5, @@ -102,6 +105,66 @@ Object.assign(client, { } }); +function initPathMesh() { + if (lineTracker) { + return lineTracker.clear(); + } + let count = 0, + max = 10000, + rot = new THREE.Euler(0,0,0), + geo = new THREE.BufferGeometry(), + mat = new THREE.LineBasicMaterial({ color: 0xffff00 }), + pos = new Float32Array(max * 6), + lines = new THREE.LineSegments(geo, mat), + vec = new THREE.Vector3(0,0,0), + ang = new THREE.Vector3(1,0,0); + geo.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + let track = lineTracker = { + xro(p) { + vec.set(p.x, p.y, p.z); + vec.applyAxisAngle(ang, p.a * A2R); + }, + add(p1, p2) { + let ind = count * 6; + track.xro(p1); + pos[ind+0] = vec.x; + pos[ind+1] = vec.y; + pos[ind+2] = vec.z; + track.xro(p2); + pos[ind+3] = vec.x; + pos[ind+4] = vec.y; + pos[ind+5] = vec.z; + count++; + if (count >= max) { + let opos = pos; + max += 10000; + pos = new Float32Array(max * 6), + pos.set(opos); + geo.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + } + geo.setDrawRange(0, count * 2); + geo.attributes.position.needsUpdate = true; + track.visible(true); + }, + clear() { + count = 0; + geo.setDrawRange(0,0); + track.rotate(0); + track.visible(false); + }, + rotate(angle) { + rot.x = angle * A2R; + lines.setRotationFromEuler(rot); + }, + visible(bool) { + if (bool !== lines.visible) { + lines.visible = bool; + } + } + } + space.world.add(lines); +} + function meshAdd(id, ind, pos, ilen, plen) { const geo = new THREE.BufferGeometry(); const pa = plen ? pos.subarray(0, plen * 3) : pos; @@ -132,6 +195,7 @@ function meshUpdate(id, ind, pos, ilen, plen) { function deleteMesh(id) { space.world.remove(meshes[id]); + meshes[id].geometry.dispose(); delete meshes[id]; } @@ -165,8 +229,7 @@ function play(opts) { } client.animate2({ speed, - steps: steps || Infinity, - pause: speedPauses[speedIndex] + steps: steps || Infinity }, handleUpdate); } @@ -177,8 +240,7 @@ function fast(opts) { button.pause.style.display = ''; client.animate2({ speed, - steps: steps || Infinity, - pause: speedPauses[speedIndex] + steps: steps || Infinity }, handleUpdate); } @@ -216,6 +278,7 @@ function handleUpdate(data) { if (data.stock_index !== undefined) { api.widgets.setAxisIndex(data.stock_index); label.a.value = -data.stock_index.toFixed(1); + lineTracker.rotate(-data.stock_index); } if (data.mesh_index) { const { id, index } = data.mesh_index; @@ -229,9 +292,12 @@ function handleUpdate(data) { const { id, ind, pos, ilen, plen } = data.mesh_update; meshUpdate(id, ind, pos, ilen, plen); } - if (data && data.progress) { + if (data.progress) { label.progress.value = (data.progress * 100).toFixed(1); } + if (data.line) { + lineTracker.add(...data.line); + } } function updateSpeed(inc = 0) { diff --git a/src/kiri/mode/cam/client.js b/src/kiri/mode/cam/client.js index 8f6af172..65eba6db 100644 --- a/src/kiri/mode/cam/client.js +++ b/src/kiri/mode/cam/client.js @@ -29,6 +29,7 @@ const { widgets: WIDGETS } = api; class Client { animVer = 0; camStock; + showStock = true; current; currentIndex; flipping; @@ -640,7 +641,8 @@ export function init() { }); api.event.on("cam.stock.toggle", (bool) => { - env.camStock && (env.camStock.visible = bool ?? !env.camStock.visible); + env.showStock = bool ?? !env.showStock; + updateStock(); }); api.event.on("boolean.click", api.platform.update_bounds); diff --git a/src/kiri/mode/cam/export.js b/src/kiri/mode/cam/export.js index 09571532..a162786c 100644 --- a/src/kiri/mode/cam/export.js +++ b/src/kiri/mode/cam/export.js @@ -358,7 +358,6 @@ export function cam_export(print, online) { // } append(nl.join('')); points++; - if (dz < 0) console.log(nl); } // look for SCALE header directive diff --git a/web/kiri/index.css b/web/kiri/index.css index ad3b7705..efc8c86d 100644 --- a/web/kiri/index.css +++ b/web/kiri/index.css @@ -1378,7 +1378,7 @@ details[open] summary::after { padding: 0 0 0 0; } #oplist { - max-height: 70vh; + max-height: calc(70vh - 200px); min-height: 0; overflow-y: auto; padding-top: 1px; From 1c6a988d272b29f74ca930600361bc4c5e30ca50 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 14 Oct 2025 00:33:36 -0400 Subject: [PATCH 11/24] make bundler more resilient to missing sources --- app.js | 7 ++++++- bin/bundler.mjs | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 0bcf5072..57b03227 100644 --- a/app.js +++ b/app.js @@ -196,8 +196,13 @@ function init(mod) { if (alt) { for (let [ key, val ] of Object.entries(append)) { + let src = `src/main/${key}.js`; + if (!fs.existsSync(src)) { + logger.log('missing:', src); + continue; + } fs.mkdirSync(`alt/main`, { recursive: true }); - let body = fs.readFileSync(`src/main/${key}.js`); + let body = fs.readFileSync(src); fs.writeFileSync(`alt/main/${key}.js`, body + val); } } diff --git a/bin/bundler.mjs b/bin/bundler.mjs index 36d66e9e..e0628abf 100644 --- a/bin/bundler.mjs +++ b/bin/bundler.mjs @@ -14,6 +14,9 @@ function* walk(dir, seen = new Set()) { if (excludes.indexOf(dir) >= 0) { return; } + if (!fs.existsSync(dir)) { + return; + } const real = fs.realpathSync(dir); if (seen.has(real)) { From e183be3bd77a6e0fe82a479d39fa1dc72dac113a Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 14 Oct 2025 02:07:49 -0400 Subject: [PATCH 12/24] fully host /kiri from /boot --- web/boot/service.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/boot/service.js b/web/boot/service.js index b28524c8..300e8693 100644 --- a/web/boot/service.js +++ b/web/boot/service.js @@ -94,6 +94,14 @@ async function fetch_safe(req) { // --- fetch handler --- self.addEventListener('fetch', e => { const { request } = e; + + if (request.url.endsWith("/kiri") || request.url.endsWith("/kiri/")) { + e.respondWith((async () => { + return Response.redirect("/kiri/index.html", 302); + })()); + return; + } + const url = new URL(request.url); if (request.method !== 'GET') return; From acbd84402d2d21a25ed0928f6e8b1c6107708e6b Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 14 Oct 2025 15:58:09 -0400 Subject: [PATCH 13/24] handle base redirects when it's a cache hit --- web/boot/index.html | 4 +--- web/boot/service.js | 35 +++++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/web/boot/index.html b/web/boot/index.html index 8616a0ee..8f7c9381 100644 --- a/web/boot/index.html +++ b/web/boot/index.html @@ -62,8 +62,6 @@ - - Kiri:Moto is Loading - +Booting Grid.Space \ No newline at end of file diff --git a/web/boot/service.js b/web/boot/service.js index 300e8693..ea27470e 100644 --- a/web/boot/service.js +++ b/web/boot/service.js @@ -94,16 +94,14 @@ async function fetch_safe(req) { // --- fetch handler --- self.addEventListener('fetch', e => { const { request } = e; - - if (request.url.endsWith("/kiri") || request.url.endsWith("/kiri/")) { - e.respondWith((async () => { - return Response.redirect("/kiri/index.html", 302); - })()); - return; - } - const url = new URL(request.url); + if (url.pathname.endsWith("/")) { + return e.respondWith(redirectOr404(appendURL(url, 'index.html').pathname)); + } else if (url.pathname.indexOf(".") < 0) { + return e.respondWith(redirectOr404(appendURL(url, '/index.html').pathname)); + } + if (request.method !== 'GET') return; if (mode == 'transparent') { @@ -115,13 +113,30 @@ self.addEventListener('fetch', e => { }); // --- helpers --- -async function fromCacheOrNetwork(req) { +function appendURL(url, append) { + return new URL(url.origin + url.pathname + append + url.search); +} + +async function redirectOr404(path) { const cache = await caches.open(CACHE_VERSION); - const hit = await cache.match(req); + const hit = await cache.match(path, { ignoreSearch: true }); + if (hit) { + return Response.redirect(path, 302); + } else { + return new Response('Not Found', { status: 404, statusText: 'Not Found' }); + } +} + +async function fromCacheOrNetwork(req, test) { + const cache = await caches.open(CACHE_VERSION); + const hit = await cache.match(req, { ignoreSearch: true }); if (hit) { // log(`hit: ${req.url}`); return hit; } + if (test) { + return test; + } const net = await fetch_safe(req); cache.put(req, net.clone()); log(`put: ${net.url}`); From 19cc99fddff69203c23ce7e870c637d3c4c42ee2 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 14 Oct 2025 16:00:16 -0400 Subject: [PATCH 14/24] fix cache vs transparent --- web/boot/service.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/boot/service.js b/web/boot/service.js index ea27470e..c32533d2 100644 --- a/web/boot/service.js +++ b/web/boot/service.js @@ -96,12 +96,6 @@ self.addEventListener('fetch', e => { const { request } = e; const url = new URL(request.url); - if (url.pathname.endsWith("/")) { - return e.respondWith(redirectOr404(appendURL(url, 'index.html').pathname)); - } else if (url.pathname.indexOf(".") < 0) { - return e.respondWith(redirectOr404(appendURL(url, '/index.html').pathname)); - } - if (request.method !== 'GET') return; if (mode == 'transparent') { @@ -109,7 +103,13 @@ self.addEventListener('fetch', e => { return; } - e.respondWith(fromCacheOrNetwork(request)); + if (url.pathname.endsWith("/")) { + return e.respondWith(redirectOr404(appendURL(url, 'index.html').pathname)); + } else if (url.pathname.indexOf(".") < 0) { + return e.respondWith(redirectOr404(appendURL(url, '/index.html').pathname)); + } else { + e.respondWith(fromCacheOrNetwork(request)); + } }); // --- helpers --- From 183c110ed93c0c47f2b97f2cbe588dea6281b978 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 14 Oct 2025 21:00:00 -0400 Subject: [PATCH 15/24] fix fan setting for empty bridge layers --- src/kiri/mode/fdm/prepare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 301d47fb..7152f12a 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1617,7 +1617,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { print.addPrintPoints(preout, output); // bridge layer fan change - if (slice.isBridgeLayer) { + if (preout.length && slice.isBridgeLayer) { preout[0].fan = Math.round(fanSpeed / 3); preout.peek().fan = fanSpeed; } From a08fedf28cc2a3e6755eeae6030ce140db0a038e Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 14 Oct 2025 21:06:18 -0400 Subject: [PATCH 16/24] fix case where max feedrates are all 0 --- src/kiri/mode/cam/export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/mode/cam/export.js b/src/kiri/mode/cam/export.js index a162786c..41280974 100644 --- a/src/kiri/mode/cam/export.js +++ b/src/kiri/mode/cam/export.js @@ -268,7 +268,7 @@ export function cam_export(print, online) { arc = out.emit == 2 || out.emit == 3, center = out.center, nl = (compact_output && lastGn === gn) ? [] : [gn], - maxf = dz < 0 ? maxZd : maxXYd, + maxf = (dz < 0 ? maxZd : maxXYd) || speed, feed = Math.min(speed || maxf, maxf), dist = Math.sqrt(dx * dx + dy * dy + dz * dz), newFeed = feed && feed !== pos.f; From 8a908cb373635b9261ed5271b4a82914eddbc065 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 15 Oct 2025 13:26:11 -0400 Subject: [PATCH 17/24] allow short move to cut in roughing --- src/kiri/mode/cam/prepare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/mode/cam/prepare.js b/src/kiri/mode/cam/prepare.js index cbeefb87..9f3b19b4 100644 --- a/src/kiri/mode/cam/prepare.js +++ b/src/kiri/mode/cam/prepare.js @@ -412,7 +412,7 @@ export function prepEach(widget, settings, print, firstPoint, update) { } // convert short planar moves to cuts in some cases - if (hasDelta && !isRough && !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) { + if (hasDelta && !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) { let iscontour = tolerance > 0; let isflat = absDeltaZ < 0.001; // restrict this to contouring From b9f9678529acc19ebe43ebc4e91f0a01aa67ceae Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 16 Oct 2025 16:54:59 -0600 Subject: [PATCH 18/24] add file meta to widget codec --- src/kiri/core/codec.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/kiri/core/codec.js b/src/kiri/core/codec.js index 3547fb6f..1ad844ac 100644 --- a/src/kiri/core/codec.js +++ b/src/kiri/core/codec.js @@ -225,7 +225,9 @@ Widget.prototype.encode = function(state) { json: json, // safe for JSON (float32array mess) group: this.group.id, track: this.track, - geo: json ? Array.from(geo) : geo + geo: json ? Array.from(geo) : geo, + meta: this.meta, + anno: this.anno, }; return coded; }; @@ -234,8 +236,12 @@ registerDecoder(TYPE.WIDGET, function(v, state) { const id = v.id, group = v.group || id, track = v.track || undefined, - widget = newWidget(id, Widget.Groups.forid(group)); + widget = newWidget(id, Widget.Groups.forid(group)), + meta= v.meta || widget.meta, + anno = v.anno || widget.anno; + widget.meta = meta; + widget.anno = anno; widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo); widget.saved = Date.now(); if (track && track.pos) { From a26b1d6bef0a741ce667a7a1792f30afa033ee9c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 16 Oct 2025 21:39:24 -0400 Subject: [PATCH 19/24] add fdm volumetric flow output caps --- package.json | 2 +- src/kiri/core/conf.js | 1 + src/kiri/mode/cam/topo4.js | 7 ++++--- src/kiri/mode/fdm/driver-be.js | 1 + src/kiri/mode/fdm/export.js | 11 ++-------- src/kiri/mode/fdm/init-menu.js | 1 + src/kiri/mode/fdm/prepare.js | 37 ++++++++++++++++++++++++++++++++-- web/boot/service.js | 6 +----- web/kiri/lang/en.js | 2 ++ 9 files changed, 48 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 1f9a9bca..0720297b 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "three": "^0.178.0", "three-mesh-bvh": "^0.7.6", "uglify-js": "3.14.5", - "validator": ">=13.7.0", + "validator": ">=13.15.15", "ws": "^8.18.2" }, "devDependencies": { diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index a66aa09d..bc3729bf 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -293,6 +293,7 @@ export const conf = { outputInvertY: false, outputLayerRetract: false, outputLoops: 0, + outputMaxFlowrate: 15, outputMinLayerTime: 10, outputMinSpeed: 5.0, outputPurgeTower: 0, diff --git a/src/kiri/mode/cam/topo4.js b/src/kiri/mode/cam/topo4.js index 95053024..3a60e611 100644 --- a/src/kiri/mode/cam/topo4.js +++ b/src/kiri/mode/cam/topo4.js @@ -38,7 +38,6 @@ export class Topo { x: axis === "x", y: axis === "y" }, - zMin = min.z + 0.0001, tolerance = op.tolerance, resolution = tolerance ? tolerance : 1 / Math.sqrt(density / (span.x * span.y)), step = this.step = (tool.traceOffset() * 2) * op.step, @@ -123,7 +122,9 @@ export class Topo { let slices = this.slices = []; let slice = { min: range.min, max: range.min + step, index }; - for (let z = range.min; z < range.max; z += resolution) { + // console.log({ shards, step, range, resolution }); + for (let z = range.min; z <= range.max; z += resolution) { + // console.log({ z }); if (z > slice.max) { slices.push(slice); slice = { min: z, max: z + step, index }; @@ -131,7 +132,7 @@ export class Topo { index++; } slices.push(slice); - // console.log({ shards, range, step, slices }); + // console.log({ shards, range, step, slices: slices.slice() }); const { minions } = self.kiri_worker; if (minions?.running > 1) { diff --git a/src/kiri/mode/fdm/driver-be.js b/src/kiri/mode/fdm/driver-be.js index f1164acc..2efb734d 100644 --- a/src/kiri/mode/fdm/driver-be.js +++ b/src/kiri/mode/fdm/driver-be.js @@ -9,6 +9,7 @@ import { fdm_export } from './export.js'; // fil = filament diameter // slice = slice height function extrudePerMM(noz, fil, slice) { + // (ratio of nozzle to filament) * (ratio of slice to nozzle) return ((Math.PI * util.sqr(noz / 2)) / (Math.PI * util.sqr(fil / 2))) * (slice / noz); }; diff --git a/src/kiri/mode/fdm/export.js b/src/kiri/mode/fdm/export.js index 6b359f56..a65d6ca9 100644 --- a/src/kiri/mode/fdm/export.js +++ b/src/kiri/mode/fdm/export.js @@ -26,7 +26,6 @@ export function fdm_export(print, online, ondone, ondebug) { { extrudeAbs } = device, { exportThumb } = controller, { extrudeMM, extrudePerMM } = FDM, - extused = Object.keys(print.extruders).map(v => parseInt(v)), timeFactor = (device.gcodeTime || 1) * 1.5, decimals = config.gcode_decimals || 4, zMoveMax = device.deviceZMax || 0, @@ -49,7 +48,6 @@ export function fdm_export(print, online, ondone, ondebug) { last = null, zpos = 0, bmax = 0, - blastz = 0, belt_add_y = (process.firstLayerYOffset || 0) - (belty || 0), oloops = process.outputLoops || 0, zhop = process.zHopDistance || 0, // range @@ -58,10 +56,8 @@ export function fdm_export(print, online, ondone, ondebug) { retDist = process.outputRetractDist || 0, // range retSpeed = process.outputRetractSpeed * 60 || 1, // range retDwell = process.outputRetractDwell || 0, // range - timeDwell = retDwell / 1000, scarfZ = process.outputScarfLength ? true : false, arcDist = isBelt || scarfZ ? 0 : (process.arcTolerance || 0), - arcMin = 1, arcRes = 20, arcDev = 0.5, arcMax = 40, @@ -85,7 +81,6 @@ export function fdm_export(print, online, ondone, ondebug) { fanSpeedSave = undefined, fanSpeedBase = 0, fanSpeed = 0, - lastType = undefined, lastNozzleTemp = nozzleTemp, lastBedTemp = bedTemp, lastFanSpeed = fanSpeed, @@ -169,7 +164,6 @@ export function fdm_export(print, online, ondone, ondebug) { retDist = params.outputRetractDist || 0; // range retSpeed = params.outputRetractSpeed * 60 || 1; // range retDwell = params.outputRetractDwell || 0; // range - timeDwell = retDwell / 1000; nozzleTemp = layer === 0 ? params.firstLayerNozzleTemp || params.outputTemp : params.outputTemp || params.firstLayerNozzleTemp; @@ -470,7 +464,7 @@ export function fdm_export(print, online, ondone, ondebug) { if (isBelt) { let zheight = path ? path.height || 0 : 0; epos.x = originCenter ? -pos.x : device.bedWidth - pos.x; - epos.z = blastz = pos.z * icos; + epos.z = pos.z * icos; epos.y = -pos.y + epos.z * bcos + belt_add_y; lout = epos; } @@ -635,7 +629,7 @@ export function fdm_export(print, online, ondone, ondebug) { // emit comment on output type chage if (last && out.type !== last.type) { - lastType = subst.feature = out.type; + subst.feature = out.type; if (gcodeFeature && gcodeFeature.length) { appendAllSub(gcodeFeature); } else { @@ -752,7 +746,6 @@ export function fdm_export(print, online, ondone, ondebug) { } // 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 * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) { // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; if (arcQ.length === 4) { diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index 07571f0b..dc062c28 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -177,6 +177,7 @@ export function menu() { arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}), 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)}), + outputMaxFlowrate: newInput(LANG.ad_maxf_s, {title:LANG.ad_maxf_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)}), diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 7152f12a..1a46dea0 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1,5 +1,6 @@ /** Copyright Stewart Allen -- All Rights Reserved */ +import { FDM } from './driver-be.js'; import { base, util } from '../../../geo/base.js'; import { poly2polyEmit, tip2tipEmit } from '../../../geo/paths.js'; import { newBounds } from '../../../geo/bounds.js'; @@ -794,10 +795,22 @@ export async function fdm_prepare(widgets, settings, update) { } }; + +/** + * Output and route Slice (shell,fill) paths for a single Slice + * + * @param {*} print Print output Object + * @param {*} slice current slice being Output + * @param {*} startPoint starting point for this layer + * @param {*} offset start point offset + * @param {*} output output prebuffer + * @param {*} opt options + */ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { const { settings } = print; const { device } = settings; const { bedWidth, bedDepth, bedRound } = device; + const { extrudePerMM } = FDM; let preout = [], process = opt.params || settings.process, @@ -806,6 +819,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { scarfLength = process.outputScarfLength || 0, minLayerTime = process.outputMinLayerTime || 0, nozzleSize = process.sliceLineWidth || device.extruders[extruder].extNozzle, + nozzles = device.extruders.map(e => e.extNozzle), firstLayer = (opt.first || false) && !opt.support, thinWall = nozzleSize * (opt.thinWall || 1.75), retractDist = opt.retractOver || (nozzleSize * 5), @@ -828,6 +842,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { zhop = process.zHopDistance || 0, zmax = opt.zmax, antiBacklash = process.antiBacklash, + maxFlowRate = process.outputMaxFlowrate, wipeDist = process.outputRetractWipe || 0, isBelt = device.bedBelt, bedOffset = originCenter ? { @@ -1595,16 +1610,34 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { if (!p.emit) continue; let mms = p.speed ?? 1; let dst = lp?.distTo2D(p.point) ?? 0; + lp = p.point; if (dst) { tt += (dst/mms); td += dst; + p.dst = dst; } - lp = p.point; } if (minLayerTime && tt < minLayerTime) { let fact = tt / minLayerTime; for (let p of preout) { - if (p.emit) p.speed = Math.max(p.speed * fact, minSpeed); + if (p.emit) { + p.speed = Math.max(p.speed * fact, minSpeed); + } + } + } + + // apply speed damping when output exceeds volumetric flowrate cap + if (maxFlowRate) { + for (let p of preout) { + if (p.emit && p.dst) { + let vfr = nozzles[p.tool] * slice.height * p.speed; + if (vfr > maxFlowRate) { + let old = p.speed; + let damp = maxFlowRate / vfr; + p.speed = p.speed * damp; + // console.log({ vfr, damp, old, new: p.speed }); + } + } } } diff --git a/web/boot/service.js b/web/boot/service.js index c32533d2..8d0f405a 100644 --- a/web/boot/service.js +++ b/web/boot/service.js @@ -127,16 +127,12 @@ async function redirectOr404(path) { } } -async function fromCacheOrNetwork(req, test) { +async function fromCacheOrNetwork(req) { const cache = await caches.open(CACHE_VERSION); const hit = await cache.match(req, { ignoreSearch: true }); if (hit) { - // log(`hit: ${req.url}`); return hit; } - if (test) { - return test; - } const net = await fetch_safe(req); cache.put(req, net.clone()); log(`put: ${net.url}`); diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 39eb200d..c73e00ca 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -868,6 +868,8 @@ self.lang['en-us'] = { ad_msol_l: ["minimum area (mm^2)","required to keep solid","must be > 0.1"], ad_mins_s: "min speed", ad_mins_l: ["minimum speed","for short segments"], + ad_maxf_s: "max flowrate", + ad_maxf_l: ["maximum volumetric flowrate supported by this print head and filament","in millimeters^3 / second"], ad_scar_s: "scarf length", ad_scar_l: ["ease up length for scarf joint. disables arc detection","0 = disabled"], ad_spol_s: "short path", From f0f0bae52b4f0a0264cb461d27fd2661b9661902 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 17 Oct 2025 00:49:21 -0400 Subject: [PATCH 20/24] remove first layer line width and flow rate since they will become range parameters in the flow rate value refactor --- src/kiri/core/conf.js | 2 -- src/kiri/mode/fdm/client.js | 1 - src/kiri/mode/fdm/init-menu.js | 6 ++---- src/kiri/mode/fdm/post.js | 7 ++----- src/kiri/mode/fdm/prepare.js | 10 ---------- src/kiri/mode/fdm/slice.js | 6 ++---- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index bc3729bf..df9fd4c4 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -270,9 +270,7 @@ export const conf = { firstLayerFanSpeed: 0, firstLayerFillRate: 35, firstLayerFlatten: 0, - firstLayerLineMult: 1.0, firstLayerNozzleTemp: 0, - firstLayerPrintMult: 1.0, firstLayerRate: 30, firstLayerYOffset: 0, firstSliceHeight: 0.25, diff --git a/src/kiri/mode/fdm/client.js b/src/kiri/mode/fdm/client.js index 7bb6d5cf..9fdbb6b0 100644 --- a/src/kiri/mode/fdm/client.js +++ b/src/kiri/mode/fdm/client.js @@ -41,7 +41,6 @@ export function init() { "sliceTopLayers": LANG.sl_ltop_s, "sliceBottomLayers": LANG.sl_lbot_s, "firstLayerRate": LANG.fl_rate_s, - "firstLayerPrintMult": LANG.fl_mult_s, "firstLayerFanSpeed": LANG.ou_fans_s, // prepare "sliceShellOrder": LANG.sl_ordr_s, diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index dc062c28..44da3b9c 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -99,6 +99,8 @@ export function menu() { _____: newGroup(LANG.fh_menu, $('fdm-heat'), { modes:FDM, driven, hideable, separator, group:"fdm-heat" }), outputTemp: newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:toInt}), outputBedTemp: newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:toInt}), + separator: newBlank({ class:"set-sep", driven }), + outputDraftShield: newBoolean(LANG.fr_draf_s, onBooleanClick, {title:LANG.fr_draf_l, trigger, show:() => isNotBelt()}), _____: 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)}), @@ -137,9 +139,6 @@ export function menu() { firstLayerFlatten: newInput(LANG.fl_flat_s, {title:LANG.fl_flat_l, convert:toFloat, show:isBelt}), firstLayerRate: newInput(LANG.fl_rate_s, {title:LANG.fl_rate_l, convert:toFloat}), firstLayerFillRate: newInput(LANG.fl_frat_s, {title:LANG.fl_frat_l, convert:toFloat, show:isNotBelt}), - separator: newBlank({ class:"set-sep", driven, show:isNotBelt }), - firstLayerLineMult: newInput(LANG.fl_sfac_s, {title:LANG.fl_sfac_l, convert:toFloat, bound:bound(0.5,2), show:isNotBelt}), - firstLayerPrintMult: newInput(LANG.fl_mult_s, {title:LANG.fl_mult_l, convert:toFloat}), separator: newBlank({ class:"set-sep", driven, show:isBelt }), firstLayerBrim: newInput(LANG.fl_brim_s, {title:LANG.fl_brim_l, convert:toInt, show:isBelt}), firstLayerBrimIn: newInput(LANG.fl_brin_s, {title:LANG.fl_brin_l, convert:toInt, show:isBelt}), @@ -154,7 +153,6 @@ export function menu() { outputRaftSpacing: newInput(LANG.fr_spac_s, {title:LANG.fr_spac_l, convert:toFloat, bound:bound(0.0,3.0), show: () => ui.outputRaft.checked && isNotBelt() }), separator: newBlank({ class:"set-sep", driven, show:isNotBelt }), outputRaft: newBoolean(LANG.fr_nabl_s, onBooleanClick, {title:LANG.fr_nabl_l, trigger, show:() => isNotBelt()}), - outputDraftShield: newBoolean(LANG.fr_draf_s, onBooleanClick, {title:LANG.fr_draf_l, trigger, show:() => isNotBelt()}), _____: newGroup(LANG.ou_menu, $('fdm-output'), { modes:FDM, driven, hideable, separator, group:"fdm-out" }), outputFeedrate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt}), outputFinishrate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt}), diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index b910958a..88168a09 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -82,12 +82,9 @@ export function slicePost(data, options) { let trg = shellFrac > 0.5 ? 1 : parts - 1; sliceShells += rem >= trg ? 1 : 0; } - const isFirst = index === 0; - const height = process.sliceHeight; - const spaceMult = isFirst ? process.firstLayerLineMult || 1 : 1; const count = isSynth ? 1 : sliceShells; - const offset = shellOffset * spaceMult; - const fillOff = fillOffset * spaceMult; + const offset = shellOffset; + const fillOff = fillOffset; const thinType = isSynth ? undefined : process.sliceDetectThin; const nutops = []; // co-locate shell processing with top generation in slicer diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 1a46dea0..62352eb0 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -683,7 +683,6 @@ export async function fdm_prepare(widgets, settings, update) { for (let rec of layer) { overate = rec.overate >= 0 ? rec.overate : overate; let brate = params.firstLayerRate || firstLayerRate; - let bmult = params.firstLayerPrintMult || params.firstLayerPrintMult; let point = rec.point; let belty = rec.belty = -point.y + (point.z * belt.slope); let lowrate = belty <= thresh ? brate : overate || brate; @@ -695,7 +694,6 @@ export async function fdm_prepare(widgets, settings, update) { if (rec.emit && belty <= thresh && lastout && Math.abs(lastout.belty - belty) < 0.01) { // apply base speed to segments touching belt rec.speed = params.firstLayerRate || Math.min(rec.speed, lowrate); - rec.emit *= bmult; rec.fan = params.firstLayerFanSpeed; minx = Math.min(minx, point.x, lastout.point.x); maxx = Math.max(maxx, point.x, lastout.point.x); @@ -831,7 +829,6 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { fanSpeed = process.outputFanSpeed, firstShellSpeed = process.firstLayerRate, firstFillSpeed = process.firstLayerFillRate, - firstPrintMult = process.firstLayerPrintMult, finishSpeed = firstLayer ? firstShellSpeed : (opt.speed || process.outputFinishrate), printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate), fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate), @@ -882,13 +879,6 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { printSpeed /= 2; } - // apply first layer extrusion multipliers - if (firstLayer) { - fillMult *= firstPrintMult; - shellMult *= firstPrintMult; - sparseMult *= firstPrintMult; - } - function retract() { let array = preout.length ? preout : output; if (array.length) { diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index 9bda3df8..5bc215da 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -498,7 +498,6 @@ export function fdm_slice(settings, widget, onupdate, ondone) { // just the top/bottom special solid layers or range defined solid layers forSlices(0.15, 0.2, slice => { let range = slice.params; - let spaceMult = slice.index === 0 ? process.firstLayerLineMult || 1 : 1; let isBottom = slice.index < bottomLayers; let isTop = topLayers && slice.index > slices.length - topLayers - 1; let isTopBase = isTop && slice.index === slices.length - topLayers; @@ -506,7 +505,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth; let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0; if (solidWidth) { - let fillSpace = fillSpacing * spaceMult * solidWidth; + let fillSpace = fillSpacing * solidWidth; doSolidLayerFill(slice, fillSpace, sliceFillAngle); } if (slice.index === slices.length - 1) { @@ -755,8 +754,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let params = slice.params || process; let first = slice.index === 0; let solidWidth = params.sliceFillWidth || 1; - let spaceMult = first ? params.firstLayerLineMult || 1 : 1; - let fillSpace = fillSpacing * spaceMult * solidWidth; + let fillSpace = fillSpacing * solidWidth; let solidMinArea = params.sliceSolidMinArea; doSolidsFill(slice, fillSpace, sliceFillAngle, solidMinArea, promises); sliceFillAngle = (sliceFillAngle + 90.0) % 360; From e614c34998bccd7cf479eaaee4922606feece6ce Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 17 Oct 2025 01:08:30 -0400 Subject: [PATCH 21/24] remove confusing/unused fill speed parameters, rename other params for clarity --- src/kiri/core/conf.js | 2 -- src/kiri/mode/fdm/client.js | 2 -- src/kiri/mode/fdm/init-menu.js | 5 +---- src/kiri/mode/fdm/prepare.js | 7 +------ web/kiri/lang/en.js | 12 ++++++------ 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index df9fd4c4..83c02e83 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -318,7 +318,6 @@ export const conf = { sliceFillAngle: 45, sliceFillGrow: 0, sliceFillOverlap: 0.35, - sliceFillRate: 0, sliceFillRepeat: 1, sliceFillSparse: 0.25, sliceFillType: "hex", @@ -332,7 +331,6 @@ export const conf = { sliceShellOrder: "in-out", sliceShells: 3, sliceSolidMinArea: 1, - sliceSolidRate: 0, sliceSupportAngle: 50, sliceSupportArea: 0.1, sliceSupportDensity: 0.1, diff --git a/src/kiri/mode/fdm/client.js b/src/kiri/mode/fdm/client.js index 9fdbb6b0..466856f9 100644 --- a/src/kiri/mode/fdm/client.js +++ b/src/kiri/mode/fdm/client.js @@ -34,9 +34,7 @@ export function init() { "sliceFillType": LANG.fi_type, "sliceFillWidth": LANG.fi_wdth_s, "sliceFillSparse": LANG.fi_pcnt_s, - "sliceFillRate": LANG.ou_feed_s, "sliceFillGrow": LANG.fi_grow_s, - "sliceSolidRate": LANG.ou_fini_s, "sliceSolidMinArea": LANG.ad_msol_s, "sliceTopLayers": LANG.sl_ltop_s, "sliceBottomLayers": LANG.sl_lbot_s, diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index 44da3b9c..453d83da 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -79,6 +79,7 @@ export function menu() { _____: 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) }), + sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,1.0)}), separator: newBlank({ class:"set-sep", driven }), sliceShellOrder: newSelect(LANG.sl_ordr_s, { title:LANG.sl_ordr_l}, "shell"), sliceDetectThin: newSelect(LANG.ad_thin_s, { title: LANG.ad_thin_l, action: thinWallSave }, "thin"), @@ -86,16 +87,12 @@ export function menu() { sliceZInterleave: newBoolean(LANG.ad_zint_s, onBooleanClick, {title:LANG.ad_zint_l, show:zIntShow}), _____: newGroup(LANG.fs_menu, $('fdm-solid'), { modes:FDM, driven, hideable, separator, group:"fdm-solid" }), sliceFillAngle: newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:toFloat}), - sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0)}), sliceFillGrow: newInput(LANG.fi_grow_s, {title:LANG.fi_grow_l, convert:toFloat}), sliceSolidMinArea: newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:toFloat}), _____: newGroup(LANG.fi_menu, $('fdm-fill'), { modes:FDM, driven, hideable, separator, group:"fdm-fill" }), sliceFillType: newSelect(LANG.fi_type, {trigger}, "infill"), sliceFillSparse: newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:toFloat, bound:bound(0.0,1.0), show:hasInfill}), sliceFillRepeat: newInput(LANG.fi_rept_s, {title:LANG.fi_rept_l, convert:toInt, bound:bound(1,10), show:fillIsLinear}), - separator: newBlank({ class:"set-sep", driven }), - sliceFillRate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt, bound:bound(0,500)}), - sliceSolidRate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt, bound:bound(0,500)}), _____: newGroup(LANG.fh_menu, $('fdm-heat'), { modes:FDM, driven, hideable, separator, group:"fdm-heat" }), outputTemp: newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:toInt}), outputBedTemp: newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:toInt}), diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 62352eb0..680cc075 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -832,7 +832,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { finishSpeed = firstLayer ? firstShellSpeed : (opt.speed || process.outputFinishrate), printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate), fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate), - infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed, + infillSpeed = opt.infillSpeed || fillSpeed || printSpeed, moveSpeed = process.outputSeekrate, minSpeed = process.outputMinSpeed, origin = startPoint.add(offset), @@ -863,11 +863,6 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { shellOrder = -shellOrder; } - // update fill speed when solids detected - if (slice.finishSolids) { - fillSpeed = process.sliceSolidRate || finishSpeed; - } - // on flats layers also slow down shell print speed if (slice.isFlatsLayer) { printSpeed = finishSpeed || printSpeed; diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index c73e00ca..da85f39a 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -356,7 +356,7 @@ self.lang['en-us'] = { ad_thin_l: ["detect and fill gaps","between shell walls"], // FDM SHELLS - sl_line_s: "shell width", + sl_line_s: "line width", 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"], @@ -369,8 +369,8 @@ self.lang['en-us'] = { // FDM SOLID fs_menu: "solid fill", - fi_over_s: "shell overlap", - fi_over_l: ["overlap with shell and other fill","for improved extrusion bonding","as fraction of nozzle diameter","0.0 - 2.0"], + fi_over_s: "fill overlap", + fi_over_l: ["overlap between shells and solid or sparse infill. can improve bonding to shells","as fraction of line width","0.0 - 1.0"], fi_angl_s: "start angle", fi_angl_l: ["starting angle in degrees","90 degrees added to ","each following layer","applies only to solid layers"], fi_grow_s: "area expand", @@ -393,7 +393,7 @@ self.lang['en-us'] = { fc_menu: "cooling", // FDM FIRST LAYER - fl_menu: "base", + fl_menu: "base layer", fl_lahi_s: "layer height", fl_lahi_l: ["height of each slice","in millimeters","should be >= slice height"], fl_rate_s: "shell speed", @@ -864,8 +864,8 @@ self.lang['en-us'] = { ad_rdwl_l: ["time between re-engaging","filament and movement","in milliseconds"], ad_scst_s: "shell coast", ad_scst_l: ["non-printing end","of perimeter shells","in millimeters"], - ad_msol_s: "min area", - ad_msol_l: ["minimum area (mm^2)","required to keep solid","must be > 0.1"], + ad_msol_s: "area filter", + ad_msol_l: ["minimum area (mm^2)","bridges and flats less than this are culled"], ad_mins_s: "min speed", ad_mins_l: ["minimum speed","for short segments"], ad_maxf_s: "max flowrate", From 9c8c9ff30bdcabc49117367676b695d8c12b62c9 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 18 Oct 2025 01:03:58 -0400 Subject: [PATCH 22/24] add lathe start/end offset option, fix large step precision --- notes.md | 1 - src/kiri/core/conf.js | 2 ++ src/kiri/mode/cam/cl-ops.js | 7 ++++- src/kiri/mode/cam/topo4.js | 61 ++++++++++++++++++++++--------------- src/kiri/run/minion.js | 1 - src/moto/space.js | 2 -- web/kiri/lang/en.js | 10 +++--- 7 files changed, 49 insertions(+), 35 deletions(-) diff --git a/notes.md b/notes.md index 56c74577..e39f50d8 100644 --- a/notes.md +++ b/notes.md @@ -32,7 +32,6 @@ * `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` new parameter to cap output flow rate (on printer device) * `F` new parameters for bridging speed / bridge fan control * `F` convert ranges to z offsets while continuing to show layer # * `F` support pillar top/bottom should conform to part diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index 83c02e83..ae5512d5 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -497,6 +497,8 @@ export const conf = { camLatheSpeed: 500, camLatheSpindle: 1000, camLatheTool: 1000, + camLatheOffStart: 0, + camLatheOffEnd: 0, camLevelDown: 0, camLevelInset: 0.5, camLevelOver: 0.75, diff --git a/src/kiri/mode/cam/cl-ops.js b/src/kiri/mode/cam/cl-ops.js index eb9232dd..5d05ed31 100644 --- a/src/kiri/mode/cam/cl-ops.js +++ b/src/kiri/mode/cam/cl-ops.js @@ -350,7 +350,9 @@ export function createPopOps() { tolerance: 'camTolerance', filter: 'camContourFilter', leave: 'camContourLeave', - linear: 'camLatheLinear' + linear: 'camLatheLinear', + offStart: 'camLatheOffStart', + offEnd: 'camLatheOffEnd' }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), // axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"), @@ -361,6 +363,9 @@ export function createPopOps() { step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 100.0) }), angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: UC.toFloat, bound: UC.bound(0.01, 180.0) }), sep: UC.newBlank({ class: "pop-sep" }), + offStart: UC.newInput(LANG.ci_laso_s, { title: LANG.ci_laso_l, convert: UC.toFloat}), + offEnd: UC.newInput(LANG.ci_laeo_s, { title: LANG.ci_laeo_l, convert: UC.toFloat}), + sep: UC.newBlank({ class: "pop-sep" }), tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4 }), leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: UC.toFloat, bound: UC.bound(0, 100) }), sep: UC.newBlank({ class: "pop-sep" }), diff --git a/src/kiri/mode/cam/topo4.js b/src/kiri/mode/cam/topo4.js index 3a60e611..f29934c2 100644 --- a/src/kiri/mode/cam/topo4.js +++ b/src/kiri/mode/cam/topo4.js @@ -39,7 +39,7 @@ export class Topo { y: axis === "y" }, tolerance = op.tolerance, - resolution = tolerance ? tolerance : 1 / Math.sqrt(density / (span.x * span.y)), + resolution = (tolerance ? tolerance : 1 / Math.sqrt(density / (span.x * span.y))).round(5), step = this.step = (tool.traceOffset() * 2) * op.step, angle = this.angle = op.angle || 1; @@ -58,10 +58,13 @@ export class Topo { this.tool = tool.generateProfile(resolution).profile; this.maxo = tool.profileDim.maxo * resolution; this.diam = tool.fluteDiameter(); + this.unit = tool.unitScale(); this.zoff = widget.track.top || 0; this.leave = op.leave || 0; this.linear = op.linear || false; this.lineColor = state.settings.controller.dark ? 0xffff00 : 0x555500; + this.offStart = op.offStart ?? 0; + this.offEnd = op.offEnd ?? 0; onupdate(0, "lathe"); @@ -85,8 +88,8 @@ export class Topo { } async slice(onupdate) { - const { vertices, resolution, tabverts, zoff } = this; - + const { vertices, resolution, tabverts, zoff, offStart, offEnd, tool, unit } = this; + const { minions } = self.kiri_worker; const range = this.range = { min: Infinity, max: -Infinity }; const box = this.box = new THREE.Box2(); @@ -100,9 +103,10 @@ export class Topo { range.max = Math.max(range.max, x); } - // add tool radius to slice min/max range ot fully carve part - range.min -= this.diam / 2; - range.max += this.diam / 2; + // add tool diameter to slice min/max range to fully carve part + range.min += offStart * unit; + range.max -= offEnd * unit; + range.max += resolution * 2; // merge in tab vertices here so they don't affect slice range / dimensions for (let i = 0, l = tabverts.length; i < l; i += 3) { @@ -115,26 +119,31 @@ export class Topo { // re-create shared vertex array for workers this.vertices = [].appendAll(vertices).appendAll(tabverts).toFloat32().toShared(); + const zSpan = range.max - range.min; const shards = Math.ceil(Math.min(25, vertices.length / 27000)); - const step = (range.max - range.min) / shards; + const totalSteps = Math.ceil(zSpan / resolution); + const stardSteps = Math.ceil(totalSteps / shards); + const stepWidth = stardSteps * resolution; - let index = 0; let slices = this.slices = []; - let slice = { min: range.min, max: range.min + step, index }; - - // console.log({ shards, step, range, resolution }); - for (let z = range.min; z <= range.max; z += resolution) { - // console.log({ z }); - if (z > slice.max) { - slices.push(slice); - slice = { min: z, max: z + step, index }; - } - index++; + for (let i=0; i 1) { return await this.sliceMinions(onupdate); } else { @@ -218,10 +227,10 @@ export class Topo { const tlen = tool.length; const slen = slices.length; - const sinc = Math.max(1, Math.ceil(step / resolution)); const heights = []; - // cull slice lines to only the ones in range (5x faster) + // console.log({ tlen, slen, sinc, slices: slices.map(s => s.z) }); + // cull slice lines to only the ones in range (~5x faster) const oslices = []; for (let slice of slices) { const lines = slice.lines; @@ -245,7 +254,9 @@ export class Topo { // iterate over all slices (real x = z) // find max real z using z ray intersect from tool point to slice lines + offset let lz = 0; - for (let si = 0; si < slen; si += sinc) { + for (let sz = 0; ; sz += step) { + let si = Math.ceil(sz / resolution); + if (si >= oslices.length) break; const rx = oslices[si].z; let mz = -Infinity; // iterate over tool offsets @@ -282,7 +293,7 @@ export class Topo { } if (mz === -Infinity && xo === 0 && yo === 0) { // tool tip is off the model - continue; + // continue; } } if (mz === -Infinity) { diff --git a/src/kiri/run/minion.js b/src/kiri/run/minion.js index ae1c2920..412da396 100644 --- a/src/kiri/run/minion.js +++ b/src/kiri/run/minion.js @@ -315,7 +315,6 @@ const funcs = self.minion = { const topo4 = Object.assign(new Topo4(), cache.lathe); const heights = topo4.lathePath(stmp, tool); - reply({ seq, heights }); } }; diff --git a/src/moto/space.js b/src/moto/space.js index 8aa5047d..45101885 100644 --- a/src/moto/space.js +++ b/src/moto/space.js @@ -98,7 +98,6 @@ let WIN = self.window || {}, fontColor = '#333333', fontScale = 1.4, // computed relative to grid size rulerColor, - axisColor, axesOn = true, volumeOn = true, viewControl, @@ -388,7 +387,6 @@ function setPlatformColor(color) { function setFont(options) { if (options.color) fontColor = options.color; if (options.scale) fontScale = options.scale; - if (options.axisColor) axisColor = options.axisColor; if (options.rulerColor) rulerColor = options.rulerColor; updateRulers(); } diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index da85f39a..ea00c48b 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -393,7 +393,7 @@ self.lang['en-us'] = { fc_menu: "cooling", // FDM FIRST LAYER - fl_menu: "base layer", + fl_menu: "base", fl_lahi_s: "layer height", fl_lahi_l: ["height of each slice","in millimeters","should be >= slice height"], fl_rate_s: "shell speed", @@ -654,6 +654,10 @@ self.lang['en-us'] = { ci_face_l: "select face to rotate facing up", ci_line_s: "linear", ci_line_l: "make linear passes along X then rotate Y", + ci_laso_s: "offset start", + ci_laso_l: ["x offet from left of part. negative values are outside of the part. in workspace units"], + ci_laeo_s: "offset end", + ci_laeo_l: ["x offet from right of part. negative values are outside of the part. in workspace units"], // CNC LASER On/Off Operations (Carvera) cl_powr_s: "power", @@ -675,10 +679,6 @@ self.lang['en-us'] = { cl_maxz_s: "max z height", cl_maxz_l: ["highest Z band position. when set to 0, uses the highest Z point of the workspace"], - - - - // CNC CUTOUT TABS ct_menu: "tabs", ct_angl_s: "angle", From 9123a70b7b8a280e6a318b760268a55b6fc7584f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 18 Oct 2025 11:55:12 -0400 Subject: [PATCH 23/24] add compression caching to bundler --- bin/bundler.mjs | 46 ++++++++++++++++++++++++++++++++++------------ package.json | 6 +++--- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/bin/bundler.mjs b/bin/bundler.mjs index e0628abf..e63aba3e 100644 --- a/bin/bundler.mjs +++ b/bin/bundler.mjs @@ -56,6 +56,20 @@ const dataParts = []; const entryMeta = []; let offset = 0; let cache = new Map(); +let compEnable = (envcomp ?? compress); +let cacheDir = cfg.cacheDir ?? ".bcache"; + +if (compEnable) { + fs.mkdirSync(cacheDir, { recursive: true }); +} + +function fileTime(path) { + try { + return (fs.statSync(path).ctimeMs/1000) | 0; + } catch (e) { + return 0; + } +} for (const { file, virt } of entries) { if (excludes.indexOf(file) >= 0) { @@ -68,20 +82,28 @@ for (const { file, virt } of entries) { } else { if (debug) console.log({ write: virt, from: file }); let data = fs.readFileSync(file); - if ((envcomp ?? compress) && file.endsWith("js")) { - console.log('compress', file); - data = uglify.minify(data.toString(), { - compress: { - merge_vars: false, - unused: false + if (compEnable && file.endsWith("js")) { + let cpath = path.resolve(cacheDir, file); + if (fileTime(cpath) > fileTime(file)) { + data = fs.readFileSync(cpath); + console.log('cached', file); + } else { + console.log('compress', file); + data = uglify.minify(data.toString(), { + compress: { + merge_vars: false, + unused: false + } + }); + // console.log({ to: typeof(data), data }); + if (!data.code) { + if (debug) console.log({ skip: file }); + continue; } - }); - // console.log({ to: typeof(data), data }); - if (!data.code) { - if (debug) console.log({ skip: file }); - continue; + data = Buffer.from(data.code); + fs.mkdirSync(path.dirname(cpath), { recursive: true }); + fs.writeFileSync(cpath, data); } - data = Buffer.from(data.code); } record = { virt, offset, length: data.length }; cache.set(file, record); diff --git a/package.json b/package.json index 0720297b..ef499c79 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "webpack-cli": "^5.1.4" }, "scripts": { - "setup": "npm run setup-mods && npm i && npm run clean && npm run dev -- --dryrun && npm run webpack-ext", + "setup": "npm run setup-mods && npm i && npm run clean && npm run dev -- --alt --dryrun && npm run webpack-ext", "setup-mods": "cd mods && npm i", "build": "npm run prebuild && electron-builder", "build-nopublish": "npm run prebuild && electron-builder --publish never", @@ -92,13 +92,13 @@ "build-win-arm": "npm run build -- --win --arm64", "build-mac": "npm run build -- --mac --arm64", "build-mac-intel": "npm run build -- --mac --x64", - "clean": "rm -rf build data dist src/pack tmp src.old web.old", + "clean": "rm -rf build data dist src/pack tmp src.old web.old .bcache", "docs-dev": "docusaurus start --config conf/docusaurus.config.js", "docs-build": "docusaurus build --config conf/docusaurus.config.js", "docs-serve": "docusaurus serve --config conf/docusaurus.config.js", "docs-check": "prettier --config ./conf/prettier.config.js ./docs --check", "dev": "npm run webpack-ext && gs-app-server --debug --single", - "prod": "npm run webpack-ext && npm run webpack-src prod && gs-app-server", + "prod": "npm run webpack-ext && npm run webpack-src prod && COMPRESS=1 node bin/bundler.mjs && gs-app-server", "prod-pack": "npm run webpack-ext && npm run webpack-src prod", "prod-dryrun": "gs-app-server --dryrun", "start": "npm run prebuild prod && electron .", From 3fb36a2254710ba1e65f7e24168c5295e0c7aa9c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 18 Oct 2025 12:14:24 -0400 Subject: [PATCH 24/24] bump rev 4.3.4 --- 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 ef499c79..49b5b331 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.3.3", + "version": "4.3.4", "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 f3975212..669e0eb7 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.3" + VERSION: "4.3.4" }; export const beta = 0; diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index 0f24310a..5d913d11 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.3.3", + "name": "Kiri:Moto 4.3.4", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/",