Merge branch 'master' of https://github.com/GridSpace/grid-apps into drill-optimization
2
.github/workflows/build-docs.yml
vendored
|
|
@ -2,6 +2,8 @@ name: 'docs-publish'
|
|||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
|
|
|
|||
2
.github/workflows/build-test.yml
vendored
|
|
@ -3,6 +3,8 @@ name: Test Electron App Production Build
|
|||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
paths:
|
||||
- '!docs/**'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
|
|
|
|||
2
.github/workflows/prettier-check.yml
vendored
|
|
@ -2,7 +2,7 @@ name: 'Docs Fomatting Check'
|
|||
on:
|
||||
push:
|
||||
paths:
|
||||
- './docs/**'
|
||||
- 'docs/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
|
|
|||
4
.github/workflows/prod-test.yml
vendored
|
|
@ -3,6 +3,8 @@ name: Test NPM Production Build
|
|||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
paths:
|
||||
- '!docs/**'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
|
|
@ -24,4 +26,4 @@ jobs:
|
|||
run: npm run setup
|
||||
|
||||
- name: dry run production build
|
||||
run: npm run prod-dryrun
|
||||
run: npm run dryrun-prod
|
||||
|
|
|
|||
5
.gitignore
vendored
|
|
@ -1,6 +1,8 @@
|
|||
.DS_Store
|
||||
.bache
|
||||
.docusaurus
|
||||
.env
|
||||
alt
|
||||
app.json
|
||||
build
|
||||
data
|
||||
|
|
@ -9,7 +11,8 @@ jszip-esm.js
|
|||
logs
|
||||
mod
|
||||
node_modules
|
||||
pack
|
||||
package-lock.json
|
||||
three.js
|
||||
tmp
|
||||
pack
|
||||
web/boot/bundle*
|
||||
|
|
|
|||
37
app-el.js
|
|
@ -9,20 +9,11 @@ const appDir = path.join(usrDir, 'gapp');
|
|||
const cnfDir = path.join(appDir, 'conf');
|
||||
const logDir = path.join(appDir, 'logs');
|
||||
const datDir = path.join(appDir, 'data');
|
||||
const debug = process.argv.slice(2).map(v => v.replaceAll('-', '')).contains('debugg');
|
||||
const devel = process.argv.slice(2).map(v => v.replaceAll('-', '')).contains('devel');
|
||||
const debug = process.argv.slice(2).map(v => v.replaceAll('-', '')).indexOf('debugg') >= 0;
|
||||
const devel = process.argv.slice(2).map(v => v.replaceAll('-', '')).indexOf('devel') >= 0;
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||
|
||||
// console.log({ appDir, usrDir, logDir, datDir, basDir });
|
||||
// console.log({ argv: process.argv, debug, devel });
|
||||
// console.log({
|
||||
// cwd: process.cwd(),
|
||||
// dir: require('fs').readdirSync('.'),
|
||||
// out: require('fs').readdirSync(appDir),
|
||||
// ___: require('fs').readdirSync(basDir)
|
||||
// });
|
||||
|
||||
server({
|
||||
port: 5309,
|
||||
apps: basDir,
|
||||
|
|
@ -40,23 +31,31 @@ function createWindow() {
|
|||
width: 1600,
|
||||
height: 900,
|
||||
webPreferences: {
|
||||
// contextIsolation: true,
|
||||
// nodeIntegration: true,
|
||||
// sandbox: true,
|
||||
// preload: undefined
|
||||
// nodeIntegration: true,
|
||||
// contextIsolation: false
|
||||
// preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
});
|
||||
|
||||
const { webContents } = mainWindow;
|
||||
|
||||
mainWindow.loadURL('http://localhost:5309/kiri');
|
||||
|
||||
// default normal url navigation or page opens to happen outside Electron
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
webContents.setWindowOpenHandler((details) => {
|
||||
// console.log('EXTERNAL', details);
|
||||
shell.openExternal(details.url);
|
||||
return { action: 'deny' }
|
||||
});
|
||||
|
||||
// prevent "other" urls from opening inside Electron (alerts are problematic)
|
||||
mainWindow.webContents.on('will-navigate', (event, url) => {
|
||||
webContents.on('will-navigate', (event, url) => {
|
||||
// console.log('DIVERT', url);
|
||||
if (url.endsWith('/kiri') || url.endsWith('/kiri/')) {
|
||||
if (url.endsWith('/kiri') || url.endsWith('/kiri/')) {
|
||||
return;
|
||||
}
|
||||
if (url.endsWith('/mesh') || url.endsWith('/mesh/')) {
|
||||
|
|
@ -66,9 +65,13 @@ function createWindow() {
|
|||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
webContents.on('did-finish-load', () => {
|
||||
mainWindow.webContents.executeJavaScript(`{ let x = document.getElementById('app-quit'); if (x) { x.onclick = () => window.close() } }; null;`);
|
||||
});
|
||||
|
||||
if (devel) {
|
||||
console.log("opening developer tools");
|
||||
mainWindow.webContents.openDevTools();
|
||||
webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,9 +80,7 @@ app.on('ready', () => {
|
|||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
|
|
|
|||
131
app.js
|
|
@ -15,6 +15,10 @@ const license = require_fresh('./src/moto/license.js');
|
|||
const version = license.version || "rogue";
|
||||
const netdb = require('@gridspace/net-level-client');
|
||||
const PATH = require('path');
|
||||
const isElectron = !!process.versions.electron ||
|
||||
process.env.ELECTRON_RUN_AS_NODE === '1' ||
|
||||
process.env.ELECTRON_NO_ATTACH === '1' ||
|
||||
process.env.ELECTRON_BUILD === '1';
|
||||
|
||||
const append = { mesh:'', kiri:'' };
|
||||
const code = {};
|
||||
|
|
@ -31,6 +35,8 @@ let oversion;
|
|||
let dversion;
|
||||
let lastmod;
|
||||
let logger;
|
||||
let single;
|
||||
let dryrun;
|
||||
let debug;
|
||||
let http;
|
||||
let util;
|
||||
|
|
@ -60,12 +66,15 @@ netdb.create = async function(map = {}) {
|
|||
|
||||
function init(mod) {
|
||||
const ENV = mod.env;
|
||||
const inElectron = isElectron || ENV.electron;
|
||||
|
||||
startTime = time();
|
||||
lastmod = mod.util.lastmod;
|
||||
logger = mod.log;
|
||||
pre = ENV.pre || mod.meta.pre;
|
||||
debug = ENV.debug || mod.meta.debug;
|
||||
dryrun = ENV.dryrun;
|
||||
single = ENV.single;
|
||||
oversion = ENV.over || mod.meta.over;
|
||||
crossOrigin = ENV.xorigin || mod.meta.xorigin || false;
|
||||
serviceWorker = (ENV.service || mod.meta.service) !== false;
|
||||
|
|
@ -74,14 +83,10 @@ function init(mod) {
|
|||
dir = mod.dir;
|
||||
log = mod.log;
|
||||
|
||||
if (ENV.single) console.log({ cwd: process.cwd(), env: ENV });
|
||||
if (single) console.log({ cwd: process.cwd(), env: ENV });
|
||||
dversion = debug ? `_${version}` : version;
|
||||
forceUseCache = ENV.cache ? true : false;
|
||||
|
||||
if (!ENV.electron) {
|
||||
generateDevices();
|
||||
}
|
||||
|
||||
if (pre) {
|
||||
for (let [k,v] of Object.entries(productionMap)) {
|
||||
productionMap[`${pre}${k}`] = `${pre}${v}`;
|
||||
|
|
@ -93,6 +98,7 @@ function init(mod) {
|
|||
req.url = req.url.substring(pre.length);
|
||||
req.app.path = req.app.path.substring(pre.length);
|
||||
req.app.ispre = true;
|
||||
// console.log('MATCH1', dir, cookie, req.url);
|
||||
return true;
|
||||
}
|
||||
let cookie = cookieValue(req.headers.cookie, "version") || '';
|
||||
|
|
@ -101,9 +107,12 @@ function init(mod) {
|
|||
vmatch = [ vmatch ];
|
||||
}
|
||||
if (vmatch.indexOf("*") >= 0) {
|
||||
// console.log('MATCH2', dir, cookie, req.url);
|
||||
return true;
|
||||
}
|
||||
return vmatch.indexOf(cookie) >= 0;
|
||||
let match = vmatch.indexOf(cookie) >= 0;
|
||||
// console.log('MATCH3', dir, cookie, req.url);
|
||||
return match;
|
||||
});
|
||||
|
||||
mod.on.testv((version) => {
|
||||
|
|
@ -122,6 +131,7 @@ function init(mod) {
|
|||
mod.add(serveWasm);
|
||||
mod.add(serveCode);
|
||||
mod.add(fullpath({
|
||||
"/boot" : redir((pre??"") + "/boot/", 301),
|
||||
"/kiri" : redir((pre??"") + "/kiri/", 301),
|
||||
"/mesh" : redir((pre??"") + "/mesh/", 301),
|
||||
"/meta" : redir((pre??"") + "/meta/", 301),
|
||||
|
|
@ -139,7 +149,8 @@ function init(mod) {
|
|||
return "reload";
|
||||
});
|
||||
}
|
||||
mod.add(rewriteHtmlVersion);
|
||||
mod.add(addAppHeaders);
|
||||
mod.static("/lib/", "alt");
|
||||
mod.static("/lib/", "src");
|
||||
mod.static("/obj/", "web/obj");
|
||||
mod.static("/font/", "web/font");
|
||||
|
|
@ -147,19 +158,26 @@ function init(mod) {
|
|||
mod.static("/mesh/", "web/mesh");
|
||||
mod.static("/moto/", "web/moto");
|
||||
mod.static("/kiri/", "web/kiri");
|
||||
mod.static("/boot/", "web/boot");
|
||||
|
||||
// module loader
|
||||
function load_modules(root, force) {
|
||||
lastmod(`${dir}/${root}`) && fs.readdirSync(`${dir}/${root}`).forEach(mdir => {
|
||||
const modpath = `${root}/${mdir}`;
|
||||
if (dir.charAt(0) === '.' && !ENV.single) return;
|
||||
if (dir.charAt(0) === '.' && !single) return;
|
||||
const stats = fs.lstatSync(`${mod.dir}/${modpath}`);
|
||||
if (!(stats.isDirectory() || stats.isSymbolicLink())) return;
|
||||
if (util.isfile(PATH.join(mod.dir,modpath,".disable"))) return;
|
||||
const isDebugMod = util.isfile(PATH.join(mod.dir,modpath,".debug"));
|
||||
const isElectronMod = util.isfile(PATH.join(mod.dir,modpath,".electron"));
|
||||
if (force || (ENV.electron && !isElectronMod)) return;
|
||||
if (force || (!ENV.electron && isElectronMod && !isDebugMod)) return;
|
||||
if (inElectron && !isElectronMod) {
|
||||
logger.log({ skip_non_electron_mod: modpath });
|
||||
return;
|
||||
}
|
||||
if (!inElectron && isElectronMod && !isDebugMod) {
|
||||
logger.log({ skip_electron_mod: modpath });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loadModule(mod, modpath);
|
||||
} catch (error) {
|
||||
|
|
@ -189,6 +207,33 @@ function init(mod) {
|
|||
append[key] = minify(append[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// create alt artifacts with module extensions
|
||||
if (dryrun || !isElectron) {
|
||||
logger.log('creating artifacts', Object.keys(append));
|
||||
for (let [ key, val ] of Object.entries(append)) {
|
||||
// append mains
|
||||
let src = `${dir}/src/main/${key}.js`;
|
||||
if (!fs.existsSync(src)) {
|
||||
logger.log('missing', src);
|
||||
continue;
|
||||
}
|
||||
fs.mkdirSync(`${dir}/alt/main`, { recursive: true });
|
||||
let body = fs.readFileSync(src);
|
||||
fs.writeFileSync(`${dir}/alt/main/${key}.js`, body + val);
|
||||
// append packed mains
|
||||
src= `${dir}/src/pack/${key}-main.js`;
|
||||
if (!fs.existsSync(src)) {
|
||||
logger.log('missing', src);
|
||||
continue;
|
||||
}
|
||||
fs.mkdirSync(`${dir}/alt/pack`, { recursive: true });
|
||||
body = fs.readFileSync(src);
|
||||
fs.writeFileSync(`${dir}/alt/pack/${key}-main.js`, body + val);
|
||||
}
|
||||
} else {
|
||||
logger.log('skipping artifacts');
|
||||
}
|
||||
};
|
||||
|
||||
// either add module assets to path or require(init.js)
|
||||
|
|
@ -253,11 +298,9 @@ function initModule(mod, file, dir) {
|
|||
const [ path, file ] = [ ...arguments ];
|
||||
if (lastmod(file)) {
|
||||
code[path] = fs.readFileSync(file);
|
||||
// console.log({ CODE: path, file });
|
||||
} else if (lastmod(mod.dir + '/' + file)) {
|
||||
const alt = mod.dir + '/' + file;
|
||||
code[path] = fs.readFileSync(alt);
|
||||
// console.log({ CODE: path, alt });
|
||||
} else {
|
||||
console.log({ MISSING_CODE: path, file });
|
||||
}
|
||||
|
|
@ -286,14 +329,14 @@ function initModule(mod, file, dir) {
|
|||
const path = mod.dir + '/' + dir + '/' + file;
|
||||
try {
|
||||
const body = fs.readFileSync(path);
|
||||
// console.log({ inject: code, file, opt });
|
||||
if (debug && !single) 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) {
|
||||
|
|
@ -412,24 +455,6 @@ function serveWasm(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
// pack/concat device script strings to inject into /code/ scripts
|
||||
function generateDevices() {
|
||||
let root = PATH.join(dir,"src","kiri","dev");
|
||||
let devs = {};
|
||||
fs.readdirSync(root).forEach(type => {
|
||||
let map = devs[type] = devs[type] || {};
|
||||
fs.readdirSync(PATH.join(root,type)).forEach(device => {
|
||||
let deviceName = device.endsWith('.json')
|
||||
? device.substring(0,device.length-5)
|
||||
: device;
|
||||
map[deviceName] = JSON.parse(fs.readFileSync(PATH.join(root,type,device)));
|
||||
});
|
||||
});
|
||||
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};`);
|
||||
}
|
||||
|
||||
function minify(code) {
|
||||
let mini = uglify.minify(code.toString(), {
|
||||
compress: {
|
||||
|
|
@ -524,7 +549,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -547,10 +578,12 @@ function cookieValue(cookie,key) {
|
|||
}
|
||||
}
|
||||
|
||||
function rewriteHtmlVersion(req, res, next) {
|
||||
function addAppHeaders(req, res, next) {
|
||||
if ([
|
||||
"/kiri/",
|
||||
"/mesh/",
|
||||
"/lib/gpu/raster.js",
|
||||
"/lib/gpu/raster-worker.js",
|
||||
"/lib/mesh/work.js",
|
||||
"/lib/kiri/run/worker.js",
|
||||
"/lib/kiri/run/minion.js",
|
||||
|
|
@ -559,36 +592,6 @@ function rewriteHtmlVersion(req, res, next) {
|
|||
].indexOf(req.app.path) >= 0) {
|
||||
addCorsHeaders(req, res);
|
||||
}
|
||||
if ([
|
||||
"/lib/main/kiri.js",
|
||||
"/lib/main/mesh.js"
|
||||
].indexOf(req.app.path) >= 0) {
|
||||
addCorsHeaders(req, res);
|
||||
// const data = append[req.app.path.split('/')[3].split('.')[0]];
|
||||
const data = append[req.app.path.split('.')[0].split('/').pop()];
|
||||
|
||||
if (!data) return next();
|
||||
if (debug) logger.log({ append: req.app.path, data: data.length });
|
||||
|
||||
const real_write = res.write;
|
||||
const real_end = res.end;
|
||||
let body = '';
|
||||
|
||||
res.write = function (chunk, encoding) {
|
||||
body += chunk.toString(encoding);
|
||||
};
|
||||
|
||||
res.end = function (chunk, encoding) {
|
||||
if (chunk) {
|
||||
body += chunk.toString(encoding);
|
||||
}
|
||||
body += data;
|
||||
res.setHeader('Content-Length', Buffer.byteLength(body));
|
||||
real_write.call(res, body);
|
||||
real_end.call(res);
|
||||
};
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
tag=${1:-latest}
|
||||
npm run clear-cache
|
||||
npm run clean
|
||||
./bin/build-upload win ${tag} && \
|
||||
./bin/build-upload linux ${tag} && \
|
||||
./bin/build-upload mac ${tag}
|
||||
|
|
|
|||
23
bin/bundle-prod.config.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compress": true,
|
||||
"outputs": {
|
||||
"bundle": "./web/boot/bundle-{version}.bin"
|
||||
},
|
||||
"inputs": [
|
||||
{ "root": "web/moto", "prefix": "moto" },
|
||||
{ "root": "web/kiri", "prefix": "kiri" },
|
||||
{ "root": "web/font", "prefix": "font" },
|
||||
{ "root": "src/wasm", "prefix": "wasm" },
|
||||
{ "root": "src/wasm", "prefix": "lib/kiri/wasm" },
|
||||
{ "src": "alt/pack/kiri-main.js", "dst": "lib/main/kiri.js" },
|
||||
{ "src": "src/pack/kiri-main.js", "dst": "lib/main/kiri.js" },
|
||||
{ "src": "src/pack/kiri-work.js", "dst": "lib/kiri/run/worker.js" },
|
||||
{ "src": "src/pack/kiri-pool.js", "dst": "lib/kiri/run/minion.js" },
|
||||
{ "src": "src/gpu/raster-worker.js", "dst": "lib/gpu/raster-worker.js" },
|
||||
{ "src": "src/ext/tween.js", "dst": "lib/ext/tween.js" }
|
||||
],
|
||||
"excludes": [
|
||||
"src/pack",
|
||||
"web/boot/bundle*"
|
||||
]
|
||||
}
|
||||
17
bin/bundle.config.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compress": false,
|
||||
"outputs": {
|
||||
"bundle": "./web/boot/bundle-{version}.bin"
|
||||
},
|
||||
"inputs": [
|
||||
{ "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*"
|
||||
]
|
||||
}
|
||||
166
bin/bundler.mjs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import uglify from 'uglify-js';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
const version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
|
||||
const cfg = JSON.parse(fs.readFileSync(process.argv[2] || './bin/bundle.config.json', 'utf8'));
|
||||
const { inputs, outputs, excludes, compress } = cfg;
|
||||
const excludePre = (excludes ?? []).filter(s => s.indexOf('*') >= 0).map(s => s.replaceAll('*',''));
|
||||
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;
|
||||
}
|
||||
seen.add(real);
|
||||
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name);
|
||||
let st;
|
||||
try { st = fs.statSync(p); } catch { continue; }
|
||||
if (st.isDirectory()) yield* walk(p, seen);
|
||||
else yield p;
|
||||
}
|
||||
}
|
||||
|
||||
// --- collect all files according to config ---
|
||||
let entries = [];
|
||||
let virtMap = new Set();
|
||||
for (const input of inputs) {
|
||||
const { root, prefix, src, dst } = input;
|
||||
|
||||
if (src && dst) {
|
||||
if (!fs.existsSync(src)) {
|
||||
console.log({ src_missing: src });
|
||||
} else if (virtMap.has(dst)) {
|
||||
console.log('pre-existing', dst);
|
||||
} else {
|
||||
entries.push({ file: src, virt: dst });
|
||||
virtMap.add(dst);
|
||||
}
|
||||
}
|
||||
|
||||
if (root)
|
||||
for (const file of walk(root)) {
|
||||
const rel = path.relative(root, file).replace(/\\/g, '/');
|
||||
const virt = prefix ? `${prefix}/${rel}` : rel;
|
||||
if (virtMap.has(virt)) {
|
||||
console.log('pre-existing', virt, rel, root);
|
||||
} else {
|
||||
entries.push({ file, virt });
|
||||
virtMap.add(virt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- build header and data ---
|
||||
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;
|
||||
}
|
||||
if (excludePre.filter(ex => file.startsWith(ex)).length) {
|
||||
continue;
|
||||
}
|
||||
let record = cache.get(file);
|
||||
if (record) {
|
||||
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 (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;
|
||||
}
|
||||
data = Buffer.from(data.code);
|
||||
fs.mkdirSync(path.dirname(cpath), { recursive: true });
|
||||
fs.writeFileSync(cpath, data);
|
||||
}
|
||||
}
|
||||
record = { virt, offset, length: data.length };
|
||||
cache.set(file, record);
|
||||
entryMeta.push(record);
|
||||
dataParts.push(data);
|
||||
offset += data.length;
|
||||
if (debug) console.log('write', virt, data.length);
|
||||
}
|
||||
}
|
||||
|
||||
// temporary header without offsets to measure total size
|
||||
let tempHeaderParts = [];
|
||||
for (const { virt, offset, length } of entryMeta) {
|
||||
const name = Buffer.from(virt, 'utf8');
|
||||
const nlen = Buffer.alloc(2); nlen.writeUInt16LE(name.length);
|
||||
const offb = Buffer.alloc(4); offb.writeUInt32LE(0); // placeholder
|
||||
const lenb = Buffer.alloc(4); lenb.writeUInt32LE(length);
|
||||
tempHeaderParts.push(nlen, name, offb, lenb);
|
||||
}
|
||||
const count = Buffer.alloc(4); count.writeUInt32LE(entries.length);
|
||||
const tempHeader = Buffer.concat([count, ...tempHeaderParts]);
|
||||
const headerSize = tempHeader.length; // true data offset base
|
||||
|
||||
// now rebuild header with correct absolute offsets
|
||||
let headerParts = [];
|
||||
for (const { virt, offset, length } of entryMeta) {
|
||||
const name = Buffer.from(virt, 'utf8');
|
||||
const nlen = Buffer.alloc(2); nlen.writeUInt16LE(name.length);
|
||||
const offb = Buffer.alloc(4); offb.writeUInt32LE(headerSize + offset);
|
||||
const lenb = Buffer.alloc(4); lenb.writeUInt32LE(length);
|
||||
headerParts.push(nlen, name, offb, lenb);
|
||||
// console.log(virt);
|
||||
}
|
||||
|
||||
const header = Buffer.concat([count, ...headerParts]);
|
||||
const full = Buffer.concat([header, ...dataParts]);
|
||||
|
||||
// --- write final bundle ---
|
||||
const outPath = (outputs.bundle || './bundle.bin').replace('{version}', version);
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||
fs.writeFileSync(outPath, full); // ⚡ write raw binary
|
||||
|
||||
console.log(`✅ Bundled ${entries.length} files → ${outPath} (${full.length} bytes)`);
|
||||
|
|
@ -5,6 +5,7 @@ exports.default = async function notarizing(context) {
|
|||
const { electronPlatformName, appOutDir } = context;
|
||||
|
||||
if (process.env.SKIPNOT || electronPlatformName !== 'darwin') {
|
||||
console.log(' ** skip notarizing:', electronPlatformName);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,30 +2,30 @@ const fs = require('fs-extra');
|
|||
const path = require('path');
|
||||
const server = require('@gridspace/app-server');
|
||||
|
||||
const altTmp = path.join('tmp','alt');
|
||||
fs.copySync("alt", altTmp, { dereference: true });
|
||||
|
||||
// deref src, web, mod for windows
|
||||
const srcTmp = path.join('tmp','src');
|
||||
fs.copySync("src", srcTmp, { dereference: true });
|
||||
fs.copySync("src", srcTmp, { dereference: true, filter:(src) => {
|
||||
return true || src.indexOf('src/main') < 0;
|
||||
} });
|
||||
|
||||
const webTmp = path.join('tmp','web');
|
||||
fs.copySync("web", webTmp, { dereference: true });
|
||||
fs.copySync("web", webTmp, { dereference: true, filter:(src) => {
|
||||
return src.indexOf('web/boot') < 0;
|
||||
} });
|
||||
|
||||
const modTmp = path.join('tmp','mod');
|
||||
if (fs.existsSync("mod"))
|
||||
fs.copySync("mod", modTmp, { dereference: true, filter:(src,dst) => {
|
||||
const ok =
|
||||
src === 'mod' ||
|
||||
src.indexOf('mod/standalone') === 0 ||
|
||||
src.indexOf('mod/node_modules') === 0;
|
||||
const ok = src === 'mod' || src.indexOf('mod/standalone') === 0;
|
||||
return ok;
|
||||
} });
|
||||
|
||||
const modsTmp = path.join('tmp','mods');
|
||||
fs.copySync("mods", modsTmp, { dereference: true, filter:(src,dst) => {
|
||||
const ok =
|
||||
src === 'mods' ||
|
||||
src.indexOf('mods/bambu') === 0 ||
|
||||
src.indexOf('mods/electron') === 0 ||
|
||||
src.indexOf('mods/node_modules') === 0;
|
||||
const ok = src === 'mods' || src.indexOf('mods/bambu') === 0;
|
||||
return ok;
|
||||
} });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { build, transform } from 'esbuild';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import glob from 'fast-glob';
|
||||
|
||||
// Get build mode from command line argument
|
||||
|
|
@ -38,6 +39,30 @@ async function appendExtraModules(extras, outfile, minify = false) {
|
|||
console.log(`Appended ${files.length} module(s) to ${outfile}`);
|
||||
}
|
||||
|
||||
async function generateDevices(dir = '') {
|
||||
let root = path.join(dir, "src", "kiri", "dev");
|
||||
let devs = {};
|
||||
let files = await fs.readdir(root);
|
||||
for (let type of files) {
|
||||
let map = devs[type] = devs[type] || {};
|
||||
let devices = await fs.readdir(path.join(root, type));
|
||||
for (let device of devices) {
|
||||
let deviceName = device.endsWith('.json')
|
||||
? device.substring(0, device.length - 5)
|
||||
: device;
|
||||
map[deviceName] = JSON.parse(await fs.readFile(path.join(root, type, device)));
|
||||
}
|
||||
}
|
||||
let dstr = JSON.stringify(devs);
|
||||
fs.mkdir(path.join(dir, "src", "pack"), { recursive: true });
|
||||
await fs.writeFile(path.join(dir, "src", "pack", "kiri-devs.js"), `export const devices = ${dstr};`);
|
||||
if (true) {
|
||||
// create alt artifacts
|
||||
await fs.mkdir('alt/pack', { recursive: true });
|
||||
await fs.writeFile(path.join(dir, "alt", "pack", "kiri-devs.js"), `export const devices = ${dstr};`);
|
||||
}
|
||||
}
|
||||
|
||||
const logOverride = {
|
||||
'duplicate-class-member': 'silent',
|
||||
'duplicate-object-key': 'silent',
|
||||
|
|
@ -66,12 +91,15 @@ const rec = {
|
|||
logOverride,
|
||||
minify: isProd, // false for dev, true for prod
|
||||
platform: 'browser',
|
||||
sourcemap: !isProd, // true for dev, false for prod
|
||||
sourcemap: false,
|
||||
target: 'es2020',
|
||||
};
|
||||
|
||||
async function buildApp() {
|
||||
try {
|
||||
// Concatenate kiri devices
|
||||
generateDevices();
|
||||
|
||||
// Bundle mesh main app
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/main/mesh.js' ],
|
||||
|
|
|
|||
23
bin/update-version.sh
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <version>"
|
||||
echo "Example: $0 4.5.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$1
|
||||
|
||||
# Update package.json (line 3, no indentation)
|
||||
awk -v ver="$VERSION" '/"version":/ {print " \"version\": \"" ver "\","; next} 1' package.json > package.json.tmp && mv package.json.tmp package.json
|
||||
|
||||
# Update src/moto/license.js (line 6, VERSION field)
|
||||
awk -v ver="$VERSION" '/VERSION:/ {print " VERSION: \"" ver "\""; next} 1' src/moto/license.js > src/moto/license.js.tmp && mv src/moto/license.js.tmp src/moto/license.js
|
||||
|
||||
# Update web/kiri/manifest.json (line 2, preserves "Kiri:Moto" prefix)
|
||||
awk -v ver="$VERSION" '/"name":/ {print " \"name\": \"Kiri:Moto " ver "\","; next} 1' web/kiri/manifest.json > web/kiri/manifest.json.tmp && mv web/kiri/manifest.json.tmp web/kiri/manifest.json
|
||||
|
||||
echo "Updated version to $VERSION in:"
|
||||
echo " - package.json"
|
||||
echo " - src/moto/license.js"
|
||||
echo " - web/kiri/manifest.json"
|
||||
|
|
@ -7,7 +7,28 @@ module.exports = {
|
|||
favicon: 'img/gs-logo.png',
|
||||
staticDirectories: ['./docs/static/'],
|
||||
// ...
|
||||
plugins: [require.resolve('docusaurus-lunr-search')],
|
||||
plugins: [
|
||||
require.resolve('docusaurus-lunr-search'),
|
||||
['@docusaurus/plugin-client-redirects',
|
||||
{
|
||||
redirects: [
|
||||
// /docs/oldDoc -> /docs/newDoc
|
||||
{
|
||||
to: '/kiri-moto',
|
||||
from: '/projects/kiri-moto',
|
||||
},
|
||||
// Redirect from multiple old paths to the new path
|
||||
{
|
||||
to: '/gridbot',
|
||||
from: '/projects/gridbot',
|
||||
},
|
||||
{
|
||||
to: '/mesh-tool',
|
||||
from: '/projects/mesh-tool',
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ const config = {
|
|||
useTabs: false,
|
||||
bracketSpacing: true,
|
||||
objectWrap: "preserve",
|
||||
tabWidth: 4,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
bracketSameLine: true,
|
||||
arrowParens: "always",
|
||||
endOfLine: "crlf"
|
||||
endOfLine: "lf"
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
|
|
@ -7,16 +7,10 @@ This is a community driven project, and we welcome any contributions you'd like
|
|||
## Running Locally
|
||||
|
||||
- Ensure you have [node](https://nodejs.org/en/download/), or an equivalent installed
|
||||
- Clone the [repository](https://github.com/GridSpace/grid-apps)
|
||||
- Run `npm run setup`
|
||||
- Run `npm run dev`
|
||||
|
||||
## Running Docs Locally
|
||||
|
||||
- Ensure you have [node](https://nodejs.org/en/download/), or an equivalent installed
|
||||
- Clone the [repository](https://github.com/GridSpace/grid-apps)
|
||||
- Run `npm run setup`
|
||||
- Run `npm run docs-dev`
|
||||
- Clone the [repository](https://github.com/GridSpace/grid-apps) from https://github.com/GridSpace/grid-apps
|
||||
- `npm run setup`
|
||||
- `npm run dev`
|
||||
- Open browser [http://localhost:8080/kiri](http://localhost:8080/kiri)
|
||||
|
||||
|
||||
## How to add a new machine
|
||||
|
|
|
|||
29
docs/kiri-moto/CAM/interface.mdx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
description: Overview of CAM mode
|
||||
---
|
||||
|
||||
import { ImageCarousel } from "@site/docs/src/components/carousel.js";
|
||||
|
||||
# Interface
|
||||
|
||||
The CAM interface is, in most respects, the same as the [general interface](/kiri-moto/interface). It is highly recommended to familiarize yourself with the general interface before using the CAM interface.
|
||||
|
||||
With that in mind, here are the key differences:
|
||||
|
||||
## Operation List
|
||||
|
||||
The Operation List defines the order in which operations are
|
||||
performed on the model. Selecting the **plus** button will open a
|
||||
popup menu for choosing an operation to add. The operations are
|
||||
draggable, enabling easy re-ordering.
|
||||
|
||||
- An operation can be disabled by **Ctrl+clicking** it. Disabled operations will be skipped in the slice/preview/export process.
|
||||
- The thin blue bar in the Operation List is also draggable. Moving the bar automatically disables all operations below it. This allows you to slice, preview, and export up to a specific point in the timeline.
|
||||
|
||||
<ImageCarousel base="/img/CAM/interface/" images="camInterface" sml />
|
||||
|
||||
## Origin Selection
|
||||
|
||||
Origin Selection (introduced in v4.3) enables automatically setting an origin to points of interest on the model, including corners and arc centers.
|
||||
|
||||

|
||||
|
|
@ -39,7 +39,6 @@ Customize how your G-code is formatted for your controller or post-processor:
|
|||
- **Enable Laser** — Enables [Laser](./ops#laser-operations) operations (typically uses `M3/M5`; adjusts movement strategy)
|
||||
- **File Extension** — Optional override for exported file type (e.g., `.nc`, `.ngc`, `.cnc`, `.cam`, `.gcode`, `.txt`)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## G-code Macros
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ description: Variable Substitutions and Expressions in GCode Macros
|
|||
- \{pos_y\} = last output Y position
|
||||
- \{pos_z\} = last output Z position
|
||||
- \{progress\} = 0-100% state of print output
|
||||
- \{remain_time\} = print time remaining in minutes
|
||||
- \{tool\} | \{nozzle\} = current tool in use
|
||||
- \{tool_count\} = number of tools used in print
|
||||
- \{tool_used\__n_\} = whether tool # _n_ is used in print
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
---
|
||||
description: A tour of Kiri:Moto's web-based interface
|
||||
---
|
||||
|
||||
# Interface
|
||||
|
||||
Kiri:Moto's interface is designed for maximum access to the workspace and to present only the options that are necessary and relevant to the current action.
|
||||
|
||||
The left menus control workspace, work mode, and objects while.
|
||||
|
||||
The right menus control the behavior and settings for slicing, path routing, and path to code exports.
|
||||
|
||||
[OBJ](https://www.cs.cmu.edu/~mbz/personal/graphics/obj.html), [STL](<https://en.wikipedia.org/wiki/STL_(file_format)>), and [3MF](https://docs.fileformat.com/3d/3mf/) files import natively. [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics), [JPG](https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format), and [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics) are auto-extruded into a 3D parts.
|
||||
|
||||

|
||||
|
||||
In CNC mode, there is an additional storyboard menu at the bottom where you can plan and re-order milling operations.
|
||||
|
||||

|
||||
|
||||
The Setup menu offers access to Device selection and customization. 
|
||||
|
||||

|
||||
|
||||
Application preferences are mode-dependent. For example, CNC preferences show animation controls.
|
||||
|
||||

|
||||
|
||||

|
||||
54
docs/kiri-moto/interface.mdx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
description: A tour of Kiri:Moto's web-based interface
|
||||
---
|
||||
|
||||
import { ImageCarousel } from "@site/docs/src/components/carousel.js";
|
||||
|
||||
# Interface
|
||||
|
||||
Kiri:Moto's interface is designed for maximum access to the workspace, showing only the options necessary and relevant to the current action.
|
||||
|
||||
[OBJ](https://www.cs.cmu.edu/~mbz/personal/graphics/obj.html), [STL](<https://en.wikipedia.org/wiki/STL_(file_format)>), and [3MF](https://docs.fileformat.com/3d/3mf/) files import natively. [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics), [JPG](https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format), and [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics) files are auto-extruded into 3D parts.
|
||||
|
||||
For the CAM interface, click [here](/kiri-moto/CAM).
|
||||
|
||||

|
||||
|
||||
## Top Menu
|
||||
|
||||
- **Left:** Buttons to move through the `Arrange → Slice → Preview → Export` workflow. These steps are also available as [keyboard shortcuts](/kiri-moto/controls). Selecting an option here will automatically run prior steps as needed.
|
||||
- **Center:** Clicking the [logo](/kiri-moto) opens a dialog with version selection, helpful links, and your current Kiri:Moto version. If you are unsure which version to use, check the [release notes](https://github.com/GridSpace/grid-apps/releases/) on GitHub.
|
||||
- **Right:** Options for importing/exporting the workspace, manipulating the view, changing modes, and setting preferences. Kiri:Moto currently supports CAM, FDM, SLA, and LASER modes. Some other modes are extensions of the LASER workflow, but do not have first-class support. The [preferences menu](#preferences) will be covered later in this section.
|
||||
- Changing the mode updates the left menu and enables/disables mode-specific options such as the `CAM Operation` menu and the `CAM Animate` step.
|
||||
- Learn about K:M file types [here](/kiri-moto/faq#what-are-the-kmz-and-km-file-formats-short-for).
|
||||
|
||||
## Left Menu
|
||||
|
||||
The left menu contains options for the current mode. These vary widely:
|
||||
|
||||
- CAM has ample documentation on [Process Options](/kiri-moto/CAM/processOpts).
|
||||
- Other modes are less documented but many options are self-explanatory if you're familiar with the manufacturing process.
|
||||
- Hovering over an option provides tooltips. When in doubt, a quick search usually explains the option in detail.
|
||||
|
||||
## Right Menu
|
||||
|
||||
The right menu contains controls for objects in the workspace.
|
||||
|
||||
- Hovering over the arrow next to an object reveals options for deleting, renaming, disabling, or replacing it.
|
||||
- Disabled objects remain visible but are ignored during slicing and exporting.
|
||||
|
||||
## Machines Dialog
|
||||
|
||||

|
||||
|
||||
The device dialog is accessed through the settings dropdown. It allows you to configure a device or select a predefined one.
|
||||
|
||||
- The dropdown only shows devices available for the current mode.
|
||||
- Stock device settings are greyed out. To modify them, click `Customize`.
|
||||
- Learn more about how K:M handles CAM devices [here](/kiri-moto/CAM/machines).
|
||||
|
||||
## Preferences
|
||||
|
||||
Application preferences are also accessed through the settings dropdown. These are mode-dependent but share many common options. For example, CNC preferences include animation controls.
|
||||
|
||||
<ImageCarousel base="/img/interface/" images="prefs" />
|
||||
|
|
@ -35,6 +35,7 @@ const sidebars = {
|
|||
id: "kiri-moto/CAM/index",
|
||||
},
|
||||
items: [
|
||||
"kiri-moto/CAM/interface",
|
||||
"kiri-moto/CAM/processOpts",
|
||||
"kiri-moto/CAM/machines",
|
||||
"kiri-moto/CAM/tools",
|
||||
|
|
|
|||
|
|
@ -4,59 +4,69 @@ import "react-responsive-carousel/lib/styles/carousel.min.css";
|
|||
|
||||
const imageDescripts = {
|
||||
outline: [
|
||||
["/1.png", "outline of a complex part"],
|
||||
["/2.png", "outline of a non-flat part with inside holes"],
|
||||
["/3.png", "outside-only outline"],
|
||||
["/4.png", "inside-only outline"],
|
||||
["/1.png", "Outline of a complex part"],
|
||||
["/2.png", "Outline of a non-flat part with inside holes"],
|
||||
["/3.png", "Outside-only outline"],
|
||||
["/4.png", "Inside-only outline"],
|
||||
],
|
||||
level: [
|
||||
["/1.png", "level of a large part"],
|
||||
["/2.png", `level of a square part's stock`],
|
||||
["/3.png", `level of a part with a non-uniform top surface`],
|
||||
["/1.png", "Level of a large part"],
|
||||
["/2.png", `Level of a square part's stock`],
|
||||
["/3.png", `Level of a part with a non-uniform top surface`],
|
||||
],
|
||||
rough: [
|
||||
["/1.png", "rough cut of two parts"],
|
||||
["/2.png", "side-view of a rough cut"],
|
||||
["/3.png", "rough cut of a part with a complex top surface"],
|
||||
["/4.gif", "animate of complex rough cut"],
|
||||
["/1.png", "Rough cut of two parts"],
|
||||
["/2.png", "Side-view of a rough cut"],
|
||||
["/3.png", "Rough cut of a part with a complex top surface"],
|
||||
["/4.gif", "Animate of complex rough cut"],
|
||||
],
|
||||
contour: [
|
||||
["/1.png", "close-up of a precise contour"],
|
||||
["/2.png", "contour of multiple parts at once"],
|
||||
["/3.png", "low precision contour"],
|
||||
["/1.png", "Close-up of a precise contour"],
|
||||
["/2.png", "Contour of multiple parts at once"],
|
||||
["/3.png", "Low precision contour"],
|
||||
],
|
||||
register: [
|
||||
["/1.png", "2-drill register"],
|
||||
["/2.png", "3-drill register"],
|
||||
["/3.png", "jigsaw register"],
|
||||
["/3.png", "Jigsaw register"],
|
||||
],
|
||||
drill: [
|
||||
["/1.png", "drills for a robot baseplate"],
|
||||
["/2.png", "drills for a pegboard"],
|
||||
["/3.png", "selecting only matching-size holes"],
|
||||
["/1.png", "Drills for a robot baseplate"],
|
||||
["/2.png", "Drills for a pegboard"],
|
||||
["/3.png", "Selecting only matching-size holes"],
|
||||
],
|
||||
trace: [
|
||||
["/1.png", "toolpath tracing a design into a part"],
|
||||
["/2.png", "selecting loops to trace"],
|
||||
["/3.png", "tracing edges for deburring"],
|
||||
["/4.png", "trace to clear a pocket"],
|
||||
["/5.png", "arbitrary offset outline"],
|
||||
["/1.png", "Toolpath tracing a design into a part"],
|
||||
["/2.png", "Selecting loops to trace"],
|
||||
["/3.png", "Tracing edges for deburring"],
|
||||
["/4.png", "Trace to clear a pocket"],
|
||||
["/5.png", "Arbitrary offset outline"],
|
||||
],
|
||||
pocket: [
|
||||
["/1.png", "selecting areas to pocket"],
|
||||
["/2.png", "toolpath for pocket"],
|
||||
["/3.gif", "pocket animation"],
|
||||
["/4.png", "toolpath for v-bit carve"],
|
||||
["/5.png", "many-pocketed part"],
|
||||
["/1.png", "Selecting areas to pocket"],
|
||||
["/2.png", "Toolpath for pocket"],
|
||||
["/3.gif", "Pocket animation"],
|
||||
["/4.png", "Toolpath for v-bit carve"],
|
||||
["/5.png", "Many-pocketed part"],
|
||||
],
|
||||
gcode: [["/1.png", "the gcode editor popup"]],
|
||||
gcode: [["/1.png", "The gcode editor popup"]],
|
||||
laserOn: [],
|
||||
laserOff: [],
|
||||
index: [],
|
||||
lathe: [],
|
||||
tabs: [
|
||||
["/1.png", "tabs added to part"],
|
||||
["/2.png", "sliced part with tabs"],
|
||||
["/1.png", "Tabs added to part"],
|
||||
["/2.png", "Sliced part with tabs"],
|
||||
],
|
||||
camInterface: [
|
||||
["/1.png", "Empty Ops list"],
|
||||
["/2.png", "Sample Ops list"],
|
||||
["/3.png", "Ops list with history bar"],
|
||||
],
|
||||
prefs: [
|
||||
["/prefs-CAM.png", "Preferences in CAM mode"],
|
||||
["/prefs-FDM.png", "Preferences in FDM mode"],
|
||||
["/prefs-LASER.png", "Preferences in laser mode"],
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -66,15 +76,17 @@ const imageDescripts = {
|
|||
* @param {keyof typeof imageDescripts} images - The key to an array of images and descriptions.
|
||||
* @returns {ReactElement} - A carousel of the given images.
|
||||
*/
|
||||
export function ImageCarousel({ base, images }) {
|
||||
export function ImageCarousel({ base, images, sml }) {
|
||||
return (
|
||||
<Carousel showThumbs={false}>
|
||||
{imageDescripts[images].map(([image, caption], index) => (
|
||||
<div>
|
||||
<img src={base + image} alt="" />
|
||||
<p className="caption">{caption}</p>
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
<div className={sml ? "carouselWrapper" : ""}>
|
||||
<Carousel showThumbs={false}>
|
||||
{imageDescripts[images].map(([image, caption], index) => (
|
||||
<div>
|
||||
<img src={base + image} alt="" />
|
||||
<p className="caption">{caption}</p>
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,3 +5,14 @@
|
|||
.navbar .navbar__items {
|
||||
height: 4.5rem;
|
||||
}
|
||||
|
||||
.carouselWrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.carouselWrapper .carousel-root {
|
||||
width: fit-content;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
|
|
|||
BIN
docs/static/img/CAM/interface/1.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/static/img/CAM/interface/2.png
vendored
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
docs/static/img/CAM/interface/3.png
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/static/img/CAM/interface/originSelect.png
vendored
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/static/img/device-dialog.png
vendored
|
Before Width: | Height: | Size: 232 KiB |
BIN
docs/static/img/interface/CAMDevice.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/static/img/interface/main.png
vendored
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
docs/static/img/interface/prefs-CAM.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/interface/prefs-FDM.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/interface/prefs-LASER.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/static/img/preferences-cam.png
vendored
|
Before Width: | Height: | Size: 68 KiB |
BIN
docs/static/img/preferences-fdm.png
vendored
|
Before Width: | Height: | Size: 70 KiB |
|
|
@ -34,7 +34,7 @@ self.kiri.load(api => {
|
|||
}).map(v => `${v.substring(1)}FF`);
|
||||
|
||||
let local_port = 5309;
|
||||
let local_url = `http://localhost:${local_port}`;
|
||||
let local_host = `localhost:${local_port}`;
|
||||
let sequence_id = (Math.random() * 0xfff) | 0;
|
||||
let user_id = ((Math.random() * 0xfffffff) | 0).toString();
|
||||
let init = false;
|
||||
|
|
@ -53,7 +53,8 @@ self.kiri.load(api => {
|
|||
if (socket.ws) {
|
||||
return;
|
||||
}
|
||||
let ws_url = api.onshape ? `${local_url}/bambu` : "/bambu";
|
||||
let ws_url = api.onshape ? `ws://${local_host}/bambu` : "/bambu";
|
||||
console.log({ bambu_ws_url: ws_url });
|
||||
let ws = socket.ws = new WebSocket(ws_url);
|
||||
ws.onerror = (error) => {
|
||||
socket.open = false;
|
||||
|
|
@ -1288,7 +1289,7 @@ self.kiri.load(api => {
|
|||
|
||||
function send(filename, gcode, start) {
|
||||
const spool = print_ams_select;
|
||||
const baseUrl = api.onshape ? `${local_url}/api/bambu_send` : "/api/bambu_send";
|
||||
const baseUrl = api.onshape ? `http://${local_host}/api/bambu_send` : "/api/bambu_send";
|
||||
const url = new URL(baseUrl, window.location.origin);
|
||||
url.searchParams.append('host', host);
|
||||
url.searchParams.append('code', password);
|
||||
|
|
|
|||
25
mods/devel/init.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// back end for device export in dev mode
|
||||
module.exports = function(server) {
|
||||
server.inject("kiri", "kiri.js");
|
||||
if (!server.env.debug) {
|
||||
return;
|
||||
}
|
||||
const fs = require('fs');
|
||||
server.api.postDevice = (req, res, next) => {
|
||||
server.handler.decodePost(req, res, () => {
|
||||
// console.log({decodePost: req.app.post});
|
||||
const { device, process, profiles } = JSON.parse(req.app.post);
|
||||
if (profiles) {
|
||||
device.profiles = Object.entries(profiles).filter(v => v[0] !== 'default').map(v => v[1]);
|
||||
} else {
|
||||
device.profiles = [ process ];
|
||||
}
|
||||
const { mode } = device;
|
||||
const { deviceName } = device;
|
||||
const path = `${server.const.rootdir}/src/kiri-dev/${mode.toLowerCase()}/${deviceName.replace(/\ /g,'.')}`;
|
||||
// console.log({ mode, device, process, path });
|
||||
fs.writeFileSync(path, JSON.stringify(device, undefined, 4));
|
||||
res.end('profile saved');
|
||||
});
|
||||
};
|
||||
};
|
||||
23
mods/devel/kiri.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// provides a device export to local disk option
|
||||
self.kiri.load(api => {
|
||||
api.event.on('load-done', () => {
|
||||
let deviceExport = api.device.export;
|
||||
api.device.export = (exp, name, opt = {}) => {
|
||||
const { event, record } = opt;
|
||||
if (event && event.shiftKey & api.const.LOCAL) {
|
||||
const { code, process, profiles } = record;
|
||||
fetch("/api/postDevice", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ device: code, process, profiles })
|
||||
})
|
||||
.then(r => r.text())
|
||||
.then(t => {
|
||||
// console.log({server_said: t});
|
||||
api.show.alert('profile saved to server');
|
||||
});
|
||||
} else {
|
||||
deviceExport(exp, name);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
if (self.kiri && !self.kiri.electron) {
|
||||
self.kiri.electron = {};
|
||||
self.kiri.load(api => {
|
||||
console.log('ELECTRON MODULE RUNNING');
|
||||
api.event.on('load-done', () => {
|
||||
$('app-name-text').innerText = "More Info";
|
||||
$('top-sep').style.display = 'flex';
|
||||
});
|
||||
api.stats.set('kiri', api.version + 'e');
|
||||
});
|
||||
}
|
||||
|
||||
if (self.mesh && self.mesh) {
|
||||
self.mesh.electron = {};
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* for electron standalone build support
|
||||
*/
|
||||
module.exports = function(server) {
|
||||
// insert script before all others in kiri client
|
||||
server.inject("kiri", "electron.js");
|
||||
// insert scripts into mesh client
|
||||
server.inject("mesh", "electron.js");
|
||||
};
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name": "grid-apps-mods",
|
||||
"version": "4.1.0",
|
||||
"description": "grid.space app modules",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gridspace/apps.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gridspace/basic-ftp": "^5.0.5",
|
||||
"aedes": "^0.51.3",
|
||||
"hexer": "^1.5.0",
|
||||
"mqtt": "^5.10.3"
|
||||
}
|
||||
}
|
||||
6
notes.md
|
|
@ -15,6 +15,7 @@
|
|||
* `P` move all persisted / workspace settings/data to IndexedDB (LS limitations)
|
||||
* `P` faster ray intersect https://github.com/gkjohnson/three-mesh-bvh/
|
||||
* `P` try material clipping planes for slice range selection
|
||||
* `P` switch png lib to https://github.com/photopea/UPNG.js
|
||||
|
||||
* `F` edit in Mesh:Tool
|
||||
* `F` custom device vars for profiles / ranges / gcode
|
||||
|
|
@ -31,10 +32,7 @@
|
|||
* `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` new base paremeter for hole and perimeter compensation
|
||||
* `F` new parameters for inner / outer perimeter compensation
|
||||
* `F` convert ranges to z offsets while continuing to show layer #
|
||||
* `F` support pillar top/bottom should conform to part
|
||||
* `F` more explicit line width control with ranges and min/max adaptive
|
||||
|
|
@ -61,9 +59,7 @@
|
|||
|
||||
* `B` rapid moves should be max of terrain zmax and last cut layer height (roughing)
|
||||
* `B` feed rate for next tool set before tool change (push/pop feed rates?)
|
||||
* `B` tab cuts cause loss of direction, parenting, depth, and it's hard to fix
|
||||
* `B` tabs do not properly track widget mirror events
|
||||
* `B` tabs are not cut to exact height
|
||||
* `B` contour does not honor clip to stock
|
||||
|
||||
* `F` add lathe step down to eliminate the need for roughing
|
||||
|
|
|
|||
112
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "grid-apps",
|
||||
"version": "4.3.1",
|
||||
"version": "4.5.0",
|
||||
"description": "grid.space 3d slicing & modeling tools",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"license": "MIT",
|
||||
|
|
@ -35,12 +35,15 @@
|
|||
"node": ">=22.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/plugin-client-redirects": "^3.9.0",
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"@gridspace/app-server": "^0.0.18",
|
||||
"@gridspace/basic-ftp": "^5.0.5",
|
||||
"@gridspace/app-server": "^0.0.20",
|
||||
"@gridspace/basic-ftp": "github:gridspace/basic-ftp#v5.0.5-gridspace",
|
||||
"@gridspace/net-level-client": "^0.2.3",
|
||||
"@gridspace/raster-path": "^1.0.7",
|
||||
"@tracespace/parser": "^5.0.0-next.0",
|
||||
"@tweenjs/tween.js": "^16.6.0",
|
||||
"aedes": "^0.51.3",
|
||||
"base64-js": "^1.5.1",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"buffer-crc32": "^0.2.13",
|
||||
|
|
@ -49,25 +52,28 @@
|
|||
"earcut": "^3.0.1",
|
||||
"express-useragent": "^1.0.13",
|
||||
"fast-glob": "^3.3.3",
|
||||
"hexer": "^1.5.0",
|
||||
"i": "^0.3.7",
|
||||
"jszip": "^3.7.1",
|
||||
"manifold-3d": "^3.2.1",
|
||||
"moment": "^2.29.4",
|
||||
"mqtt": "^5.10.3",
|
||||
"npm": "^11.4.2",
|
||||
"prettier": "^3.5.3",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"serve-static": "^1.14.1",
|
||||
"serve-static": "^2.2.0",
|
||||
"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": {
|
||||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@docusaurus/core": "^3.9.0",
|
||||
"@docusaurus/preset-classic": "^3.9.0",
|
||||
"@electron/notarize": "^3.0.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"docusaurus-lunr-search": "^3.6.0",
|
||||
"dotenv": "latest",
|
||||
"electron": "^35.0.1",
|
||||
|
|
@ -81,34 +87,39 @@
|
|||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "npm i && npm run clean && npm run dev -- --dryrun && npm run webpack-ext",
|
||||
"build": "npm run prebuild && electron-builder",
|
||||
"build-nopublish": "npm run prebuild && electron-builder --publish never",
|
||||
"build-debug": "npm run prebuild && DEBUG=electron-builder electron-builder",
|
||||
"build-linux": "npm run build -- --linux --x64",
|
||||
"build-debug": "npm run prebuild && cross-env DEBUG=electron-builder electron-builder",
|
||||
"build-linux-arm": "npm run build -- --linux --arm64",
|
||||
"build-win": "npm run build -- --win --x64",
|
||||
"build-win-arm": "npm run build -- --win --arm64",
|
||||
"build-mac": "npm run build -- --mac --arm64",
|
||||
"build-linux": "npm run build -- --linux --x64",
|
||||
"build-mac-intel": "npm run build -- --mac --x64",
|
||||
"clean": "rm -rf build data dist src/pack tmp src.old web.old",
|
||||
"docs-dev": "docusaurus start --config conf/docusaurus.config.js",
|
||||
"build-mac": "npm run build -- --mac --arm64",
|
||||
"build-nopublish": "npm run prebuild && electron-builder --publish never",
|
||||
"build-win-arm": "npm run build -- --win --arm64",
|
||||
"build-win": "npm run build -- --win --x64",
|
||||
"build": "npm run prebuild && electron-builder",
|
||||
"bundle:dev": "node bin/bundler.mjs bin/bundle.config.json",
|
||||
"bundle:prod": "node bin/bundler.mjs bin/bundle-prod.config.json",
|
||||
"bundle": "cross-env COMPRESS=1 npm run bundle:prod",
|
||||
"clean": "rm -rf alt build data dist src/pack src.old tmp web.old .bcache",
|
||||
"dev": "npm run pack-dev && gs-app-server --debug --single",
|
||||
"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-pack": "npm run webpack-ext && npm run webpack-src prod",
|
||||
"prod-dryrun": "gs-app-server --dryrun",
|
||||
"start": "npm run prebuild prod && electron .",
|
||||
"start-dev": "npm run prebuild prod && electron . --devel",
|
||||
"docs-dev": "docusaurus start --config conf/docusaurus.config.js",
|
||||
"docs-serve": "docusaurus serve --config conf/docusaurus.config.js",
|
||||
"dryrun": "gs-app-server --debug --single --dryrun",
|
||||
"dryrun-prod": "gs-app-server --single --dryrun",
|
||||
"mac-verify": "spctl --assess -vv --type install dist/*/*.app",
|
||||
"mk-links": "find src web -type l | xargs -I{} sh -c 'echo \"{},$(readlink {})\"' > conf/links.csv",
|
||||
"pack-dev": "npm run webpack-src && npm run dryrun && npm run bundle:dev",
|
||||
"pack-prod": "npm run webpack-src prod && npm run dryrun && npm run bundle",
|
||||
"postbuild": "node bin/electron-post.js",
|
||||
"prebuild": "npm run setup && cross-env ELECTRON_BUILD=1 npm run pack-dev && node bin/electron-pre.js",
|
||||
"preinstall": "node bin/install-pre.js",
|
||||
"prod": "npm run pack-prod && gs-app-server",
|
||||
"setup": "npm run clean && npm i && npm run webpack-ext",
|
||||
"start-dbg": "npm run prebuild && electron . --debugg",
|
||||
"start-ddb": "npm run prebuild && electron . --devel --debugg",
|
||||
"mk-links": "find src web -type l | xargs -I{} sh -c 'echo \"{},$(readlink {})\"' > conf/links.csv",
|
||||
"mac-verify": "spctl --assess -vv --type install dist/*/*.app",
|
||||
"prebuild": "npm run setup && npm run webpack-src prod && node bin/electron-pre.js",
|
||||
"postbuild": "node bin/electron-post.js",
|
||||
"preinstall": "node bin/install-pre.js",
|
||||
"start-dev": "npm run prebuild prod && electron . --devel",
|
||||
"start": "npm run prebuild && electron .",
|
||||
"webpack-ext": "npm run webpack-three && npm run webpack-zip",
|
||||
"webpack-src": "node bin/esbuild.config.mjs",
|
||||
"webpack-three": "npx webpack --config bin/webpack-three-esm.js",
|
||||
|
|
@ -122,52 +133,17 @@
|
|||
"artifactName": "KiriMoto-${os}-${arch}.${ext}",
|
||||
"files": [
|
||||
{
|
||||
"from": "tmp/src",
|
||||
"to": "src",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/web",
|
||||
"to": "web",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mod",
|
||||
"to": "mod",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mod/node_modules",
|
||||
"to": "mod/node_modules",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mods",
|
||||
"to": "mods",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mods/node_modules",
|
||||
"to": "mods/node_modules",
|
||||
"from": "tmp",
|
||||
"to": "",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"app-el.js",
|
||||
"app.js",
|
||||
"bin/*",
|
||||
"conf/**/*",
|
||||
"data/cache/**/*",
|
||||
"app-el.js",
|
||||
"app.js",
|
||||
"package.json"
|
||||
],
|
||||
"extraFiles": [],
|
||||
|
|
|
|||
|
|
@ -9,27 +9,27 @@
|
|||

|
||||

|
||||
|
||||
|
||||
# Community
|
||||
|
||||
[Discord](https://discord.com/invite/suyCCgr) -- Live Chat
|
||||
[Forums](https://forum.grid.space/) -- Long Form and Archival Discussion
|
||||
[BlueSky](https://bsky.app/profile/grid.space) -- Like the Good 'Ol Days
|
||||
[YouTube](https://www.youtube.com/c/gridspace) -- Content when I have time
|
||||
|
||||
# Free and Open Source
|
||||
|
||||
Kiri:Moto and Mesh:Tool are completely open source and free for use without restriction. Over 12 years in development, this passion project has grown well beyond its original scope. It has consumed most of my free time for many years. Please consider donating to support continued development GitHub sponsorship or PayPal.
|
||||
Kiri:Moto and Mesh:Tool are completely open source and free for use without restriction. Over 13 years in development, this passion project has grown well beyond its original scope. It has consumed most of my free time for many years. Please consider donating to support continued development GitHub sponsorship or PayPal.
|
||||
|
||||
[](https://github.com/sponsors/GridSpace)
|
||||
[](https://paypal.me/gridspace3d?locale.x=en_US)
|
||||
|
||||
# Documentation
|
||||
|
||||
[Documentation](https://docs.grid.space/) -- Could really use help with this
|
||||
|
||||
Docs are build with [Docusaurus](https://docusaurus.io/) and served using [GitHub Pages](https://pages.github.com/).
|
||||
|
||||
You can build and view the docs locally with:
|
||||
|
||||
```
|
||||
git clone git@github.com:GridSpace/grid-apps.git
|
||||
cd grid-apps
|
||||
|
|
@ -45,7 +45,6 @@ npm run docs-dev
|
|||
|
||||
[`Mesh:Tool`](https://grid.space/mesh) is a browser-based mesh repair and editing tool
|
||||
|
||||
|
||||
# Electron Builds (Desktop Binaries)
|
||||
|
||||
https://github.com/GridSpace/grid-apps/releases/
|
||||
|
|
@ -62,7 +61,6 @@ chmod 755 KiriMoto-linux-x86_64.AppImage
|
|||
|
||||
The Windows and Mac binaries are not signed, so you will need to jump through a few safety hoops to get them to open the first time.
|
||||
|
||||
|
||||
# Linux / Mac Developers
|
||||
|
||||
## Testing Locally (with Docker)
|
||||
|
|
|
|||
156
release.md
|
|
@ -2,6 +2,162 @@
|
|||
|
||||
Full docs @ https://docs.grid.space/projects/kiri-moto
|
||||
|
||||
# Release 4.4.0
|
||||
|
||||
## General
|
||||
|
||||
* add version numbering utility script
|
||||
* split out help/info menu and language menus
|
||||
* move install/uninstall/quit menu to center app menu
|
||||
* add help menu visual callout
|
||||
* improve language translation coverage
|
||||
* improve build and serve workflow for alternate builds
|
||||
* update service worker configuration
|
||||
* add install/uninstall options in setup menu
|
||||
|
||||
## CAM
|
||||
|
||||
* implement tab hopping - toolpath goes up and over tab instead of interrupting cut (fixes #207)
|
||||
* add GPU accelerated contouring for faster operations
|
||||
* add radial GPU raster support (experimental)
|
||||
* add new register mode that marks bottom cutout tabs
|
||||
* add curves-only support to GPU contour
|
||||
* add zsafe and move to safe z at start of new operations
|
||||
* fix 'safe' moves during operation changes to be moves, not cuts
|
||||
* fix tolerance/resolution for GPU raster mesh calculations
|
||||
* fix NullPointerException with tabs in contour
|
||||
* improve merge/co-planar point handling
|
||||
* add slice layer naming using operation notes (fixes #447)
|
||||
* fix level step down units (fixes #446)
|
||||
|
||||
## FDM
|
||||
|
||||
* document remain_time macro variable (refs #339)
|
||||
* re-add base flow rate multiplier for belt mode (fixes #444)
|
||||
* fix belt fan control ordering (fixes #438)
|
||||
* exclude belt from bridge fan layer
|
||||
* add centering feature for fill areas (fixes #448)
|
||||
|
||||
## Mesh:Tool
|
||||
|
||||
* add STEP export capability
|
||||
* add STEP import with face generation
|
||||
* improve face generation with inner holes
|
||||
* add mesh scripting tool and persistence
|
||||
* add plane and plane.loft operations
|
||||
* add global edge map and edge reuse for better topology
|
||||
* add devel device export option
|
||||
|
||||
## Electron
|
||||
|
||||
* clean up electron build process
|
||||
* merge electron mod into core
|
||||
* add service routes when available
|
||||
* set proper content-type for appended code bodies
|
||||
|
||||
## Dependencies
|
||||
|
||||
* update @gridspace/raster-path to latest version with GPU acceleration
|
||||
* update @gridspace/app-server for proper fallthrough handling
|
||||
* switch from npmjs.org to git repos for @gridspace packages
|
||||
* update WebGPU implementation
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* fix Bambu Lab local URL and module load order
|
||||
* fix progress reporting for raster operations
|
||||
* fix GPU lathe progress and contour API usage
|
||||
* fix tool offsets when using GPU acceleration
|
||||
* fix indexed rotations epsilon value for accurate zflat calculation
|
||||
* fix traceload debug option
|
||||
* sanitize topo3 resolution to avoid GPU floating-point errors
|
||||
|
||||
|
||||
# Release 4.3
|
||||
|
||||
## General
|
||||
|
||||
* add lathe mode for CAM operations
|
||||
* add volumetric flow calculations for FDM
|
||||
* improve bundler with better dependency tracking
|
||||
* migrate to ESM module format across codebase
|
||||
* improve arc support in gcode generation
|
||||
|
||||
## FDM
|
||||
|
||||
* add scarf seams for better surface finish
|
||||
* add hole compensation feature
|
||||
* add spiral layer start option
|
||||
* overhaul thin wall detection and handling
|
||||
* improve volumetric flow rate controls
|
||||
* add retraction tuning for better print quality
|
||||
|
||||
## CAM
|
||||
|
||||
* add lathe operation support with threading
|
||||
* improve arc support for smoother toolpaths
|
||||
* add drill from stock top option
|
||||
* improve tab positioning and generation
|
||||
* enhance surface selection and filtering
|
||||
* add 4th axis lathe debug and testing mode
|
||||
* improve tool path optimization
|
||||
|
||||
## Devices
|
||||
|
||||
* add new machine profiles
|
||||
* improve device profile management
|
||||
|
||||
|
||||
# Release 4.2
|
||||
|
||||
## CAM
|
||||
|
||||
* add arc support to gcode output for smoother paths
|
||||
* add drill from stock top feature
|
||||
* improve contour operations
|
||||
* fix animation issues with shared tool numbers
|
||||
* enhance trace operations with arc support
|
||||
* improve pocket smoothing algorithms
|
||||
|
||||
## FDM
|
||||
|
||||
* improve support generation
|
||||
* add new retraction options
|
||||
* enhance layer time controls
|
||||
|
||||
## General
|
||||
|
||||
* improve gcode arc import and export
|
||||
* fix file loading edge cases
|
||||
* update device profiles
|
||||
|
||||
|
||||
# Release 4.1
|
||||
|
||||
## CAM
|
||||
|
||||
* improve drill operations and positioning
|
||||
* fix multi-part drilling bugs
|
||||
* enhance trace line selection
|
||||
* add dogbone support to trace ops
|
||||
* improve pocket operation reliability
|
||||
* fix trace selection with flip operations
|
||||
|
||||
## FDM
|
||||
|
||||
* add RatRig machine profiles
|
||||
* improve Bambu Lab device control integration
|
||||
* enhance support placement algorithms
|
||||
* fix belt mode support generation
|
||||
|
||||
## General
|
||||
|
||||
* improve file import handling
|
||||
* fix workspace restore for complex projects
|
||||
* enhance mobile touch interactions
|
||||
* update machine profiles for popular devices
|
||||
|
||||
|
||||
# Release 4.0 (2024-01-21)
|
||||
|
||||
* major UI refactor
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ SP.init = function(options = {}) {
|
|||
request = IDB.open(name, this.version);
|
||||
|
||||
request.onupgradeneeded = function(event) {
|
||||
let db = storage.db = request.result;
|
||||
let db = request.result;
|
||||
let current = [...db.objectStoreNames];
|
||||
let stores = storage.stores;
|
||||
// add missing stores
|
||||
|
|
@ -184,13 +184,12 @@ SP.init = function(options = {}) {
|
|||
// remove obsolete stores
|
||||
for (let curr of current) {
|
||||
if (stores.indexOf(curr) < 0) {
|
||||
storage.db.deleteObjectStore(curr);
|
||||
db.deleteObjectStore(curr);
|
||||
console.log({index_dropped: curr});
|
||||
}
|
||||
}
|
||||
event.target.transaction.oncomplete = function(event) {
|
||||
storage.runQueue();
|
||||
};
|
||||
// allow transactions
|
||||
storage.db = db;
|
||||
};
|
||||
|
||||
request.onsuccess = function(event) {
|
||||
|
|
@ -217,6 +216,7 @@ SP.runQueue = function() {
|
|||
let i = 0, q = this.queue, e;
|
||||
while (i < q.length) {
|
||||
e = q[i++];
|
||||
// console.log('runQ', i, ...e);
|
||||
switch (e.shift()) {
|
||||
case 'iterate': this.iterate(...e); break;
|
||||
case 'keys': this.keys(...e); break;
|
||||
|
|
@ -252,7 +252,11 @@ SP.put = function(key, value, callback, store = this.current) {
|
|||
req.onerror = function(event) { callback(false) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.name === 'InvalidStateError') {
|
||||
console.log({ putRetry: key });
|
||||
return this.queue.push(['put', key, value, callback, store]);
|
||||
}
|
||||
console.log(e.name, e);
|
||||
if (callback) callback(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -275,6 +279,10 @@ SP.get = function(key, callback, store = this.current) {
|
|||
req.onerror = function(event) { callback(null) };
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.name === 'InvalidStateError') {
|
||||
console.log({ getRetry: key });
|
||||
return this.queue.push(['get', key, callback, store]);
|
||||
}
|
||||
console.log(e);
|
||||
if (callback) callback(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
import './pngjs.js';
|
||||
|
||||
// Re-export the PNG object
|
||||
export const PNG = self.png ? self.png.PNG : null;
|
||||
export const PNG = globalThis.png ? globalThis.png.PNG : null;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.png = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function(f){globalThis.png = f();if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.png = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function (Buffer){
|
||||
"use strict";
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,12 @@ class Point {
|
|||
return p;
|
||||
}
|
||||
|
||||
// annotate instance with other field data (see clone())
|
||||
annotate(obj = {}) {
|
||||
Object.assign(this, obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
slopeTo(p) {
|
||||
return newSlope(this, p);
|
||||
}
|
||||
|
|
@ -441,10 +447,6 @@ class Point {
|
|||
return this === p || (this.x === p.x && this.y === p.y && this.z === p.z);
|
||||
}
|
||||
|
||||
isEqual2D(p) {
|
||||
return this === p || (this.x === p.x && this.y === p.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if points are close enough to be considered equivalent
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -908,6 +908,7 @@ export class Polygon {
|
|||
* todo make more efficient
|
||||
*
|
||||
* @param {Boolean} deep
|
||||
* @param {String[]} fields to copy beyond class fields
|
||||
* @returns {Polygon}
|
||||
*/
|
||||
clone(deep, fields) {
|
||||
|
|
@ -2145,6 +2146,7 @@ export class Polygon {
|
|||
return null;
|
||||
}
|
||||
|
||||
// annotate instance with other field data (see clone())
|
||||
annotate(obj = {}) {
|
||||
Object.assign(this, obj);
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { polygons } from '../geo/polygons.js';
|
|||
import { newPoint } from '../geo/point.js';
|
||||
import { config } from '../geo/base.js';
|
||||
|
||||
const epsilon = 10e-5;
|
||||
|
||||
function dval(v, dv) {
|
||||
return v !== undefined ? v : dv;
|
||||
}
|
||||
|
|
@ -75,7 +77,7 @@ export async function slice(points, options = {}) {
|
|||
// used to calculate buckets (rough sum of z span)
|
||||
zSum += (Math.abs(p1.z - p2.z) + Math.abs(p2.z - p3.z) + Math.abs(p3.z - p1.z));
|
||||
// use co-flat and co-line detection to adjust slice Z
|
||||
if (p1.z === p2.z && p2.z === p3.z && p1.z >= zMin) {
|
||||
if (Math.abs(p1.z - p2.z) < epsilon && Math.abs(p2.z - p3.z) < epsilon && p1.z >= zMin) {
|
||||
// detect faces co-planar with Z and sum the enclosed area
|
||||
let zkey = p1.z,
|
||||
area = Math.abs(base.util.area2(p1,p2,p3)) / 2;
|
||||
|
|
|
|||
|
|
@ -60,13 +60,6 @@ export class Slope {
|
|||
return this;
|
||||
}
|
||||
|
||||
invert() {
|
||||
this.dx = -this.dx;
|
||||
this.dy = -this.dy;
|
||||
this.angle = 180 - this.angle;
|
||||
return this;
|
||||
}
|
||||
|
||||
angleDiff(s2,sign) {
|
||||
const n1 = this.angle;
|
||||
const n2 = s2.angle;
|
||||
|
|
|
|||
1
src/gpu/raster-worker.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../node_modules/@gridspace/raster-path/build/raster-worker.js
|
||||
1
src/gpu/raster.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../node_modules/@gridspace/raster-path/build/raster-path.js
|
||||
|
|
@ -9,7 +9,7 @@ import { client as work } from './client.js';
|
|||
import { consts, COLOR as color, LISTS as lists } from './consts.js';
|
||||
import { device, devices } from './devices.js';
|
||||
import { catalog, dialog, event, group, help, hide, image } from './main.js';
|
||||
import { modal, mode, probe, process, show, space, util, view } from './main.js';
|
||||
import { modal, mode, process, show, space, util, view } from './main.js';
|
||||
import { local as dataLocal } from '../../data/local.js';
|
||||
import { noop, ajax, o2js, js2o } from './utils.js';
|
||||
import { functions } from './function.js';
|
||||
|
|
@ -96,6 +96,7 @@ let UC = UI.prefix('kiri').inputAction(settings.conf.update),
|
|||
put: (key, val) => localSet(key, val),
|
||||
set: (key, val) => localSet(key, val),
|
||||
setItem: (key, val) => localSet(key, val),
|
||||
removeItem: (key) => localRemove(key)
|
||||
},
|
||||
tweak = {
|
||||
line_precision(v) { api.work.config({ base: { clipperClean: v } }) },
|
||||
|
|
@ -119,6 +120,11 @@ function localSet(key, val) {
|
|||
return val;
|
||||
}
|
||||
|
||||
function localRemove(key) {
|
||||
let sloc = api.conf.get().local;
|
||||
return delete sloc[key];
|
||||
}
|
||||
|
||||
export const api = {
|
||||
ajax,
|
||||
beta,
|
||||
|
|
@ -137,6 +143,7 @@ export const api = {
|
|||
dialog,
|
||||
doit,
|
||||
event,
|
||||
electron: navigator.userAgent.includes('Electron'),
|
||||
feature,
|
||||
function: functions,
|
||||
group,
|
||||
|
|
@ -156,7 +163,6 @@ export const api = {
|
|||
o2js,
|
||||
onkey,
|
||||
platform,
|
||||
probe,
|
||||
process,
|
||||
sdb: dataLocal,
|
||||
selection,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -182,33 +182,33 @@ function normalize(settings) {
|
|||
|
||||
// auto field renaming on import
|
||||
const renamed = {
|
||||
roughingTool: "camRoughTool",
|
||||
roughingSpindle: "camRoughSpindle",
|
||||
roughingDown: "camRoughDown",
|
||||
roughingOver: "camRoughOver",
|
||||
roughingSpeed: "camRoughSpeed",
|
||||
roughingPlunge: "camRoughPlunge",
|
||||
roughingStock: "camRoughStock",
|
||||
roughingPocket: "camRoughVoid",
|
||||
roughingOn: "camRoughOn",
|
||||
finishingTool: "camOutlineTool",
|
||||
finishingSpindle: "camOutlineSpindle",
|
||||
camWideCutout: "camOutlineWide",
|
||||
drillDown: "camDrillDown",
|
||||
drillDownSpeed: "camDrillDownSpeed",
|
||||
drillDwell: "camDrillDwell",
|
||||
drillingOn: "camDrillingOn",
|
||||
drillLift: "camDrillLift",
|
||||
drillSpindle: "camDrillSpindle",
|
||||
drillTool: "camDrillTool",
|
||||
finishingDown: "camOutlineDown",
|
||||
finishingOver: "camContourOver",
|
||||
finishingSpeed: "camOutlineSpeed",
|
||||
finishingPlunge: "camOutlinePlunge",
|
||||
finishingOn: "camOutlineOn",
|
||||
finishingOver: "camContourOver",
|
||||
finishingPlunge: "camOutlinePlunge",
|
||||
finishingSpeed: "camOutlineSpeed",
|
||||
finishingSpindle: "camOutlineSpindle",
|
||||
finishingTool: "camOutlineTool",
|
||||
finishingXOn: "camContourXOn",
|
||||
finishingYOn: "camContourYOn",
|
||||
drillTool: "camDrillTool",
|
||||
drillSpindle: "camDrillSpindle",
|
||||
drillDownSpeed: "camDrillDownSpeed",
|
||||
drillDown: "camDrillDown",
|
||||
drillDwell: "camDrillDwell",
|
||||
drillLift: "camDrillLift",
|
||||
drillingOn: "camDrillingOn",
|
||||
camWideCutout: "camOutlineWide",
|
||||
outputClockwise: "camConventional"
|
||||
outputClockwise: "camConventional",
|
||||
roughingDown: "camRoughDown",
|
||||
roughingOn: "camRoughOn",
|
||||
roughingOver: "camRoughOver",
|
||||
roughingPlunge: "camRoughPlunge",
|
||||
roughingPocket: "camRoughVoid",
|
||||
roughingSpeed: "camRoughSpeed",
|
||||
roughingSpindle: "camRoughSpindle",
|
||||
roughingStock: "camRoughStock",
|
||||
roughingTool: "camRoughTool",
|
||||
};
|
||||
|
||||
export const conf = {
|
||||
|
|
@ -258,95 +258,95 @@ export const conf = {
|
|||
},
|
||||
// process defaults FDM:Process
|
||||
p:{
|
||||
antiBacklash: 0,
|
||||
arcTolerance: 0,
|
||||
firstLayerBedTemp: 0,
|
||||
firstLayerBeltBump: 0,
|
||||
firstLayerBeltLead: 3,
|
||||
firstLayerBeltFact: 1,
|
||||
firstLayerBrim: 0,
|
||||
firstLayerBrimGap: 0,
|
||||
firstLayerBrimIn: 0,
|
||||
firstLayerBrimTrig: 0,
|
||||
firstLayerFanSpeed: 0,
|
||||
firstLayerFillRate: 35,
|
||||
firstLayerFlatten: 0,
|
||||
firstLayerNozzleTemp: 0,
|
||||
firstLayerRate: 30,
|
||||
firstLayerYOffset: 0,
|
||||
firstSliceHeight: 0.25,
|
||||
outputAlternating: false,
|
||||
outputAvoidGaps: true,
|
||||
outputBedTemp: 60,
|
||||
outputBeltFirst: false,
|
||||
outputBrimCount: 2,
|
||||
outputBrimOffset: 2,
|
||||
outputCoastDist: 0,
|
||||
outputDraftShield: false,
|
||||
outputFanLayer: 1,
|
||||
outputFanSpeed: 255,
|
||||
outputFeedrate: 50,
|
||||
outputFillMult: 1.25,
|
||||
outputFinishrate: 50,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
outputLayerRetract: false,
|
||||
outputLoops: 0,
|
||||
outputMaxFlowrate: 15,
|
||||
outputMinLayerTime: 10,
|
||||
outputMinSpeed: 5.0,
|
||||
outputPurgeTower: 0,
|
||||
outputRaft: false,
|
||||
outputRaftSpacing: 0.2,
|
||||
outputRetractDist: 1.5,
|
||||
outputRetractDwell: 20,
|
||||
outputRetractSpeed: 40,
|
||||
outputRetractWipe: 0,
|
||||
outputScarfLength: 0,
|
||||
outputSeekrate: 80,
|
||||
outputShellMult: 1.25,
|
||||
outputShortPoly: 0.0,
|
||||
outputSparseMult: 1.25,
|
||||
outputTemp: 200,
|
||||
processName: "default",
|
||||
ranges: [],
|
||||
sliceAdaptive: false,
|
||||
sliceAngle: 45,
|
||||
sliceBottomLayers: 3,
|
||||
sliceCompInner: 0,
|
||||
sliceCompOuter: 0,
|
||||
sliceDetectThin: "basic",
|
||||
sliceFillAngle: 45,
|
||||
sliceFillGrow: 0,
|
||||
sliceFillOverlap: 0.35,
|
||||
sliceFillRepeat: 1,
|
||||
sliceFillSparse: 0.25,
|
||||
sliceFillType: "hex",
|
||||
sliceFillWidth: 1,
|
||||
sliceHeight: 0.25,
|
||||
sliceShells: 3,
|
||||
sliceShellOrder: "in-out",
|
||||
sliceLayerStart: "last",
|
||||
sliceLayerStartX: 0,
|
||||
sliceLayerStartY: 0,
|
||||
sliceLineWidth: 0,
|
||||
sliceFillGrow: 0,
|
||||
sliceFillAngle: 45,
|
||||
sliceFillWidth: 1,
|
||||
sliceFillOverlap: 0.35,
|
||||
sliceFillSparse: 0.25,
|
||||
sliceFillRepeat: 1,
|
||||
sliceFillRate: 0,
|
||||
sliceSolidRate: 0,
|
||||
sliceFillType: "hex",
|
||||
sliceSupportDensity: 0.1,
|
||||
sliceSupportOffset: 1.0,
|
||||
sliceSupportGap: 1,
|
||||
sliceSupportSize: 5,
|
||||
sliceSupportArea: 0.1,
|
||||
sliceSupportSpan: 5,
|
||||
sliceSupportGrow: 0,
|
||||
sliceSupportExtra: 0,
|
||||
sliceSupportAngle: 50,
|
||||
sliceSupportNozzle: 0,
|
||||
sliceSupportEnable: false,
|
||||
sliceSupportOutline: true,
|
||||
sliceZInterleave: false,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceBottomLayers: 3,
|
||||
sliceTopLayers: 3,
|
||||
firstSliceHeight: 0.25,
|
||||
firstLayerRate: 30,
|
||||
firstLayerFillRate: 35,
|
||||
firstLayerPrintMult: 1.0,
|
||||
firstLayerLineMult: 1.0,
|
||||
firstLayerYOffset: 0,
|
||||
firstLayerNozzleTemp: 0,
|
||||
firstLayerBedTemp: 0,
|
||||
firstLayerBrim: 0,
|
||||
firstLayerBrimIn: 0,
|
||||
firstLayerBrimTrig: 0,
|
||||
firstLayerBrimGap: 0,
|
||||
firstLayerBeltLead: 3,
|
||||
firstLayerBeltBump: 0,
|
||||
firstLayerFanSpeed: 0,
|
||||
firstLayerFlatten: 0,
|
||||
outputRaft: false,
|
||||
outputRaftSpacing: 0.2,
|
||||
outputDraftShield: false,
|
||||
outputTemp: 200,
|
||||
outputBedTemp: 60,
|
||||
outputFeedrate: 50,
|
||||
outputFinishrate: 50,
|
||||
outputSeekrate: 80,
|
||||
outputShellMult: 1.25,
|
||||
outputFillMult: 1.25,
|
||||
outputSparseMult: 1.25,
|
||||
outputFanLayer: 1,
|
||||
outputFanSpeed: 255,
|
||||
outputRetractDist: 1.5,
|
||||
outputRetractSpeed: 40,
|
||||
outputRetractWipe: 0,
|
||||
outputRetractDwell: 20,
|
||||
outputBrimCount: 2,
|
||||
outputBrimOffset: 2,
|
||||
outputScarfLength: 0,
|
||||
outputShortPoly: 100.0,
|
||||
outputMinSpeed: 10.0,
|
||||
outputMinLayerTime: 0,
|
||||
outputCoastDist: 0,
|
||||
outputPurgeTower: 0,
|
||||
outputBeltFirst: false,
|
||||
outputAvoidGaps: true,
|
||||
outputAlternating: false,
|
||||
outputLayerRetract: false,
|
||||
outputLoops: 0,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
sliceDetectThin: "off",
|
||||
sliceMinHeight: 0,
|
||||
sliceAdaptive: false,
|
||||
sliceShellOrder: "in-out",
|
||||
sliceShells: 3,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceSupportAngle: 50,
|
||||
sliceSupportArea: 0.1,
|
||||
sliceSupportDensity: 0.1,
|
||||
sliceSupportEnable: false,
|
||||
sliceSupportExtra: 0,
|
||||
sliceSupportGap: 1,
|
||||
sliceSupportGrow: 0,
|
||||
sliceSupportNozzle: 0,
|
||||
sliceSupportOffset: 1.0,
|
||||
sliceSupportOutline: true,
|
||||
sliceSupportSize: 5,
|
||||
sliceSupportSpan: 5,
|
||||
sliceTopLayers: 3,
|
||||
sliceZInterleave: false,
|
||||
zHopDistance: 0.2,
|
||||
arcTolerance: 0,
|
||||
antiBacklash: 0,
|
||||
ranges: []
|
||||
}
|
||||
},
|
||||
sla:{
|
||||
|
|
@ -420,186 +420,187 @@ export const conf = {
|
|||
},
|
||||
// process defaults CAM:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
camLevelTool: 1000,
|
||||
camLevelSpindle: 1000,
|
||||
camLevelOver: 0.75,
|
||||
camLevelStepZ: 0,
|
||||
camLevelSpeed: 1000,
|
||||
camLevelDown: 0,
|
||||
camLevelInset: 0.5,
|
||||
camLevelStock: true,
|
||||
camRoughTool: 1000,
|
||||
camRoughSpindle: 1000,
|
||||
camRoughDown: 2,
|
||||
camRoughOver: 0.4,
|
||||
camRoughSpeed: 1000,
|
||||
camRoughPlunge: 250,
|
||||
camRoughStock: 0,
|
||||
camRoughStockZ: 0,
|
||||
camRoughAll: true,
|
||||
camRoughVoid: false,
|
||||
camRoughFlat: true,
|
||||
camRoughTop: true,
|
||||
camRoughIn: true,
|
||||
camRoughOn: true,
|
||||
camRoughOmitThru: false,
|
||||
camRoughOmitVoid: false,
|
||||
camOutlineTool: 1000,
|
||||
camOutlineSpindle: 1000,
|
||||
camOutlineTop: true,
|
||||
camOutlineDown: 3,
|
||||
camOutlineOver: 0.4,
|
||||
camOutlineOverCount: 1,
|
||||
camOutlineSpeed: 800,
|
||||
camOutlinePlunge: 250,
|
||||
camOutlineWide: false,
|
||||
camOutlineDogbone: false,
|
||||
camOutlineOmitThru: false,
|
||||
camOutlineOmitVoid: false,
|
||||
camOutlineOut: true,
|
||||
camOutlineIn: false,
|
||||
camOutlineOn: true,
|
||||
camContourTool: 1000,
|
||||
camContourSpindle: 1000,
|
||||
camContourOver: 0.5,
|
||||
camContourSpeed: 1000,
|
||||
camArcEnabled: false,
|
||||
camArcResolution: 5,
|
||||
camArcTolerance: 0.15,
|
||||
camContourAngle: 85,
|
||||
camContourLeave: 0,
|
||||
camContourReduce: 2,
|
||||
camContourBottom: false,
|
||||
camContourBridge: 0,
|
||||
camContourCurves: false,
|
||||
camContourIn: false,
|
||||
camContourLeave: 0,
|
||||
camContourOver: 0.5,
|
||||
camContourReduce: 2,
|
||||
camContourSpeed: 1000,
|
||||
camContourSpindle: 1000,
|
||||
camContourTool: 1000,
|
||||
camContourXOn: true,
|
||||
camContourYOn: true,
|
||||
camLatheTool: 1000,
|
||||
camLatheSpindle: 1000,
|
||||
camLatheOver: 0.1,
|
||||
camLatheAngle: 1,
|
||||
camLatheSpeed: 500,
|
||||
camLatheLinear: true,
|
||||
camTolerance: 0,
|
||||
camTraceTool: 1000,
|
||||
camTraceSpindle: 1000,
|
||||
camTraceType: "follow",
|
||||
camTraceOver: 0.5,
|
||||
camTraceDown: 0,
|
||||
camTraceThru: 0,
|
||||
camTraceSpeed: 250,
|
||||
camTracePlunge: 200,
|
||||
camTraceOffOver: 0,
|
||||
camTraceDogbone: false,
|
||||
camTraceMerge: false,
|
||||
camTraceLines: false,
|
||||
camTraceZTop: 0,
|
||||
camTraceZBottom: 0,
|
||||
camPocketSpindle: 1000,
|
||||
camPocketTool: 1000,
|
||||
camPocketOver: 0.25,
|
||||
camPocketDown: 1,
|
||||
camPocketSpeed: 250,
|
||||
camPocketPlunge: 200,
|
||||
camPocketExpand: 0,
|
||||
camPocketSmooth: 0,
|
||||
camPocketRefine: 20,
|
||||
camPocketFollow: 5,
|
||||
camPocketContour: false,
|
||||
camPocketEngrave: false,
|
||||
camPocketOutline: false,
|
||||
camPocketZTop: 0,
|
||||
camPocketZBottom: 0,
|
||||
camDrillTool: 1006,
|
||||
camDrillSpindle: 1000,
|
||||
camDrillDownSpeed: 250,
|
||||
camConventional: false, // outputClockwise
|
||||
camDepthFirst: false,
|
||||
camDrillDown: 5,
|
||||
camDrillDownSpeed: 250,
|
||||
camDrillDwell: 250,
|
||||
camDrillFromStockTop: false,
|
||||
camDrillingOn: false,
|
||||
camDrillLift: 2,
|
||||
camDrillMark: false,
|
||||
camDrillFromStockTop: false,
|
||||
camDrillThru: 5,
|
||||
camDrillPrecision: 1,
|
||||
camDrillingOn: false,
|
||||
camRegisterSpeed: 1000,
|
||||
camRegisterThru: 5,
|
||||
camRegisterOffset: 10,
|
||||
camHelicalTool: 1000,
|
||||
camHelicalSpindle: 1000,
|
||||
camHelicalDownSpeed: 250,
|
||||
camHelicalSpeed: 1000,
|
||||
camHelicalDown: 5,
|
||||
camHelicalBottomFinish: true,
|
||||
camHelicalThru: 0,
|
||||
camHelicalOffset: "auto",
|
||||
camHelicalForceStartAngle: false,
|
||||
camHelicalStartAngle: 0,
|
||||
camHelicalOffsetOverride: 0,
|
||||
camHelicalEntry: false,
|
||||
camHelicalEntryOffset:0,
|
||||
camHelicalReverse: false,
|
||||
camHelicalClockwise: true,
|
||||
camFlipAxis: "X",
|
||||
camFlipOther: "",
|
||||
camLaserEnable: ["M321"],
|
||||
camLaserDisable: ["M322"],
|
||||
camLaserOn: ["M3"],
|
||||
camLaserOff: ["M5"],
|
||||
camLaserSpeed: 100,
|
||||
camLaserPower: 1,
|
||||
camLaserAdaptive: false,
|
||||
camLaserAdaptMod: false,
|
||||
camLaserFlatten: false,
|
||||
camLaserFlatZ: 0,
|
||||
camLaserPowerMin: 0,
|
||||
camLaserPowerMax: 1,
|
||||
camLaserZMin: 0,
|
||||
camLaserZMax: 0,
|
||||
camTabsWidth: 5,
|
||||
camTabsHeight: 5,
|
||||
camTabsDepth: 5,
|
||||
camTabsMidline: false,
|
||||
camDepthFirst: false,
|
||||
camInnerFirst: false,
|
||||
camEaseDown: false,
|
||||
camDrillSpindle: 1000,
|
||||
camDrillThru: 5,
|
||||
camDrillTool: 1006,
|
||||
camEaseAngle: 10,
|
||||
camOriginTop: true,
|
||||
camZAnchor: "middle",
|
||||
camZOffset: 0,
|
||||
camZTop: 0,
|
||||
camZBottom: 0,
|
||||
camZClearance: 1,
|
||||
camZThru: 0,
|
||||
camEaseDown: false,
|
||||
camExpertFast: false,
|
||||
camFastFeed: 6000,
|
||||
camFastFeedZ: 300,
|
||||
camTolerance: 0,
|
||||
camFirstZMax: false,
|
||||
camFlatness: 0.001,
|
||||
camContourBridge: 0,
|
||||
camStockX: 5,
|
||||
camStockY: 5,
|
||||
camStockZ: 5,
|
||||
camStockOffset: true,
|
||||
camStockClipTo: false,
|
||||
camStockIndexed: false,
|
||||
camStockIndexGrid: true,
|
||||
camIndexAxis: 0,
|
||||
camFlipAxis: "X",
|
||||
camFlipOther: "",
|
||||
camForceZMax: false,
|
||||
camFullEngage: 0.8,
|
||||
camHelicalBottomFinish: true,
|
||||
camHelicalClockwise: true,
|
||||
camHelicalDown: 5,
|
||||
camHelicalDownSpeed: 250,
|
||||
camHelicalEntry: false,
|
||||
camHelicalEntryOffset:0,
|
||||
camHelicalForceStartAngle: false,
|
||||
camHelicalOffset: "auto",
|
||||
camHelicalOffsetOverride: 0,
|
||||
camHelicalReverse: false,
|
||||
camHelicalSpeed: 1000,
|
||||
camHelicalSpindle: 1000,
|
||||
camHelicalStartAngle: 0,
|
||||
camHelicalThru: 0,
|
||||
camHelicalTool: 1000,
|
||||
camIndexAbs: true,
|
||||
camConventional: false, // outputClockwise
|
||||
camIndexAxis: 0,
|
||||
camInnerFirst: false,
|
||||
camLaserAdaptive: false,
|
||||
camLaserAdaptMod: false,
|
||||
camLaserDisable: ["M322"],
|
||||
camLaserEnable: ["M321"],
|
||||
camLaserFlatten: false,
|
||||
camLaserFlatZ: 0,
|
||||
camLaserOff: ["M5"],
|
||||
camLaserOn: ["M3"],
|
||||
camLaserPower: 1,
|
||||
camLaserPowerMax: 1,
|
||||
camLaserPowerMin: 0,
|
||||
camLaserSpeed: 100,
|
||||
camLaserZMax: 0,
|
||||
camLaserZMin: 0,
|
||||
camLatheAngle: 1,
|
||||
camLatheLinear: true,
|
||||
camLatheOver: 0.1,
|
||||
camLatheSpeed: 500,
|
||||
camLatheSpindle: 1000,
|
||||
camLatheTool: 1000,
|
||||
camLatheOffStart: 0,
|
||||
camLatheOffEnd: 0,
|
||||
camLevelDown: 0,
|
||||
camLevelInset: 0.5,
|
||||
camLevelOver: 0.75,
|
||||
camLevelSpeed: 1000,
|
||||
camLevelSpindle: 1000,
|
||||
camLevelStepZ: 0,
|
||||
camLevelStock: true,
|
||||
camLevelTool: 1000,
|
||||
camOriginCenter: false,
|
||||
camOriginOffX: 0,
|
||||
camOriginOffY: 0,
|
||||
camOriginOffZ: 0,
|
||||
camOriginTop: true,
|
||||
camOutlineDogbone: false,
|
||||
camOutlineDown: 3,
|
||||
camOutlineIn: false,
|
||||
camOutlineOmitThru: false,
|
||||
camOutlineOmitVoid: false,
|
||||
camOutlineOn: true,
|
||||
camOutlineOut: true,
|
||||
camOutlineOver: 0.4,
|
||||
camOutlineOverCount: 1,
|
||||
camOutlinePlunge: 250,
|
||||
camOutlineSpeed: 800,
|
||||
camOutlineSpindle: 1000,
|
||||
camOutlineTool: 1000,
|
||||
camOutlineTop: true,
|
||||
camOutlineWide: false,
|
||||
camPocketContour: false,
|
||||
camPocketDown: 1,
|
||||
camPocketEngrave: false,
|
||||
camPocketExpand: 0,
|
||||
camPocketFollow: 5,
|
||||
camPocketOutline: false,
|
||||
camPocketOver: 0.25,
|
||||
camPocketPlunge: 200,
|
||||
camPocketRefine: 20,
|
||||
camPocketSmooth: 1,
|
||||
camPocketSpeed: 250,
|
||||
camPocketSpindle: 1000,
|
||||
camPocketTool: 1000,
|
||||
camPocketZBottom: 0,
|
||||
camPocketZTop: 0,
|
||||
camRegisterOffset: 10,
|
||||
camRegisterSpeed: 1000,
|
||||
camRegisterThru: 5,
|
||||
camRoughAll: true,
|
||||
camRoughDown: 2,
|
||||
camRoughFlat: true,
|
||||
camRoughIn: true,
|
||||
camRoughOmitThru: false,
|
||||
camRoughOmitVoid: false,
|
||||
camRoughOn: true,
|
||||
camRoughOver: 0.4,
|
||||
camRoughPlunge: 250,
|
||||
camRoughSpeed: 1000,
|
||||
camRoughSpindle: 1000,
|
||||
camRoughStock: 0,
|
||||
camRoughStockZ: 0,
|
||||
camRoughTool: 1000,
|
||||
camRoughTop: true,
|
||||
camRoughVoid: false,
|
||||
camStockClipTo: false,
|
||||
camStockIndexed: false,
|
||||
camStockIndexGrid: true,
|
||||
camStockOffset: true,
|
||||
camStockX: 5,
|
||||
camStockY: 5,
|
||||
camStockZ: 5,
|
||||
camTabsDepth: 5,
|
||||
camTabsHeight: 5,
|
||||
camTabsMidline: false,
|
||||
camTabsWidth: 5,
|
||||
camTolerance: 0,
|
||||
camToolInit: true,
|
||||
camTraceDogbone: false,
|
||||
camTraceDown: 0,
|
||||
camTraceLines: false,
|
||||
camTraceMerge: false,
|
||||
camTraceOffOver: 0,
|
||||
camTraceOver: 0.5,
|
||||
camTracePlunge: 200,
|
||||
camTraceSpeed: 250,
|
||||
camTraceSpindle: 1000,
|
||||
camTraceThru: 0,
|
||||
camTraceTool: 1000,
|
||||
camTraceType: "follow",
|
||||
camTraceZBottom: 0,
|
||||
camTraceZTop: 0,
|
||||
camTrueShadow: false,
|
||||
camZAnchor: "middle",
|
||||
camZBottom: 0,
|
||||
camZClearance: 1,
|
||||
camZOffset: 0,
|
||||
camZThru: 0,
|
||||
camZTop: 0,
|
||||
op2: [], // flip ops
|
||||
ops: [], // current ops
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
camExpertFast: false,
|
||||
camTrueShadow: false,
|
||||
camArcEnabled: false,
|
||||
camArcTolerance: 0.15,
|
||||
camArcResolution: 5,
|
||||
camForceZMax: false,
|
||||
camFirstZMax: false,
|
||||
camToolInit: true,
|
||||
camFullEngage: 0.8,
|
||||
ops: [], // current ops
|
||||
op2: [] // flip ops
|
||||
processName: "default",
|
||||
}
|
||||
},
|
||||
laser: {
|
||||
|
|
@ -915,37 +916,37 @@ export const conf = {
|
|||
devproc: {},
|
||||
// application ui and control preferences (Q menu)
|
||||
controller:{
|
||||
view: null,
|
||||
dark: false,
|
||||
shiny: false,
|
||||
drawer: false,
|
||||
scrolls: true,
|
||||
zoomSpeed: 1.0,
|
||||
lineType: "path",
|
||||
autoSave: true,
|
||||
animesh: "800",
|
||||
antiAlias: true,
|
||||
assembly: false,
|
||||
autoLayout: true,
|
||||
autoSave: true,
|
||||
dark: false,
|
||||
detail: "50",
|
||||
devel: false,
|
||||
drawer: false,
|
||||
edgeangle: 20,
|
||||
exportOcto: false,
|
||||
exportPreview: false,
|
||||
exportThumb: false,
|
||||
freeLayout: true,
|
||||
healMesh: false,
|
||||
lineType: "path",
|
||||
manifold: false,
|
||||
ortho: false,
|
||||
reverseZoom: true,
|
||||
scrolls: true,
|
||||
shiny: true,
|
||||
showOrigin: false,
|
||||
showRulers: true,
|
||||
showSpeeds: true,
|
||||
freeLayout: true,
|
||||
autoLayout: true,
|
||||
spaceRandoX: false,
|
||||
spaceLayout: 1,
|
||||
edgeangle: 20,
|
||||
units: "mm",
|
||||
exportOcto: false,
|
||||
exportGhost: false,
|
||||
exportLocal: false,
|
||||
exportThumb: false,
|
||||
exportPreview: false,
|
||||
detail: "50",
|
||||
animesh: "800",
|
||||
healMesh: false,
|
||||
spaceRandoX: false,
|
||||
threaded: true,
|
||||
assembly: false,
|
||||
ortho: false,
|
||||
devel: false
|
||||
units: "mm",
|
||||
view: null,
|
||||
webGPU: false,
|
||||
zoomSpeed: 1.0,
|
||||
},
|
||||
// default hidden ui groups
|
||||
hidden: {
|
||||
|
|
|
|||
|
|
@ -108,7 +108,8 @@ const LISTS = {
|
|||
regaxis: [
|
||||
{ name: "X" },
|
||||
{ name: "Y" },
|
||||
{ name: "-" }
|
||||
{ name: "-" },
|
||||
{ name: "=" }
|
||||
],
|
||||
regpoints: [
|
||||
{ name: "2" },
|
||||
|
|
|
|||
|
|
@ -218,207 +218,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
ajax.send(form);
|
||||
}
|
||||
|
||||
function gridhost_tracker(host,key) {
|
||||
ajax(host+"/api/check?key="+key, function(data) {
|
||||
data = js2o(data);
|
||||
console.log(data);
|
||||
if (!(data.done || data.error)) {
|
||||
setTimeout(function() { gridhost_tracker(host,key) }, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function gridlocal_probe(ev, devs) {
|
||||
if (ev && ev.code !== 'Enter') return;
|
||||
|
||||
if (!devs && api.probe.local(gridlocal_probe)) return;
|
||||
|
||||
grid_local = devs;
|
||||
|
||||
let gdev = localGet('grid-local');
|
||||
let gloc = $('grid-local');
|
||||
let html = [];
|
||||
for (let uuid in devs) {
|
||||
gdev = gdev || uuid;
|
||||
let dev = devs[uuid];
|
||||
let sel = uuid === gdev ? ' selected' : '';
|
||||
html.push(`<option id="gl-${uuid}" value="${uuid}" ${sel}>${dev.stat.device.name}</option>`);
|
||||
}
|
||||
|
||||
if (html.length) {
|
||||
gloc.innerHTML = html.join('\n');
|
||||
gloc.onchange = (ev) => {
|
||||
localSet('grid-local', gloc.options[gloc.selectedIndex].value);
|
||||
};
|
||||
$('send-to-gridhead').style.display = 'flex';
|
||||
$('send-to-gridspool').style.display = 'flex';
|
||||
} else {
|
||||
$('send-to-gridhead').style.display = '';
|
||||
$('send-to-gridspool').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function sendto_gridlocal() {
|
||||
let uuid = $('grid-local').value;
|
||||
let dev = grid_local[uuid];
|
||||
if (dev) {
|
||||
let file = $('print-filename').value;
|
||||
fetch(
|
||||
`${api.probe.live}/api/grid_send?uuid=${uuid}&file=${encodeURIComponent(file + "." + fileext)}`,
|
||||
{method: "POST", body: gcode}
|
||||
)
|
||||
.then(t => t.text())
|
||||
.then(t => {
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_local_ok`);
|
||||
console.log({grid_spool_said: t});
|
||||
})
|
||||
.catch(e => {
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_local_err`);
|
||||
console.log({grid_local_spool_error: e});
|
||||
})
|
||||
.finally(() => {
|
||||
api.modal.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function admin_gridlocal() {
|
||||
let dev = grid_local[$('grid-local').value];
|
||||
if (dev && dev.stat && dev.stat.device) {
|
||||
let dsd = dev.stat.device;
|
||||
window.open(`http://${dsd.addr[0]}:${dsd.port || 4080}`, "_grid_admin");
|
||||
}
|
||||
}
|
||||
|
||||
function gridhost_probe(ev, set_host) {
|
||||
if (ev && ev.code !== 'Enter') return;
|
||||
if (!(grid_host && grid_apik)) return;
|
||||
|
||||
if (set_host) grid_host.value = set_host;
|
||||
|
||||
let xhtr = new XMLHttpRequest(),
|
||||
host = grid_host.value,
|
||||
apik = grid_apik.value,
|
||||
target = grid_target.value;
|
||||
|
||||
if (!apik) $('gpapik').style.display = 'none';
|
||||
|
||||
if (!host && api.probe.grid(gridhost_probe)) return;
|
||||
|
||||
if (!host) return;
|
||||
|
||||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
if (xhtr.status >= 200 && xhtr.status < 300) {
|
||||
localSet('grid-host', host);
|
||||
localSet('grid-apik', apik);
|
||||
let res = JSON.parse(xhtr.responseText);
|
||||
let sel = false;
|
||||
let match = false;
|
||||
let first = null;
|
||||
let html = [];
|
||||
grid_targets = {};
|
||||
for (let key in res) {
|
||||
first = first || key;
|
||||
if (!localGet('grid-target')) {
|
||||
localSet('grid-target', key);
|
||||
sel = true;
|
||||
} else {
|
||||
sel = localGet('grid-target') === key;
|
||||
}
|
||||
match = match || sel;
|
||||
grid_targets[html.length] = key;
|
||||
html.push(
|
||||
"<option id='gpo-'" + key + " value='" +key + "'" +
|
||||
(sel ? " selected" : "") +
|
||||
">" +
|
||||
(res[key].comment || key) +
|
||||
"</option>"
|
||||
);
|
||||
}
|
||||
if (!match) {
|
||||
localSet('grid-target', first);
|
||||
}
|
||||
grid_target.innerHTML = html.join('\n');
|
||||
} else if (xhtr.status === 401) {
|
||||
$('gpapik').style.display = '';
|
||||
} else {
|
||||
local.removeItem('grid-host');
|
||||
local.removeItem('grid-apik');
|
||||
console.log("invalid grid:host url");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhtr.open("GET", host + "/api/active?key=" + apik);
|
||||
xhtr.send();
|
||||
}
|
||||
|
||||
function sendto_gridhost() {
|
||||
if (!(grid_host && grid_apik)) return;
|
||||
|
||||
let xhtr = new XMLHttpRequest(),
|
||||
host = grid_host.value,
|
||||
apik = grid_apik.value,
|
||||
target = localGet('grid-target') || '';
|
||||
|
||||
if (target === '') {
|
||||
api.show.alert('invalid or missing target');
|
||||
return;
|
||||
}
|
||||
if (host.indexOf("http") !== 0) {
|
||||
api.show.alert("host missing protocol (http:// or https://)");
|
||||
return;
|
||||
}
|
||||
if (host.indexOf("://") < 0) {
|
||||
api.show.alert("host:port malformed");
|
||||
return;
|
||||
}
|
||||
if (api.const.SECURE && !api.util.isSecure(host)) {
|
||||
api.show.alert("host must begin with 'https' on a secure site");
|
||||
return;
|
||||
}
|
||||
|
||||
localSet('grid-host', host.trim());
|
||||
localSet('grid-apik', apik.trim());
|
||||
|
||||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
let status = xhtr.status;
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_grid_${status}`);
|
||||
if (status >= 200 && status < 300) {
|
||||
let json = js2o(xhtr.responseText);
|
||||
gridhost_tracker(host,json.key);
|
||||
api.ajax(host+"/api/wait?key="+json.key, function(data) {
|
||||
data = js2o(data);
|
||||
console.log(data);
|
||||
api.show.alert("print to "+target+": "+data.status, 600);
|
||||
});
|
||||
} else {
|
||||
api.show.alert("grid:host error\nstatus: "+status+"\nmessage: "+xhtr.responseText, 10000);
|
||||
}
|
||||
api.show.progress(0);
|
||||
}
|
||||
};
|
||||
xhtr.upload.addEventListener('progress', function(evt) {
|
||||
api.show.progress(evt.loaded/evt.total, "sending");
|
||||
});
|
||||
filename = $('print-filename').value;
|
||||
xhtr.open("POST",
|
||||
host + "/api/print?" +
|
||||
"filename=" + filename +
|
||||
"&target=" + target +
|
||||
"&key=" + apik +
|
||||
"&time=" + Math.round(info.time) +
|
||||
"&length=" + Math.round(info.distance) +
|
||||
"&image=" + filename
|
||||
);
|
||||
xhtr.setRequestHeader("Content-Type", "text/plain");
|
||||
let snapshot = api.view.snapshot;
|
||||
xhtr.send(snapshot ? [gcode,snapshot].join("\0") : gcode);
|
||||
api.modal.hide();
|
||||
}
|
||||
|
||||
function download() {
|
||||
filename = $('print-filename').value;
|
||||
api.util.download(gcode, filename + "." + fileext);
|
||||
|
|
@ -463,8 +262,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
let set = api.conf.get();
|
||||
let fdm = MODE === MODES.FDM;
|
||||
let octo = set.controller.exportOcto && MODE !== MODES.CAM;
|
||||
let ghost = set.controller.exportGhost;
|
||||
let local = set.controller.exportLocal;
|
||||
let preview = set.controller.exportPreview;
|
||||
$('code-preview-head').style.display = preview ? '' : 'none';
|
||||
$('code-preview').style.display = preview ? '' : 'none';
|
||||
|
|
@ -484,8 +281,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
// persist fields when changed
|
||||
bindField('octo-host', 'octo-host');
|
||||
bindField('octo-apik', 'octo-apik');
|
||||
bindField('grid-host', 'grid-host');
|
||||
bindField('grid-apik', 'grid-apik');
|
||||
|
||||
// in cam mode, show zip file option
|
||||
let downloadZip = $('print-zip');
|
||||
|
|
@ -699,37 +494,11 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
$('ophost').style.display = 'none';
|
||||
$('opapik').style.display = 'none';
|
||||
$('oph1nt').style.display = 'none';
|
||||
$('send-to-gridhost').style.display = 'none';
|
||||
}
|
||||
octo_host.value = localGet('octo-host') || '';
|
||||
octo_apik.value = localGet('octo-apik') || '';
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// grid:host setup
|
||||
$('send-to-hosthead').style.display = ghost ? '' : 'none';
|
||||
$('send-to-gridhost').style.display = ghost ? '' : 'none';
|
||||
if (ghost) try {
|
||||
$('print-gridhost').onclick = sendto_gridhost;
|
||||
$('grid-host').onkeyup = gridhost_probe;
|
||||
$('grid-apik').onkeyup = gridhost_probe;
|
||||
grid_host = $('grid-host');
|
||||
grid_apik = $('grid-apik');
|
||||
grid_target = $('grid-target');
|
||||
grid_target.onchange = function(ev) {
|
||||
localSet('grid-target', grid_targets[grid_target.selectedIndex]);
|
||||
};
|
||||
grid_host.value = localGet('grid-host') || '';
|
||||
grid_apik.value = localGet('grid-apik') || '';
|
||||
gridhost_probe();
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// grid:local setup
|
||||
if (local) try {
|
||||
gridlocal_probe();
|
||||
$('print-gridlocal').onclick = sendto_gridlocal;
|
||||
$('admin-gridlocal').onclick = admin_gridlocal;
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// preview of the generated GCODE (first 64k max)
|
||||
if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,28 +142,28 @@ function booleanSave() {
|
|||
if (control.antiAlias != ui.antiAlias.checked) {
|
||||
api.show.alert('Page Reload Required to Change Aliasing');
|
||||
}
|
||||
control.shiny = ui.shiny.checked;
|
||||
control.antiAlias = ui.antiAlias.checked;
|
||||
control.assembly = ui.assembly.checked;
|
||||
control.autoLayout = ui.autoLayout.checked;
|
||||
control.autoSave = ui.autoSave.checked;
|
||||
control.dark = ui.dark.checked;
|
||||
control.devel = ui.devel.checked;
|
||||
control.drawer = ui.drawer.checked;
|
||||
control.exportOcto = ui.exportOcto.checked;
|
||||
control.exportPreview = ui.exportPreview.checked;
|
||||
control.exportThumb = ui.exportThumb.checked;
|
||||
control.freeLayout = ui.freeLayout.checked;
|
||||
control.healMesh = ui.healMesh.checked;
|
||||
control.ortho = ui.ortho.checked;
|
||||
control.manifold = ui.manifold.checked;
|
||||
control.reverseZoom = ui.reverseZoom.checked;
|
||||
control.scrolls = ui.scrolls.checked;
|
||||
control.shiny = ui.shiny.checked;
|
||||
control.showOrigin = ui.showOrigin.checked;
|
||||
control.showRulers = ui.showRulers.checked;
|
||||
control.autoLayout = ui.autoLayout.checked;
|
||||
control.freeLayout = ui.freeLayout.checked;
|
||||
control.spaceRandoX = ui.spaceRandoX.checked;
|
||||
control.autoSave = ui.autoSave.checked;
|
||||
control.antiAlias = ui.antiAlias.checked;
|
||||
control.reverseZoom = ui.reverseZoom.checked;
|
||||
control.dark = ui.dark.checked;
|
||||
control.exportOcto = ui.exportOcto.checked;
|
||||
control.exportGhost = ui.exportGhost.checked;
|
||||
control.exportLocal = ui.exportLocal.checked;
|
||||
control.exportThumb = ui.exportThumb.checked;
|
||||
control.exportPreview = ui.exportPreview.checked;
|
||||
control.healMesh = ui.healMesh.checked;
|
||||
control.threaded = setThreaded(ui.threaded.checked);
|
||||
control.assembly = ui.assembly.checked;
|
||||
control.ortho = ui.ortho.checked;
|
||||
control.devel = ui.devel.checked;
|
||||
control.webGPU = ui.webGPU.checked;
|
||||
space.view.setZoom(control.reverseZoom, control.zoomSpeed);
|
||||
// platform.layout();
|
||||
api.conf.save();
|
||||
|
|
@ -904,7 +904,6 @@ function init_one() {
|
|||
xany: $('mod-x-any'),
|
||||
xsla: $('mod-x-sla'),
|
||||
xlaser: $('mod-x-laser'),
|
||||
local: $('mod-local'),
|
||||
don8: $('mod-don8'),
|
||||
any: $('mod-any'),
|
||||
},
|
||||
|
|
@ -1048,6 +1047,7 @@ function init_one() {
|
|||
showSpeeds: newBoolean(LANG.op_sped_s, speedSave, {title:LANG.op_sped_l}),
|
||||
shiny: newBoolean(LANG.op_shny_s, booleanSave, {title:LANG.op_shny_l, modes:FDM}),
|
||||
lineType: newSelect(LANG.op_line_s, {title: LANG.op_line_l, action: lineTypeSave, modes:FDM}, "linetype"),
|
||||
manifold: newBoolean(LANG.op_mani_s, booleanSave, {title: LANG.op_mani_l, modes:CAM}, "manifold"),
|
||||
animesh: newSelect(LANG.op_anim_s, {title: LANG.op_anim_l, action: aniMeshSave, modes:CAM}, "animesh"),
|
||||
units: newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, action: unitsSave, modes:CAM}, "units"),
|
||||
edgeangle: newInput(LANG.op_spoa_s, {title:LANG.op_spoa_l, convert:toFloat, size:3}),
|
||||
|
|
@ -1058,8 +1058,6 @@ function init_one() {
|
|||
spaceRandoX: newBoolean(LANG.op_spcx_s, booleanSave, {title:LANG.op_spcx_l, show:isBelt}),
|
||||
spaceLayout: newInput(LANG.op_spcr_s, {title:LANG.op_spcr_l, convert:toFloat, size:3, units}),
|
||||
_____: newGroup(LANG.xp_menu, $('prefs-xpo'), {inline: true}),
|
||||
exportLocal: newBoolean(`Grid:Local`, booleanSave, {title:LANG.op_exgl_l}),
|
||||
exportGhost: newBoolean(`Grid:Host`, booleanSave, {title:LANG.op_exgh_l}),
|
||||
exportOcto: newBoolean(`OctoPrint`, booleanSave, {title:LANG.op_exop_l}),
|
||||
exportThumb: newBoolean(`Thumbnail`, booleanSave, {modes:FDM}),
|
||||
exportPreview: newBoolean(`Code Preview`, booleanSave),
|
||||
|
|
@ -1068,6 +1066,7 @@ function init_one() {
|
|||
healMesh: newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}),
|
||||
threaded: newBoolean(LANG.pt_thrd_s, booleanSave, {title: LANG.pt_thrd_l, modes:THREED}),
|
||||
assembly: newBoolean(LANG.pt_assy_s, booleanSave, {title: LANG.pt_assy_l, modes:THREED}),
|
||||
webGPU: newBoolean(LANG.pt_wgpu_s, booleanSave, {title: LANG.pt_wgpu_l, modes:THREED}),
|
||||
|
||||
prefadd: uc.checkpoint($('prefs-add')),
|
||||
|
||||
|
|
@ -1635,20 +1634,23 @@ function init_two() {
|
|||
space.platform.setZOff(0.05);
|
||||
|
||||
// restore UI state from settings
|
||||
ui.antiAlias.checked = control.antiAlias;
|
||||
ui.assembly.checked = control.assembly;
|
||||
ui.autoLayout.checked = control.autoLayout;
|
||||
ui.autoSave.checked = control.autoSave;
|
||||
ui.devel.checked = control.devel;
|
||||
ui.freeLayout.checked = control.freeLayout;
|
||||
ui.healMesh.checked = control.healMesh;
|
||||
ui.manifold.checked = control.manifold;
|
||||
ui.ortho.checked = control.ortho;
|
||||
ui.reverseZoom.checked = control.reverseZoom;
|
||||
ui.showOrigin.checked = control.showOrigin;
|
||||
ui.showRulers.checked = control.showRulers;
|
||||
ui.showSpeeds.checked = control.showSpeeds;
|
||||
ui.freeLayout.checked = control.freeLayout;
|
||||
ui.autoLayout.checked = control.autoLayout;
|
||||
ui.spaceRandoX.checked = control.spaceRandoX;
|
||||
ui.antiAlias.checked = control.antiAlias;
|
||||
ui.reverseZoom.checked = control.reverseZoom;
|
||||
ui.autoSave.checked = control.autoSave;
|
||||
ui.healMesh.checked = control.healMesh;
|
||||
ui.threaded.checked = setThreaded(control.threaded);
|
||||
ui.assembly.checked = control.assembly;
|
||||
ui.ortho.checked = control.ortho;
|
||||
ui.devel.checked = control.devel;
|
||||
ui.webGPU.checked = control.webGPU;
|
||||
|
||||
lineTypeSave();
|
||||
detailSave();
|
||||
updateStats();
|
||||
|
|
@ -1789,6 +1791,7 @@ function init_two() {
|
|||
$('context-duplicate').onclick = duplicateSelection;
|
||||
$('context-mirror').onclick = mirrorSelection;
|
||||
$('context-layflat').onclick = () => { api.event.emit("tool.mesh.lay-flat") };
|
||||
$('context-lefty').onclick = () => { api.event.emit("tool.mesh.lefty") };
|
||||
$('context-setfocus').onclick = () => {
|
||||
api.event.emit(
|
||||
"tool.camera.focus",
|
||||
|
|
|
|||
|
|
@ -114,12 +114,6 @@ export const mode = {
|
|||
}
|
||||
};
|
||||
|
||||
export const probe = {
|
||||
live: "https://live.grid.space",
|
||||
grid: noop,
|
||||
local: noop
|
||||
};
|
||||
|
||||
export const process = {
|
||||
code: currentProcessCode,
|
||||
get: currentProcessName
|
||||
|
|
@ -784,43 +778,7 @@ function showHelpFile(local,then) {
|
|||
}
|
||||
|
||||
function showLocal() {
|
||||
showModal('local');
|
||||
api.probe.local((err,data) => {
|
||||
let devc = 0;
|
||||
let bind = [];
|
||||
let html = ['<table>'];
|
||||
html.push(`<thead><tr><th>device</th><th>type</th><th>status</th><th></th></tr></thead>`);
|
||||
html.push(`<tbody>`);
|
||||
let recs = [];
|
||||
for (let k in data) {
|
||||
recs.push(data[k].stat);
|
||||
}
|
||||
recs.sort((a,b) => {
|
||||
return a.device.name < b.device.name ? -1 : 1;
|
||||
});
|
||||
for (let r of recs) {
|
||||
bind.push({uuid: r.device.uuid, host: r.device.addr[0], port: r.device.port});
|
||||
html.push(`<tr>`);
|
||||
html.push(`<td>${r.device.name}</td>`);
|
||||
html.push(`<td>${r.device.mode}</td>`);
|
||||
html.push(`<td>${r.state}</td>`);
|
||||
html.push(`<td><button id="${r.device.uuid}">admin</button></td>`);
|
||||
html.push(`</tr>`);
|
||||
devc++;
|
||||
}
|
||||
html.push(`</tbody>`);
|
||||
html.push(`</table>`);
|
||||
if (devc) {
|
||||
$('mod-local').innerHTML = html.join('');
|
||||
} else {
|
||||
$('mod-local').innerHTML = `<br><b>no local devices</b>`;
|
||||
}
|
||||
bind.forEach(rec => {
|
||||
$(rec.uuid).onclick = () => {
|
||||
window.open(`http://${rec.host}:${rec.port||4080}/`);
|
||||
};
|
||||
});
|
||||
});
|
||||
// deprecated
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
|
|
|
|||
|
|
@ -163,19 +163,19 @@ function platformUpdateTopZ() {
|
|||
const wzmax = bounds.max.z;
|
||||
const zdelta = process.camZOffset || 0;
|
||||
switch (process.camZAnchor) {
|
||||
case 'top':
|
||||
widget.setTopZ(stock.z - zdelta);
|
||||
break;
|
||||
case 'middle':
|
||||
if (widget.isIndexed) {
|
||||
widget.setTopZ(zdelta);
|
||||
} else {
|
||||
widget.setTopZ(stock.z - (stock.z - wzmax) / 2 + zdelta);
|
||||
}
|
||||
break;
|
||||
case 'bottom':
|
||||
widget.setTopZ(wzmax + zdelta);
|
||||
break;
|
||||
case 'top':
|
||||
widget.setTopZ(stock.z - zdelta, true);
|
||||
break;
|
||||
case 'middle':
|
||||
if (widget.isIndexed) {
|
||||
widget.setTopZ(zdelta, true);
|
||||
} else {
|
||||
widget.setTopZ(stock.z - (stock.z - wzmax) / 2 + zdelta, true);
|
||||
}
|
||||
break;
|
||||
case 'bottom':
|
||||
widget.setTopZ(wzmax + zdelta, true);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
widget.setTopZ(0);
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ class Print {
|
|||
// drop duplicates (usually intruced by FDM bisections)
|
||||
if (lastPoint && point && !arc && type !== 'lerp') {
|
||||
// nested due to uglify confusing browser
|
||||
const { x, y, z } = lastPoint;
|
||||
if (point.x == x && point.y == y && point.z == z && lastEmit == emit) {
|
||||
const { x, y, z, a } = lastPoint;
|
||||
if (point.x == x && point.y == y && point.z == z && point.a == z && lastEmit == emit) {
|
||||
return lastOut;
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +129,7 @@ class Print {
|
|||
close = !options.open,
|
||||
scarf = !poly.open ? (options.scarf ?? 0) : false,
|
||||
tool = options.tool,
|
||||
zmax = options.zmax,
|
||||
last = startPoint,
|
||||
first = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ function rotate(x, y, z) {
|
|||
space.update();
|
||||
}
|
||||
|
||||
function merge() {
|
||||
function merge({ deleteMerged = true }) {
|
||||
let sel = widgets();
|
||||
if (sel.length === 0) {
|
||||
sel = api.widgets.all();
|
||||
|
|
@ -207,7 +207,7 @@ function merge() {
|
|||
}
|
||||
});
|
||||
let w = api.platform.load_verts([], obj, "merged");
|
||||
api.platform.delete(sel);
|
||||
if (deleteMerged) api.platform.delete(sel);
|
||||
api.platform.select(w);
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ function isolateBodies(){
|
|||
if(bodies.length ==1){
|
||||
api.platform.select(widget);
|
||||
}else{
|
||||
console.log(bodies,Array.from(bodies.entries()))
|
||||
// console.log(bodies,Array.from(bodies.entries()))
|
||||
for(let [i,verts] of bodies.entries()){
|
||||
let w = api.platform.load_verts([], verts, `isolate ${i} of ${ widget.anno.file}`);
|
||||
api.platform.select(w)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,24 @@ function onLayFlatSelect() {
|
|||
// endIt();
|
||||
}
|
||||
|
||||
function onLeftySelect() {
|
||||
// Project the face normal onto the XY plane (ignore Z component)
|
||||
let projectedNormal = new Vector3(lastface.normal.x, lastface.normal.y, 0).normalize();
|
||||
|
||||
// Target direction on XY plane (negative Y axis)
|
||||
let targetDir = new Vector3(0, -1, 0);
|
||||
|
||||
// Calculate angle between projected normal and target
|
||||
let angle = Math.atan2(
|
||||
projectedNormal.x * targetDir.y - projectedNormal.y * targetDir.x, // cross product Z component
|
||||
projectedNormal.x * targetDir.x + projectedNormal.y * targetDir.y // dot product
|
||||
);
|
||||
|
||||
// Create quaternion for Z-axis rotation only
|
||||
let q = new Quaternion().setFromAxisAngle(ZAXIS, angle);
|
||||
api.selection.rotate(q);
|
||||
}
|
||||
|
||||
function onFaceUpSelect() {
|
||||
api.event.emit('tool.mesh.face-normal', lastface.normal);
|
||||
// endIt();
|
||||
|
|
@ -105,6 +123,12 @@ api.event.on('tool.mesh.lay-flat', () => {
|
|||
startIt();
|
||||
});
|
||||
|
||||
api.event.on('tool.mesh.lefty', () => {
|
||||
opName = 'align y axis';
|
||||
onDone = onLeftySelect;
|
||||
startIt();
|
||||
});
|
||||
|
||||
function scale() {
|
||||
let cam = space.internals().camera;
|
||||
let dist = cam.position.distanceTo(pmesh.position);
|
||||
|
|
|
|||
|
|
@ -473,10 +473,12 @@ class Widget {
|
|||
|
||||
/**
|
||||
* moves top of widget to given Z
|
||||
* only non-zero in CAM mode
|
||||
*
|
||||
* @param {number} z position
|
||||
* @param {boolean} cam mode
|
||||
*/
|
||||
setTopZ(z) {
|
||||
setTopZ(z, cam) {
|
||||
let mesh = this.mesh,
|
||||
track = this.track,
|
||||
mbb = mesh.getBoundingBox(),
|
||||
|
|
@ -488,7 +490,7 @@ class Widget {
|
|||
track.top = mbz;
|
||||
}
|
||||
// difference between top of stock/bounds and top of widget (cam mode)
|
||||
track.tzoff = mbz - z;
|
||||
track.tzoff = cam ? mbz - z : 0;
|
||||
this._updateMeshPosition();
|
||||
}
|
||||
|
||||
|
|
@ -503,8 +505,7 @@ class Widget {
|
|||
}
|
||||
|
||||
_move(x, y, z, abs) {
|
||||
let mesh = this.mesh,
|
||||
mat = this.getMaterial(),
|
||||
let mat = this.getMaterial(),
|
||||
pos = this.track.pos;
|
||||
// do not allow moves in pure slice view
|
||||
if (!mat.visible) return;
|
||||
|
|
@ -528,13 +529,10 @@ class Widget {
|
|||
track = this.track,
|
||||
tzoff = track.tzoff,
|
||||
top = track.top,
|
||||
tz = tzoff !== top ? -tzoff : 0,
|
||||
tz = -tzoff,
|
||||
{ x, y, z } = track.pos;
|
||||
if (track.indexed) {
|
||||
let rad = track.indexRad;
|
||||
let ln = track.box.d / 2;
|
||||
let dx = Math.sin(rad) * ln;
|
||||
let dy = ln - Math.cos(rad) * ln;
|
||||
this.mesh.rotation.x = -rad;
|
||||
z = top;
|
||||
} else {
|
||||
|
|
@ -782,9 +780,11 @@ class Widget {
|
|||
}
|
||||
if (set) {
|
||||
let dark = this.api.space.is_dark();
|
||||
let cam = this.api.mode.is_cam();
|
||||
let color = dark && !cam ? 0xffffff : 0;
|
||||
let angle = this.api.conf.get().controller.edgeangle || 20;
|
||||
let edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||
let material = new THREE.LineBasicMaterial({ color: 0 });
|
||||
let material = new THREE.LineBasicMaterial({ color });
|
||||
this.outline = new THREE.LineSegments(edges, material);
|
||||
this.outline.renderOrder = -20;
|
||||
mesh.add(this.outline);
|
||||
|
|
|
|||
|
|
@ -5,38 +5,37 @@
|
|||
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,
|
||||
animateClear = false,
|
||||
animating = false,
|
||||
renderDone = false,
|
||||
renderSteps = 0,
|
||||
renderSpeed = 0,
|
||||
indexCount = 0,
|
||||
updates = 0,
|
||||
maxR;
|
||||
|
||||
export function init(worker) {
|
||||
const { dispatch } = worker;
|
||||
|
||||
dispatch.animate_setup2 = function (data, send) {
|
||||
const { settings } = data;
|
||||
const { process } = settings;
|
||||
const { controller, process } = settings;
|
||||
const print = worker.current.print;
|
||||
const density = parseInt(settings.controller.animesh) * 1000;
|
||||
const isIndexed = process.camStockIndexed;
|
||||
|
|
@ -45,8 +44,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,25 +53,27 @@ 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 (controller.manifold)
|
||||
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);
|
||||
stockSlices.push(slice);
|
||||
slice.updateMesh([]);
|
||||
slice.send(send);
|
||||
// send({ mesh_move: { id: slice.id, pos: { x:0, y:0, z: stock.z/2} } });
|
||||
// send.data({ mesh_move: { id: slice.id, pos: { x:0, y:0, z: stock.z/2} } });
|
||||
}
|
||||
|
||||
send.done();
|
||||
};
|
||||
|
||||
dispatch.animate2 = function (data, send) {
|
||||
renderPause = data.pause || renderPause;
|
||||
renderSpeed = data.speed || 0;
|
||||
if (animating) {
|
||||
return send.done();
|
||||
|
|
@ -126,7 +125,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 +141,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, da, 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,26 +201,25 @@ 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, da, sp, md } = pos;
|
||||
toolMove(pos);
|
||||
// console.log('renderMoves', {id, moves, seed});
|
||||
let subs = 0;
|
||||
if (dx || dy || dz < 0)
|
||||
if (dx || dy || dz < 0 || da)
|
||||
for (let slice of stockSlices) {
|
||||
if (slice.bounds.intersectsBox(toolMesh.bounds)) {
|
||||
slice.subtractTool(toolMesh);
|
||||
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,29 +10,35 @@ 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,
|
||||
dark = false,
|
||||
manifold = false,
|
||||
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) {
|
||||
let alert = api.alerts.show("building animation");
|
||||
let settings = api.conf.get();
|
||||
dark = settings.controller.dark;
|
||||
manifold = settings.controller.manifold;
|
||||
client.animate_setup2(settings, data => {
|
||||
handleUpdate(data);
|
||||
if (data) {
|
||||
|
|
@ -48,7 +54,8 @@ export function animate2(api, delay) {
|
|||
speed: anim.speed,
|
||||
trans: anim.trans,
|
||||
model: anim.model,
|
||||
shade: anim.shade
|
||||
shade: anim.shade,
|
||||
path: anim.path
|
||||
});
|
||||
Object.assign(label, {
|
||||
progress: anim.progress,
|
||||
|
|
@ -59,6 +66,14 @@ export function animate2(api, delay) {
|
|||
a: anim.vala
|
||||
});
|
||||
|
||||
if (manifold) {
|
||||
anim.trans.classList.remove('hide');
|
||||
// anim.shade.classList.remove('hide');
|
||||
} else {
|
||||
anim.trans.classList.add('hide');
|
||||
// anim.shade.classList.add('hide');
|
||||
}
|
||||
|
||||
updateSpeed(0);
|
||||
setTimeout(step, delay || 0);
|
||||
toggleTrans(undefined, false);
|
||||
|
|
@ -72,6 +87,7 @@ export function animate2(api, delay) {
|
|||
button.trans.onclick = toggleTrans;
|
||||
button.model.onclick = toggleModel;
|
||||
button.shade.onclick = toggleStock;
|
||||
button.path.onclick = togglePath;
|
||||
button.play.style.display = '';
|
||||
button.pause.style.display = 'none';
|
||||
|
||||
|
|
@ -86,8 +102,9 @@ Object.assign(client, {
|
|||
},
|
||||
|
||||
animate_setup2(settings, ondone) {
|
||||
color = settings.controller.dark ? 0x888888 : 0;
|
||||
material = new THREE.MeshPhongMaterial({
|
||||
initPathMesh();
|
||||
color = dark ? 0x888888 : 0;
|
||||
material = material ?? new THREE.MeshPhongMaterial({
|
||||
flatShading: true,
|
||||
transparent: false,
|
||||
opacity: 0.5,
|
||||
|
|
@ -102,6 +119,73 @@ 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: dark ? 0xffff00 : 0x771100 }),
|
||||
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),
|
||||
show = api.local.get('cam.anim.path') ?? true;
|
||||
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 (show && bool !== lines.visible) {
|
||||
lines.visible = bool;
|
||||
} else if (!show) {
|
||||
lines.visible = false;
|
||||
}
|
||||
},
|
||||
show(bool) {
|
||||
show = bool;
|
||||
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 +216,7 @@ function meshUpdate(id, ind, pos, ilen, plen) {
|
|||
|
||||
function deleteMesh(id) {
|
||||
space.world.remove(meshes[id]);
|
||||
meshes[id].geometry.dispose();
|
||||
delete meshes[id];
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +226,15 @@ function toggleModel(ev, bool) {
|
|||
}
|
||||
|
||||
function toggleStock(ev, bool, set) {
|
||||
set !== false && api.local.toggle('cam.anim.stock', bool);
|
||||
return api.event.emit('cam.stock.toggle', bool ?? undefined);
|
||||
bool = api.local.toggle('cam.anim.stock', bool);
|
||||
for (let [ id, mesh ] of Object.entries(meshes)) {
|
||||
if (id >= 0) mesh.visible = bool;
|
||||
}
|
||||
}
|
||||
|
||||
function togglePath(ev, bool, set) {
|
||||
bool = api.local.toggle('cam.anim.path', bool);
|
||||
lineTracker?.show(bool);
|
||||
}
|
||||
|
||||
function toggleTrans(ev, bool) {
|
||||
|
|
@ -165,8 +257,7 @@ function play(opts) {
|
|||
}
|
||||
client.animate2({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
steps: steps || Infinity
|
||||
}, handleUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +268,7 @@ function fast(opts) {
|
|||
button.pause.style.display = '';
|
||||
client.animate2({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
steps: steps || Infinity
|
||||
}, handleUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +306,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 +320,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;
|
||||
|
|
@ -206,7 +207,7 @@ export function createPopOps() {
|
|||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
|
||||
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
|
||||
stepz: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, bound: UC.bound(0, 100.0) }),
|
||||
stepz: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true, bound: UC.bound(0, 100.0) }),
|
||||
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
|
||||
down: UC.newInput(LANG.cc_loff_s, { title: LANG.cc_loff_l, convert: UC.toFloat, units: true }),
|
||||
inset: UC.newInput(LANG.cc_lxyo_s, { title: LANG.cc_lxyo_l, convert: UC.toFloat, units: true, show: () => !env.popOp.level.rec.stock }),
|
||||
|
|
@ -282,7 +283,7 @@ export function createPopOps() {
|
|||
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
|
||||
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
|
||||
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0), show: () => env.popOp.outline.rec.wide }),
|
||||
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: UC.toInt, bound: UC.bound(1, 5), show: () => env.popOp.outline.rec.wide }),
|
||||
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: UC.toInt, bound: UC.bound(1, 500), show: () => env.popOp.outline.rec.wide }),
|
||||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
top: UC.newBoolean(LANG.co_clrt_s, undefined, { title: LANG.co_clrt_l }),
|
||||
inside: UC.newBoolean(LANG.co_olin_s, undefined, { title: LANG.co_olin_l, show: (op) => { return !op.inputs.outside.checked } }),
|
||||
|
|
@ -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" }),
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ function pointHoverUp(int, ev) {
|
|||
x += stock.x / 2;
|
||||
y += stock.y / 2;
|
||||
}
|
||||
process.camOriginOffX = x;
|
||||
process.camOriginOffY = y;
|
||||
process.camOriginOffX = x - stock.center.x;
|
||||
process.camOriginOffY = y - stock.center.y;
|
||||
process.camOriginOffZ = z;
|
||||
api.settings.update_fields(process);
|
||||
api.platform.update_origin();
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export function traceHoverUp(int, ev) {
|
|||
|
||||
export function traceToggle(obj, skip) {
|
||||
let material = obj.material[0] || obj.material;
|
||||
if (!material) return;
|
||||
if (!(material && obj.trace)) return;
|
||||
let { color, colorSave } = material;
|
||||
let { widget, poly } = obj.trace;
|
||||
let areas = env.poppedRec.areas;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Layers } from '../../core/layers.js';
|
|||
import { Stack } from '../../core/stack.js';
|
||||
import { updateStock } from './cl-stock.js';
|
||||
import { createPopOps } from './cl-ops.js';
|
||||
import { tabAdd, tabDone, tabClear, restoreTabs, rotateTabs, updateTabs, clearTabs } from './cl-tab.js';
|
||||
import { tabAdd, tabDone, tabClear, restoreTabs, rotateTabs, updateTabs, clearTabs, mirrorTabs } from './cl-tab.js';
|
||||
import { traceOn, traceDone, unselectTraces } from './cl-trace.js';
|
||||
import { holeSelOn, selectHolesDone, clearHolesRec } from './cl-hole.js';
|
||||
import { surfaceOn, surfaceDone } from './cl-surface.js';
|
||||
|
|
@ -29,6 +29,7 @@ const { widgets: WIDGETS } = api;
|
|||
class Client {
|
||||
animVer = 0;
|
||||
camStock;
|
||||
showStock = true;
|
||||
current;
|
||||
currentIndex;
|
||||
flipping;
|
||||
|
|
@ -320,6 +321,7 @@ export function opRender() {
|
|||
el.classList.add("disabled");
|
||||
}
|
||||
bounds.push(el);
|
||||
let parent = $('camops');
|
||||
let timer = null;
|
||||
let inside = true;
|
||||
let popped = false;
|
||||
|
|
@ -329,14 +331,14 @@ export function opRender() {
|
|||
}
|
||||
el.rec = rec;
|
||||
el.unpop = () => {
|
||||
let pos = [...el.childNodes].indexOf(poprec.div);
|
||||
let pos = [...parent.childNodes].indexOf(poprec.div);
|
||||
if (pos >= 0) {
|
||||
el.removeChild(poprec.div);
|
||||
parent.removeChild(poprec.div);
|
||||
}
|
||||
popped = false;
|
||||
};
|
||||
function onEnter(ev) {
|
||||
if ((surfaceOn || traceOn) && env.poppedRec != rec) {
|
||||
if ((clock || surfaceOn || traceOn) && env.poppedRec != rec) {
|
||||
return;
|
||||
}
|
||||
if (popped && env.poppedRec != rec) {
|
||||
|
|
@ -351,22 +353,24 @@ export function opRender() {
|
|||
popped = true;
|
||||
poprec.use(rec);
|
||||
env.hoveredOp = el;
|
||||
if (!clock) {
|
||||
// offset Y position of pop div by % of Y screen location of button
|
||||
el.appendChild(poprec.div);
|
||||
poprec.addNote();
|
||||
const { innerHeight } = window;
|
||||
const brect = ev.target.getBoundingClientRect();
|
||||
const prect = poprec.div.getBoundingClientRect();
|
||||
const pcty = (brect.top / innerHeight) * 0.9;
|
||||
const offpx = -pcty * prect.height;
|
||||
poprec.div.style.transform = `translateY(${offpx}px)`;
|
||||
}
|
||||
// offset Y position of pop div by % of Y screen location of button
|
||||
parent.appendChild(poprec.div);
|
||||
poprec.addNote();
|
||||
const { innerHeight } = window;
|
||||
const brect = ev.target.getBoundingClientRect();
|
||||
const prect = parent.getBoundingClientRect();
|
||||
const Prect = poprec.div.getBoundingClientRect();
|
||||
const tdiff = prect.top - brect.top;
|
||||
const botoff = innerHeight - (brect.top + Prect.height);
|
||||
const offpx = -tdiff + (botoff < 0 ? botoff : -Prect.height/3);
|
||||
poprec.div.style.transform = `translateY(${offpx}px)`;
|
||||
poprec.div.onmouseenter = () => { inside = true };
|
||||
poprec.div.onmouseleave = onLeave;
|
||||
// option click event appears latent
|
||||
// and overides the sticky settings
|
||||
setTimeout(() => {
|
||||
UC.setSticky(false);
|
||||
}, 0);
|
||||
}, 10);
|
||||
}
|
||||
function onLeave(ev) {
|
||||
inside = false;
|
||||
|
|
@ -637,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);
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ export function cam_export(print, online) {
|
|||
const widget = print.widgets[0];
|
||||
if (!widget) return;
|
||||
|
||||
console.log({ cam_export: print });
|
||||
|
||||
const { settings } = print;
|
||||
const { device, tools } = settings;
|
||||
const { device } = settings;
|
||||
|
||||
let i,
|
||||
time = 0,
|
||||
|
|
@ -257,17 +255,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;
|
||||
|
|
@ -424,7 +423,9 @@ export function cam_export(print, online) {
|
|||
toolz[out.tool] = ctool;
|
||||
}
|
||||
point = out.point;
|
||||
if (!point || point.mod) return;
|
||||
if (!point || point.mod) {
|
||||
return;
|
||||
}
|
||||
// ensure not point is modified twice
|
||||
point.mod = 1;
|
||||
if (isOffset) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export function menu() {
|
|||
anim.trans = newButton(null,null,{icon:'<i class="fa-solid fa-border-none"></i>',title:"transparency",class:"padleft"}),
|
||||
anim.model = newButton(null,null,{icon:'<i class="fa-solid fa-eye"></i>',title:"show model"}),
|
||||
anim.shade = newButton(null,null,{icon:'<i class="fa-solid fa-cube"></i>',title:"stock box"}),
|
||||
anim.path = newButton(null,null,{icon:'<i class="fa-solid fa-draw-polygon"></i>',title:"path"}),
|
||||
])
|
||||
},
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ export function menu() {
|
|||
separator: newBlank({ class:"set-sep", driven }),
|
||||
camEaseAngle: newInput(LANG.ou_eang_s, {title:LANG.ou_eang_l, convert:toFloat, bound:bound(0.1,85), show:() => ui.camEaseDown.checked}),
|
||||
camFullEngage: newInput(LANG.ou_feng_s, {title:LANG.ou_feng_l, convert:toFloat, bound:bound(0.1,1.0)}),
|
||||
_____: newGroup(LANG.co_menu, $('cam-origin'), { modes:CAM, driven, separator }),
|
||||
_____: newGroup(LANG.or_menu, $('cam-origin'), { modes:CAM, driven, separator }),
|
||||
camOriginTop: newBoolean(LANG.or_topp_s, onBooleanClick, {title:LANG.or_topp_l}),
|
||||
camOriginCenter: newBoolean(LANG.or_cntr_s, onBooleanClick, {title:LANG.or_cntr_l}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
|
|
|
|||
|
|
@ -4,26 +4,70 @@ import { CamOp } from './op.js';
|
|||
import { Tool } from './tool.js';
|
||||
import { generate as Topo } from './topo3.js';
|
||||
import { newPoint } from '../../../geo/point.js';
|
||||
import { newPolygon } from '../../../geo/polygon.js';
|
||||
import { tip2tipEmit } from '../../../geo/paths.js';
|
||||
|
||||
function createFilter(op) {
|
||||
function createFilter(op, origin, axis) {
|
||||
// console.log({ origin, axis });
|
||||
let ok = () => true;
|
||||
let filter = slices => slices;
|
||||
let filterString = op.filter?.map(l => l.trim()).join('\n');
|
||||
if (filterString) {
|
||||
try {
|
||||
const obj = eval(`( ${filterString} )`);
|
||||
let idx = 0;
|
||||
if (obj && obj.slices) {
|
||||
const nadd = [];
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (obj.slices(slice, idx++)) {
|
||||
nadd.push(slice);
|
||||
let box = obj?.box;
|
||||
let slice_fn = obj?.slices;
|
||||
let index = 0;
|
||||
const accept = [];
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (slice_fn && slice_fn(slice, index++)) {
|
||||
accept.push(slice);
|
||||
} else if (box) {
|
||||
// slice.z = x when axis = y
|
||||
// slice.z = y when axis = x
|
||||
let { x, y, z } = box;
|
||||
x = x ?? [ -Infinity, Infinity ];
|
||||
y = y ?? [ -Infinity, Infinity ];
|
||||
z = z ?? [ -Infinity, Infinity ];
|
||||
let ok = false;
|
||||
if (axis === 'x') {
|
||||
let sy = slice.z + origin.y;
|
||||
if (sy >= y[0] && sy <= y[1]) {
|
||||
ok = true;
|
||||
for (let p of slice.camLines) {
|
||||
p.points = p.points.filter(p =>
|
||||
p.x - origin.x >= x[0] && p.x - origin.x <= x[1] &&
|
||||
p.z - origin.z >= z[0] && p.z - origin.z <= z[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let sx = slice.z - origin.x;
|
||||
if (sx >= x[0] && sx <= x[1]) {
|
||||
ok = true;
|
||||
for (let p of slice.camLines) {
|
||||
p.points = p.points.filter(p =>
|
||||
p.y + origin.y >= y[0] && p.y + origin.y <= y[1] &&
|
||||
p.z - origin.z >= z[0] && p.z - origin.z <= z[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
slice.camLines = slice.camLines.map(p => {
|
||||
if (p.points.length > 1) {
|
||||
return newPolygon(p.points).setOpen(true);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}).filter(p => p);
|
||||
accept.push(slice);
|
||||
}
|
||||
}
|
||||
return nadd;
|
||||
};
|
||||
}
|
||||
}
|
||||
return accept;
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('filter parse error', e, op.filter);
|
||||
}
|
||||
|
|
@ -38,8 +82,8 @@ class OpContour extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { addSlices, settings, updateToolDiams } = state;
|
||||
let filter = createFilter(op);
|
||||
let { color, addSlices, settings, updateToolDiams } = state;
|
||||
let filter = createFilter(op, settings.origin, op.axis.toLowerCase());
|
||||
let toolDiam = this.toolDiam = new Tool(settings, op.tool).fluteDiameter();
|
||||
updateToolDiams(toolDiam);
|
||||
// we need topo for safe travel moves when roughing and outlining
|
||||
|
|
@ -54,6 +98,11 @@ class OpContour extends CamOp {
|
|||
slices = filter(slices);
|
||||
this.sliceOut = slices;
|
||||
addSlices(slices);
|
||||
for (let slice of slices) {
|
||||
slice.output()
|
||||
.setLayer(`contour ${op.axis}`, { face: color, line: color })
|
||||
.addPolys(slice.camLines);
|
||||
}
|
||||
},
|
||||
contour: op,
|
||||
state: state
|
||||
|
|
@ -64,11 +113,11 @@ class OpContour extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state, sliceOut } = this;
|
||||
let { settings, widget } = state;
|
||||
let { settings } = state;
|
||||
let { process } = settings;
|
||||
|
||||
let { setTolerance, setTool, setSpindle, setPrintPoint } = ops;
|
||||
let { camOut, polyEmit, newLayer, printPoint, lastPoint } = ops;
|
||||
let { camOut, newLayer, printPoint, lastPoint } = ops;
|
||||
let { bounds, zmax } = ops;
|
||||
|
||||
let toolDiam = this.toolDiam;
|
||||
|
|
@ -87,9 +136,9 @@ class OpContour extends CamOp {
|
|||
if (!slice.camLines) {
|
||||
continue;
|
||||
}
|
||||
let polys = [], poly, emit;
|
||||
let polys = [], poly;
|
||||
slice.camLines.forEach(function (poly) {
|
||||
if (depthFirst) poly = poly.clone(true);
|
||||
if (depthFirst) poly = poly.clone(true).annotate({ slice: slice.index + 1 });
|
||||
polys.push({ first: poly.first(), last: poly.last(), poly: poly });
|
||||
});
|
||||
if (depthFirst) {
|
||||
|
|
@ -101,7 +150,7 @@ class OpContour extends CamOp {
|
|||
poly.reverse();
|
||||
}
|
||||
poly.forEachPoint(function (point, pidx) {
|
||||
camOut(point.clone(), pidx > 0, stepover);
|
||||
camOut(point.clone(), pidx > 0, { moveLen: stepover });
|
||||
}, false);
|
||||
});
|
||||
newLayer();
|
||||
|
|
@ -115,7 +164,7 @@ class OpContour extends CamOp {
|
|||
poly.reverse();
|
||||
}
|
||||
poly.forEachPoint(function (point, pidx) {
|
||||
camOut(point.clone(), pidx > 0, stepover);
|
||||
camOut(point.clone().annotate({ slice: poly.slice }), pidx > 0, { moveLen: stepover });
|
||||
}, false);
|
||||
newLayer();
|
||||
return lastPoint();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ class OpDrill extends CamOp {
|
|||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, addSlices, widget, updateToolDiams } = state;
|
||||
let { zBottom, zThru, thruHoles, color } = state;
|
||||
let { drills, drillThrough } = op
|
||||
let { color } = state;
|
||||
let { drills } = op
|
||||
|
||||
let drillTool = new Tool(settings, op.tool),
|
||||
drillToolDiam = drillTool.fluteDiameter(),
|
||||
|
|
@ -47,7 +47,7 @@ class OpDrill extends CamOp {
|
|||
slice.camLines = [poly];
|
||||
|
||||
slice.output()
|
||||
.setLayer("drill", { face: color, line: color })
|
||||
.setLayer(state.layername, { face: color, line: color })
|
||||
.addPolys(slice.camLines);
|
||||
addSlices(slice);
|
||||
sliceOut.push(slice);
|
||||
|
|
@ -55,11 +55,10 @@ class OpDrill extends CamOp {
|
|||
}
|
||||
|
||||
prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, addSlices, updateToolDiams } = state;
|
||||
let { op } = this;
|
||||
let { setTool, setSpindle, setDrill, emitDrills } = ops;
|
||||
setTool(op.tool, undefined, op.rate);
|
||||
setDrill(op.down, op.lift, op.dwell, op.thru);
|
||||
setDrill(op.down, op.lift, op.dwell);
|
||||
setSpindle(op.spindle);
|
||||
emitDrills(this.sliceOut.map(slice => slice.camLines).flat());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export class OpIndex extends CamOp {
|
|||
}
|
||||
|
||||
prepare(ops, progress) {
|
||||
const { lastPoint, zmax, zclear, camOut, stock } = ops;
|
||||
const { lastPoint, zmax, zclear, camOut, stock, newLayer } = ops;
|
||||
let last = lastPoint();
|
||||
if (last) {
|
||||
// max point of stock corner radius when rotating (safe z when indexing)
|
||||
|
|
@ -34,7 +34,7 @@ export class OpIndex extends CamOp {
|
|||
// move above rotating stock
|
||||
camOut(last = last.clone().setZ(zmove), 0);
|
||||
// issue rotation command
|
||||
camOut(last = last.clone().setY(0).setA(this.degrees), 0);
|
||||
camOut(last = last.clone().setZ(zmove).setA(this.degrees), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,22 +6,23 @@ import { generate as topo4_generate } from './topo4.js';
|
|||
import { newPoint } from '../../../geo/point.js';
|
||||
|
||||
function createFilter(op) {
|
||||
let ok = () => true;
|
||||
let filter = slices => slices;
|
||||
if (op.filter) {
|
||||
let filterString = op.filter?.map(l => l.trim()).join('\n');
|
||||
if (filterString) {
|
||||
try {
|
||||
const obj = eval(`( ${op.filter.join('\n')} )`);
|
||||
let idx = 0;
|
||||
if (obj && obj.slices) {
|
||||
const nadd = [];
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (obj.slices(slice, idx++)) {
|
||||
nadd.push(slice);
|
||||
}
|
||||
const obj = eval(`( ${filterString} )`);
|
||||
const accept = [];
|
||||
let index = 0;
|
||||
let sl_fn = obj?.slices ?? ok;
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (sl_fn(slice, index++)) {
|
||||
accept.push(slice);
|
||||
}
|
||||
return nadd;
|
||||
};
|
||||
}
|
||||
}
|
||||
return accept;
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('filter parse error', e, op.filter);
|
||||
}
|
||||
|
|
@ -36,9 +37,8 @@ class OpLathe extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { addSlices } = state;
|
||||
let { addSlices, color } = state;
|
||||
let filter = createFilter(op);
|
||||
|
||||
this.topo = await topo4_generate({
|
||||
op,
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ class OpLevel extends CamOp {
|
|||
|
||||
let points = [];
|
||||
let clear = op.stock ?
|
||||
[ newPolygon().centerRectangle({x:0,y:0,z:0}, stock.x, stock.y) ] :
|
||||
[ newPolygon().centerRectangle({x:0,y:0,z:0}, stock.x + toolDiam/2, stock.y) ] :
|
||||
POLY.outer(POLY.offset(tshadow, toolDiam * (op.inset || 0)));
|
||||
|
||||
POLY.fillArea(clear, 90, stepOver, points);
|
||||
POLY.fillArea(clear, 1090, stepOver, points);
|
||||
|
||||
let layers = this.layers = [];
|
||||
for (let z of zList) {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ class OpOutline extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, slicer, addSlices, tshadow, thruHoles, unsafe, color } = state;
|
||||
let { settings, slicer, addSlices, tshadow, thruHoles, unsafe, color, widget } = state;
|
||||
let { updateToolDiams, tabs, cutTabs, cutPolys, workarea, zMax, shadowAt } = state;
|
||||
let { process, stock } = settings;
|
||||
let { controller } = settings;
|
||||
let center_off = widget.track.pos ?? { x: 0, y: 0, z: 0};
|
||||
|
||||
if (op.down <= 0) {
|
||||
throw `invalid step down "${op.down}"`;
|
||||
|
|
@ -39,6 +41,8 @@ class OpOutline extends CamOp {
|
|||
let indices = slicer.interval(op.down, intopt);
|
||||
let trueShadow = process.camTrueShadow === true;
|
||||
let lastShadowZ;
|
||||
let stockClip;
|
||||
|
||||
// shift out first (top-most) slice
|
||||
indices.shift();
|
||||
// add flats to shadow
|
||||
|
|
@ -168,16 +172,12 @@ class OpOutline extends CamOp {
|
|||
let stepover = toolDiam * op.step;
|
||||
let wideCuts = [] //accumulator for wide cuts
|
||||
for (let c = (op.steps || 1); c > 0; c--){
|
||||
offset.slice().forEach(op => {
|
||||
// clone removes inners but the real solution is
|
||||
// to limit expanded shells to through holes
|
||||
let wideCut = POLY.expand([op.clone(true)], stepover*c, slice.z, [], 1);
|
||||
wideCut.forEach(cut =>{ //set order of cuts when wide
|
||||
cut.order = c
|
||||
if(cut.inner) cut.inner.forEach(inn =>{ inn.order = c })
|
||||
});
|
||||
wideCuts.push(...wideCut)
|
||||
let wideCut = POLY.expand(offset.clone(true), stepover * c, slice.z, [], 1);
|
||||
wideCut.forEach(cut =>{ //set order of cuts when wide
|
||||
cut.order = c
|
||||
if(cut.inner) cut.inner.forEach(inn =>{ inn.order = c })
|
||||
});
|
||||
wideCuts.push(...wideCut)
|
||||
}
|
||||
offset.appendAll(wideCuts);
|
||||
}
|
||||
|
|
@ -187,16 +187,19 @@ class OpOutline extends CamOp {
|
|||
addDogbones(offset, toolDiam / 5);
|
||||
}
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let { center } = stock;
|
||||
let x = center.x - center_off.x;
|
||||
let y = center.y - center_off.y;
|
||||
stockClip = newPolygon().centerRectangle({ x, y }, stock.x + 0.001, stock.y + 0.001);
|
||||
offset = cutPolys([stockClip], offset, slice.z, true);
|
||||
}
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
offset = cutTabs(tabs, offset, slice.z);
|
||||
}
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let rect = newPolygon().centerRectangle({x:0,y:0}, stock.x, stock.y);
|
||||
offset = cutPolys([rect], offset, slice.z, true);
|
||||
offset = cutTabs(tabs, offset);
|
||||
}
|
||||
|
||||
// offset.xout(`slice ${slice.z}`);
|
||||
|
|
@ -208,11 +211,17 @@ class OpOutline extends CamOp {
|
|||
|
||||
// project empty up and render
|
||||
for (let slice of slices) {
|
||||
if (false) slice.output()
|
||||
if (controller.devel) slice.output()
|
||||
.setLayer("slice", {line: 0xaaaa00}, false)
|
||||
.addPolys(slice.topPolys())
|
||||
if (controller.devel) slice.output()
|
||||
.setLayer("shadow", {line: 0x557799}, false)
|
||||
.addPolys(slice.shadow)
|
||||
if (controller.devel && stockClip) slice.output()
|
||||
.setLayer("stock clip", {line: 0x557799}, false)
|
||||
.addPolys([ stockClip ])
|
||||
slice.output()
|
||||
.setLayer("outline", {face: color, line: color})
|
||||
.setLayer(state.layername, {face: color, line: color})
|
||||
.addPolys(slice.camLines);
|
||||
}
|
||||
|
||||
|
|
@ -224,9 +233,9 @@ class OpOutline extends CamOp {
|
|||
let { op, state, sliceOut } = this;
|
||||
let { setTool, setSpindle, setPrintPoint } = ops;
|
||||
let { polyEmit, depthOutlinePath } = ops;
|
||||
let { camOut, newLayer, printPoint } = ops;
|
||||
let { settings, widget } = state;
|
||||
let { process, controller } = settings;
|
||||
let { newLayer, printPoint } = ops;
|
||||
let { settings } = state;
|
||||
let { process } = settings;
|
||||
|
||||
let easeDown = process.camEaseDown;
|
||||
let toolDiam = this.toolDiam;
|
||||
|
|
|
|||
|
|
@ -19,13 +19,11 @@ class OpPocket extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
const pocket = this;
|
||||
const debug = false;
|
||||
let { op, state } = this;
|
||||
let { tool, rate, down, plunge, expand, contour, smooth, tolerance } = op;
|
||||
let { ov_botz, ov_conv } = op;
|
||||
let { settings, widget, addSlices, zBottom, zThru, tabs, color } = state;
|
||||
let { settings, widget, addSlices, zBottom, tabs, color } = state;
|
||||
let { updateToolDiams, cutTabs, healPolys, shadowAt, workarea } = state;
|
||||
let { process } = settings;
|
||||
zBottom = ov_botz ? workarea.bottom_stock + ov_botz : zBottom;
|
||||
// generate tracing offsets from chosen features
|
||||
let sliceOut;
|
||||
|
|
@ -36,24 +34,18 @@ class OpPocket extends CamOp {
|
|||
let cutdir = ov_conv;
|
||||
let engrave = contour && op.engrave;
|
||||
let zTop = workarea.top_z;
|
||||
let devel = settings.controller.devel;
|
||||
let smoothVal = (smooth ?? 0) / 10;
|
||||
if (contour) {
|
||||
down = 0;
|
||||
this.topo = await Topo({
|
||||
// onupdate: (update, msg) => {
|
||||
onupdate: (index, total, msg) => {
|
||||
progress((index / total) * 0.9, msg);
|
||||
},
|
||||
ondone: (slices) => {
|
||||
// console.log({ contour: slices });
|
||||
},
|
||||
contour: {
|
||||
tool,
|
||||
tolerance,
|
||||
inside: true,
|
||||
axis: "-"
|
||||
},
|
||||
state: state
|
||||
});
|
||||
this.contour = {
|
||||
axis: "-",
|
||||
inside: true,
|
||||
nogpu: true,
|
||||
step: toolOver,
|
||||
tolerance,
|
||||
tool,
|
||||
};
|
||||
}
|
||||
updateToolDiams(toolDiam);
|
||||
if (tabs) {
|
||||
|
|
@ -69,7 +61,7 @@ class OpPocket extends CamOp {
|
|||
sliceOut.push(slice);
|
||||
return slice;
|
||||
}
|
||||
function clearZ(polys, z, down) {
|
||||
async function clearZ(polys, z, down) {
|
||||
if (down) {
|
||||
// adjust step down to a value <= down that
|
||||
// ends on the lowest z specified
|
||||
|
|
@ -92,14 +84,14 @@ class OpPocket extends CamOp {
|
|||
let clip = [], shadow;
|
||||
if (contour) {
|
||||
if (smooth) {
|
||||
clip = POLY.offset(POLY.offset([ poly ], smooth), -smooth);
|
||||
clip = POLY.offset(POLY.offset([ poly ], smoothVal), -smoothVal);
|
||||
} else {
|
||||
clip = [ poly ];
|
||||
}
|
||||
} else {
|
||||
shadow = shadowAt(z);
|
||||
if (smooth) {
|
||||
shadow = POLY.setZ(POLY.offset(POLY.offset(shadow, smooth), -smooth), z);
|
||||
shadow = POLY.setZ(POLY.offset(POLY.offset(shadow, smoothVal), -smoothVal), z);
|
||||
}
|
||||
POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0);
|
||||
if (op.outline) {
|
||||
|
|
@ -130,16 +122,16 @@ class OpPocket extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, cutdir, false);
|
||||
if (contour) {
|
||||
slice.camLines = pocket.conform(slice.camLines, op.refine, engrave, pct => {
|
||||
slice.camLines = await pocket.conform(slice.camLines, op.refine, engrave, pct => {
|
||||
progress(0.9 + (zpro + zinc * pct) * 0.1, "conform");
|
||||
});
|
||||
}
|
||||
slice.output()
|
||||
.setLayer("pocket", {line: color}, false)
|
||||
.setLayer(state.layername, {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
if (debug && shadow) slice.output()
|
||||
if (devel && shadow) slice.output()
|
||||
.setLayer("pocket shadow", {line: 0xff8811}, false)
|
||||
.addPolys(shadow)
|
||||
.addPolys(shadow);
|
||||
if (!contour) {
|
||||
progress(zpro, "pocket");
|
||||
}
|
||||
|
|
@ -168,23 +160,37 @@ class OpPocket extends CamOp {
|
|||
outline = POLY.setWinding(outline, cutdir, false);
|
||||
outline = healPolys(outline);
|
||||
if (smooth) {
|
||||
outline = POLY.offset(POLY.offset(outline, smooth), -smooth);
|
||||
outline = POLY.offset(POLY.offset(outline, smoothVal), -smoothVal);
|
||||
}
|
||||
if (outline.length) {
|
||||
// option to skip interior features (holes, pillars)
|
||||
if (op.outline) {
|
||||
POLY.clearInner(outline);
|
||||
}
|
||||
if (debug) newSliceOut(zmin).output()
|
||||
await clearZ(outline, zmin + 0.0001, down);
|
||||
if (devel && sliceOut?.length) sliceOut[0].output()
|
||||
.setLayer("pocket area", {line: 0x1188ff}, false)
|
||||
.addPolys(outline)
|
||||
clearZ(outline, zmin + 0.0001, down);
|
||||
progress(1, "pocket");
|
||||
}
|
||||
}
|
||||
|
||||
// mold cam output lines to the surface of the topo offset by tool geometry
|
||||
conform(camLines, refine, engrave, progress) {
|
||||
async conform(camLines, refine, engrave, progress) {
|
||||
if (!this.topo) {
|
||||
console.log('deferred topo');
|
||||
this.topo = await Topo({
|
||||
// onupdate: (update, msg) => {
|
||||
onupdate: (index, total, msg) => {
|
||||
progress((index / total) * 0.9, msg);
|
||||
},
|
||||
ondone: (slices) => {
|
||||
// console.log({ contour: slices });
|
||||
},
|
||||
contour: this.contour,
|
||||
state: this.state
|
||||
});
|
||||
}
|
||||
const topo = this.topo;
|
||||
// re-segment polygon to a higher resolution
|
||||
const hirez = camLines.map(p => p.segment(topo.tolerance * 2));
|
||||
|
|
@ -236,7 +242,7 @@ class OpPocket extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state, pockets } = this;
|
||||
let { setTool, setSpindle, setTolerance, sliceOutput, getPrintPoint } = ops;
|
||||
let { getPrintPoint , pocket, setTool, setSpindle, setTolerance } = ops;
|
||||
let { process } = state.settings;
|
||||
|
||||
setTool(op.tool, op.rate);
|
||||
|
|
@ -269,11 +275,12 @@ class OpPocket extends CamOp {
|
|||
}
|
||||
if (min.pocket) {
|
||||
min.pocket.used = true;
|
||||
sliceOutput(min.pocket, {
|
||||
pocket({
|
||||
cutdir: op.ov_conv,
|
||||
depthFirst: process.camDepthFirst && !state.isIndexed,
|
||||
easeDown: op.down && process.easeDown ? op.down : 0,
|
||||
progress: (n,m) => progress(n/m, "pocket")
|
||||
progress: (n,m) => progress(n/m, "pocket"),
|
||||
slices: min.pocket
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class OpRegister extends CamOp {
|
|||
|
||||
let tool = new Tool(settings, op.tool);
|
||||
let sliceOut = this.sliceOut = [];
|
||||
let axis = op.axis.toLowerCase();
|
||||
|
||||
updateToolDiams(tool.fluteDiameter());
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ class OpRegister extends CamOp {
|
|||
return;
|
||||
}
|
||||
|
||||
switch (op.axis.toLowerCase()) {
|
||||
switch (axis) {
|
||||
case "x":
|
||||
if (op.points == 3) {
|
||||
pathPoints.push(newPoint(boundMinX - cutOffset, centerY, 0));
|
||||
|
|
@ -64,6 +65,7 @@ class OpRegister extends CamOp {
|
|||
}
|
||||
break;
|
||||
case "-":
|
||||
case "=":
|
||||
let halfOffset = toolOffset / 2,
|
||||
loopMinX = boundMinX - cutOffset,
|
||||
loopMaxX = boundMaxX + cutOffset,
|
||||
|
|
@ -84,7 +86,7 @@ class OpRegister extends CamOp {
|
|||
}
|
||||
function rept(count, step, fn) {
|
||||
while (count-- > 0) {
|
||||
fn(step, count === 0);
|
||||
fn(step, count);
|
||||
step = -step;
|
||||
}
|
||||
}
|
||||
|
|
@ -93,9 +95,16 @@ class OpRegister extends CamOp {
|
|||
addSlices(slice);
|
||||
sliceOut.push(slice);
|
||||
start(z);
|
||||
rept(4, halfOffset, oy => {
|
||||
rept(4, halfOffset, (oy, count) => {
|
||||
move(0, -oy);
|
||||
move(deltaX, 0);
|
||||
if (axis === '=' && count % 2 === 1) {
|
||||
move(deltaX/2, 0);
|
||||
move(0, oy/2);
|
||||
move(0, -oy/2);
|
||||
move(deltaX/2, 0);
|
||||
} else {
|
||||
move(deltaX, 0);
|
||||
}
|
||||
});
|
||||
rept(4, halfOffset, ox => {
|
||||
move(ox, 0);
|
||||
|
|
@ -136,11 +145,10 @@ class OpRegister extends CamOp {
|
|||
}
|
||||
|
||||
prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, addSlices, updateToolDiams } = state;
|
||||
let { op } = this;
|
||||
let { setTool, setSpindle, setDrill, emitDrills } = ops;
|
||||
|
||||
if (op.axis === '-') {
|
||||
if (op.axis === '-' || op.axis === '=') {
|
||||
setTool(op.tool, op.feed, op.rate);
|
||||
setSpindle(op.spindle);
|
||||
for (let slice of this.sliceOut) {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ class OpRough extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, slicer, addSlices, unsafe, color } = state;
|
||||
let { settings, slicer, addSlices, unsafe, color, widget } = state;
|
||||
let { updateToolDiams, thruHoles, tabs, cutTabs, cutPolys } = state;
|
||||
let { ztOff, zMax, shadowAt, isIndexed} = state;
|
||||
let { workarea } = state;
|
||||
let { process, stock } = settings;
|
||||
let { controller, process, stock } = settings;
|
||||
let center_off = widget.track.pos ?? { x: 0, y: 0, z: 0};
|
||||
|
||||
if (op.down <= 0) {
|
||||
throw `invalid step down "${op.down}"`;
|
||||
|
|
@ -36,6 +37,12 @@ class OpRough extends CamOp {
|
|||
|
||||
updateToolDiams(toolDiam);
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
}
|
||||
|
||||
// clear the stock above the area to be roughed out
|
||||
if (workarea.top_z > workarea.top_part && !cutThruBypass) {
|
||||
let shadow = state.shadow.base.clone();
|
||||
|
|
@ -58,14 +65,11 @@ class OpRough extends CamOp {
|
|||
slice.z = z;
|
||||
let polys = POLY.setZ(facing.clone(true), slice.z + roughLeaveZ);
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
polys = cutTabs(tabs, polys, slice.z);
|
||||
}
|
||||
slice.camLines = polys;
|
||||
slice.output()
|
||||
.setLayer("roughing", {face: color, line: color})
|
||||
.setLayer(state.layername, {face: color, line: color})
|
||||
.addPolys(slice.camLines);
|
||||
addSlices(slice);
|
||||
camFaces.push(slice);
|
||||
|
|
@ -206,20 +210,6 @@ class OpRough extends CamOp {
|
|||
}
|
||||
}
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
offset = cutTabs(tabs, offset, slice.z);
|
||||
}
|
||||
|
||||
if (!offset) return;
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let rect = newPolygon().centerRectangle(stock.center, stock.x, stock.y);
|
||||
offset = cutPolys([rect], offset, slice.z, true);
|
||||
}
|
||||
|
||||
// elimate double inset on inners
|
||||
offset.forEach(op => {
|
||||
if (op.inner) {
|
||||
|
|
@ -236,24 +226,38 @@ class OpRough extends CamOp {
|
|||
}
|
||||
});
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let { center } = stock;
|
||||
let x = center.x - center_off.x;
|
||||
let y = center.y - center_off.y;
|
||||
let rect = newPolygon().centerRectangle({ x, y }, stock.x + 0.001, stock.y + 0.001);
|
||||
offset = cutPolys([rect], offset, slice.z, true);
|
||||
}
|
||||
|
||||
if (!offset) return;
|
||||
|
||||
slice.camLines = offset;
|
||||
if (roughLeaveZ) {
|
||||
// offset roughing in Z as well to minimize
|
||||
// tool marks on curved surfaces
|
||||
// offset roughing in Z as well to minimize tool marks on curved surfaces
|
||||
// const roughLeaveZ = 1 * Math.min(roughDown, roughLeave / 2);
|
||||
slice.camLines.forEach(p => {
|
||||
p.setZ(p.getZ() + roughLeaveZ);
|
||||
});
|
||||
for (let poly of slice.camLines) {
|
||||
for (let point of poly.points) point.z += roughLeaveZ;
|
||||
}
|
||||
}
|
||||
if (controller.devel) {
|
||||
if (tabs) slice.output()
|
||||
.setLayer("tabs clip", {line: 0xaa00aa}, true)
|
||||
.addPolys(tabs.map(tab => tab.off).flat());
|
||||
slice.output()
|
||||
.setLayer("slice", {line: 0xaaaa00}, true)
|
||||
.addPolys(slice.topPolys())
|
||||
// .setLayer("top shadow", {line: 0x0000aa})
|
||||
// .addPolys(tshadow)
|
||||
// .setLayer("rough shadow", {line: 0x00aa00})
|
||||
// .addPolys(shadow)
|
||||
.setLayer("shadow clip", {line: 0xaa0000})
|
||||
.addPolys(shell);
|
||||
}
|
||||
if (false) slice.output()
|
||||
.setLayer("slice", {line: 0xaaaa00}, true)
|
||||
.addPolys(slice.topPolys())
|
||||
// .setLayer("top shadow", {line: 0x0000aa})
|
||||
// .addPolys(tshadow)
|
||||
// .setLayer("rough shadow", {line: 0x00aa00})
|
||||
// .addPolys(shadow)
|
||||
.setLayer("rough shell", {line: 0xaa0000})
|
||||
.addPolys(shell);
|
||||
progress(0.5 + 0.5 * (index / slices.length));
|
||||
});
|
||||
|
||||
|
|
@ -278,8 +282,11 @@ class OpRough extends CamOp {
|
|||
}
|
||||
|
||||
slices.forEach(slice => {
|
||||
if (slice.camLines && tabs) {
|
||||
slice.camLines = cutTabs(tabs, slice.camLines);
|
||||
}
|
||||
slice.output()
|
||||
.setLayer("roughing", {face: color, line: color})
|
||||
.setLayer(state.layername, {face: color, line: color})
|
||||
.addPolys(slice.camLines);
|
||||
});
|
||||
this.sliceOut = slices.filter(slice => slice.camLines);
|
||||
|
|
@ -289,15 +296,13 @@ class OpRough extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state, sliceOut, camFaces } = this;
|
||||
let { setTool, setSpindle, setPrintPoint, sliceOutput, polyEmit } = ops;
|
||||
let { camOut, newLayer, printPoint } = ops;
|
||||
let { setTool, setSpindle, setPrintPoint, pocket, polyEmit, newLayer, printPoint } = ops;
|
||||
let { settings } = state;
|
||||
let { process } = settings;
|
||||
|
||||
let easeDown = process.camEaseDown;
|
||||
let cutdir = op.ov_conv;
|
||||
let depthFirst = process.camDepthFirst && !state.isIndexed;
|
||||
let depthData = [];
|
||||
|
||||
setTool(op.tool, op.rate, op.plunge);
|
||||
setSpindle(op.spindle);
|
||||
|
|
@ -325,11 +330,12 @@ class OpRough extends CamOp {
|
|||
|
||||
// output the roughing passes
|
||||
setPrintPoint(printPoint);
|
||||
sliceOutput(sliceOut, {
|
||||
pocket({
|
||||
cutdir,
|
||||
depthFirst,
|
||||
easeDown: op.down && easeDown ? 0.001 : 0,
|
||||
progress: (n,m) => progress(n/m, "routing")
|
||||
progress: (n,m) => progress(n/m, "routing"),
|
||||
slices: sliceOut
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, offset === "outside" ? !cutdir : cutdir, false);
|
||||
slice.output()
|
||||
.setLayer("trace follow", {line: color}, false)
|
||||
.setLayer(state.layername, {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
}
|
||||
function clearZnew(polys, z, down) {
|
||||
|
|
@ -154,7 +154,7 @@ class OpTrace extends CamOp {
|
|||
.setLayer("trace poly", {line: 0x1188ff}, false)
|
||||
.addPolys([ poly ])
|
||||
slice.output()
|
||||
.setLayer("trace", {line: color}, false)
|
||||
.setLayer(state.layername, {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
progress(zpro, "trace");
|
||||
zpro += zinc;
|
||||
|
|
@ -295,7 +295,6 @@ class OpTrace extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings } = state;
|
||||
let { setTool, setSpindle } = ops;
|
||||
|
||||
setTool(op.tool, op.rate);
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
|
||||
if (widget.camops.length === 0 || widget.meta.disabled) return;
|
||||
|
||||
let device = settings.device,
|
||||
process = settings.process,
|
||||
let { device, process } = settings,
|
||||
isIndexed = process.camStockIndexed,
|
||||
startCenter = process.camOriginCenter,
|
||||
alignTop = settings.controller.alignTop,
|
||||
|
|
@ -65,7 +64,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
stockz = stock.z * (isIndexed ? 0.5 : 1),
|
||||
outer = settings.bounds || widget.getPositionBox(),
|
||||
outerz = outer.max.z,
|
||||
slices = widget.slices,
|
||||
zclear = (process.camZClearance || 1),
|
||||
zmax_force = process.camForceZMax || false,
|
||||
zmax_outer = stockz + zclear,
|
||||
|
|
@ -75,13 +73,13 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
boundsz = isIndexed ? stock.z / 2 : bounds.max.z + ztOff,
|
||||
zadd = !isIndexed ? stock.z - boundsz : alignTop ? outerz - boundsz : 0,
|
||||
zmax = outerz + zclear + (process.camOriginOffZ || 0),
|
||||
zsafe = isIndexed ? Math.hypot(bounds.dim.y, bounds.dim.z) / 2 + zclear : zmax,
|
||||
wmpos = widget.track.pos,
|
||||
wmx = wmpos.x,
|
||||
wmy = wmpos.y,
|
||||
originx = (startCenter ? 0 : -stock.x / 2) + (process.camOriginOffX || 0),
|
||||
originy = (startCenter ? 0 : -stock.y / 2) + (process.camOriginOffY || 0),
|
||||
origin = newPoint(originx + wmx, originy + wmy, zmax),
|
||||
output = print.output,
|
||||
easeDown = process.camEaseDown,
|
||||
easeAngle = process.camEaseAngle,
|
||||
depthFirst = process.camDepthFirst,
|
||||
|
|
@ -94,13 +92,11 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
drillDown = 0,
|
||||
drillLift = 0,
|
||||
drillDwell = 0,
|
||||
drillThru = 0,
|
||||
lasering = false,
|
||||
laserPower = 0,
|
||||
newOutput = print.output || [],
|
||||
layerOut = [],
|
||||
printPoint,
|
||||
isNewMode,
|
||||
isPocket,
|
||||
isContour,
|
||||
isRough,
|
||||
|
|
@ -112,6 +108,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
toolDiamMove,
|
||||
plungeRate = process.camFastFeedZ,
|
||||
feedRate,
|
||||
lastOp,
|
||||
lastTool,
|
||||
lastPush,
|
||||
lastPoint,
|
||||
|
|
@ -187,11 +184,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
laserPower = power;
|
||||
}
|
||||
|
||||
function setDrill(down, lift, dwell, thru) {
|
||||
function setDrill(down, lift, dwell) {
|
||||
drillDown = down;
|
||||
drillLift = lift;
|
||||
drillDwell = dwell;
|
||||
drillThru = thru;
|
||||
}
|
||||
|
||||
function emitDrills(polys) {
|
||||
|
|
@ -264,10 +260,11 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
*/
|
||||
function layerPush(point, emit, speed, tool, options) {
|
||||
const { type, center, arcPoints } = options ?? {};
|
||||
const dz = (point && lastPush && lastPush.point) ? point.z - lastPush.point.z : 0;
|
||||
if (dz < 0 && speed > plungeRate) {
|
||||
const dz = (point && lastPush?.point) ? point.z - lastPush.point.z : 0;
|
||||
if (dz < -0.05 && speed > plungeRate) {
|
||||
speed = plungeRate;
|
||||
}
|
||||
// if (options?.type !== 'lerp') console.log( point, currentOp.type );
|
||||
layerOut.mode = currentOp;
|
||||
if (lasering) {
|
||||
let power = emit ? laserPower : 0;
|
||||
|
|
@ -320,7 +317,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
p.x + wmx,
|
||||
p.y + wmy,
|
||||
p.z + zadd
|
||||
).setA(p.a ?? lastPoint?.a);
|
||||
).setA(p.a ?? lastPoint?.a).annotate({ slice: p.slice });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -333,7 +330,26 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
* @param {number} opts.factor speed scale factor
|
||||
*/
|
||||
function camOut(point, emit = 1, opts) {
|
||||
// console.trace({point, emit, opts})
|
||||
let lop = lastOp;
|
||||
lastOp = currentOp;
|
||||
|
||||
// on operation changes:
|
||||
// 1. move to safe z of current point preserving angle
|
||||
// 2. move to safe z of new point preserving old angle
|
||||
// 3. move to safe z of new point with new angle
|
||||
if (lop && lop !== currentOp && lastPoint) {
|
||||
// compensate for applyWidgetMovement() applied to lastPoint
|
||||
let lpo = lastPoint.clone().move({ x: -wmx, y: -wmy, z: -zadd });
|
||||
camOut(lpo.clone().setZ(zsafe).setA(lastPoint.a), 0);
|
||||
camOut(point.clone().setZ(zsafe).setA(lastPoint.a), 0);
|
||||
camOut(point.clone().setZ(zsafe), 0);
|
||||
}
|
||||
|
||||
if (lop?.type === 'index' && lop !== currentOp ) {
|
||||
// console.log('post index first point', point);
|
||||
camOut(point.clone().setZ(lastPoint.z).setA(lastPoint.a), 1);
|
||||
}
|
||||
|
||||
let {
|
||||
center = {},
|
||||
clockwise = true,
|
||||
|
|
@ -342,14 +358,12 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
factor = 1,
|
||||
} = opts ?? {}
|
||||
|
||||
// console.log({radius})
|
||||
const isArc = emit == 2 || emit == 3;
|
||||
const pointA = point.a;
|
||||
|
||||
// apply widget movement pos
|
||||
const pointA = point.a;
|
||||
point = applyWidgetMovement(point);
|
||||
|
||||
// console.log(point.z);
|
||||
if (nextIsMove) {
|
||||
emit = 0;
|
||||
nextIsMove = false;
|
||||
|
|
@ -370,7 +384,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
for (let a of lerp) {
|
||||
let lp = point.clone().setA(a);
|
||||
// console.log(lp.a, lp.x, lp.y, lp.z);
|
||||
lastPoint = layerPush(
|
||||
layerPush(
|
||||
lp,
|
||||
emit,
|
||||
rate,
|
||||
|
|
@ -403,16 +417,20 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
layerPush(point.clone().setZ(stockz + 1), 0, 0, tool);
|
||||
// change to cutting move for remainder of plunge
|
||||
emit = 1;
|
||||
isMove = false;
|
||||
}
|
||||
|
||||
// drop points too close together
|
||||
if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z) {
|
||||
if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z && point.a === lastPoint.a) {
|
||||
// console.trace(["drop dup",lastPoint,point]);
|
||||
return;
|
||||
}
|
||||
|
||||
// no jump moves in contour mode to adjacent slice points
|
||||
let steady = lastPoint && currentOp.type === 'contour' && Math.abs(point.slice - lastPoint.slice) < 4;
|
||||
|
||||
// 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
|
||||
|
|
@ -431,7 +449,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
} else if (point.z < lastPoint.z) {
|
||||
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
|
||||
}
|
||||
} else if (isMove) {
|
||||
} else if (isMove && !steady) {
|
||||
// for longer moves, check the terrain to see if we need to go up and over
|
||||
const bigXY = (deltaXY > moveLen && !lasering);
|
||||
const bigZ = (deltaZ > toolDiam / 2 && deltaXY > tolerance);
|
||||
|
|
@ -478,13 +496,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
}
|
||||
|
||||
// set new plunge rate
|
||||
let tmprate;
|
||||
if (false)
|
||||
if (!lasering && !isLathe && deltaZ < -tolerance) {
|
||||
let threshold = Math.min(deltaXY / 2, absDeltaZ),
|
||||
modifier = threshold / absDeltaZ;
|
||||
if (synthPlunge && threshold && modifier && deltaXY > tolerance) {
|
||||
// use modifier to speed up long XY move plunge rates
|
||||
// console.log('modifier', modifier);
|
||||
rate = Math.max(
|
||||
tmprate = Math.max(
|
||||
plungeRate,
|
||||
Math.round(plungeRate + ((feedRate - plungeRate) * modifier))
|
||||
);
|
||||
|
|
@ -492,7 +512,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
let L = Math.hypot(deltaXY, absDeltaZ);
|
||||
let limXY = feedRate * L / deltaXY;
|
||||
let limZ = plungeRate * L / Math.abs(deltaZ);
|
||||
rate = Math.min(feedRate, limXY, limZ);
|
||||
tmprate = Math.min(feedRate, limXY, limZ);
|
||||
console.log({ rate_override: rate, was: rate });
|
||||
// let zps = len / absDeltaZ;
|
||||
// rate = Math.max(
|
||||
// plungeRate,
|
||||
|
|
@ -508,7 +529,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
lastPoint = layerPush(
|
||||
point,
|
||||
clockwise ? 2 : 3,
|
||||
rate,
|
||||
tmprate ?? rate,
|
||||
tool,
|
||||
{
|
||||
center,
|
||||
|
|
@ -523,7 +544,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
lastPoint = layerPush(
|
||||
point,
|
||||
emit,
|
||||
rate,
|
||||
tmprate ?? rate,
|
||||
tool
|
||||
);
|
||||
}
|
||||
|
|
@ -533,16 +554,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
* output an array of slices that form a pocket
|
||||
* used by rough and pocket ops, does not support arcs
|
||||
*
|
||||
* @param {Slice[]} top-down Z stack of slices
|
||||
* @param {*} opts
|
||||
* @param {Slice[]} slices top-down Z stack of slices
|
||||
* @param {boolean} cutdir true=CW false=CCW
|
||||
* @param {boolean} depthFirst prioritize cut depth in pockets by nesting
|
||||
*/
|
||||
function sliceOutput(sliceOut, opts = {}) {
|
||||
const { cutdir, depthFirst, easeDown, progress } = opts;
|
||||
|
||||
function pocket({ slices, cutdir, depthFirst, easeDown, progress }) {
|
||||
let total = 0;
|
||||
let depthData = [];
|
||||
|
||||
for (let slice of sliceOut) {
|
||||
for (let slice of slices) {
|
||||
let polys = [], t = [], c = [];
|
||||
POLY.flatten(slice.camLines).forEach(function (poly) {
|
||||
let child = poly.parent;
|
||||
|
|
@ -569,7 +589,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
}, { swapdir: false });
|
||||
newLayer();
|
||||
}
|
||||
progress(++total, sliceOut.length);
|
||||
progress(++total, slices.length);
|
||||
}
|
||||
|
||||
// crucially returns true for -0 as well as other negative #s
|
||||
|
|
@ -663,44 +683,32 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
// make top start offset configurable
|
||||
printPoint = firstPoint || origin;
|
||||
|
||||
// accumulated data for depth-first optimizations
|
||||
// let depthData = {
|
||||
// rough: [],
|
||||
// outline: [],
|
||||
// roughDiam: 0,
|
||||
// outlineDiam: 0,
|
||||
// contourx: [],
|
||||
// contoury: [],
|
||||
// trace: [],
|
||||
// drill: [],
|
||||
// layer: 0,
|
||||
// };
|
||||
|
||||
let ops = {
|
||||
stock,
|
||||
setTool,
|
||||
setDrill,
|
||||
setSpindle,
|
||||
setTolerance,
|
||||
setPrintPoint,
|
||||
setLasering,
|
||||
getPrintPoint() { return printPoint },
|
||||
printPoint,
|
||||
newLayer,
|
||||
addGCode,
|
||||
bounds,
|
||||
camOut,
|
||||
polyEmit,
|
||||
poly2polyEmit,
|
||||
tip2tipEmit,
|
||||
depthRoughPath,
|
||||
depthOutlinePath,
|
||||
sliceOutput,
|
||||
depthRoughPath,
|
||||
emitDrills,
|
||||
emitTrace,
|
||||
bounds,
|
||||
getPrintPoint() { return printPoint },
|
||||
lastPoint: () => { return lastPoint },
|
||||
newLayer,
|
||||
pocket,
|
||||
poly2polyEmit,
|
||||
polyEmit,
|
||||
printPoint,
|
||||
setDrill,
|
||||
setLasering,
|
||||
setPrintPoint,
|
||||
setSpindle,
|
||||
setTolerance,
|
||||
setTool,
|
||||
stock,
|
||||
tip2tipEmit,
|
||||
zclear,
|
||||
zmax,
|
||||
lastPoint: () => { return lastPoint }
|
||||
zsafe,
|
||||
};
|
||||
|
||||
let opSum = 0;
|
||||
|
|
@ -973,7 +981,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
|
||||
|
||||
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center })
|
||||
.map(applyWidgetMovement);
|
||||
.map(applyWidgetMovement);
|
||||
|
||||
// console.log({arcPoints})
|
||||
|
||||
|
|
@ -1096,7 +1104,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
if (lastPoint && newOutput.length) {
|
||||
let lastLayer = newOutput.filter(layer => Array.isArray(layer)).peek();
|
||||
if (Array.isArray(lastLayer)) {
|
||||
print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
|
||||
printPoint = lastPoint.clone();
|
||||
if (printPoint.z < zmax_outer) printPoint.setZ(zmax_outer);
|
||||
print.addOutput(lastLayer, printPoint, 0, 0, tool);
|
||||
// print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
|
||||
}
|
||||
}
|
||||
// console.log("prepare output", newOutput);
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
ondone(msg);
|
||||
}
|
||||
|
||||
function alert(msg) {
|
||||
onupdate(null,null,msg);
|
||||
}
|
||||
|
||||
if (unsafe) {
|
||||
console.log("disabling overhang safeties");
|
||||
}
|
||||
|
|
@ -135,20 +139,20 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
let maxDelta = 1e-3;
|
||||
|
||||
if (stock.x + maxDelta < (bounds.max.x - bounds.min.x)) {
|
||||
return error('stock X too small for part. resize stock or use offset stock');
|
||||
alert('stock X too small for part');
|
||||
}
|
||||
|
||||
if (stock.y + maxDelta < (bounds.max.y - bounds.min.y)) {
|
||||
return error('stock Y too small for part. resize stock or use offset stock');
|
||||
alert('stock Y too small for part');
|
||||
}
|
||||
|
||||
if (stock.z + maxDelta < (bounds.max.z - bounds.min.z)) {
|
||||
return error('stock Z too small for part. resize stock or use offset stock');
|
||||
alert('stock Z too small for part');
|
||||
}
|
||||
}
|
||||
|
||||
if (zMin >= bounds.max.z) {
|
||||
return error(`invalid z bottom ${(zMin / units).round(3)} >= bounds z max ${(zMax / units).round(3)}`);
|
||||
alert(`invalid z bottom ${(zMin / units).round(3)} >= bounds z max ${(zMax / units).round(3)}`);
|
||||
}
|
||||
|
||||
let mark = Date.now();
|
||||
|
|
@ -158,31 +162,31 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
let shadows = {};
|
||||
let slicer;
|
||||
let state = {
|
||||
settings,
|
||||
widget,
|
||||
addSlices,
|
||||
bounds,
|
||||
tabs,
|
||||
cutTabs,
|
||||
cutPolys,
|
||||
color,
|
||||
computeShadows,
|
||||
contourPolys,
|
||||
cutPolys,
|
||||
cutTabs,
|
||||
dark,
|
||||
healPolys,
|
||||
isIndexed,
|
||||
ops: opList,
|
||||
setAxisIndex,
|
||||
settings,
|
||||
shadowAt,
|
||||
slicer,
|
||||
addSlices,
|
||||
isIndexed,
|
||||
setAxisIndex,
|
||||
updateToolDiams,
|
||||
tabs,
|
||||
unsafe,
|
||||
updateSlicer,
|
||||
computeShadows,
|
||||
updateToolDiams,
|
||||
widget,
|
||||
zBottom,
|
||||
zMax,
|
||||
zThru,
|
||||
ztOff,
|
||||
zMax,
|
||||
zTop,
|
||||
unsafe,
|
||||
color,
|
||||
dark,
|
||||
ops: opList
|
||||
zTop
|
||||
};
|
||||
let tracker = setSliceTracker({ rotation: 0 });
|
||||
|
||||
|
|
@ -310,8 +314,11 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
workover.bottom_z = isIndexed ? valz.ov_botz : bottom_stock + valz.ov_botz;
|
||||
workover.bottom_cut = Math.max(workover.bottom_z, -zThru);
|
||||
}
|
||||
// state.workarea = workover;
|
||||
let { note, type } = op.op;
|
||||
let named = note ? note.split(' ').filter(v => v.charAt(0) === '#') : [];
|
||||
let layername = named.length ? named : (note ? `${type} (${note})` : type);
|
||||
Object.assign(state, {
|
||||
layername,
|
||||
zBottom,
|
||||
zThru,
|
||||
ztOff,
|
||||
|
|
@ -688,7 +695,7 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
let opts = { each: onEach, progress: (num, total) => onProgress(num / total * 0.5, "slice") };
|
||||
await slicer.slice(indices, opts);
|
||||
let shadowedDrills = false;
|
||||
// console.log("slices",slices)
|
||||
|
||||
for (let [i, slice] of slices.entries()) {
|
||||
for (let top of slice.tops) {
|
||||
// console.log("slicing",slice.z,top)
|
||||
|
|
@ -698,13 +705,13 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
continue;
|
||||
}
|
||||
for (let poly of inner) {
|
||||
if (poly.points.length < 7) continue;
|
||||
|
||||
if (poly.points.length < 7) {
|
||||
continue;
|
||||
}
|
||||
let center = poly.calcCircleCenter();
|
||||
center.area = poly.area();
|
||||
center.overlapping = [center];
|
||||
center.depth = 0;
|
||||
// console.log("center",center)
|
||||
if (poly.circularity() < 0.98) {
|
||||
// if not circular, don't add to holes
|
||||
continue;
|
||||
|
|
@ -717,8 +724,7 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
let overlap = false;
|
||||
for (let [i, circle] of circles.entries()) {
|
||||
let dist = circle.distTo2D(center);
|
||||
|
||||
// //if on the same xy point,
|
||||
// if on the same xy point,
|
||||
if (dist <= centerDiff) {
|
||||
// console.log("overlap",center,circle);
|
||||
circle.overlapping.push(center);
|
||||
|
|
@ -732,12 +738,10 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
}
|
||||
onProgress(0.5 + (i / slices.length * 0.25), "recognize circles");
|
||||
}
|
||||
|
||||
let drills = [];
|
||||
|
||||
for (let [i, c] of circles.entries()) {
|
||||
let overlapping = c.overlapping;
|
||||
|
||||
let last = overlapping.shift();
|
||||
while (overlapping.length) {
|
||||
let circ = overlapping.shift();
|
||||
|
|
@ -775,14 +779,33 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
return drills;
|
||||
}
|
||||
|
||||
function cutTabs(tabs, offset, z, inter) {
|
||||
tabs = tabs.filter(tab => z < tab.pos.z + tab.dim.z / 2).map(tab => tab.off).flat();
|
||||
return cutPolys(tabs, offset, z, false);
|
||||
function cutTabs(tabs, offset) {
|
||||
let out = [];
|
||||
for (let tab of tabs) {
|
||||
tab.top = tab.pos.z + tab.dim.z / 2;
|
||||
}
|
||||
for (let poly of offset) {
|
||||
let polyZ = poly.getZ();
|
||||
let cut = tabs.filter(tab => tab.top >= polyZ);
|
||||
let lo = poly.cut(cut.map(tab => tab.off).flat(), false);
|
||||
let hi = [];
|
||||
for (let tab of cut) {
|
||||
hi.appendAll(POLY.setZ(poly.cut(tab.off, true), tab.top));
|
||||
}
|
||||
if (hi.length) {
|
||||
let heal = healPolys([ ...lo, ...hi ], false);
|
||||
POLY.setWinding(heal, poly.isClockwise());
|
||||
out.push(...heal);
|
||||
} else {
|
||||
out.push(poly);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function cutPolys(polys, offset, z, inter) {
|
||||
function cutPolys(polys, offset) {
|
||||
let noff = [];
|
||||
offset.forEach(op => noff.appendAll(op.cut(POLY.union(polys, 0, true), inter)));
|
||||
offset.forEach(op => noff.appendAll( op.cut(POLY.union(polys, 0, true), true) ));
|
||||
return healPolys(noff);
|
||||
}
|
||||
|
||||
|
|
@ -799,7 +822,17 @@ function contourPolys(widget, polys) {
|
|||
}
|
||||
}
|
||||
|
||||
function healPolys(noff) {
|
||||
function dedup(arr, same) {
|
||||
const out = [];
|
||||
let prev;
|
||||
for (const x of arr) {
|
||||
if (!prev || !same(prev, x)) out.push(x);
|
||||
prev = x;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function healPolys(noff, sameZ = true) {
|
||||
for (let p of noff) {
|
||||
if (p.appearsClosed()) {
|
||||
p.points.pop();
|
||||
|
|
@ -818,29 +851,29 @@ function healPolys(noff) {
|
|||
let s2 = ntmp[j];
|
||||
if (!s2) continue;
|
||||
// require polys at same Z to heal
|
||||
if (Math.abs(s1.getZ() - s2.getZ()) > 0.01) {
|
||||
if (sameZ && Math.abs(s1.getZ() - s2.getZ()) > 0.01) {
|
||||
continue;
|
||||
}
|
||||
if (!(s1.open && s2.open)) continue;
|
||||
if (s1.last().isMergable2D(s2.first())) {
|
||||
s1.addPoints(s2.points.slice(1));
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s2.last().isMergable2D(s1.first())) {
|
||||
s2.addPoints(s1.points.slice(1));
|
||||
s2.addPoints(s1.points);
|
||||
ntmp[i] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.first().isMergable2D(s2.first())) {
|
||||
s1.reverse();
|
||||
s1.addPoints(s2.points.slice(1));
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.last().isMergable2D(s2.last())) {
|
||||
s2.reverse();
|
||||
s1.addPoints(s2.points.slice(1));
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
|
|
@ -854,9 +887,14 @@ function healPolys(noff) {
|
|||
}
|
||||
// close poly if head meets tail
|
||||
for (let poly of noff) {
|
||||
if (poly.open && poly.first().isMergable2D(poly.last())) {
|
||||
poly.points.pop();
|
||||
poly.open = false;
|
||||
poly.points = dedup(poly.points, (a,b) => a.isMergable3D(b));
|
||||
if (poly.open) {
|
||||
if (poly.first().isMergable3D(poly.last())) {
|
||||
poly.points.pop();
|
||||
poly.open = false;
|
||||
} else if (poly.first().isMergable2D(poly.last())) {
|
||||
poly.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const { sliceConnect, sliceDedup } = slicer;
|
|||
const { config } = base;
|
||||
const timing = false;
|
||||
const zDecimal = 3;
|
||||
const epsilon = 10e-5;
|
||||
|
||||
const begin = function () {
|
||||
if (timing) console.time(...arguments);
|
||||
|
|
@ -94,7 +95,7 @@ export class Slicer {
|
|||
countZ(p3.z);
|
||||
}
|
||||
// use co-flat and co-line detection to adjust slice Z
|
||||
if (p1.z === p2.z && p2.z === p3.z) {
|
||||
if (Math.abs(p1.z - p2.z) < epsilon && Math.abs(p2.z - p3.z) < epsilon) {
|
||||
// detect zFlat faces to avoid slicing directly on them
|
||||
let zkey = p1.z.toFixed(zDecimal),
|
||||
area = Math.abs(util.area2(p1, p2, p3)) / 2;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ export class Topo {
|
|||
|
||||
async generate(opt = {}) {
|
||||
let { state, contour, onupdate, ondone } = opt;
|
||||
let { widget, settings, tshadow, center, tabs, color } = opt.state;
|
||||
let { widget, settings, tshadow, tabs } = opt.state;
|
||||
|
||||
let { controller, process } = settings,
|
||||
{ webGPU } = controller,
|
||||
animesh = parseInt(controller.animesh || 100) * 2500,
|
||||
axis = contour.axis.toLowerCase(),
|
||||
contourX = axis === "x",
|
||||
|
|
@ -37,16 +38,15 @@ export class Topo {
|
|||
boundsY = maxY - minY,
|
||||
inside = contour.inside,
|
||||
density = 1 + (contour.reduction || 0),
|
||||
resolution = tolerance ? tolerance : 1 / Math.sqrt(animesh / (boundsX * boundsY)),
|
||||
resolution = (tolerance ? tolerance : 1 / Math.sqrt(animesh / (boundsX * boundsY))).round(5),
|
||||
tool = new Tool(settings, contour.tool),
|
||||
toolOffset = tool.generateProfile(resolution).profile,
|
||||
toolDiameter = tool.fluteDiameter(),
|
||||
toolStep = toolDiameter * contour.step, //tool.contourOffset(contour.step),
|
||||
toolStep = toolDiameter * contour.step,
|
||||
leave = contour.leave || 0,
|
||||
maxangle = contour.angle,
|
||||
curvesOnly = contour.curves,
|
||||
bridge = contour.bridging || 0,
|
||||
// R2A = 180 / Math.PI,
|
||||
stepsX = Math.ceil(boundsX / resolution),
|
||||
stepsY = Math.ceil(boundsY / resolution),
|
||||
widtopo = widget.topo,
|
||||
|
|
@ -75,12 +75,7 @@ export class Topo {
|
|||
clipTo = inside ? shadow : POLY.expand(shadow, toolDiameter / 2 + resolution * 3),
|
||||
partOff = inside ? 0 : toolDiameter / 2 + resolution,
|
||||
gridDelta = Math.floor(partOff / resolution),
|
||||
debug = false,
|
||||
debug_clips = debug && true,
|
||||
debug_topo = debug && true,
|
||||
debug_topo_lines = debug && true,
|
||||
debug_topo_shells = debug && true,
|
||||
time = Date.now;
|
||||
debug_clips = false;
|
||||
|
||||
if (tolerance === 0 && !topoCache) {
|
||||
console.log(widget.id, 'topo auto tolerance', resolution.round(4));
|
||||
|
|
@ -90,7 +85,7 @@ export class Topo {
|
|||
// used by Pocket -> Contour
|
||||
this.tolerance = resolution;
|
||||
|
||||
if (tabs) {
|
||||
if (tabsOn) {
|
||||
clipTab.appendAll(tabs.map(tab => {
|
||||
let ctab = POLY.expand([tab.poly], toolDiameter / 2);
|
||||
ctab.forEach(ct => ct.z = tab.dim.z / 2 + tab.pos.z);
|
||||
|
|
@ -107,6 +102,190 @@ export class Topo {
|
|||
newslices.push(debug);
|
||||
}
|
||||
|
||||
if (webGPU && !contour.nogpu) {
|
||||
// invert tool Z offset for gpu code
|
||||
let toolBounds = new THREE.Box3()
|
||||
.expandByPoint({ x: -toolDiameter/2, y: -toolDiameter/2, z: 0 })
|
||||
.expandByPoint({ x: toolDiameter/2, y: toolDiameter/2, z: 0 });
|
||||
let toolPos = tool.profile.slice();
|
||||
for (let i=0; i<toolPos.length; i+= 3) {
|
||||
// toolPos[i+2] = -toolPos[i+2];
|
||||
toolBounds.expandByPoint({ x: toolPos[i], y: toolPos[i+1], z: toolPos[i+2] });
|
||||
}
|
||||
let toolData = { positions: toolPos, bounds: toolBounds };
|
||||
|
||||
const vertices = widget.getGeoVertices({ unroll: true, translate: true });
|
||||
const wbounds = widget.getBoundingBox();
|
||||
if (!inside) {
|
||||
wbounds.expandByVector({ x: toolDiameter/2, y: toolDiameter/2, z: 0 });
|
||||
}
|
||||
|
||||
if (contourY) {
|
||||
// console.time('swap XY vertices');
|
||||
// swap XY vertices
|
||||
for (let i=0; i<vertices.length; i+= 3) {
|
||||
let tmp = vertices[i+1];
|
||||
vertices[i+1] = vertices[i+0];
|
||||
vertices[i+0] = tmp;
|
||||
}
|
||||
['min','max'].forEach(ext => {
|
||||
ext = wbounds[ext];
|
||||
let tmp = ext.x;
|
||||
ext.x = ext.y;
|
||||
ext.y = tmp;
|
||||
});
|
||||
// console.timeEnd('swap XY vertices');
|
||||
}
|
||||
|
||||
let trace = contour.trace;
|
||||
let gpu = await self.get_raster_gpu({
|
||||
mode: trace ? "tracing" : "planar",
|
||||
resolution
|
||||
});
|
||||
let xStep = density;
|
||||
let yStep = Math.ceil(toolStep / resolution);
|
||||
let epsilon = 10e-4;
|
||||
await gpu.loadTool({
|
||||
sparseData: toolData
|
||||
});
|
||||
let terrain = await gpu.loadTerrain({
|
||||
triangles: vertices,
|
||||
boundsOverride: wbounds
|
||||
});
|
||||
let { gridWidth, positions } = terrain;
|
||||
let output = await gpu.generateToolpaths({
|
||||
xStep,
|
||||
yStep,
|
||||
zFloor: zBottom - 1,
|
||||
onProgress(pct) { console.log({ pct }); onupdate(pct/100, 100) }
|
||||
});
|
||||
gpu.terminate();
|
||||
|
||||
let { numScanlines, pointsPerLine, pathData } = output;
|
||||
let xmult = xStep * resolution;
|
||||
let ymult = yStep * resolution;
|
||||
let xoff = wbounds.min.x;
|
||||
let yoff = wbounds.min.y;
|
||||
let slices = [];
|
||||
function calcPoint(x, y, z, tx, ty) {
|
||||
let p = newPoint(x, y, z);
|
||||
p.tx = tx;
|
||||
p.ty = ty;
|
||||
return p;
|
||||
}
|
||||
for (let i=0; i<numScanlines; i++) {
|
||||
let lineStart = i * pointsPerLine;
|
||||
let lineData = pathData.slice(lineStart, lineStart + pointsPerLine);
|
||||
let points = contourY
|
||||
? Array.from(lineData).map((v,j) => calcPoint(i * ymult + yoff, j * xmult + xoff, v, i * xStep, j * yStep))
|
||||
: Array.from(lineData).map((v,j) => calcPoint(j * xmult + xoff, i * ymult + yoff, v, j * xStep, i * yStep))
|
||||
;
|
||||
let slice = newSlice(i);
|
||||
let poly = newPolygon().setOpen();
|
||||
let lines = [ ];
|
||||
let skip = 0;
|
||||
let lp;
|
||||
for (let p of points) {
|
||||
if (inside && positions[p.ty * gridWidth + p.tx] < zBottom - epsilon) {
|
||||
if (poly.length > 1) {
|
||||
lines.push(poly);
|
||||
poly = newPolygon().setOpen();
|
||||
} else if (poly.length === 1) {
|
||||
poly = newPolygon().setOpen();
|
||||
}
|
||||
lp = undefined;
|
||||
skip = 0;
|
||||
continue;
|
||||
}
|
||||
if (p.z < zBottom - epsilon) {
|
||||
lp = p;
|
||||
if (poly.length === 0) {
|
||||
skip++;
|
||||
continue;
|
||||
}
|
||||
if (poly.length > 1) {
|
||||
skip = 0;
|
||||
p.z = zBottom;
|
||||
poly.push(p);
|
||||
lines.push(poly);
|
||||
poly = newPolygon().setOpen();
|
||||
continue;
|
||||
}
|
||||
} else if (lp && skip && poly.length === 0) {
|
||||
lp.z = zBottom;
|
||||
poly.push(lp);
|
||||
}
|
||||
poly.push(lp = p);
|
||||
skip = 0;
|
||||
}
|
||||
// require two points or more
|
||||
if (poly.length > 1) {
|
||||
lines.push(poly);
|
||||
}
|
||||
// raize output points when inside tab boundaries
|
||||
if (!inside && tabsOn && clipTab.length)
|
||||
for (let poly of lines) {
|
||||
for (let p of poly.points) {
|
||||
for (let clip of clipTab) {
|
||||
if (p.z < clip.z && p.isInPolygon(clip)) {
|
||||
p.z = clip.z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// drop interior points along a continuous slope
|
||||
for (let poly of lines) {
|
||||
let { points } = poly;
|
||||
let merged = [ points[0] ];
|
||||
let lp = points[1];
|
||||
let dz = points[0].z - lp.z;
|
||||
for (let i=2; i<points.length; i++) {
|
||||
let np = points[i];
|
||||
let ndz = np.z - lp.z;
|
||||
if (Math.abs(dz - ndz) > epsilon) {
|
||||
// slope changed
|
||||
merged.push(lp);
|
||||
dz = ndz;
|
||||
} else if (curvesOnly && Math.abs(ndz) < epsilon) {
|
||||
// add empty points as path separators
|
||||
merged.push(undefined);
|
||||
}
|
||||
lp = np;
|
||||
}
|
||||
if (merged.peek() !== lp) {
|
||||
merged.push(lp);
|
||||
}
|
||||
poly.points = merged;
|
||||
}
|
||||
// drop flat regions when enabled
|
||||
if (curvesOnly) {
|
||||
let nulines = [];
|
||||
for (let poly of lines) {
|
||||
let { points } = poly;
|
||||
let newp = [];
|
||||
for (let p of points) {
|
||||
if (p) {
|
||||
newp.push(p);
|
||||
} else if (newp.length) {
|
||||
nulines.push(newPolygon(newp).setOpen());
|
||||
newp = [];
|
||||
}
|
||||
}
|
||||
nulines.push(newPolygon(newp).setOpen());
|
||||
}
|
||||
lines = nulines.filter(p => p.perimeter() > toolStep);
|
||||
}
|
||||
slice.camLines = lines;
|
||||
slices.push(slice);
|
||||
if (inside) {
|
||||
onupdate(i, numScanlines);
|
||||
}
|
||||
}
|
||||
ondone(slices);
|
||||
return this;
|
||||
}
|
||||
|
||||
const probe = this.probe = new Probe({
|
||||
profile: toolOffset,
|
||||
data,
|
||||
|
|
@ -177,8 +356,7 @@ export class Topo {
|
|||
clipTabZ: clipTab ? clipTab.map(t => t.z) : undefined,
|
||||
tabHeight,
|
||||
newslices,
|
||||
leave,
|
||||
color
|
||||
leave
|
||||
}, (i, l, p) => {
|
||||
onupdate(l / 2 + i / 2, l, p);
|
||||
});
|
||||
|
|
@ -304,7 +482,7 @@ export class Topo {
|
|||
|
||||
const { minX, maxX, minY, maxY, boundsX, boundsY, stepsX, stepsY } = params;
|
||||
const { gridDelta, resolution, density, partOff, toolStep, contourX, contourY } = params;
|
||||
const { clipTo, clipTab, clipTabZ, tabHeight, newslices, color, leave } = params;
|
||||
const { clipTo, clipTab, clipTabZ, tabHeight, newslices, leave } = params;
|
||||
|
||||
let stepsTaken = 0,
|
||||
stepsTotal = 0;
|
||||
|
|
@ -370,11 +548,8 @@ export class Topo {
|
|||
gridy
|
||||
}, segments => {
|
||||
if (segments.length > 0) {
|
||||
let slice = newSlice(gridx);
|
||||
let slice = newSlice(x);
|
||||
slice.camLines = segments;
|
||||
slice.output()
|
||||
.setLayer("contour y", { face: color, line: color })
|
||||
.addPolys(segments);
|
||||
slicesY.push(slice);
|
||||
}
|
||||
onupdate(++stepsTaken, stepsTotal, "contour y");
|
||||
|
|
@ -399,11 +574,8 @@ export class Topo {
|
|||
gridy
|
||||
}, segments => {
|
||||
if (segments.length > 0) {
|
||||
let slice = newSlice(gridy);
|
||||
let slice = newSlice(y);
|
||||
slice.camLines = segments;
|
||||
slice.output()
|
||||
.setLayer("contour x", { face: color, line: color })
|
||||
.addPolys(segments);
|
||||
slicesX.push(slice);
|
||||
}
|
||||
onupdate(++stepsTaken, stepsTotal, "contour x");
|
||||
|
|
@ -534,9 +706,9 @@ export class Trace {
|
|||
console.log(...arguments);
|
||||
}
|
||||
|
||||
const push_point = this.push_point = function (x, y, z) {
|
||||
this.push_point = function (x, y, z) {
|
||||
const newP = newPoint(x, y, z);
|
||||
const lastP = lastPP;//trace.last();
|
||||
const lastP = lastPP;
|
||||
|
||||
if (lastP) {
|
||||
const dl = (x - lastP.x) || (y - lastP.y);
|
||||
|
|
@ -553,7 +725,6 @@ export class Trace {
|
|||
}
|
||||
if (curvesOnly) {
|
||||
const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
|
||||
// const dz = lastPP.z - z;
|
||||
const angle = Math.atan2(Math.abs(dz), dv) * RAD2DEG;
|
||||
if (angle > maxangle) {
|
||||
end_poly();
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ export class Topo {
|
|||
|
||||
async generate(opt = {}) {
|
||||
let { state, op, onupdate, ondone } = opt;
|
||||
let { widget, settings, tabs } = opt.state;
|
||||
let { widget, settings, tabs, color } = state;
|
||||
let { controller, process } = settings;
|
||||
let { webGPU } = controller;
|
||||
|
||||
let axis = op.axis.toLowerCase(),
|
||||
tool = new Tool(settings, op.tool),
|
||||
|
|
@ -38,9 +39,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,31 +52,36 @@ 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.units = controller.units === 'in' ? 25.4 : 1
|
||||
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.lineColor = color;//controller.dark ? 0xffff00 : 0x555500;
|
||||
this.offStart = op.offStart ?? 0;
|
||||
this.offEnd = op.offEnd ?? 0;
|
||||
this.bounds = bounds;
|
||||
this.gpu = webGPU ?? false;
|
||||
|
||||
onupdate(0, "lathe");
|
||||
|
||||
const parts = webGPU ? [ 0.8, 0.2 ] : [ 0.25, 0.75 ];
|
||||
const range = this.range = { min: Infinity, max: -Infinity };
|
||||
const slices = this.sliced = await this.slice(scale(onupdate, 0.25, 0));
|
||||
const slices = this.sliced = await this.slice(scale(onupdate, parts[0], 0));
|
||||
|
||||
for (let slice of slices) {
|
||||
range.min = Math.min(range.min, slice.z);
|
||||
range.max = Math.max(range.max, slice.z);
|
||||
slice.output()
|
||||
.setLayer("lathe", { line: 0x888888 })
|
||||
.addPolys(slice.topPolys());
|
||||
}
|
||||
|
||||
const lathe = await this.lathe(scale(onupdate, 0.75, 0.25));
|
||||
const lathe = await this.lathe(scale(onupdate, parts[1], parts[0]));
|
||||
|
||||
onupdate(1, "lathe");
|
||||
ondone(lathe);
|
||||
|
|
@ -86,10 +91,9 @@ export class Topo {
|
|||
}
|
||||
|
||||
async slice(onupdate) {
|
||||
const { vertices, resolution, tabverts, zoff } = this;
|
||||
|
||||
const { vertices, resolution, tabverts, zoff, offStart, offEnd, units } = this;
|
||||
const { minions } = self.kiri_worker;
|
||||
const range = this.range = { min: Infinity, max: -Infinity };
|
||||
const box = this.box = new THREE.Box2();
|
||||
|
||||
// swap XZ in shared array
|
||||
for (let i = 0, l = vertices.length; i < l; i += 3) {
|
||||
|
|
@ -101,9 +105,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 * units;
|
||||
range.max -= offEnd * units;
|
||||
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 +121,36 @@ export class Topo {
|
|||
// re-create shared vertex array for workers
|
||||
this.vertices = [].appendAll(vertices).appendAll(tabverts).toFloat32().toShared();
|
||||
|
||||
const shards = Math.ceil(Math.min(25, vertices.length / 27000));
|
||||
const step = (range.max - range.min) / shards;
|
||||
|
||||
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++;
|
||||
// rp.rasterizeMesh(vertices, resolution).then(rpo => console.log({ rpo }));
|
||||
if (this.gpu) {
|
||||
return await this.sliceGPU(onupdate);
|
||||
}
|
||||
slices.push(slice);
|
||||
// console.log({ shards, range, step, slices });
|
||||
|
||||
const { minions } = self.kiri_worker;
|
||||
const zSpan = range.max - range.min;
|
||||
const shards = Math.ceil(Math.min(25, vertices.length / 27000));
|
||||
const totalSteps = Math.ceil(zSpan / resolution);
|
||||
const stardSteps = Math.ceil(totalSteps / shards);
|
||||
const stepWidth = stardSteps * resolution;
|
||||
|
||||
let slices = this.slices = [];
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
// console.log({
|
||||
// shards,
|
||||
// step: stepWidth,
|
||||
// range,
|
||||
// resolution,
|
||||
// slices: slices.slice(),
|
||||
// minions
|
||||
// });
|
||||
|
||||
if (minions?.running > 1) {
|
||||
return await this.sliceMinions(onupdate);
|
||||
} else {
|
||||
|
|
@ -141,6 +158,96 @@ export class Topo {
|
|||
}
|
||||
}
|
||||
|
||||
async sliceGPU(onupdate) {
|
||||
const { angle, diam, linear, offStart, offEnd, resolution, tool, units, vertices, zBottom } = this;
|
||||
|
||||
// invert tool Z offset for gpu code
|
||||
let toolBounds = new THREE.Box3()
|
||||
.expandByPoint({ x: -this.diam/2, y: -diam/2, z: 0 })
|
||||
.expandByPoint({ x: this.diam/2, y: diam/2, z: 0 });
|
||||
let toolPos = tool.slice();
|
||||
let minz = Infinity;
|
||||
for (let i=0; i<toolPos.length; i += 3) {
|
||||
// toolPos[i+2] = -toolPos[i+2];
|
||||
toolBounds.expandByPoint({ x: 0, y: 0, z: toolPos[i+2] });
|
||||
minz = Math.min(minz,toolPos[i+2]);
|
||||
}
|
||||
let toolData = { positions: toolPos, bounds: toolBounds };
|
||||
|
||||
// console.time('swap XZ vertices');
|
||||
// swap XZ vertices for gpu code
|
||||
for (let i=0; i<vertices.length; i+= 3) {
|
||||
let tmp = vertices[i+2];
|
||||
vertices[i+2] = vertices[i+0];
|
||||
vertices[i+1] = -vertices[i+1];
|
||||
vertices[i+0] = tmp;
|
||||
}
|
||||
// console.timeEnd('swap XZ vertices');
|
||||
|
||||
let gpu = await self.get_raster_gpu({
|
||||
mode: "radial",
|
||||
resolution,
|
||||
rotationStep: angle,
|
||||
radialRotationOffset: 90
|
||||
});
|
||||
let xStep = Math.max(1, Math.round(this.step / resolution));
|
||||
let boundsOverride = this.bounds.clone();
|
||||
boundsOverride.min.x += offStart * units;
|
||||
boundsOverride.max.x -= offEnd * units;
|
||||
await gpu.loadTool({
|
||||
sparseData: toolData
|
||||
});
|
||||
await gpu.loadTerrain({
|
||||
triangles: vertices,
|
||||
boundsOverride,
|
||||
onProgress(pct) { onupdate(pct/100) }
|
||||
});
|
||||
let output = await gpu.generateToolpaths({
|
||||
xStep,
|
||||
yStep: 1,
|
||||
zFloor: zBottom,
|
||||
onProgress(i,j) { onupdate(i/j) }
|
||||
});
|
||||
gpu.terminate();
|
||||
|
||||
let { numStrips, strips } = output;
|
||||
let degPerRow = 360 / numStrips;
|
||||
let slices = this.gpu_slices = [];
|
||||
let xmult = resolution * xStep;
|
||||
let xoff = boundsOverride.min.x;
|
||||
let rows = [];
|
||||
for (let i=0; i<numStrips; i++) {
|
||||
let points = Array.from(strips[i].pathData).map((v,j) => newPoint(j * xmult + xoff, 0, v).setA(-i * degPerRow));
|
||||
rows.push(points);
|
||||
}
|
||||
if (linear) {
|
||||
for (let i=0; i<rows.length; i++) {
|
||||
let slice = newSlice(i);
|
||||
slice.index = i;
|
||||
slice.camLines = [ newPolygon(rows[i]).setOpen() ];
|
||||
if (i % 2 === 1) slice.camLines[0].reverse();
|
||||
slices.push(slice);
|
||||
}
|
||||
} else {
|
||||
let { pointsPerLine } = strips[0];
|
||||
for (let i=0; i<pointsPerLine; i++) {
|
||||
let slice = newSlice(i);
|
||||
let points = rows.map(row => row[i]);
|
||||
if (i % 2 === 1) points.reverse();
|
||||
slice.index = i;
|
||||
slice.camLines = [ newPolygon(points).setOpen() ];
|
||||
slices.push(slice);
|
||||
}
|
||||
}
|
||||
for (let slice of slices) {
|
||||
slice.output()
|
||||
.setLayer("lathe", { line: this.lineColor })
|
||||
.addPoly(slice.camLines[0].clone().applyRotations());
|
||||
}
|
||||
// console.log({ webGPU: output, slices });
|
||||
return slices;
|
||||
}
|
||||
|
||||
async sliceWorker(onupdate) {
|
||||
const { vertices, slices, resolution } = this;
|
||||
|
||||
|
|
@ -205,22 +312,28 @@ export class Topo {
|
|||
|
||||
async lathe(onupdate) {
|
||||
const { minions } = self.kiri_worker;
|
||||
if (minions?.running > 1) {
|
||||
if (this.gpu) {
|
||||
return await this.latheGPU(onupdate);
|
||||
} else if (minions?.running > 1) {
|
||||
return await this.latheMinions(onupdate);
|
||||
} else {
|
||||
return await this.latheWorker(onupdate);
|
||||
}
|
||||
}
|
||||
|
||||
async latheGPU(onupdate) {
|
||||
return this.gpu_slices;
|
||||
}
|
||||
|
||||
lathePath(slices, tool) {
|
||||
const { resolution, step, zBottom, maxo } = this;
|
||||
|
||||
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 +357,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 +396,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;
|
||||
|
|
@ -297,8 +291,9 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
append(`; Bed type: ${bedType}`);
|
||||
append(`; Target: ${filter[mode]}`);
|
||||
// inject thumbnail preview
|
||||
if (exportThumb && worker.snap) {
|
||||
let { width, height, url } = worker.snap;
|
||||
let { current } = self.kiri_worker ?? {};
|
||||
if (exportThumb && current?.snap) {
|
||||
let { width, height, url } = current.snap;
|
||||
let data = url.substring(url.indexOf(',') + 1);
|
||||
append(`; thumbnail begin ${width} ${height} ${data.length}`);
|
||||
for (let i=0; i<data.length; i += 78) {
|
||||
|
|
@ -470,7 +465,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;
|
||||
}
|
||||
|
|
@ -613,6 +608,9 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
for (pidx=0; pidx<path.length; pidx++) {
|
||||
out = path[pidx];
|
||||
speedMMM = (out.speed || process.outputFeedrate) * 60; // range
|
||||
if (!isBelt) {
|
||||
fanSpeed = out.fan ?? fanSpeed;
|
||||
}
|
||||
|
||||
// hint to controller that we're working on a specific object
|
||||
// so that gcode between start/stop comments can be cancelled
|
||||
|
|
@ -634,7 +632,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 {
|
||||
|
|
@ -687,10 +685,10 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
// in belt mode, fan can change per segment (for base)
|
||||
if (isBelt) {
|
||||
setTempFanSpeed(out.fan);
|
||||
if (fanSpeed !== lastFanSpeed) {
|
||||
appendAllSub(gcodeFan);
|
||||
lastFanSpeed = fanSpeed;
|
||||
}
|
||||
}
|
||||
if (fanSpeed !== lastFanSpeed) {
|
||||
appendAllSub(gcodeFan);
|
||||
lastFanSpeed = fanSpeed;
|
||||
}
|
||||
|
||||
if (lastp && out.emit) {
|
||||
|
|
@ -751,7 +749,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)}),
|
||||
|
|
@ -135,11 +134,9 @@ export function menu() {
|
|||
firstLayerFanSpeed: newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:toInt, bound:bound(0,255), show:isBelt}),
|
||||
firstLayerYOffset: newInput(LANG.fl_zoff_s, {title:LANG.fl_zoff_l, convert:toFloat, show:isBelt}),
|
||||
firstLayerFlatten: newInput(LANG.fl_flat_s, {title:LANG.fl_flat_l, convert:toFloat, show:isBelt}),
|
||||
firstLayerBeltFact: newInput(LANG.fl_mult_s, {title:LANG.fl_mult_l, convert:toFloat, bound:bound(0, 2), 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 +151,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,10 +173,13 @@ 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)}),
|
||||
outputCoastDist: newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, convert:toFloat, bound:bound(0.0,10)}),
|
||||
sliceCompInner: newInput(LANG.sl_ofin_s, {title:LANG.sl_ofin_l, convert:toFloat, bound:bound(-10.0,10.0)}),
|
||||
sliceCompOuter: newInput(LANG.sl_ofot_s, {title:LANG.sl_ofot_l, convert:toFloat, bound:bound(-10.0,10.0)}),
|
||||
zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}),
|
||||
outputLoops: newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, convert:toInt, bound:bound(-1,1000), show:isBelt}),
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@ slicer.slicePost.FDM = slicePost;
|
|||
* data object. return is ignored.
|
||||
*/
|
||||
export function slicePost(data, options) {
|
||||
const { z, lines, groups } = data;
|
||||
const { useAssembly, post_args, zIndexes } = options;
|
||||
const { process, isSynth, vaseMode } = post_args;
|
||||
const { shellOffset, fillOffset, clipOffset } = post_args;
|
||||
const { groups, z } = data;
|
||||
const { post_args, useAssembly, zIndexes } = options;
|
||||
const { isSynth, process, vaseMode } = post_args;
|
||||
const { compInner, compOuter, pump } = post_args;
|
||||
const { clipOffset, fillOffset, shellOffset } = post_args;
|
||||
|
||||
if (options.pump) {
|
||||
if (pump) {
|
||||
let nugroups = [];
|
||||
let pump = shellOffset / 10;
|
||||
for (let p of groups) {
|
||||
|
|
@ -41,6 +42,19 @@ export function slicePost(data, options) {
|
|||
data.tops = POLY.nest(groups);
|
||||
}
|
||||
|
||||
// perimeter and inner hole compensation offsets
|
||||
if (compInner || compOuter) {
|
||||
let inner = POLY.inner(data.tops).flat();
|
||||
let outer = POLY.outer(data.tops);
|
||||
if (compInner) {
|
||||
inner = POLY.flatten(POLY.offset(inner, compInner/2));
|
||||
}
|
||||
if (compOuter) {
|
||||
outer = POLY.flatten(POLY.offset(outer, -compOuter/2));
|
||||
}
|
||||
data.tops = POLY.nest([...inner, ...outer]);
|
||||
}
|
||||
|
||||
if (isSynth) {
|
||||
const process = post_args.process;
|
||||
if (process.sliceSupportGrow > 0) {
|
||||
|
|
@ -54,6 +68,7 @@ export function slicePost(data, options) {
|
|||
delete data.groups;
|
||||
return;
|
||||
}
|
||||
|
||||
const index = zIndexes.indexOf(z);
|
||||
const range = getRangeParameters(process, index);
|
||||
// calculate fractional shells
|
||||
|
|
@ -67,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
|
||||
|
|
@ -129,7 +141,7 @@ function offset_default(params) {
|
|||
}
|
||||
|
||||
const deltaBig = 160;
|
||||
const deltSmall = 20;
|
||||
const deltaSmall = 20;
|
||||
|
||||
// look for close points on adjacent segmented polys and merge
|
||||
let test_polys = first !== last ? [ ...first, ...last ] : last;
|
||||
|
|
@ -139,6 +151,8 @@ function offset_default(params) {
|
|||
for (let poly of test_flats)
|
||||
poly.forEachSegment((p0, p1) => {
|
||||
p0.angle = new Slope(p0, p1);
|
||||
// todo calc vertex angle
|
||||
// exclude segment vertex from merge when angle < deltaSmall
|
||||
});
|
||||
|
||||
// segment each poly to be tested
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -46,8 +47,6 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
output = [],
|
||||
layerout = [];
|
||||
|
||||
let lastLayerStart = null;
|
||||
|
||||
// compute bounds if missing
|
||||
if (!bounds) {
|
||||
bounds = new THREE.Box3();
|
||||
|
|
@ -553,12 +552,9 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
offset,
|
||||
layerout,
|
||||
{
|
||||
routeAround: process.outputAvoidGaps,
|
||||
seedPoint: printPoint.sub(offset),
|
||||
params, // range parameters
|
||||
first: slice.index === 0,
|
||||
support: slice.widget.support,
|
||||
onBelt: beltStart,
|
||||
params, // range parameters
|
||||
pretract: (wipeDist) => {
|
||||
if (!(lastLayer && lastLayer.length)) {
|
||||
return;
|
||||
|
|
@ -573,7 +569,11 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
print.addOutput(lastLayer, endpoint, null, null, lastOut.tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
routeAround: process.outputAvoidGaps,
|
||||
seedPoint: printPoint.sub(offset),
|
||||
support: slice.widget.support,
|
||||
zmax,
|
||||
}
|
||||
);
|
||||
layerout.print_time = printPoint.layer_time;
|
||||
|
|
@ -682,8 +682,8 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
|
||||
for (let rec of layer) {
|
||||
overate = rec.overate >= 0 ? rec.overate : overate;
|
||||
let bmult = params.firstLayerBeltFact ?? 1;
|
||||
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,8 +695,9 @@ 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;
|
||||
// apply base flowrate multiplier to segments touching belt
|
||||
rec.emit *= bmult;
|
||||
minx = Math.min(minx, point.x, lastout.point.x);
|
||||
maxx = Math.max(maxx, point.x, lastout.point.x);
|
||||
maxy = Math.max(maxy, point.y);
|
||||
|
|
@ -795,10 +796,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,
|
||||
|
|
@ -807,6 +820,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),
|
||||
|
|
@ -815,17 +829,20 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
shellOrder = {"out-in":-1,"in-out":1,"alternate":-2}[process.sliceShellOrder] || -1,
|
||||
sparseMult = process.outputSparseMult,
|
||||
coastDist = process.outputCoastDist || 0,
|
||||
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 ? {
|
||||
|
|
@ -849,11 +866,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;
|
||||
|
|
@ -862,13 +874,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
// override: adapt bridge layer speed
|
||||
if (slice.isBridgeLayer) {
|
||||
fillSpeed /= 2;
|
||||
}
|
||||
|
||||
// apply first layer extrusion multipliers
|
||||
if (firstLayer) {
|
||||
fillMult *= firstPrintMult;
|
||||
shellMult *= firstPrintMult;
|
||||
sparseMult *= firstPrintMult;
|
||||
printSpeed /= 2;
|
||||
}
|
||||
|
||||
function retract() {
|
||||
|
|
@ -1100,9 +1106,6 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
coast: firstLayer ? 0 : coastDist,
|
||||
extrude: numOrDefault(opt.extrude, shellMult),
|
||||
nozzleSize,
|
||||
rate: finishShell ? finishSpeed : printSpeed,
|
||||
scarf,
|
||||
tool: extruder,
|
||||
onfirst: (firstPoint, preout) => {
|
||||
let from = seedPoint || startPoint;
|
||||
if (from.distTo2D(firstPoint) > retractDist) {
|
||||
|
|
@ -1111,7 +1114,11 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
}
|
||||
}
|
||||
seedPoint = null;
|
||||
}
|
||||
},
|
||||
rate: finishShell ? finishSpeed : printSpeed,
|
||||
scarf,
|
||||
tool: extruder,
|
||||
zmax
|
||||
});
|
||||
lastPoly = slice.lastPoly = poly;
|
||||
}
|
||||
|
|
@ -1591,16 +1598,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 *= fact;
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1612,6 +1637,12 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
// add offset points to total print
|
||||
print.addPrintPoints(preout, output);
|
||||
|
||||
// bridge layer fan change
|
||||
if (preout.length && slice.isBridgeLayer) {
|
||||
preout[0].fan = Math.round(fanSpeed / 3);
|
||||
preout.peek().fan = fanSpeed;
|
||||
}
|
||||
|
||||
// create next layer start point and annotate
|
||||
// with print time for previous layer (hack to
|
||||
// avoid changing return signature rn)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
fillOffset = lineWidth * fillOffsetMult,
|
||||
clipOffset = process.sliceSupportOffset,
|
||||
sliceFillAngle = process.sliceFillAngle,
|
||||
supportDensity = process.sliceSupportDensity;
|
||||
supportDensity = process.sliceSupportDensity,
|
||||
{ sliceCompInner, sliceCompOuter } = process;
|
||||
|
||||
// override globals used by vopt()
|
||||
isFlat = controller.lineType === "flat";
|
||||
|
|
@ -230,6 +231,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
useAssembly,
|
||||
post: 'FDM',
|
||||
post_args: {
|
||||
compInner: sliceCompInner,
|
||||
compOuter: sliceCompOuter,
|
||||
shellOffset,
|
||||
fillOffset,
|
||||
clipOffset,
|
||||
|
|
@ -495,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;
|
||||
|
|
@ -503,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) {
|
||||
|
|
@ -752,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;
|
||||
|
|
|
|||