Merge branch 'rel-4.3'
This commit is contained in:
commit
5d3e32e576
30 changed files with 644 additions and 206 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,7 +9,8 @@ jszip-esm.js
|
|||
logs
|
||||
mod
|
||||
node_modules
|
||||
pack
|
||||
package-lock.json
|
||||
three.js
|
||||
tmp
|
||||
pack
|
||||
web/boot/bundle.bin
|
||||
|
|
|
|||
31
app.js
31
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,19 @@ function init(mod) {
|
|||
append[key] = minify(append[key]);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fs.writeFileSync(`alt/main/${key}.js`, body + val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// either add module assets to path or require(init.js)
|
||||
|
|
@ -288,14 +303,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 +445,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) {
|
||||
|
|
@ -526,7 +545,13 @@ function fixedmap(prefix, map) {
|
|||
// HTTP 307 redirect
|
||||
function redir(path, type) {
|
||||
return (req, res, next) => {
|
||||
http.redirect(res, path, type);
|
||||
let query = req.url.split('?')[1];
|
||||
let rpath = path;
|
||||
if (query && path.indexOf('?') < 0) {
|
||||
rpath += `?${query}`;
|
||||
}
|
||||
// console.log({ redir: req.url, to: path, query, new_path: rpath });
|
||||
http.redirect(res, rpath, type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
{
|
||||
"compress": false,
|
||||
"outputs": {
|
||||
"bundle": "./web/kiri/bundle.bin"
|
||||
"bundle": "./web/boot/bundle.bin"
|
||||
},
|
||||
"inputs": [
|
||||
{ "root": "./web", "prefix": "" },
|
||||
{ "root": "./src", "prefix": "lib" },
|
||||
{ "root": "./src/wasm", "prefix": "wasm" },
|
||||
{ "root": "./src/wasm", "prefix": "lib/kiri/wasm" }
|
||||
{ "root": "web", "prefix": "" },
|
||||
{ "root": "alt", "prefix": "lib"},
|
||||
{ "root": "src", "prefix": "lib"},
|
||||
{ "root": "src/wasm", "prefix": "wasm" },
|
||||
{ "root": "src/wasm", "prefix": "lib/kiri/wasm" }
|
||||
],
|
||||
"excludes": [
|
||||
"src/pack",
|
||||
"web/boot/bundle.bin"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,21 @@ 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 compress = process.env.UGLIFY ? true : false
|
||||
const cfg = JSON.parse(fs.readFileSync(process.argv[2] || './bin/bundle.config.json', 'utf8'));
|
||||
const { inputs, outputs, excludes, compress } = cfg;
|
||||
const envcomp = process.env.COMPRESS ? JSON.parse(process.env.COMPRESS) : undefined;
|
||||
const debug = process.env.DEBUG;
|
||||
|
||||
// --- recursive directory walker (follows symlinks safely) ---
|
||||
function* walk(dir, seen = new Set()) {
|
||||
dir = path.normalize(dir);
|
||||
if (excludes.indexOf(dir) >= 0) {
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const real = fs.realpathSync(dir);
|
||||
if (seen.has(real)) {
|
||||
return;
|
||||
|
|
@ -25,13 +35,19 @@ function* walk(dir, seen = new Set()) {
|
|||
|
||||
// --- collect all files according to config ---
|
||||
let entries = [];
|
||||
for (const input of cfg.inputs) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,28 +56,54 @@ 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) {
|
||||
continue;
|
||||
}
|
||||
let record = cache.get(file);
|
||||
if (record) {
|
||||
// console.log({ dup: file });
|
||||
if (debug) console.log({ alias: virt, from: file });
|
||||
entryMeta.push({ ...record, virt });
|
||||
} else {
|
||||
if (debug) console.log({ write: virt, from: file });
|
||||
let data = fs.readFileSync(file);
|
||||
if (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) {
|
||||
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);
|
||||
|
|
@ -99,7 +141,7 @@ const header = Buffer.concat([count, ...headerParts]);
|
|||
const full = Buffer.concat([header, ...dataParts]);
|
||||
|
||||
// --- write final bundle ---
|
||||
const outPath = cfg.outputs.bundle || './bundle.bin';
|
||||
const outPath = outputs.bundle || './bundle.bin';
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||
fs.writeFileSync(outPath, full); // ⚡ write raw binary
|
||||
|
||||
|
|
|
|||
1
notes.md
1
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
|
||||
|
|
|
|||
10
package.json
10
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 <sa@grid.space>",
|
||||
"license": "MIT",
|
||||
|
|
@ -61,7 +61,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": {
|
||||
|
|
@ -82,7 +82,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",
|
||||
|
|
@ -93,13 +93,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 .",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -293,6 +291,7 @@ export const conf = {
|
|||
outputInvertY: false,
|
||||
outputLayerRetract: false,
|
||||
outputLoops: 0,
|
||||
outputMaxFlowrate: 15,
|
||||
outputMinLayerTime: 10,
|
||||
outputMinSpeed: 5.0,
|
||||
outputPurgeTower: 0,
|
||||
|
|
@ -302,7 +301,7 @@ export const conf = {
|
|||
outputRetractDwell: 20,
|
||||
outputRetractSpeed: 40,
|
||||
outputRetractWipe: 0,
|
||||
outputScarfLength: 5,
|
||||
outputScarfLength: 0,
|
||||
outputSeekrate: 80,
|
||||
outputShellMult: 1.25,
|
||||
outputShortPoly: 0.0,
|
||||
|
|
@ -319,7 +318,6 @@ export const conf = {
|
|||
sliceFillAngle: 45,
|
||||
sliceFillGrow: 0,
|
||||
sliceFillOverlap: 0.35,
|
||||
sliceFillRate: 0,
|
||||
sliceFillRepeat: 1,
|
||||
sliceFillSparse: 0.25,
|
||||
sliceFillType: "hex",
|
||||
|
|
@ -333,7 +331,6 @@ export const conf = {
|
|||
sliceShellOrder: "in-out",
|
||||
sliceShells: 3,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceSolidRate: 0,
|
||||
sliceSupportAngle: 50,
|
||||
sliceSupportArea: 0.1,
|
||||
sliceSupportDensity: 0.1,
|
||||
|
|
@ -500,6 +497,8 @@ export const conf = {
|
|||
camLatheSpeed: 500,
|
||||
camLatheSpindle: 1000,
|
||||
camLatheTool: 1000,
|
||||
camLatheOffStart: 0,
|
||||
camLatheOffEnd: 0,
|
||||
camLevelDown: 0,
|
||||
camLevelInset: 0.5,
|
||||
camLevelOver: 0.75,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -349,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"),
|
||||
|
|
@ -360,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" }),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) || speed,
|
||||
feed = Math.min(speed || maxf, maxf),
|
||||
dist = Math.sqrt(dx * dx + dy * dy + dz * dz),
|
||||
newFeed = feed && feed !== pos.f;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -38,9 +38,8 @@ 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)),
|
||||
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;
|
||||
|
||||
|
|
@ -52,17 +51,20 @@ 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();
|
||||
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");
|
||||
|
||||
|
|
@ -86,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();
|
||||
|
||||
|
|
@ -101,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) {
|
||||
|
|
@ -116,24 +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 };
|
||||
|
||||
for (let z = range.min; z < range.max; z += resolution) {
|
||||
if (z > slice.max) {
|
||||
slices.push(slice);
|
||||
slice = { min: z, max: z + step, index };
|
||||
}
|
||||
index++;
|
||||
for (let i=0; i<shards; i++) {
|
||||
let s;
|
||||
slices.push(s = {
|
||||
min: range.min + i * stepWidth,
|
||||
max: Math.min(range.max, range.min + (i+1) * stepWidth),
|
||||
index: i * stardSteps
|
||||
});
|
||||
}
|
||||
slices.push(slice);
|
||||
// console.log({ shards, range, step, slices });
|
||||
|
||||
const { minions } = self.kiri_worker;
|
||||
// console.log({
|
||||
// shards,
|
||||
// step: stepWidth,
|
||||
// range,
|
||||
// resolution,
|
||||
// slices: slices.slice(),
|
||||
// minions
|
||||
// });
|
||||
|
||||
if (minions?.running > 1) {
|
||||
return await this.sliceMinions(onupdate);
|
||||
} else {
|
||||
|
|
@ -217,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;
|
||||
|
|
@ -244,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
|
||||
|
|
@ -281,12 +293,12 @@ export class Topo {
|
|||
}
|
||||
if (mz === -Infinity && xo === 0 && yo === 0) {
|
||||
// tool tip is off the model
|
||||
continue;
|
||||
// continue;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -34,14 +34,11 @@ 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,
|
||||
"firstLayerRate": LANG.fl_rate_s,
|
||||
"firstLayerPrintMult": LANG.fl_mult_s,
|
||||
"firstLayerFanSpeed": LANG.ou_fans_s,
|
||||
// prepare
|
||||
"sliceShellOrder": LANG.sl_ordr_s,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,19 +87,17 @@ 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}),
|
||||
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 +136,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 +150,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}),
|
||||
|
|
@ -177,6 +172,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)}),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- 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';
|
||||
|
|
@ -682,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;
|
||||
|
|
@ -694,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);
|
||||
|
|
@ -794,10 +793,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 +817,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),
|
||||
|
|
@ -817,17 +829,17 @@ 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),
|
||||
infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
|
||||
infillSpeed = opt.infillSpeed || fillSpeed || printSpeed,
|
||||
moveSpeed = process.outputSeekrate,
|
||||
minSpeed = process.outputMinSpeed,
|
||||
origin = startPoint.add(offset),
|
||||
zhop = process.zHopDistance || 0,
|
||||
zmax = opt.zmax,
|
||||
antiBacklash = process.antiBacklash,
|
||||
maxFlowRate = process.outputMaxFlowrate,
|
||||
wipeDist = process.outputRetractWipe || 0,
|
||||
isBelt = device.bedBelt,
|
||||
bedOffset = originCenter ? {
|
||||
|
|
@ -851,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;
|
||||
|
|
@ -867,13 +874,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) {
|
||||
|
|
@ -1595,16 +1595,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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1617,7 +1635,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const terms = {
|
||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - 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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
67
web/boot/index.html
Normal file
67
web/boot/index.html
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kiri:Moto (loading)</title>
|
||||
<style>
|
||||
body {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<script type="module">
|
||||
import { version } from '/lib/moto/license.js';
|
||||
(async () => {
|
||||
const reg = await navigator.serviceWorker.register('/boot/service.js', { scope: '/' });
|
||||
await reg.update();
|
||||
await navigator.serviceWorker.ready;
|
||||
|
||||
if (!navigator.serviceWorker.controller) {
|
||||
console.log('No controller → reloading under SW control');
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// turn url params into key/value map
|
||||
const map = Object.fromEntries(
|
||||
(location.search || location.hash).slice(1).split('&')
|
||||
.map(v => v.split('='))
|
||||
.map(([k, v = true]) => [k, v])
|
||||
);
|
||||
if (map.debug) {
|
||||
console.log({ map });
|
||||
}
|
||||
|
||||
navigator.serviceWorker.controller.postMessage({
|
||||
clear: map.clear,
|
||||
disable: map.disable,
|
||||
version: map.version || version
|
||||
});
|
||||
|
||||
navigator.serviceWorker.addEventListener('message', e => {
|
||||
console.log('[SM]', e.data);
|
||||
setTimeout(() => {
|
||||
if (map.mesh) {
|
||||
location.replace('/mesh/index.html');
|
||||
} else {
|
||||
location.replace('/kiri/index.html');
|
||||
}
|
||||
}, map.delay || 10);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>Booting Grid.Space</body>
|
||||
|
||||
</html>
|
||||
203
web/boot/service.js
Normal file
203
web/boot/service.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// --- configurable ---
|
||||
const CACHE_VERSION = 'boot-001';
|
||||
const BUNDLE_URL = '/boot/bundle.bin';
|
||||
const VERSION_KEY = '/__version__';
|
||||
|
||||
let loaded = 0;
|
||||
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 => {
|
||||
const data = e.data || {};
|
||||
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);
|
||||
let needPreload = false;
|
||||
|
||||
const existing = await cache.match(VERSION_KEY);
|
||||
if (!existing) {
|
||||
log('no version stored → preload');
|
||||
needPreload = true;
|
||||
} else {
|
||||
const stored = await existing.text();
|
||||
if (stored !== incomingVersion) {
|
||||
log(`mismatch ${stored} ≠ ${incomingVersion} → preload`);
|
||||
needPreload = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needPreload) {
|
||||
await preloadBundle();
|
||||
await cache.put(
|
||||
VERSION_KEY,
|
||||
new Response(String(incomingVersion), { headers: { 'Content-Type': 'text/plain' } })
|
||||
);
|
||||
broadcast(`preload added ${loaded} files`);
|
||||
} 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;
|
||||
}
|
||||
|
||||
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 ---
|
||||
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(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) {
|
||||
const cache = await caches.open(CACHE_VERSION);
|
||||
const hit = await cache.match(req, { ignoreSearch: true });
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
const net = await fetch_safe(req);
|
||||
cache.put(req, net.clone());
|
||||
log(`put: ${net.url}`);
|
||||
return net;
|
||||
}
|
||||
|
||||
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() {
|
||||
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);
|
||||
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 };
|
||||
const resp = new Response(blob, { headers });
|
||||
loaded++;
|
||||
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 });
|
||||
}
|
||||
|
||||
const files = {};
|
||||
for (const { name, offset, len } of table)
|
||||
files[name] = buf.slice(offset, offset + len);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -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",
|
||||
|
|
@ -864,10 +864,12 @@ 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",
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -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/",
|
||||
|
|
|
|||
Loading…
Reference in a new issue