Merge branch 'rel-4.3' into helical-op
This commit is contained in:
commit
dfc2cd7f17
325 changed files with 18349 additions and 25230 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -22,8 +22,8 @@ jobs:
|
|||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Setup Build Environment
|
||||
run: npm run setup
|
||||
|
||||
- name: Build Electron app
|
||||
env:
|
||||
|
|
|
|||
2
.github/workflows/prod-test.yml
vendored
2
.github/workflows/prod-test.yml
vendored
|
|
@ -24,4 +24,4 @@ jobs:
|
|||
run: npm run setup
|
||||
|
||||
- name: dry run production build
|
||||
run: npm run prod-dryrun
|
||||
run: npm run prod-dryrun
|
||||
|
|
|
|||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,13 +1,14 @@
|
|||
.DS_Store
|
||||
.docusaurus
|
||||
.env
|
||||
app.json
|
||||
build
|
||||
data
|
||||
dist
|
||||
logs
|
||||
mod
|
||||
node_modules
|
||||
package-lock.json
|
||||
src/ext/three.js
|
||||
three.js
|
||||
tmp
|
||||
.docusaurus
|
||||
build
|
||||
pack
|
||||
|
|
@ -2,6 +2,7 @@ const { app, shell, session, BrowserWindow } = require('electron');
|
|||
const path = require('path');
|
||||
const server = require('@gridspace/app-server');
|
||||
|
||||
const pkgd = app.isPackaged;
|
||||
const basDir = __dirname;
|
||||
const usrDir = app.getPath("userData");
|
||||
const appDir = path.join(usrDir, 'gapp');
|
||||
|
|
@ -11,6 +12,8 @@ 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');
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||
|
||||
// console.log({ appDir, usrDir, logDir, datDir, basDir });
|
||||
// console.log({ argv: process.argv, debug, devel });
|
||||
// console.log({
|
||||
|
|
@ -26,9 +29,9 @@ server({
|
|||
data: datDir,
|
||||
conf: cnfDir,
|
||||
logs: logDir,
|
||||
cache: path.join(basDir, "data", "cache"),
|
||||
single: true,
|
||||
electron: true,
|
||||
pkgd,
|
||||
debug
|
||||
});
|
||||
|
||||
|
|
|
|||
611
app.js
611
app.js
|
|
@ -12,31 +12,26 @@ const uglify = require('uglify-js');
|
|||
const moment = require('moment');
|
||||
const agent = require('express-useragent');
|
||||
const license = require_fresh('./src/moto/license.js');
|
||||
const version = license.VERSION || "rogue";
|
||||
const version = license.version || "rogue";
|
||||
const netdb = require('@gridspace/net-level-client');
|
||||
const PATH = require('path');
|
||||
|
||||
const fileCache = {};
|
||||
const code_src = {};
|
||||
const append = { mesh:'', kiri:'' };
|
||||
const code = {};
|
||||
const mods = {};
|
||||
const load = [];
|
||||
const synth = {};
|
||||
const api = {};
|
||||
const wrap = {};
|
||||
|
||||
let forceUseCache = false;
|
||||
let serviceWorker = true;
|
||||
let crossOrigin = false;
|
||||
let setupFn;
|
||||
let cacheDir;
|
||||
let startTime;
|
||||
let oversion;
|
||||
let dversion;
|
||||
let lastmod;
|
||||
let logger;
|
||||
let debug;
|
||||
let over;
|
||||
let http;
|
||||
let util;
|
||||
let dir;
|
||||
|
|
@ -58,25 +53,6 @@ netdb.create = async function(map = {}) {
|
|||
return client;
|
||||
};
|
||||
|
||||
// experimental wrapping of esm modules using embedded data urls
|
||||
function wrap_module(buf, as) {
|
||||
const base64 = Buffer.from(buf).toString('base64');
|
||||
const path = as.split('.');
|
||||
const exp = path.pop();
|
||||
return [
|
||||
`gapp.register("${path.join('.')}", [], (root, exports) => {`,
|
||||
`const url = "data:text/javascript;base64,${base64}";`,
|
||||
`exports({ ["${exp}"]: import(url) });`,
|
||||
"});"
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function addonce(array, v) {
|
||||
if (array.indexOf(v) < 0) {
|
||||
array.push(v);
|
||||
}
|
||||
};
|
||||
|
||||
function init(mod) {
|
||||
const ENV = mod.env;
|
||||
|
||||
|
|
@ -94,8 +70,6 @@ function init(mod) {
|
|||
|
||||
if (ENV.single) console.log({ cwd: process.cwd(), env: ENV });
|
||||
dversion = debug ? `_${version}` : version;
|
||||
cacheDir = ENV.cache || mod.util.datadir("cache");
|
||||
if (ENV.single) logger.log({ cacheDir });
|
||||
forceUseCache = ENV.cache ? true : false;
|
||||
|
||||
const approot = PATH.join("main","gapp");
|
||||
|
|
@ -103,172 +77,8 @@ function init(mod) {
|
|||
const callstack = [];
|
||||
let xxxx = false;
|
||||
|
||||
function find_refs(cache, path, ismod) {
|
||||
let rec = refcache[path];
|
||||
if (rec) {
|
||||
let crec = cache[path];
|
||||
if (!crec) {
|
||||
cache[path] = rec;
|
||||
for (let d of rec.deps) find_refs(cache, d);
|
||||
for (let u of rec.uses) find_refs(cache, u);
|
||||
}
|
||||
return;
|
||||
}
|
||||
callstack.push(path);
|
||||
rec = cache[path] = refcache[path] = {
|
||||
uses: [],
|
||||
deps: [ approot ]
|
||||
};
|
||||
let full = PATH.join(dir,"src",`${path}.js`);
|
||||
try {
|
||||
fs.lstatSync(full);
|
||||
} catch (e) {
|
||||
console.log({missing: full, callstack});
|
||||
throw e;
|
||||
}
|
||||
// skip interrogating file if it's a module (external compacted)
|
||||
if (ismod) {
|
||||
wrap[`src/${path}.js`] = path.replaceAll('/','.');
|
||||
return;
|
||||
}
|
||||
let lines = fs.readFileSync(full)
|
||||
.toString()
|
||||
.split('\n');
|
||||
for (let line of lines) {
|
||||
let arr, pos;
|
||||
let upos = line.indexOf('// use:');
|
||||
if (upos >= 0) {
|
||||
arr = rec.uses;
|
||||
pos = upos + 7;
|
||||
}
|
||||
let dpos = line.indexOf('// dep:');
|
||||
if (dpos >= 0) {
|
||||
arr = rec.deps;
|
||||
pos = dpos + 7;
|
||||
}
|
||||
let mpos = line.indexOf('// mod:');
|
||||
if (mpos >= 0) {
|
||||
arr = rec.deps;
|
||||
pos = mpos + 7;
|
||||
}
|
||||
if (upos >= 0 && dpos >= 0) {
|
||||
console.log(`invalid line: ${line}`);
|
||||
process.exit();
|
||||
}
|
||||
if (arr && pos >= 0) {
|
||||
let path = line.substring(pos).trim().replace(/\./g,'/').trim();
|
||||
addonce(arr, path);
|
||||
find_refs(cache, path, mpos >= 0);
|
||||
}
|
||||
}
|
||||
// if (xxxx) console.log({path, ...rec});
|
||||
if (false) {
|
||||
let seek = 'mesh/api';
|
||||
if (rec.uses.indexOf(seek) >= 0 || rec.deps.indexOf(seek) >= 0) {
|
||||
console.log({PULLS:seek, path});
|
||||
}
|
||||
}
|
||||
callstack.pop();
|
||||
}
|
||||
|
||||
// return record position indicated by path
|
||||
function pos(path, list) {
|
||||
for (let i=0; i<list.length; i++) {
|
||||
if (list[i].path === path) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
console.trace(`not found: ${path}`);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
function order_refs(cache) {
|
||||
const recs = Object.entries(cache).map(entry => {
|
||||
return { path: entry[0], deps: entry[1].deps }
|
||||
}).sort((a,b) => {
|
||||
return a.path === b.path ? 0 : a.path < b.path ? -1 : 1;
|
||||
});
|
||||
if (xxxx) console.log({ordering: recs});
|
||||
|
||||
// for each rec, ensure that dependencies are inserted before it
|
||||
let lrec = recs.slice();
|
||||
for (let rec of lrec) {
|
||||
let { path, deps } = rec;
|
||||
for (let dep of deps) {
|
||||
let rpos = pos(path, recs);
|
||||
let dpos = pos(dep, recs);
|
||||
if (dpos > rpos) {
|
||||
let drec = recs[dpos];
|
||||
// remove old dep record
|
||||
recs.splice(dpos, 1);
|
||||
// insert dep before
|
||||
recs.splice(rpos, 0, drec);
|
||||
let nrpos = pos(path, recs);
|
||||
let ndpos = pos(dep, recs);
|
||||
let fail = nrpos != ndpos + 1;
|
||||
// if (xxxx) { console.log({move: dep, dpos, before: path, rpos}); }
|
||||
if (fail) {
|
||||
console.log({move: dep, dpos, before: path, rpos, ndpos, nrpos, recs: recs.slice(0,10)});
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xxxx) console.log({recs});
|
||||
return recs.map(rec => rec.path);
|
||||
}
|
||||
|
||||
// process script dependencies, expand paths
|
||||
for (let [ key, val ] of Object.entries(script)) {
|
||||
if (val.indexOf(approot) < 0) {
|
||||
val = [ approot, ...val ];
|
||||
}
|
||||
const list = val.map(p => p.charAt(0) === '&' ? p.substring(1) : p);
|
||||
const cache = {};
|
||||
const roots = [];
|
||||
// xxxx = key === "kiri_work";
|
||||
// for each path in the list, find deps and add to list
|
||||
for (let path of val) {
|
||||
let fc = path.charAt(0);
|
||||
if (fc === '@') {
|
||||
continue;
|
||||
}
|
||||
if (fc === '#') {
|
||||
continue;
|
||||
}
|
||||
if (fc === '&') {
|
||||
path = path.substring(1);
|
||||
addonce(roots, path);
|
||||
}
|
||||
find_refs(cache, path);
|
||||
}
|
||||
if (xxxx) console.log({ processing: key, val });
|
||||
let refs = order_refs(cache).filter(p => roots.indexOf(p) < 0);
|
||||
// remove paths that are in refs
|
||||
let paths = list.filter(p => {
|
||||
if (p.charAt(0) === '&') {
|
||||
p = p.substring(1);
|
||||
}
|
||||
return refs.indexOf(p) < 0 && roots.indexOf(p) < 0;
|
||||
});
|
||||
// when dependency roots exist, re-write val array
|
||||
if (roots.length) {
|
||||
val = [...refs, ...paths, ...roots];
|
||||
}
|
||||
// val.splice(1, 0, ...roots);
|
||||
if (xxxx) console.log({key, cache, refs, paths, roots, val});
|
||||
script[key] = val.map(p => {
|
||||
let fc = p.charAt(0);
|
||||
if (fc === '@') return p;
|
||||
if (fc === '#') {
|
||||
fc = p.split('#');
|
||||
let nupath = `src/${fc[1]}.js`;
|
||||
wrap[nupath] = fc[1].replaceAll('/','.');
|
||||
return nupath;
|
||||
}
|
||||
return `src/${p}.js`;
|
||||
});
|
||||
// console.log({script: key, files: script[key]});
|
||||
if (!ENV.electron) {
|
||||
generateDevices();
|
||||
}
|
||||
|
||||
mod.on.test((req) => {
|
||||
|
|
@ -285,7 +95,8 @@ function init(mod) {
|
|||
|
||||
mod.add(handleSetup);
|
||||
mod.add(handleOptions);
|
||||
mod.add(handleWasm);
|
||||
mod.add(serveWasm);
|
||||
mod.add(serveCode);
|
||||
mod.add(fullpath({
|
||||
"/kiri" : redir("/kiri/", 301),
|
||||
"/mesh" : redir("/mesh/", 301),
|
||||
|
|
@ -295,10 +106,6 @@ function init(mod) {
|
|||
"/meta/index.html" : redir("/meta/", 301)
|
||||
}));
|
||||
mod.add(handleVersion);
|
||||
mod.add(prepath([
|
||||
[ "/code/", handleCode ],
|
||||
// [ "/wasm/", handleWasm ]
|
||||
]));
|
||||
mod.add(fixedmap("/api/", api));
|
||||
if (debug) {
|
||||
mod.static("/mod/", "mod");
|
||||
|
|
@ -309,25 +116,14 @@ function init(mod) {
|
|||
});
|
||||
}
|
||||
mod.add(rewriteHtmlVersion);
|
||||
mod.add((req, res, next) => {
|
||||
const path = req.gs.path.substring(1);
|
||||
if (wrap[path]) {
|
||||
const data = getCachedFile(path, file => {
|
||||
console.log({ hot_wrap: file });
|
||||
return fs.readFileSync(file);
|
||||
});
|
||||
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
|
||||
return res.end(data);
|
||||
}
|
||||
next();
|
||||
});
|
||||
mod.static("/src/", "src");
|
||||
// example of how to use the new middleware
|
||||
// mod.add(appendContent('/kiri/index.html', '<script>console.log("hello")</script>'));
|
||||
mod.static("/lib/", "src");
|
||||
mod.static("/obj/", "web/obj");
|
||||
mod.static("/font/", "web/font");
|
||||
mod.static("/fon2/", "web/fon2");
|
||||
mod.static("/mesh/", "web/mesh");
|
||||
mod.static("/moto/", "web/moto");
|
||||
mod.static("/meta/", "web/meta");
|
||||
mod.static("/kiri/", "web/kiri");
|
||||
|
||||
function load_modules(root, force) {
|
||||
|
|
@ -365,8 +161,12 @@ function init(mod) {
|
|||
}
|
||||
}
|
||||
|
||||
// runs after module loads / injects
|
||||
prepareScripts();
|
||||
// minify appends
|
||||
if (!debug) {
|
||||
for (let key of Object.keys(append)) {
|
||||
append[key] = minify(append[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// either add module assets to path or require(init.js)
|
||||
|
|
@ -384,12 +184,11 @@ function loadModule(mod, dir) {
|
|||
|
||||
// load module and call returned function with helper object
|
||||
function initModule(mod, file, dir) {
|
||||
logger.log({module: file});
|
||||
logger.log({ module: file, dir });
|
||||
require_fresh(file)({
|
||||
// express functions added here show up at "/api/" url root
|
||||
api: api,
|
||||
adm: {
|
||||
reload: prepareScripts,
|
||||
setver: (ver) => { oversion = ver },
|
||||
crossOrigin: (bool) => { crossOrigin = bool }
|
||||
},
|
||||
|
|
@ -398,7 +197,6 @@ function initModule(mod, file, dir) {
|
|||
args: {},
|
||||
meta: mod.meta,
|
||||
debug: debug,
|
||||
script: script,
|
||||
moddir: dir,
|
||||
rootdir: mod.dir,
|
||||
version: oversion || version
|
||||
|
|
@ -425,40 +223,43 @@ function initModule(mod, file, dir) {
|
|||
logger: log.new
|
||||
},
|
||||
inject: (code, file, opt = {}) => {
|
||||
let codelist = script[code];
|
||||
if (!codelist) {
|
||||
return logger.log(`inject missing target "${code}"`);
|
||||
}
|
||||
const path = `${dir}/${file}`;
|
||||
codelist.push(path);
|
||||
if (opt.cachever) {
|
||||
cachever[path] = opt.cachever;
|
||||
const path = mod.dir + '/' + dir + '/' + file;
|
||||
try {
|
||||
const body = fs.readFileSync(path);
|
||||
// console.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 });
|
||||
}
|
||||
},
|
||||
path: {
|
||||
any: arg => { mod.add(arg) },
|
||||
pre: arg => { mod.add(prepath(arg)) },
|
||||
map: arg => { mod.add(fixedmap(arg)) },
|
||||
code() {
|
||||
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 });
|
||||
}
|
||||
},
|
||||
full: arg => { mod.add(fullpath(arg)) },
|
||||
map: arg => { mod.add(fixedmap(arg)) },
|
||||
pre: arg => { mod.add(prepath(arg)) },
|
||||
redir: redir,
|
||||
remap: remap,
|
||||
setup: fn => { setupFn = fn },
|
||||
static: (root, pre) => {
|
||||
mod.static(pre || "/", root);
|
||||
},
|
||||
code: (endpoint, path) => {
|
||||
let fpath = PATH.join(mod.dir, path);
|
||||
if (debug) {
|
||||
code[endpoint] = fs.readFileSync(fpath);
|
||||
} else {
|
||||
code[endpoint] = minify(fpath);
|
||||
}
|
||||
code_src[endpoint] = {
|
||||
endpoint,
|
||||
path: path,
|
||||
mod: lastmod(fpath)
|
||||
};
|
||||
},
|
||||
redir: redir,
|
||||
remap: remap,
|
||||
setup: fn => { setupFn = fn }
|
||||
},
|
||||
handler: {
|
||||
addCORS: addCorsHeaders,
|
||||
|
|
@ -479,51 +280,6 @@ function initModule(mod, file, dir) {
|
|||
});
|
||||
}
|
||||
|
||||
const script = {
|
||||
kiri : [
|
||||
"@devices",
|
||||
"kiri/ui",
|
||||
"&main/kiri",
|
||||
"&kiri/lang-en"
|
||||
],
|
||||
kiri_work : [
|
||||
"kiri-run/worker",
|
||||
"&main/kiri",
|
||||
],
|
||||
kiri_pool : [
|
||||
"&kiri-run/minion",
|
||||
"&main/kiri",
|
||||
],
|
||||
engine : [
|
||||
"@kiri_work",
|
||||
"&kiri-run/engine",
|
||||
"&main/kiri",
|
||||
],
|
||||
frame : [
|
||||
"kiri-run/frame"
|
||||
],
|
||||
meta : [
|
||||
"main/meta",
|
||||
],
|
||||
mesh : [
|
||||
"&main/mesh"
|
||||
],
|
||||
mesh_work : [
|
||||
"&mesh/work"
|
||||
],
|
||||
mesh_pool : [
|
||||
"&mesh/pool"
|
||||
],
|
||||
cache : [
|
||||
"moto/license",
|
||||
"main/service",
|
||||
],
|
||||
service : [
|
||||
"moto/license",
|
||||
"moto/service"
|
||||
]
|
||||
};
|
||||
|
||||
// prevent caching of specified modules
|
||||
const cachever = {};
|
||||
|
||||
|
|
@ -559,14 +315,33 @@ function handleSetup(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
const productionMap = {
|
||||
'/lib/mesh/work.js' : '/lib/pack/mesh-work.js',
|
||||
'/lib/main/mesh.js' : '/lib/pack/mesh-main.js',
|
||||
'/lib/main/kiri.js' : '/lib/pack/kiri-main.js',
|
||||
'/lib/kiri/run/engine.js' : '/lib/pack/kiri-eng.js',
|
||||
'/lib/kiri/run/minion.js' : '/lib/pack/kiri-pool.js',
|
||||
'/lib/kiri/run/worker.js' : '/lib/pack/kiri-work.js',
|
||||
// '/lib/kiri/run/frame.js' : '/lib/pack/kiri-frame.js',
|
||||
};
|
||||
|
||||
function handleVersion(req, res, next) {
|
||||
let vstr = oversion || dversion || version;
|
||||
if (["/kiri/","/mesh/","/meta/"].indexOf(req.app.path) >= 0 && req.url.indexOf(vstr) < 0) {
|
||||
if (["/kiri/","/mesh/"].indexOf(req.app.path) >= 0 && req.url.indexOf(vstr) < 0) {
|
||||
if (req.url.indexOf("?") > 0) {
|
||||
return http.redirect(res, `${req.url},ver:${vstr}`);
|
||||
} else {
|
||||
return http.redirect(res, `${req.url}?ver:${vstr}`);
|
||||
}
|
||||
} else if (!debug) {
|
||||
// in production serve packed bundles
|
||||
let { path } = req.app;
|
||||
let mapped = productionMap[path];
|
||||
if (mapped) {
|
||||
req.url = mapped;
|
||||
addCorsHeaders(req, res);
|
||||
}
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
|
|
@ -587,7 +362,20 @@ function handleOptions(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleWasm(req, res, next) {
|
||||
function serveCode(req, res, next) {
|
||||
let { path } = req.app;
|
||||
if (path.startsWith("/code/")) {
|
||||
path = path.split('/')[2].split('.')[0];
|
||||
if (code[path]) {
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('Cache-Control', 'public, max-age=600');
|
||||
return res.end(code[path]);
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
function serveWasm(req, res, next) {
|
||||
let file = req.app.path.split('/').pop();
|
||||
let ext = (file || '').split('.')[1];
|
||||
let path = PATH.join(dir,"src","wasm",file);
|
||||
|
|
@ -610,61 +398,9 @@ function handleWasm(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleCode(req, res, next) {
|
||||
let key = req.app.path.split('/')[2].split('.')[0],
|
||||
ck = code_src[key],
|
||||
js = code[key];
|
||||
|
||||
if (!js) {
|
||||
return http.reply404(req, res);
|
||||
}
|
||||
if (ck) {
|
||||
let mpath = `${dir}/${ck.path}`;
|
||||
let mod = lastmod(mpath);
|
||||
if (mod > ck.mod) {
|
||||
if (debug) {
|
||||
js = code[ck.endpoint] = fs.readFileSync(mpath);
|
||||
} else {
|
||||
js = code[ck.endpoint] = minify(mpath);
|
||||
}
|
||||
ck.mod = mod;
|
||||
}
|
||||
}
|
||||
|
||||
addCorsHeaders(req, res);
|
||||
serveCode(req, res, {
|
||||
code: js,
|
||||
mtime: startTime
|
||||
});
|
||||
}
|
||||
|
||||
function serveCode(req, res, code) {
|
||||
if (code.deny) {
|
||||
return http.reply404(req, res);
|
||||
}
|
||||
|
||||
let imd = ifModifiedDate(req);
|
||||
if (imd && code.mtime <= imd && !code.nocache) {
|
||||
res.writeHead(304, "Not Modified");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
let cacheControl = code.nocache ?
|
||||
'private, max-age=0' :
|
||||
'public, max-age=600';
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/javascript',
|
||||
'Cache-Control': cacheControl,
|
||||
'Last-Modified': new Date(code.mtime).toGMTString(),
|
||||
});
|
||||
res.end(code.code);
|
||||
}
|
||||
|
||||
// pack/concat device script strings to inject into /code/ scripts
|
||||
function generateDevices() {
|
||||
let root = PATH.join(dir,"src","kiri-dev");
|
||||
let root = PATH.join(dir,"src","kiri","dev");
|
||||
let devs = {};
|
||||
fs.readdirSync(root).forEach(type => {
|
||||
let map = devs[type] = devs[type] || {};
|
||||
|
|
@ -675,132 +411,12 @@ function generateDevices() {
|
|||
map[deviceName] = JSON.parse(fs.readFileSync(PATH.join(root,type,device)));
|
||||
});
|
||||
});
|
||||
synth.devices = `self.devices = ${JSON.stringify(devs)};`;
|
||||
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};`);
|
||||
}
|
||||
|
||||
// pack/concat code modules served under "/code/"
|
||||
function prepareScripts() {
|
||||
generateDevices();
|
||||
for (let key of Object.keys(script)) {
|
||||
code[key] = concatCode(key);
|
||||
}
|
||||
}
|
||||
|
||||
function concatCode(key) {
|
||||
let array = script[key];
|
||||
let code = [];
|
||||
let direct = array.filter(f => f.charAt(0) !== '@');
|
||||
let inject = array.filter(f => f.charAt(0) === '@').map(f => f.substring(1));
|
||||
|
||||
synth.inject = "/* injection point */";
|
||||
|
||||
// in debug mode, the script should load dependent
|
||||
// scripts instead of serving a complete bundle
|
||||
if (debug) {
|
||||
inject.forEach(key => {
|
||||
code.push(synth[key]);
|
||||
});
|
||||
code.push(...[
|
||||
oversion ? `self.debug_version='${oversion}';self.enable_service=${serviceWorker};` : '',
|
||||
'self.debug=true;',
|
||||
'(function() { let load = [ '
|
||||
]);
|
||||
direct.forEach(file => {
|
||||
const vers = cachever[file] || oversion || dversion || version;
|
||||
code.push(`"/${file.replace(/\\/g,'/')}?${vers}",`);
|
||||
});
|
||||
code.push([
|
||||
']; function load_next() {',
|
||||
'let file = load.shift();',
|
||||
'if (!file) return;',
|
||||
// 'console.log("loading", file);',
|
||||
'if (!self.document) { importScripts(file); return load_next() }',
|
||||
'let s = document.createElement("script");',
|
||||
's.type = "text/javascript";',
|
||||
's.src = file;',
|
||||
's.onload = load_next;',
|
||||
'document.head.appendChild(s);',
|
||||
'} load_next(); })();'
|
||||
].join('\n'));
|
||||
code = code.join('\n');
|
||||
} else {
|
||||
inject.forEach(key => {
|
||||
code.push(synth[key]);
|
||||
});
|
||||
direct.forEach(file => {
|
||||
let cached = getCachedFile(file, path => {
|
||||
return minify(PATH.join(dir,file));
|
||||
});
|
||||
if (oversion) {
|
||||
cached = `self.debug_version='${oversion}';self.enable_service=${serviceWorker};` + cached;
|
||||
}
|
||||
code.push(cached);
|
||||
});
|
||||
code = code.join('');
|
||||
synth[key] = `self.${key} = "${Buffer.from(code).toString('base64')}";\n`;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
function getCachedFile(file, fn) {
|
||||
let filePath = PATH.join(dir,file);
|
||||
let cachePath = cacheDir + PATH.sep + file
|
||||
.replace(/\//g,'_')
|
||||
.replace(/\\/g,'_')
|
||||
.replace(/:/g,'_'),
|
||||
cached = fileCache[filePath],
|
||||
now = time();
|
||||
|
||||
if (cached) {
|
||||
if (now - cached.lastcheck > 60000) {
|
||||
let smod = lastmod(filePath),
|
||||
cmod = cached.mtime;
|
||||
|
||||
if (!smod) {
|
||||
throw "missing source file";
|
||||
}
|
||||
if (smod > cmod) {
|
||||
cached = null;
|
||||
} else {
|
||||
cached.lastcheck = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
let smod = lastmod(filePath),
|
||||
cmod = lastmod(cachePath),
|
||||
cacheData;
|
||||
|
||||
if (cmod >= smod || (forceUseCache && cmod)) {
|
||||
cacheData = fs.readFileSync(cachePath);
|
||||
} else {
|
||||
logger.log({update_cache:filePath});
|
||||
cacheData = fn(filePath);
|
||||
// console.log(`NEW_CACHE_FILE: ${cachePath}`);
|
||||
fs.writeFileSync(cachePath, cacheData);
|
||||
}
|
||||
|
||||
if (wrap[file]) {
|
||||
// console.log('WRAP', filePath, cacheData.length);
|
||||
cacheData = wrap_module(cacheData, wrap[file]);
|
||||
}
|
||||
|
||||
cached = {
|
||||
data: cacheData,
|
||||
mtime: cmod || now,
|
||||
lastcheck: now
|
||||
};
|
||||
|
||||
fileCache[filePath] = cached;
|
||||
}
|
||||
|
||||
// console.log('[*]', filePath, cached.data.length);
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
function minify(path) {
|
||||
let code = fs.readFileSync(path);
|
||||
function minify(code) {
|
||||
let mini = uglify.minify(code.toString(), {
|
||||
compress: {
|
||||
merge_vars: false,
|
||||
|
|
@ -837,10 +453,10 @@ function addCorsHeaders(req, res) {
|
|||
if (req.headers['access-control-request-private-network'] === 'true') {
|
||||
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
||||
}
|
||||
if (!crossOrigin) {
|
||||
// if (!crossOrigin) {
|
||||
res.setHeader("Cross-Origin-Opener-Policy", 'same-origin');
|
||||
res.setHeader("Cross-Origin-Embedder-Policy", 'require-corp');
|
||||
}
|
||||
// }
|
||||
res.setHeader("Allow", "GET,POST,OPTIONS");
|
||||
}
|
||||
|
||||
|
|
@ -893,7 +509,9 @@ function fixedmap(prefix, map) {
|
|||
|
||||
// HTTP 307 redirect
|
||||
function redir(path, type) {
|
||||
return (req, res, next) => http.redirect(res, path, type);
|
||||
return (req, res, next) => {
|
||||
http.redirect(res, path, type);
|
||||
}
|
||||
}
|
||||
|
||||
// mangle request path
|
||||
|
|
@ -916,26 +534,43 @@ function cookieValue(cookie,key) {
|
|||
}
|
||||
|
||||
function rewriteHtmlVersion(req, res, next) {
|
||||
if (["/kiri/","/mesh/","/meta/","/kiri/engine.html","/kiri/frame.html"].indexOf(req.app.path) >= 0) {
|
||||
if ([
|
||||
"/kiri/",
|
||||
"/mesh/",
|
||||
"/lib/mesh/work.js",
|
||||
"/lib/kiri/run/worker.js",
|
||||
"/lib/kiri/run/minion.js",
|
||||
"/lib/kiri/run/engine.js",
|
||||
"/lib/kiri/run/frame.js"
|
||||
].indexOf(req.app.path) >= 0) {
|
||||
addCorsHeaders(req, res);
|
||||
let real_write = res.write;
|
||||
let real_end = res.end;
|
||||
let mlen = '{{version}}'.length;
|
||||
let vstr = oversion || dversion || version;
|
||||
if (vstr.length < mlen) {
|
||||
vstr = vstr.padStart(mlen,0);
|
||||
} else if (vstr.length > mlen) {
|
||||
vstr = vstr.substring(0,mlen);
|
||||
}
|
||||
res.write = function() {
|
||||
arguments[0] = arguments[0].toString().replace(/{{version}}/g,vstr);
|
||||
real_write.apply(res, arguments);
|
||||
}
|
||||
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]];
|
||||
|
||||
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() {
|
||||
if (arguments[0]) {
|
||||
arguments[0] = arguments[0].toString().replace(/{{version}}/g,vstr);
|
||||
|
||||
res.end = function (chunk, encoding) {
|
||||
if (chunk) {
|
||||
body += chunk.toString(encoding);
|
||||
}
|
||||
real_end.apply(res, arguments);
|
||||
body += data;
|
||||
res.setHeader('Content-Length', Buffer.byteLength(body));
|
||||
real_write.call(res, body);
|
||||
real_end.call(res);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ fs.copySync("mods", modsTmp, { dereference: true, filter:(src,dst) => {
|
|||
} });
|
||||
|
||||
// create minified asset cache
|
||||
server({
|
||||
electron: true,
|
||||
dryrun: true,
|
||||
single: true
|
||||
});
|
||||
// server({
|
||||
// electron: true,
|
||||
// dryrun: true,
|
||||
// single: true
|
||||
// });
|
||||
|
|
|
|||
106
bin/esbuild.config.mjs
Normal file
106
bin/esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { build, transform } from 'esbuild';
|
||||
import fs from 'fs/promises';
|
||||
import glob from 'fast-glob';
|
||||
|
||||
// Get build mode from command line argument
|
||||
const mode = process.argv[2] || 'dev';
|
||||
const isProd = mode === 'prod';
|
||||
|
||||
console.log(`Building in ${mode} mode...`);
|
||||
|
||||
const MESH_OUTFILE = 'src/pack/mesh-main.js';
|
||||
const MESH_EXTRAS = [ ];
|
||||
|
||||
const KIRI_OUTFILE = 'src/pack/kiri-main.js';
|
||||
const KIRI_EXTRAS = [ ];
|
||||
|
||||
async function appendExtraModules(extras, outfile, minify = false) {
|
||||
const files = await glob(extras);
|
||||
if (files.length === 0) return;
|
||||
|
||||
const transformed = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const code = await fs.readFile(file, 'utf8');
|
||||
if (!minify) return code;
|
||||
|
||||
const result = await transform(code, {
|
||||
minify: true,
|
||||
loader: 'js',
|
||||
target: 'es2020',
|
||||
});
|
||||
return result.code;
|
||||
})
|
||||
);
|
||||
|
||||
await fs.appendFile(outfile, transformed.join(''));
|
||||
console.log(`Appended ${files.length} module(s) to ${outfile}`);
|
||||
}
|
||||
|
||||
const logOverride = {
|
||||
'duplicate-class-member': 'silent',
|
||||
'duplicate-object-key': 'silent',
|
||||
'direct-eval': 'silent'
|
||||
};
|
||||
|
||||
const rec = {
|
||||
bundle: true,
|
||||
define: { 'process.env.NODE_ENV': `"${mode}"` },
|
||||
external: ['module'],
|
||||
format: 'esm',
|
||||
logOverride,
|
||||
minify: isProd, // false for dev, true for prod
|
||||
platform: 'browser',
|
||||
sourcemap: !isProd, // true for dev, false for prod
|
||||
target: 'es2020',
|
||||
};
|
||||
|
||||
async function buildApp() {
|
||||
try {
|
||||
// Bundle mesh main app
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/main/mesh.js' ],
|
||||
outfile: MESH_OUTFILE,
|
||||
}));
|
||||
|
||||
appendExtraModules(MESH_EXTRAS, MESH_OUTFILE, isProd);
|
||||
|
||||
// Bundle mesh worker
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/mesh/work.js' ],
|
||||
outfile: 'src/pack/mesh-work.js',
|
||||
}));
|
||||
|
||||
// Bundle kiri main app
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/main/kiri.js' ],
|
||||
outfile: KIRI_OUTFILE,
|
||||
}));
|
||||
|
||||
appendExtraModules(KIRI_EXTRAS, KIRI_OUTFILE, isProd);
|
||||
|
||||
// Bundle kiri worker
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/kiri/run/worker.js' ],
|
||||
outfile: 'src/pack/kiri-work.js',
|
||||
}));
|
||||
|
||||
// Bundle kiri minion
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/kiri/run/minion.js' ],
|
||||
outfile: 'src/pack/kiri-pool.js',
|
||||
}));
|
||||
|
||||
// Bundle kiri engine
|
||||
await build(Object.assign({}, rec, {
|
||||
entryPoints: [ 'src/kiri/run/engine.js' ],
|
||||
outfile: 'src/pack/kiri-eng.js',
|
||||
}));
|
||||
|
||||
console.log(`Build completed successfully in ${mode} mode!`);
|
||||
} catch (error) {
|
||||
console.error('Build failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
buildApp();
|
||||
|
|
@ -5,14 +5,14 @@ const path = require('path');
|
|||
async function main() {
|
||||
console.log('npm pre running');
|
||||
|
||||
const links = fs.readFileSync("links.csv")
|
||||
const links = fs.readFileSync("conf/links.csv")
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.map(line => line.split(',').map(v => v.trim()));
|
||||
|
||||
if (os.platform() === 'win32')
|
||||
if (os.platform() === 'win32') {
|
||||
// convert links to the contents of the files/directories they reference
|
||||
for (let [link, target] of links) {
|
||||
const absoluteTarget = path.resolve(path.dirname(link), target);
|
||||
|
|
@ -24,6 +24,9 @@ async function main() {
|
|||
console.error(`Error creating symlink: ${link} -> ${absoluteTarget}`, err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('skipping symlink conversion');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => console.error('Error', err));
|
||||
|
|
|
|||
3
bin/mk-links.sh
Executable file
3
bin/mk-links.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
find src/ web/ -type l | while read link; do echo "$link,$(readlink "$link")"; done > conf/links.csv
|
||||
|
|
@ -8,9 +8,16 @@ import { LineGeometry } from '../node_modules/three/examples/jsm/lines/LineGeome
|
|||
import { LineSegments2 } from '../node_modules/three/examples/jsm/lines/LineSegments2.js';
|
||||
import { LineSegmentsGeometry } from '../node_modules/three/examples/jsm/lines/LineSegmentsGeometry.js';
|
||||
|
||||
import * as MeshBVHLib from '../node_modules/three-mesh-bvh/build/index.module.js';
|
||||
|
||||
export {
|
||||
THREE, SVGLoader, BufferGeometryUtils,
|
||||
THREE,
|
||||
SVGLoader,
|
||||
BufferGeometryUtils,
|
||||
LineMaterial,
|
||||
Line2, LineGeometry,
|
||||
LineSegments2, LineSegmentsGeometry
|
||||
};
|
||||
Line2,
|
||||
LineGeometry,
|
||||
LineSegments2,
|
||||
LineSegmentsGeometry,
|
||||
MeshBVHLib
|
||||
};
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: "./bin/webpack-three-bundle.js",
|
||||
entry: path.resolve(__dirname, './webpack-three-bundle.js'),
|
||||
output: {
|
||||
path: path.resolve('src/ext'),
|
||||
path: path.resolve(__dirname, '../src/ext'),
|
||||
filename: 'three.js',
|
||||
library: 'ThreeBundle',
|
||||
libraryTarget: 'umd',
|
||||
globalObject: 'this'
|
||||
library: {
|
||||
type: 'module'
|
||||
},
|
||||
module: true
|
||||
},
|
||||
experiments: {
|
||||
outputModule: true
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.mjs', '.js'],
|
||||
|
|
@ -25,21 +28,11 @@ module.exports = {
|
|||
],
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
terserOptions: {
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
minimize: false
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
maxAssetSize: 2 * 1024 * 1024,
|
||||
maxEntrypointSize: 2 * 1024 * 1024,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -5,7 +5,6 @@ src/ext/earcut.js,../../node_modules/earcut/src/earcut.js
|
|||
src/ext/tween.js,../../node_modules/@tweenjs/tween.js/src/Tween.js
|
||||
src/ext/jszip.js,../../node_modules/jszip/dist/jszip.js
|
||||
src/wasm/manifold.wasm,../../node_modules/manifold-3d/manifold.wasm
|
||||
src/kiri-dev/fdm/GridBot.Two.json,GridBot.One.json
|
||||
src/kiri/lang-en.js,../../web/kiri/lang/en.js
|
||||
web/fon2,../node_modules/bootstrap-icons/font/
|
||||
web/kiri/lang/pl.js,pl-pl.js
|
||||
|
|
|
@ -25,7 +25,7 @@ This is a community driven project, and we welcome any contributions you'd like
|
|||
- Open the developer console
|
||||
- Run the following code: `kiri.api.conf.get().device`
|
||||
- Right click on the object and select `Copy object`
|
||||
- Make a new file in the `src/kiri-dev/<mode>` directory, with the name of your machine, no spaces or special characters, and a `.json` extension.
|
||||
- Make a new file in the `src/kiri/dev/<mode>` directory, with the name of your machine, no spaces or special characters, and a `.json` extension.
|
||||
- Paste the copied object into the new file, and save it.
|
||||
- Publish your changes to a git repo
|
||||
- Submit a [pull request](https://github.com/GridSpace/grid-apps/compare)
|
||||
|
|
@ -2,9 +2,8 @@ self.kiri.load(api => {
|
|||
|
||||
console.log('BAMBU MODULE RUNNING');
|
||||
|
||||
const { kiri, moto } = self;
|
||||
const { ui } = kiri;
|
||||
const h = moto.webui;
|
||||
const { uc: ui } = api;
|
||||
const { $, h } = api.web;
|
||||
const defams = ";; DEFINE BAMBU-AMS ";
|
||||
const readonly = true;
|
||||
const stock_colors = Object.values({
|
||||
|
|
@ -565,7 +564,7 @@ self.kiri.load(api => {
|
|||
|
||||
function monitoring() {
|
||||
let mon = selected?.rec?.serial ?? '';
|
||||
ui.setVisible($('bbl_connect'), select.value !== '' && monitors.indexOf(mon) < 0);
|
||||
ui.setVisible($('bbl_connect'), select?.value && monitors.indexOf(mon) < 0);
|
||||
return mon ? true : false;
|
||||
}
|
||||
|
||||
|
|
@ -661,7 +660,7 @@ self.kiri.load(api => {
|
|||
}
|
||||
});
|
||||
|
||||
api.event.on("init-done", function() {
|
||||
api.event.on("load-done", function() {
|
||||
if (init) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
if (self.kiri)
|
||||
self.kiri.load(api => {
|
||||
console.log('ELECTRON MODULE RUNNING');
|
||||
api.electron = {};
|
||||
api.event.on('init-done', () => {
|
||||
$('app-name-text').innerText = "More Info";
|
||||
$('top-sep').style.display = 'flex';
|
||||
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');
|
||||
});
|
||||
api.stats.set('kiri', self.kiri.version + 'e');
|
||||
});
|
||||
if (self.mesh && self.mesh.api) {
|
||||
self.mesh.api.electron = {};
|
||||
}
|
||||
|
||||
if (self.mesh && self.mesh) {
|
||||
self.mesh.electron = {};
|
||||
}
|
||||
|
|
|
|||
59
package.json
59
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "grid-apps",
|
||||
"version": "4.2.0",
|
||||
"version": "4.3.0",
|
||||
"description": "grid.space 3d slicing & modeling tools",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"license": "MIT",
|
||||
|
|
@ -32,11 +32,12 @@
|
|||
"slicer"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"@gridspace/app-server": "^0.0.17",
|
||||
"@gridspace/basic-ftp": "^5.0.5",
|
||||
"@gridspace/net-level-client": "^0.2.3",
|
||||
"@tracespace/parser": "^5.0.0-next.0",
|
||||
"@tweenjs/tween.js": "^16.6.0",
|
||||
|
|
@ -45,15 +46,16 @@
|
|||
"buffer-crc32": "^0.2.13",
|
||||
"compression": "^1.7.4",
|
||||
"connect": "^3.7.0",
|
||||
"earcut": "^2.2.3",
|
||||
"earcut": "^3.0.1",
|
||||
"express-useragent": "^1.0.13",
|
||||
"fast-glob": "^3.3.3",
|
||||
"jszip": "^3.7.1",
|
||||
"manifold-3d": "^2.5.1",
|
||||
"manifold-3d": "^3.1.1",
|
||||
"moment": "^2.29.4",
|
||||
"prettier": "^3.5.3",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"serve-static": "^1.14.1",
|
||||
"three": "^0.174.0",
|
||||
"three": "^0.178.0",
|
||||
"three-mesh-bvh": "^0.7.6",
|
||||
"uglify-js": "3.14.5",
|
||||
"validator": ">=13.7.0",
|
||||
|
|
@ -63,25 +65,21 @@
|
|||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@electron/notarize": "^3.0.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"docusaurus-lunr-search": "^3.6.0",
|
||||
"dotenv": "latest",
|
||||
"electron": "^35.0.1",
|
||||
"electron-builder": "^24.9.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"fs-extra": "^11.2.0",
|
||||
"imports-loader": "^5.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"webpack": "^5.92.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "npm i && cd mods && npm i",
|
||||
"dev": "gs-app-server --debug",
|
||||
"prod": "gs-app-server",
|
||||
"prod-dryrun": "gs-app-server --dryrun",
|
||||
"start": "npm run prebuild && electron .",
|
||||
"start-dev": "npm run prebuild && electron . --devel",
|
||||
"start-dbg": "npm run prebuild && electron . --debugg",
|
||||
"start-ddb": "npm run prebuild && electron . --devel --debugg",
|
||||
"setup": "npm i && npm run clean && npm run dev -- --dryrun && npm run webpack3",
|
||||
"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",
|
||||
|
|
@ -91,16 +89,25 @@
|
|||
"build-win-arm": "npm run build -- --win --arm64",
|
||||
"build-mac": "npm run build -- --mac --arm64",
|
||||
"build-mac-intel": "npm run build -- --mac --x64",
|
||||
"mklinks": "find src web -type l | xargs -I{} sh -c 'echo \"{},$(readlink {})\"' > links.csv",
|
||||
"clean": "rm -rf build data dist src/pack tmp src.old web.old",
|
||||
"docs-dev": "docusaurus start --config conf/docusaurus.config.js",
|
||||
"docs-build": "docusaurus build --config conf/docusaurus.config.js",
|
||||
"docs-serve": "docusaurus serve --config conf/docusaurus.config.js",
|
||||
"docs-check": "prettier --config ./conf/prettier.config.js ./docs --check",
|
||||
"dev": "npm run webpack3 && gs-app-server --debug --single",
|
||||
"prod": "npm run webpack prod && gs-app-server",
|
||||
"prod-dryrun": "gs-app-server --dryrun",
|
||||
"start": "npm run prebuild prod && electron .",
|
||||
"start-dev": "npm run prebuild prod && electron . --devel",
|
||||
"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",
|
||||
"clear-cache": "rm -rf data/cache/* dist/ tmp/*",
|
||||
"prebuild": "node bin/electron-pre.js",
|
||||
"prebuild": "npm run setup && npm run webpack prod && node bin/electron-pre.js",
|
||||
"postbuild": "node bin/electron-post.js",
|
||||
"preinstall": "node bin/install-pre.js && npx webpack --config bin/webpack-three.js",
|
||||
"docs-dev": "docusaurus start",
|
||||
"docs-build": "docusaurus build",
|
||||
"docs-serve": "docusaurus serve",
|
||||
"docs-check": "prettier ./docs --check"
|
||||
"preinstall": "node bin/install-pre.js",
|
||||
"webpack": "npm run webpack3 && node bin/esbuild.config.mjs",
|
||||
"webpack3": "npx webpack --config bin/webpack-three-esm.js"
|
||||
},
|
||||
"main": "app-el.js",
|
||||
"build": {
|
||||
|
|
@ -172,8 +179,12 @@
|
|||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"differentialPackage": false,
|
||||
"allowElevation": true
|
||||
},
|
||||
"dmg": {
|
||||
"writeUpdateInfo": false
|
||||
},
|
||||
"mac": {
|
||||
"icon": "bin/GS.icns",
|
||||
"category": "public.app-category.utilities",
|
||||
|
|
@ -182,8 +193,7 @@
|
|||
"entitlements": "bin/electron-entitlements.plist",
|
||||
"entitlementsInherit": "bin/electron-entitlements.plist",
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
"dmg"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
|
|
@ -193,5 +203,6 @@
|
|||
]
|
||||
},
|
||||
"afterSign": "bin/electron-notarize.js"
|
||||
}
|
||||
},
|
||||
"sideEffects": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// extend Array, String, Number, Math
|
||||
gapp.register("add.array", [], (root, exports) => {
|
||||
|
||||
let AP = {};
|
||||
const AP = {};
|
||||
|
||||
if (!AP.flat) {
|
||||
AP.flat = function() {
|
||||
try {
|
||||
return [].concat.apply([], this);
|
||||
} catch (e) {
|
||||
// console.log({flat_error: e});
|
||||
// console.log({ flat_error: e, array: this });
|
||||
let out = [];
|
||||
for (let e of this.slice()) {
|
||||
out.push(...e);
|
||||
|
|
@ -216,12 +212,11 @@ Array.handle = function(fn) {
|
|||
}
|
||||
};
|
||||
|
||||
for (let i in AP) {
|
||||
Object.defineProperty(Array.prototype, i, {
|
||||
value: AP[i],
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
// Apply all methods to Array.prototype
|
||||
Object.assign(Array.prototype, AP);
|
||||
|
||||
// Export for ES modules
|
||||
export { AP as arrayPrototype };
|
||||
|
||||
Float32Array.prototype.toShared = function() {
|
||||
const newvert = new Float32Array(new SharedArrayBuffer(this.buffer.byteLength));
|
||||
|
|
@ -265,5 +260,3 @@ Number.prototype.round = function(digits) {
|
|||
const pow = Math.pow(10, digits || 3);
|
||||
return Math.round(this.valueOf() * pow) / pow;
|
||||
};
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: add.array
|
||||
gapp.register("add.class", [], (root, exports) => {
|
||||
import { arrayPrototype } from './array.js';
|
||||
|
||||
self.ArrayWriter = class ArrayWriter {
|
||||
// Make classes available globally
|
||||
class ArrayWriter {
|
||||
constructor() {
|
||||
this.pos = 0;
|
||||
this.array = [];
|
||||
|
|
@ -48,7 +47,7 @@ self.ArrayWriter = class ArrayWriter {
|
|||
}
|
||||
};
|
||||
|
||||
self.DataWriter = class DataWriter {
|
||||
class DataWriter {
|
||||
constructor(view, pos) {
|
||||
this.view = view;
|
||||
this.pos = pos || 0;
|
||||
|
|
@ -106,7 +105,7 @@ self.DataWriter = class DataWriter {
|
|||
}
|
||||
}
|
||||
|
||||
self.DataReader = class DataReader {
|
||||
class DataReader {
|
||||
constructor(view, pos) {
|
||||
this.view = view;
|
||||
this.pos = pos || 0;
|
||||
|
|
@ -171,4 +170,9 @@ self.DataReader = class DataReader {
|
|||
}
|
||||
}
|
||||
|
||||
});
|
||||
self.ArrayWriter = ArrayWriter;
|
||||
self.DataWriter = DataWriter;
|
||||
self.DataReader = DataReader;
|
||||
|
||||
// Export for ES modules
|
||||
export { ArrayWriter, DataWriter, DataReader };
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: ext.three
|
||||
gapp.register("add.three", [], (root, exports) => {
|
||||
|
||||
const {
|
||||
import {
|
||||
THREE, SVGLoader, BufferGeometryUtils,
|
||||
LineMaterial, Line2, LineGeometry, LineSegments2, LineSegmentsGeometry
|
||||
} = ThreeBundle;
|
||||
|
||||
root.THREE = THREE;
|
||||
LineMaterial, Line2, LineGeometry, LineSegments2,
|
||||
LineSegmentsGeometry, MeshBVHLib } from '../ext/three.js';
|
||||
|
||||
// Make THREE available globally
|
||||
self.THREE = THREE;
|
||||
THREE.BufferGeometryUtils = BufferGeometryUtils;
|
||||
THREE.SVGLoader = SVGLoader.SVGLoader;
|
||||
THREE.LineMaterial = LineMaterial;
|
||||
|
|
@ -147,4 +144,5 @@ THREE.Object3D.prototype.removeAll = function() {
|
|||
this.children = [];
|
||||
};
|
||||
|
||||
});
|
||||
// Export for ES modules
|
||||
export { THREE };
|
||||
|
|
|
|||
|
|
@ -23,37 +23,37 @@
|
|||
"geo/wasm",
|
||||
"data/local",
|
||||
"load/stl",
|
||||
"kiri/conf",
|
||||
"kiri/utils",
|
||||
"kiri/slice",
|
||||
"kiri/consts",
|
||||
"kiri/client",
|
||||
"kiri/widget",
|
||||
"kiri/layers",
|
||||
"kiri-mode/cam/driver",
|
||||
"kiri-mode/cam/export",
|
||||
"kiri-mode/cam/ops",
|
||||
"kiri-mode/cam/prepare",
|
||||
"kiri-mode/cam/slice",
|
||||
"kiri-mode/cam/slicer",
|
||||
"kiri-mode/cam/tool",
|
||||
"kiri-mode/cam/topo",
|
||||
"kiri-mode/fdm/driver",
|
||||
"kiri-mode/fdm/export",
|
||||
"kiri-mode/fdm/fill",
|
||||
"kiri-mode/fdm/post",
|
||||
"kiri-mode/fdm/prepare",
|
||||
"kiri-mode/fdm/slice",
|
||||
"kiri-mode/laser/driver",
|
||||
"kiri-mode/sla/driver",
|
||||
"kiri-mode/sla/x_cxdlp",
|
||||
"kiri-mode/sla/x_photon",
|
||||
"kiri-mode/sla/export",
|
||||
"kiri-mode/sla/slice",
|
||||
"kiri/codec",
|
||||
"kiri/print",
|
||||
"kiri/pack",
|
||||
"kiri-run/worker",
|
||||
"kiri-run/engine",
|
||||
"kiri/core/conf",
|
||||
"kiri/core/utils",
|
||||
"kiri/core/slice",
|
||||
"kiri/core/consts",
|
||||
"kiri/core/client",
|
||||
"kiri/core/widget",
|
||||
"kiri/core/layers",
|
||||
"kiri/mode/cam/driver",
|
||||
"kiri/mode/cam/export",
|
||||
"kiri/mode/cam/ops",
|
||||
"kiri/mode/cam/prepare",
|
||||
"kiri/mode/cam/slice",
|
||||
"kiri/mode/cam/slicer",
|
||||
"kiri/mode/cam/tool",
|
||||
"kiri/mode/cam/topo",
|
||||
"kiri/mode/fdm/driver",
|
||||
"kiri/mode/fdm/export",
|
||||
"kiri/mode/fdm/fill",
|
||||
"kiri/mode/fdm/post",
|
||||
"kiri/mode/fdm/prepare",
|
||||
"kiri/mode/fdm/slice",
|
||||
"kiri/mode/laser/driver",
|
||||
"kiri/mode/sla/driver",
|
||||
"kiri/mode/sla/x_cxdlp",
|
||||
"kiri/mode/sla/x_photon",
|
||||
"kiri/mode/sla/export",
|
||||
"kiri/mode/sla/slice",
|
||||
"kiri/core/codec",
|
||||
"kiri/core/print",
|
||||
"kiri/core/pack",
|
||||
"kiri/run/worker",
|
||||
"kiri/run/engine",
|
||||
"main/kiri"
|
||||
]
|
||||
]
|
||||
|
|
@ -1,19 +1,14 @@
|
|||
/** Copyright Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("data.index", [], (root, exports) => {
|
||||
|
||||
const { data } = root;
|
||||
|
||||
data.Index = IDBStore;
|
||||
|
||||
data.open = function() {
|
||||
return new IDBStore(...arguments);
|
||||
};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
||||
|
||||
export const data = {
|
||||
Index: IDBStore,
|
||||
open() {
|
||||
return new IDBStore(...arguments);
|
||||
}
|
||||
};
|
||||
|
||||
let IDB, IRR, local = null;
|
||||
|
||||
try {
|
||||
|
|
@ -319,4 +314,5 @@ SP.clear = function(key, store = this.current) {
|
|||
.objectStore(store).clear();
|
||||
};
|
||||
|
||||
});
|
||||
export const open = data.open;
|
||||
export const Index = IDBStore;
|
||||
|
|
|
|||
|
|
@ -1,47 +1,44 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
class Local {
|
||||
__data__ = {};
|
||||
__mem__ = true;
|
||||
|
||||
gapp.register("data.local", [], (root, exports) => {
|
||||
getItem(key) {
|
||||
return this[key];
|
||||
}
|
||||
|
||||
const { data } = root;
|
||||
setItem(key, val) {
|
||||
this.__data__[key] = val;
|
||||
this[key] = val;
|
||||
}
|
||||
|
||||
function Local() {
|
||||
this.__data__ = {};
|
||||
this.__mem__ = true;
|
||||
removeItem(key) {
|
||||
delete this.__data__[key];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.__data__ = {};
|
||||
}
|
||||
}
|
||||
|
||||
var LS = Local.prototype;
|
||||
|
||||
LS.getItem = function(key) {
|
||||
return this[key];
|
||||
};
|
||||
|
||||
LS.setItem = function(key, val) {
|
||||
this.__data__[key] = val;
|
||||
this[key] = val;
|
||||
};
|
||||
|
||||
LS.removeItem = function(key) {
|
||||
delete this.__data__[key];
|
||||
};
|
||||
|
||||
LS.clear = function() {
|
||||
this.__data__ = {};
|
||||
};
|
||||
let setLocal;
|
||||
|
||||
try {
|
||||
// deprecate 'Local' at some point
|
||||
let local = data.local =self.localStorage;
|
||||
if (typeof window === 'undefined') {
|
||||
setLocal = new Local();
|
||||
} else {
|
||||
// deprecate 'Local' at some point
|
||||
setLocal = self.localStorage;
|
||||
}
|
||||
let testkey = '__test';
|
||||
local.setItem(testkey, 1);
|
||||
local.getItem(testkey);
|
||||
local.removeItem(testkey);
|
||||
setLocal.setItem(testkey, 1);
|
||||
setLocal.getItem(testkey);
|
||||
setLocal.removeItem(testkey);
|
||||
} catch (e) {
|
||||
data.local = new Local();
|
||||
setLocal = new Local();
|
||||
let msg = "localStorage disabled: application may not function properly";
|
||||
console.log(msg);
|
||||
// alert(msg);
|
||||
}
|
||||
|
||||
});
|
||||
export const local = setLocal;
|
||||
|
|
|
|||
6986
src/ext/clip.js
6986
src/ext/clip.js
File diff suppressed because it is too large
Load diff
7
src/ext/clip2.esm.js
Normal file
7
src/ext/clip2.esm.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
// Import the original Clipper library
|
||||
import './clip2.js';
|
||||
|
||||
// Re-export the ClipperLib object
|
||||
export const ClipperLib = self.ClipperLib;
|
||||
270
src/ext/fsave.js
270
src/ext/fsave.js
|
|
@ -1,270 +0,0 @@
|
|||
/* FileSaver.js
|
||||
* A saveAs() FileSaver implementation.
|
||||
* 1.1.20151003
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* License: MIT
|
||||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self */
|
||||
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
|
||||
|
||||
var saveAs = saveAs || (function(view) {
|
||||
"use strict";
|
||||
// IE <10 is explicitly unsupported
|
||||
if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
|
||||
return;
|
||||
}
|
||||
var
|
||||
doc = view.document
|
||||
// only get URL when necessary in case Blob.js hasn't overridden it yet
|
||||
, get_URL = function() {
|
||||
return view.URL || view.webkitURL || view;
|
||||
}
|
||||
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||||
, can_use_save_link = "download" in save_link
|
||||
, click = function(node) {
|
||||
var event = new MouseEvent("click");
|
||||
node.dispatchEvent(event);
|
||||
}
|
||||
, is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
|
||||
, webkit_req_fs = view.webkitRequestFileSystem
|
||||
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
|
||||
, throw_outside = function(ex) {
|
||||
(view.setImmediate || view.setTimeout)(function() {
|
||||
throw ex;
|
||||
}, 0);
|
||||
}
|
||||
, force_saveable_type = "application/octet-stream"
|
||||
, fs_min_size = 0
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
|
||||
// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
|
||||
// for the reasoning behind the timeout and revocation flow
|
||||
, arbitrary_revoke_timeout = 500 // in ms
|
||||
, revoke = function(file) {
|
||||
var revoker = function() {
|
||||
if (typeof file === "string") { // file is an object URL
|
||||
get_URL().revokeObjectURL(file);
|
||||
} else { // file is a File
|
||||
file.remove();
|
||||
}
|
||||
};
|
||||
if (view.chrome) {
|
||||
revoker();
|
||||
} else {
|
||||
setTimeout(revoker, arbitrary_revoke_timeout);
|
||||
}
|
||||
}
|
||||
, dispatch = function(filesaver, event_types, event) {
|
||||
event_types = [].concat(event_types);
|
||||
var i = event_types.length;
|
||||
while (i--) {
|
||||
var listener = filesaver["on" + event_types[i]];
|
||||
if (typeof listener === "function") {
|
||||
try {
|
||||
listener.call(filesaver, event || filesaver);
|
||||
} catch (ex) {
|
||||
throw_outside(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
, auto_bom = function(blob) {
|
||||
// prepend BOM for UTF-8 XML and text/* types (including HTML)
|
||||
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||
return new Blob(["\ufeff", blob], {type: blob.type});
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
, FileSaver = function(blob, name, no_auto_bom) {
|
||||
if (!no_auto_bom) {
|
||||
blob = auto_bom(blob);
|
||||
}
|
||||
// First try a.download, then web filesystem, then object URLs
|
||||
var
|
||||
filesaver = this
|
||||
, type = blob.type
|
||||
, blob_changed = false
|
||||
, object_url
|
||||
, target_view
|
||||
, dispatch_all = function() {
|
||||
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
||||
}
|
||||
// on any filesys errors revert to saving with object URLs
|
||||
, fs_error = function() {
|
||||
if (target_view && is_safari && typeof FileReader !== "undefined") {
|
||||
// Safari doesn't allow downloading of blob urls
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
var base64Data = reader.result;
|
||||
target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/));
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
return;
|
||||
}
|
||||
// don't create more object URLs than needed
|
||||
if (blob_changed || !object_url) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
}
|
||||
if (target_view) {
|
||||
target_view.location.href = object_url;
|
||||
} else {
|
||||
var new_tab = view.open(object_url, "_blank");
|
||||
if (new_tab == undefined && is_safari) {
|
||||
//Apple do not allow window.open, see http://bit.ly/1kZffRI
|
||||
view.location.href = object_url
|
||||
}
|
||||
}
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
}
|
||||
, abortable = function(func) {
|
||||
return function() {
|
||||
if (filesaver.readyState !== filesaver.DONE) {
|
||||
return func.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
, create_if_not_found = {create: true, exclusive: false}
|
||||
, slice
|
||||
;
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
if (!name) {
|
||||
name = "download";
|
||||
}
|
||||
if (can_use_save_link) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
setTimeout(function() {
|
||||
save_link.href = object_url;
|
||||
save_link.download = name;
|
||||
click(save_link);
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Object and web filesystem URLs have a problem saving in Google Chrome when
|
||||
// viewed in a tab, so I force save with application/octet-stream
|
||||
// http://code.google.com/p/chromium/issues/detail?id=91158
|
||||
// Update: Google errantly closed 91158, I submitted it again:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=389642
|
||||
if (view.chrome && type && type !== force_saveable_type) {
|
||||
slice = blob.slice || blob.webkitSlice;
|
||||
blob = slice.call(blob, 0, blob.size, force_saveable_type);
|
||||
blob_changed = true;
|
||||
}
|
||||
// Since I can't be sure that the guessed media type will trigger a download
|
||||
// in WebKit, I append .download to the filename.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=65440
|
||||
if (webkit_req_fs && name !== "download") {
|
||||
name += ".download";
|
||||
}
|
||||
if (type === force_saveable_type || webkit_req_fs) {
|
||||
target_view = view;
|
||||
}
|
||||
if (!req_fs) {
|
||||
fs_error();
|
||||
return;
|
||||
}
|
||||
fs_min_size += blob.size;
|
||||
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
|
||||
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
|
||||
var save = function() {
|
||||
dir.getFile(name, create_if_not_found, abortable(function(file) {
|
||||
file.createWriter(abortable(function(writer) {
|
||||
writer.onwriteend = function(event) {
|
||||
target_view.location.href = file.toURL();
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch(filesaver, "writeend", event);
|
||||
revoke(file);
|
||||
};
|
||||
writer.onerror = function() {
|
||||
var error = writer.error;
|
||||
if (error.code !== error.ABORT_ERR) {
|
||||
fs_error();
|
||||
}
|
||||
};
|
||||
"writestart progress write abort".split(" ").forEach(function(event) {
|
||||
writer["on" + event] = filesaver["on" + event];
|
||||
});
|
||||
writer.write(blob);
|
||||
filesaver.abort = function() {
|
||||
writer.abort();
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
};
|
||||
filesaver.readyState = filesaver.WRITING;
|
||||
}), fs_error);
|
||||
}), fs_error);
|
||||
};
|
||||
dir.getFile(name, {create: false}, abortable(function(file) {
|
||||
// delete file if it already exists
|
||||
file.remove();
|
||||
save();
|
||||
}), abortable(function(ex) {
|
||||
if (ex.code === ex.NOT_FOUND_ERR) {
|
||||
save();
|
||||
} else {
|
||||
fs_error();
|
||||
}
|
||||
}));
|
||||
}), fs_error);
|
||||
}), fs_error);
|
||||
}
|
||||
, FS_proto = FileSaver.prototype
|
||||
, saveAs = function(blob, name, no_auto_bom) {
|
||||
return new FileSaver(blob, name, no_auto_bom);
|
||||
}
|
||||
;
|
||||
// IE 10+ (native saveAs)
|
||||
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
|
||||
return function(blob, name, no_auto_bom) {
|
||||
if (!no_auto_bom) {
|
||||
blob = auto_bom(blob);
|
||||
}
|
||||
return navigator.msSaveOrOpenBlob(blob, name || "download");
|
||||
};
|
||||
}
|
||||
|
||||
FS_proto.abort = function() {
|
||||
var filesaver = this;
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch(filesaver, "abort");
|
||||
};
|
||||
FS_proto.readyState = FS_proto.INIT = 0;
|
||||
FS_proto.WRITING = 1;
|
||||
FS_proto.DONE = 2;
|
||||
|
||||
FS_proto.error =
|
||||
FS_proto.onwritestart =
|
||||
FS_proto.onprogress =
|
||||
FS_proto.onwrite =
|
||||
FS_proto.onabort =
|
||||
FS_proto.onerror =
|
||||
FS_proto.onwriteend =
|
||||
null;
|
||||
|
||||
return saveAs;
|
||||
}(
|
||||
typeof self !== "undefined" && self
|
||||
|| typeof window !== "undefined" && window
|
||||
|| this.content
|
||||
));
|
||||
// `self` is undefined in Firefox for Android content script context
|
||||
// while `this` is nsIContentFrameMessageManager
|
||||
// with an attribute `content` that corresponds to the window
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports.saveAs = saveAs;
|
||||
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
|
||||
define([], function() {
|
||||
return saveAs;
|
||||
});
|
||||
}
|
||||
7
src/ext/jszip.esm.js
Normal file
7
src/ext/jszip.esm.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
//import original library
|
||||
import * as JSZip from "./jszip.js";
|
||||
|
||||
//export object attached to the window object
|
||||
export default self.JSZip;
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
// SOURCE: https://stackoverflow.com/questions/1655769/fastest-md5-implementation-in-javascript
|
||||
gapp.register("ext.md5", [], (root, exports) => {
|
||||
|
||||
exports({ hash });
|
||||
|
||||
function hash(e) {
|
||||
export function hash(e) {
|
||||
function h(a, b) {
|
||||
var c, d, e, f, g;
|
||||
e = a & 2147483648;
|
||||
|
|
@ -67,6 +64,4 @@ function hash(e) {
|
|||
d = 271733878;
|
||||
for (e = 0; e < f.length; e += 16) q = a, r = b, s = c, t = d, a = k(a, b, c, d, f[e + 0], 7, 3614090360), d = k(d, a, b, c, f[e + 1], 12, 3905402710), c = k(c, d, a, b, f[e + 2], 17, 606105819), b = k(b, c, d, a, f[e + 3], 22, 3250441966), a = k(a, b, c, d, f[e + 4], 7, 4118548399), d = k(d, a, b, c, f[e + 5], 12, 1200080426), c = k(c, d, a, b, f[e + 6], 17, 2821735955), b = k(b, c, d, a, f[e + 7], 22, 4249261313), a = k(a, b, c, d, f[e + 8], 7, 1770035416), d = k(d, a, b, c, f[e + 9], 12, 2336552879), c = k(c, d, a, b, f[e + 10], 17, 4294925233), b = k(b, c, d, a, f[e + 11], 22, 2304563134), a = k(a, b, c, d, f[e + 12], 7, 1804603682), d = k(d, a, b, c, f[e + 13], 12, 4254626195), c = k(c, d, a, b, f[e + 14], 17, 2792965006), b = k(b, c, d, a, f[e + 15], 22, 1236535329), a = l(a, b, c, d, f[e + 1], 5, 4129170786), d = l(d, a, b, c, f[e + 6], 9, 3225465664), c = l(c, d, a, b, f[e + 11], 14, 643717713), b = l(b, c, d, a, f[e + 0], 20, 3921069994), a = l(a, b, c, d, f[e + 5], 5, 3593408605), d = l(d, a, b, c, f[e + 10], 9, 38016083), c = l(c, d, a, b, f[e + 15], 14, 3634488961), b = l(b, c, d, a, f[e + 4], 20, 3889429448), a = l(a, b, c, d, f[e + 9], 5, 568446438), d = l(d, a, b, c, f[e + 14], 9, 3275163606), c = l(c, d, a, b, f[e + 3], 14, 4107603335), b = l(b, c, d, a, f[e + 8], 20, 1163531501), a = l(a, b, c, d, f[e + 13], 5, 2850285829), d = l(d, a, b, c, f[e + 2], 9, 4243563512), c = l(c, d, a, b, f[e + 7], 14, 1735328473), b = l(b, c, d, a, f[e + 12], 20, 2368359562), a = m(a, b, c, d, f[e + 5], 4, 4294588738), d = m(d, a, b, c, f[e + 8], 11, 2272392833), c = m(c, d, a, b, f[e + 11], 16, 1839030562), b = m(b, c, d, a, f[e + 14], 23, 4259657740), a = m(a, b, c, d, f[e + 1], 4, 2763975236), d = m(d, a, b, c, f[e + 4], 11, 1272893353), c = m(c, d, a, b, f[e + 7], 16, 4139469664), b = m(b, c, d, a, f[e + 10], 23, 3200236656), a = m(a, b, c, d, f[e + 13], 4, 681279174), d = m(d, a, b, c, f[e + 0], 11, 3936430074), c = m(c, d, a, b, f[e + 3], 16, 3572445317), b = m(b, c, d, a, f[e + 6], 23, 76029189), a = m(a, b, c, d, f[e + 9], 4, 3654602809), d = m(d, a, b, c, f[e + 12], 11, 3873151461), c = m(c, d, a, b, f[e + 15], 16, 530742520), b = m(b, c, d, a, f[e + 2], 23, 3299628645), a = n(a, b, c, d, f[e + 0], 6, 4096336452), d = n(d, a, b, c, f[e + 7], 10, 1126891415), c = n(c, d, a, b, f[e + 14], 15, 2878612391), b = n(b, c, d, a, f[e + 5], 21, 4237533241), a = n(a, b, c, d, f[e + 12], 6, 1700485571), d = n(d, a, b, c, f[e + 3], 10, 2399980690), c = n(c, d, a, b, f[e + 10], 15, 4293915773), b = n(b, c, d, a, f[e + 1], 21, 2240044497), a = n(a, b, c, d, f[e + 8], 6, 1873313359), d = n(d, a, b, c, f[e + 15], 10, 4264355552), c = n(c, d, a, b, f[e + 6], 15, 2734768916), b = n(b, c, d, a, f[e + 13], 21, 1309151649), a = n(a, b, c, d, f[e + 4], 6, 4149444226), d = n(d, a, b, c, f[e + 11], 10, 3174756917), c = n(c, d, a, b, f[e + 2], 15, 718787259), b = n(b, c, d, a, f[e + 9], 21, 3951481745), a = h(a, q), b = h(b, r), c = h(c, s), d = h(d, t);
|
||||
return (p(a) + p(b) + p(c) + p(d)).toLowerCase()
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
7
src/ext/pngjs.esm.js
Normal file
7
src/ext/pngjs.esm.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
// Import the original PNG library
|
||||
import './pngjs.js';
|
||||
|
||||
// Re-export the PNG object
|
||||
export const PNG = self.png ? self.png.PNG : null;
|
||||
|
|
@ -1,12 +1,8 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { newPoint } from './point.js';
|
||||
import earcut from '../ext/earcut.js';
|
||||
|
||||
// use: add.array
|
||||
// use: add.class
|
||||
gapp.register("geo.base", [], (root, exports) => {
|
||||
|
||||
const base = root.base = {};
|
||||
const round_decimal_precision = 5;
|
||||
|
||||
function time() {
|
||||
|
|
@ -219,7 +215,7 @@ function intersect(p1, p2, p3, p4, test, parallelok) {
|
|||
if (test === keys.SEGINT && !segint) return null;
|
||||
if (test === keys.RAYINT && !rayint) return null;
|
||||
|
||||
let ip = base.newPoint(
|
||||
let ip = newPoint(
|
||||
p1x + (a * d1x), // x
|
||||
p1y + (a * d1y), // y
|
||||
p3.z || p4.z, // z
|
||||
|
|
@ -264,7 +260,7 @@ function intersectRayLine(ro, s1, p1, p2, infinite) {
|
|||
b = n2 / d;
|
||||
|
||||
if (infinite || (inCloseRange(b, 0, 1) && a >= 0)) {
|
||||
let ip = base.newPoint(
|
||||
let ip = newPoint(
|
||||
p1x + (a * s1x),
|
||||
p1y + (a * s1y),
|
||||
p2.z || ro.z,
|
||||
|
|
@ -528,7 +524,7 @@ function comma(v) {
|
|||
* Connect to base
|
||||
******************************************************************* */
|
||||
|
||||
base.key = {
|
||||
export const key = {
|
||||
NONE: "",
|
||||
PROJECT: "project",
|
||||
SEGINT: "segint",
|
||||
|
|
@ -536,7 +532,7 @@ base.key = {
|
|||
PARALLEL: "parallel"
|
||||
};
|
||||
|
||||
base.config = {
|
||||
export const config = {
|
||||
// size of gcode debug arrow head
|
||||
debug_arrow: 0.25,
|
||||
// default # of decimal places in generated gcode
|
||||
|
|
@ -602,7 +598,7 @@ base.config = {
|
|||
clipperClean: 250
|
||||
};
|
||||
|
||||
base.util = {
|
||||
export const util = {
|
||||
sqr,
|
||||
lerp,
|
||||
time,
|
||||
|
|
@ -639,4 +635,10 @@ base.util = {
|
|||
zInPlane
|
||||
};
|
||||
|
||||
});
|
||||
export const base = {
|
||||
config,
|
||||
key,
|
||||
util
|
||||
};
|
||||
|
||||
export { earcut };
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { config, util } from './base.js';
|
||||
import { newPoint } from './point.js';
|
||||
|
||||
// dep: geo.base
|
||||
gapp.register("geo.bounds", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { config, util } = base;
|
||||
|
||||
class Bounds {
|
||||
export class Bounds {
|
||||
constructor() {
|
||||
this.minx = 10e7;
|
||||
this.miny = 10e7;
|
||||
|
|
@ -121,7 +116,7 @@ class Bounds {
|
|||
}
|
||||
|
||||
center(z = 0) {
|
||||
return base.newPoint(this.centerx(), this.centery(), z);
|
||||
return newPoint(this.centerx(), this.centery(), z);
|
||||
}
|
||||
|
||||
centerx() {
|
||||
|
|
@ -133,9 +128,6 @@ class Bounds {
|
|||
}
|
||||
}
|
||||
|
||||
gapp.overlay(base, {
|
||||
Bounds,
|
||||
newBounds() { return new Bounds() }
|
||||
});
|
||||
|
||||
});
|
||||
export function newBounds() {
|
||||
return new Bounds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,17 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// mod: ext.manifold
|
||||
gapp.register("geo.csg", [], (root, exports) => {
|
||||
import { THREE } from '../ext/three.js';
|
||||
import manifold from '../ext/manifold.js';
|
||||
|
||||
const { base, ext } = root;
|
||||
const debug = true;
|
||||
const precision = 0.001;
|
||||
const factor = 1/precision;
|
||||
|
||||
function log() {
|
||||
if (root?.mesh?.log) {
|
||||
root.mesh.log(...arguments)
|
||||
} else if (root.debug === true) {
|
||||
console.log(...arguments);
|
||||
}
|
||||
console.log(...arguments);
|
||||
}
|
||||
|
||||
const CSG = {
|
||||
export const CSG = {
|
||||
// accepts 2 or more arguments with threejs vertex position arrays
|
||||
union() {
|
||||
return CSG.moduleOp('manifold.union', 'union', ...arguments);
|
||||
|
|
@ -123,23 +116,10 @@ function indexVertices(pos) {
|
|||
}
|
||||
|
||||
let Instance;
|
||||
|
||||
ext.manifold.then(mod => {
|
||||
mod.default({
|
||||
locateFile(a,b,c) {
|
||||
return "/wasm/manifold.wasm";
|
||||
}
|
||||
}).then(inst => {
|
||||
manifold({
|
||||
locateFile() { return "/wasm/manifold.wasm" }
|
||||
}).then(inst => {
|
||||
inst.setup();
|
||||
Instance = inst;
|
||||
CSG.Instance = () => { return Instance };
|
||||
}).catch(error => {
|
||||
console.log('manifold load error', error);
|
||||
});
|
||||
});
|
||||
|
||||
gapp.overlay(base, {
|
||||
CSG
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
/** Copyright Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
gapp.register("geo.gyroid", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const PI2 = Math.PI * 2;
|
||||
|
||||
let cache = {};
|
||||
|
|
@ -17,7 +11,7 @@ let lastSlice = 0;
|
|||
* @param off {number} z offset value from 0-1
|
||||
* @param res {number} resolution (pixels/slices per side)
|
||||
*/
|
||||
function slice(off, res, val) {
|
||||
export function slice(off, res, val) {
|
||||
// auto clear cach if it hasn't been hit in the last 20 seconds
|
||||
// or the requested resolution or tip values have changed
|
||||
let now = Date.now();
|
||||
|
|
@ -170,7 +164,7 @@ function slice(off, res, val) {
|
|||
}
|
||||
|
||||
// merge co-linear and distance threshold
|
||||
function filter(poly, inc) {
|
||||
export function filter(poly, inc) {
|
||||
if (poly.length <= 2) {
|
||||
return poly;
|
||||
}
|
||||
|
|
@ -200,11 +194,10 @@ function filter(poly, inc) {
|
|||
}
|
||||
e1 = el;
|
||||
}
|
||||
nupoly.push(poly[poly.length-1]);
|
||||
return nupoly;
|
||||
}
|
||||
|
||||
function distTo(a, b, dir) {
|
||||
export function distTo(a, b, dir) {
|
||||
let dx = a.x - b.x;
|
||||
let dy = a.y - b.y;
|
||||
// bias distance by prevailing direction of discovery to join stragglers
|
||||
|
|
@ -212,9 +205,3 @@ function distTo(a, b, dir) {
|
|||
if (dir === 'td') dy = dy / 2;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
gapp.overlay(base, {
|
||||
gyroid: { slice }
|
||||
})
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
gapp.register("geo.line", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
|
||||
class Line {
|
||||
export class Line {
|
||||
constructor(p1, p2, key) {
|
||||
if (!key) key = [p1.key, p2.key].join(';');
|
||||
this.p1 = p1;
|
||||
|
|
@ -54,18 +47,10 @@ class Line {
|
|||
}
|
||||
}
|
||||
|
||||
function newLine(p1, p2, key) {
|
||||
export function newLine(p1, p2, key) {
|
||||
return new Line(p1, p2, key);
|
||||
}
|
||||
|
||||
function newOrderedLine(p1, p2, key) {
|
||||
export function newOrderedLine(p1, p2, key) {
|
||||
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
|
||||
}
|
||||
|
||||
gapp.overlay(base, {
|
||||
Line,
|
||||
newLine,
|
||||
newOrderedLine
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
150
src/geo/paths.js
150
src/geo/paths.js
|
|
@ -1,19 +1,7 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// path & routing output utilities
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
|
||||
gapp.register("geo.paths", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { util, config, newPoint } = base;
|
||||
const { sqr, numOrDefault } = util;
|
||||
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
import { base } from './base.js';
|
||||
import { newPoint } from './point.js';
|
||||
|
||||
/**
|
||||
* emit each element in an array based on
|
||||
|
|
@ -21,21 +9,21 @@ const DEG2RAD = Math.PI / 180;
|
|||
* elements with { first, last } points and
|
||||
* may be open polys, unlike poly2polyEmit
|
||||
*/
|
||||
function tip2tipEmit(array, startPoint, emitter) {
|
||||
export function tip2tipEmit(array, startPoint, emitter) {
|
||||
let mindist, dist, found, count = 0;
|
||||
for (;;) {
|
||||
found = null;
|
||||
mindist = Infinity;
|
||||
array.forEach(function(el) {
|
||||
array.forEach(function (el) {
|
||||
if (el.delete) return;
|
||||
dist = startPoint.distTo2D(el.first);
|
||||
if (dist < mindist) {
|
||||
found = {el:el, first:el.first, last:el.last};
|
||||
found = { el: el, first: el.first, last: el.last };
|
||||
mindist = dist;
|
||||
}
|
||||
dist = startPoint.distTo2D(el.last);
|
||||
if (dist < mindist) {
|
||||
found = {el:el, first:el.last, last:el.first};
|
||||
found = { el: el, first: el.last, last: el.first };
|
||||
mindist = dist;
|
||||
}
|
||||
});
|
||||
|
|
@ -56,7 +44,7 @@ function tip2tipEmit(array, startPoint, emitter) {
|
|||
* to be more like outputOrderClosest() and have the option to account for
|
||||
* depth in determining distance
|
||||
*/
|
||||
function poly2polyEmit(array, startPoint, emitter, opt = {}) {
|
||||
export function poly2polyEmit(array, startPoint, emitter, opt = {}) {
|
||||
let marker = opt.mark || 'delete';
|
||||
let mindist, dist, found, count = 0;
|
||||
for (;;) {
|
||||
|
|
@ -74,21 +62,21 @@ function poly2polyEmit(array, startPoint, emitter, opt = {}) {
|
|||
}
|
||||
if (d2l < mindist && d2l < d2f && opt.swapdir !== false) {
|
||||
poly.reverse();
|
||||
found = {poly:poly, index:0, point:poly.first()};
|
||||
found = { poly: poly, index: 0, point: poly.first() };
|
||||
mindist = d2l;
|
||||
} else if (d2f < mindist) {
|
||||
found = {poly:poly, index:0, point:poly.first()};
|
||||
found = { poly: poly, index: 0, point: poly.first() };
|
||||
mindist = d2f;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let area = poly.open ? 1 : poly.area();
|
||||
poly.forEachPoint(function(point, index) {
|
||||
poly.forEachPoint(function (point, index) {
|
||||
dist = opt.weight ?
|
||||
startPoint.distTo3D(point) * area * area :
|
||||
startPoint.distTo2D(point);
|
||||
if (dist < mindist) {
|
||||
found = {poly:poly, index:index, point:point};
|
||||
found = { poly: poly, index: index, point: point };
|
||||
mindist = dist;
|
||||
}
|
||||
});
|
||||
|
|
@ -102,23 +90,23 @@ function poly2polyEmit(array, startPoint, emitter, opt = {}) {
|
|||
|
||||
// undo delete marks
|
||||
if (opt.perm !== true) {
|
||||
array.forEach(function(poly) { poly[marker] = false });
|
||||
array.forEach(function (poly) { poly[marker] = false });
|
||||
}
|
||||
|
||||
return startPoint;
|
||||
}
|
||||
|
||||
function calc_normal(p1, p2) {
|
||||
export function calc_normal(p1, p2) {
|
||||
let dx = p2.x - p1.x;
|
||||
let dy = p2.y - p1.y;
|
||||
let len = Math.sqrt(dx * dx + dy * dy);
|
||||
let mn = (1 / len);
|
||||
dx *= mn;
|
||||
dy *= mn;
|
||||
return({ dx: dy, dy: -dx, p1, p2, len });
|
||||
return ({ dx: dy, dy: -dx, p1, p2, len });
|
||||
}
|
||||
|
||||
function end_vertex(n1, n2, off, start) {
|
||||
export function end_vertex(n1, n2, off, start) {
|
||||
let dx, dy;
|
||||
if (start) {
|
||||
dx = n2.dx * off;
|
||||
|
|
@ -130,7 +118,7 @@ function end_vertex(n1, n2, off, start) {
|
|||
return { dx, dy, vp: n1.p2 };
|
||||
}
|
||||
|
||||
function calc_vertex(n1, n2, off, vp) {
|
||||
export function calc_vertex(n1, n2, off, vp) {
|
||||
let dx, dy, io, vl, q, r;
|
||||
r = 1 + (n1.dx * n2.dx + n1.dy * n2.dy);
|
||||
q = off / r;
|
||||
|
|
@ -148,7 +136,7 @@ function calc_vertex(n1, n2, off, vp) {
|
|||
return { dx, dy, vp: vp || n1.p2, io, vl };
|
||||
}
|
||||
|
||||
function v2pl(rec) {
|
||||
export function v2pl(rec) {
|
||||
let p = rec.vp.clone();
|
||||
p.x += rec.dx;
|
||||
p.y += rec.dy;
|
||||
|
|
@ -156,7 +144,7 @@ function v2pl(rec) {
|
|||
return p;
|
||||
}
|
||||
|
||||
function v2pr(rec) {
|
||||
export function v2pr(rec) {
|
||||
let p = rec.vp.clone();
|
||||
p.x -= rec.dx;
|
||||
p.y -= rec.dy;
|
||||
|
|
@ -164,17 +152,17 @@ function v2pr(rec) {
|
|||
return p;
|
||||
}
|
||||
|
||||
function pointsToPath(points, offset, open, miter = 1.5) {
|
||||
export function pointsToPath(points, offset, open, miter = 1.5) {
|
||||
const absoff = Math.abs(offset);
|
||||
// calculate segment normals which are used to calculate vertex normals
|
||||
// next segment info is associated with the current point
|
||||
const nupoints = [];
|
||||
const length = points.length;
|
||||
if (length === 2 && points[0].isEqual(points[1])) {
|
||||
return { };
|
||||
return {};
|
||||
}
|
||||
const dedup = (open && length > 2) || (!open && length > 3);
|
||||
for (let i=0; i<length; i++) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
let p1 = points[i];
|
||||
let p2 = points[(i + 1) % length];
|
||||
p1.normal = calc_normal(p1, p2);
|
||||
|
|
@ -191,7 +179,7 @@ function pointsToPath(points, offset, open, miter = 1.5) {
|
|||
nupoints.push(p1);
|
||||
}
|
||||
if (nupoints.length === 1) {
|
||||
console.log({points, nupoints});
|
||||
console.log({ points, nupoints });
|
||||
}
|
||||
// when points are dropped, we need the new array
|
||||
points = nupoints;
|
||||
|
|
@ -204,10 +192,10 @@ function pointsToPath(points, offset, open, miter = 1.5) {
|
|||
// calculate vertex normals from segments normals
|
||||
// vertex info is associated with the origin point
|
||||
let fl, fr;
|
||||
for (let i=0, l=points.length; i<l; i++) {
|
||||
let n1 = points[(i+l-1)%l].normal;
|
||||
let n2 = points[(i+l)%l].normal;
|
||||
let vn = open && (i === 0 || i === l-1) ?
|
||||
for (let i = 0, l = points.length; i < l; i++) {
|
||||
let n1 = points[(i + l - 1) % l].normal;
|
||||
let n2 = points[(i + l) % l].normal;
|
||||
let vn = open && (i === 0 || i === l - 1) ?
|
||||
end_vertex(n1, n2, offset, i === 0) :
|
||||
calc_vertex(n1, n2, offset);
|
||||
let { p1, p2 } = n2;
|
||||
|
|
@ -229,7 +217,7 @@ function pointsToPath(points, offset, open, miter = 1.5) {
|
|||
// shorten each leg and insert new point
|
||||
let delta = 0.1;
|
||||
let np1 = p1.clone().move({ x: n1.dy * delta, y: -n1.dx * delta, z: 0 });
|
||||
let np2 = p1.clone().move({ x:-n2.dy * delta, y: n2.dx * delta, z: 0 });
|
||||
let np2 = p1.clone().move({ x: -n2.dy * delta, y: n2.dx * delta, z: 0 });
|
||||
let sn1 = np1.normal = calc_normal(np1, np2);
|
||||
let sn2 = np2.normal = p1.normal;
|
||||
let nv1 = calc_vertex(n1.p1.normal, sn1, offset, np1);
|
||||
|
|
@ -336,7 +324,7 @@ function pointsToPath(points, offset, open, miter = 1.5) {
|
|||
return { left, right, faces, normals, open };
|
||||
}
|
||||
|
||||
function pathTo3D(path, height, z) {
|
||||
export function pathTo3D(path, height, z) {
|
||||
const { faces, normals, left, right, open } = path;
|
||||
const out = [];
|
||||
const nrm = [];
|
||||
|
|
@ -356,14 +344,14 @@ function pathTo3D(path, height, z) {
|
|||
}
|
||||
nrm.appendAll(normals);
|
||||
// reverse normals to match faces, but underside so reverse Z as well
|
||||
for (let i=normals.length-1; i>0; i-=3) {
|
||||
nrm.push(normals[i-2]);
|
||||
nrm.push(normals[i-1]);
|
||||
nrm.push(-normals[i-0]);
|
||||
for (let i = normals.length - 1; i > 0; i -= 3) {
|
||||
nrm.push(normals[i - 2]);
|
||||
nrm.push(normals[i - 1]);
|
||||
nrm.push(-normals[i - 0]);
|
||||
}
|
||||
for (let i=0, l=left.length, tl = open ? l-1 : l; i<tl; i++) {
|
||||
for (let i = 0, l = left.length, tl = open ? l - 1 : l; i < tl; i++) {
|
||||
let p0 = left[i];
|
||||
let p1 = left[(i+1)%l];
|
||||
let p1 = left[(i + 1) % l];
|
||||
out.push(p0.x, p0.y, p0.z + height);
|
||||
out.push(p0.x, p0.y, p0.z - height);
|
||||
out.push(p1.x, p1.y, p1.z - height);
|
||||
|
|
@ -372,15 +360,15 @@ function pathTo3D(path, height, z) {
|
|||
out.push(p0.x, p0.y, p0.z + height);
|
||||
let ln = p0.vp.normal;
|
||||
nrm.push(ln.dx, ln.dy, -1);
|
||||
nrm.push(ln.dx, ln.dy, 1);
|
||||
nrm.push(ln.dx, ln.dy, 1);
|
||||
nrm.push(ln.dx, ln.dy, 1);
|
||||
nrm.push(ln.dx, ln.dy, 1);
|
||||
nrm.push(ln.dx, ln.dy, 1);
|
||||
nrm.push(ln.dx, ln.dy, 1);
|
||||
nrm.push(ln.dx, ln.dy, -1);
|
||||
nrm.push(ln.dx, ln.dy, -1);
|
||||
}
|
||||
for (let i=0, l=right.length, tl = open ? l-1 : l; i<tl; i++) {
|
||||
for (let i = 0, l = right.length, tl = open ? l - 1 : l; i < tl; i++) {
|
||||
let p0 = right[i];
|
||||
let p1 = right[(i+1)%l];
|
||||
let p1 = right[(i + 1) % l];
|
||||
out.push(p0.x, p0.y, p0.z + height);
|
||||
out.push(p1.x, p1.y, p1.z - height);
|
||||
out.push(p0.x, p0.y, p0.z - height);
|
||||
|
|
@ -388,12 +376,12 @@ function pathTo3D(path, height, z) {
|
|||
out.push(p0.x, p0.y, p0.z + height);
|
||||
out.push(p1.x, p1.y, p1.z + height);
|
||||
let rn = p0.vp.normal;
|
||||
nrm.push(-rn.dy, rn.dx, 1);
|
||||
nrm.push(-rn.dy, rn.dx, 1);
|
||||
nrm.push(-rn.dy, rn.dx, -1);
|
||||
nrm.push(-rn.dy, rn.dx, -1);
|
||||
nrm.push(-rn.dy, rn.dx, -1);
|
||||
nrm.push(-rn.dy, rn.dx, 1);
|
||||
nrm.push(-rn.dy, rn.dx, 1);
|
||||
nrm.push(-rn.dy, rn.dx, 1);
|
||||
nrm.push(-rn.dy, rn.dx, 1);
|
||||
}
|
||||
if (open) {
|
||||
// begin cap
|
||||
|
|
@ -406,12 +394,12 @@ function pathTo3D(path, height, z) {
|
|||
out.push(r0.x, r0.y, r0.z - height);
|
||||
out.push(l0.x, l0.y, l0.z + height);
|
||||
let ln = l0.vp.normal;
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, -1);
|
||||
nrm.push(-ln.dy, ln.dx, -1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, -1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
// end cap
|
||||
let le = left.peek();
|
||||
let re = right.peek();
|
||||
|
|
@ -422,11 +410,11 @@ function pathTo3D(path, height, z) {
|
|||
out.push(le.x, le.y, le.z + height);
|
||||
out.push(re.x, re.y, re.z - height);
|
||||
ln = re.vp.normal;
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, -1);
|
||||
nrm.push(-ln.dy, ln.dx, -1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, 1);
|
||||
nrm.push(-ln.dy, ln.dx, -1);
|
||||
}
|
||||
return { faces: out, normals: nrm };
|
||||
|
|
@ -434,7 +422,7 @@ function pathTo3D(path, height, z) {
|
|||
|
||||
// produces indexed geometry which isn't ideal for rendering because
|
||||
// the default threejs generated vertex normals aren't accurate
|
||||
function shapeToPath(shape, points, closed) {
|
||||
export function shapeToPath(shape, points, closed) {
|
||||
closed = closed !== undefined ? closed : true;
|
||||
|
||||
const profileGeometry = new THREE.ShapeGeometry(shape);
|
||||
|
|
@ -451,9 +439,9 @@ function shapeToPath(shape, points, closed) {
|
|||
let hA = halfAngle;
|
||||
let tA = v2.angle() + Math.PI * .5;
|
||||
|
||||
if (!closed){
|
||||
if (i == 0 || i == points.length - 1) {hA = Math.PI * .5;}
|
||||
if (i == points.length - 1) {tA = v1.angle() - Math.PI * .5;}
|
||||
if (!closed) {
|
||||
if (i == 0 || i == points.length - 1) { hA = Math.PI * .5; }
|
||||
if (i == points.length - 1) { tA = v1.angle() - Math.PI * .5; }
|
||||
}
|
||||
|
||||
const shift = Math.tan(hA - Math.PI * .5);
|
||||
|
|
@ -488,7 +476,7 @@ function shapeToPath(shape, points, closed) {
|
|||
}
|
||||
|
||||
const index = [];
|
||||
const lastCorner = closed == false ? points.length - 1: points.length;
|
||||
const lastCorner = closed == false ? points.length - 1 : points.length;
|
||||
|
||||
for (let i = 0; i < lastCorner; i++) {
|
||||
for (let j = 0; j < profile.count; j++) {
|
||||
|
|
@ -524,7 +512,7 @@ function shapeToPath(shape, points, closed) {
|
|||
index.push(p8, p7, p5);
|
||||
}
|
||||
|
||||
return {index, faces};
|
||||
return { index, faces };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -539,18 +527,15 @@ function shapeToPath(shape, points, closed) {
|
|||
*
|
||||
* @return {Array<Point>} an array of points representing the arc.
|
||||
*/
|
||||
function arcToPath( start, end,arcdivs=24,opts) {
|
||||
export function arcToPath(start, end, arcdivs = 24, opts) {
|
||||
|
||||
let { clockwise, center, radius } = opts;
|
||||
|
||||
// @type {Point}
|
||||
|
||||
|
||||
if (end.x === undefined && end.x === undefined && center === undefined) {
|
||||
// bambu generates loop z or wipe loop arcs in place
|
||||
// console.log({ skip_empty_arc: rec });
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if(arcdivs <= 2){
|
||||
|
|
@ -560,14 +545,13 @@ function arcToPath( start, end,arcdivs=24,opts) {
|
|||
if (center) {
|
||||
// center = center.add(start);
|
||||
center.r = center.distTo2D(start);
|
||||
|
||||
} else if (radius !== undefined) {
|
||||
let pd = { x: end.x - start.x, y: end.y - start.y }; //position delta
|
||||
let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; // distance
|
||||
let pr2;
|
||||
if (Math.abs(dst - radius) < 0.001) {
|
||||
// center point radius
|
||||
pr2 = { x: (end.x + start.x) / 2, y: (end.y + start.y) / 2};
|
||||
pr2 = { x: (end.x + start.x) / 2, y: (end.y + start.y) / 2 };
|
||||
} else {
|
||||
// triangulate
|
||||
pr2 = base.util.center2pr(start, end, radius, clockwise);
|
||||
|
|
@ -576,7 +560,7 @@ function arcToPath( start, end,arcdivs=24,opts) {
|
|||
center.y = pr2.y;
|
||||
center.r = radius;
|
||||
} else {
|
||||
console.log({malfomed_arc: {radius,center, clockwise, start, end}});
|
||||
console.log({ malfomed_arc: { radius, center, clockwise, start, end } });
|
||||
}
|
||||
|
||||
//deltas
|
||||
|
|
@ -601,9 +585,9 @@ function arcToPath( start, end,arcdivs=24,opts) {
|
|||
let dd = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
let arr = [] // point accumulator
|
||||
for (let i=0; i<=steps-2; i++) {
|
||||
for (let i = 0; i <= steps - 2; i++) {
|
||||
if (isNaN(center.r) || isNaN(center.x) || isNaN(center.y)) {
|
||||
console.log({malfomed_arc: {radius, clockwise, start, end}});
|
||||
console.log({ malfomed_arc: { radius, clockwise, start, end } });
|
||||
}
|
||||
arr.push(newPoint(
|
||||
center.x + Math.cos(rot) * center.r,
|
||||
|
|
@ -618,7 +602,7 @@ function arcToPath( start, end,arcdivs=24,opts) {
|
|||
return arr
|
||||
}
|
||||
|
||||
class FloatPacker {
|
||||
export class FloatPacker {
|
||||
constructor(size, factor) {
|
||||
this.size = size;
|
||||
this.factor = Math.min(factor || 1.2, 1.1);
|
||||
|
|
@ -637,7 +621,7 @@ class FloatPacker {
|
|||
this.array = nuarray;
|
||||
this.size = nusize;
|
||||
}
|
||||
for (let i=0; i<args; i++) {
|
||||
for (let i = 0; i < args; i++) {
|
||||
array[this.pos++] = arguments[i];
|
||||
}
|
||||
}
|
||||
|
|
@ -651,15 +635,11 @@ class FloatPacker {
|
|||
}
|
||||
}
|
||||
|
||||
base.paths = {
|
||||
poly2polyEmit,
|
||||
export const paths = {
|
||||
tip2tipEmit,
|
||||
poly2polyEmit,
|
||||
shapeToPath,
|
||||
pointsToPath,
|
||||
pathTo3D,
|
||||
arcToPath,
|
||||
vertexNormal: calc_vertex,
|
||||
segmentNormal: calc_normal
|
||||
};
|
||||
|
||||
});
|
||||
FloatPacker
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { util, config, key } from './base.js';
|
||||
import { newLine } from './line.js';
|
||||
import { newSlope } from './slope.js';
|
||||
|
||||
// dep: geo.base
|
||||
gapp.register("geo.point", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { util, config, key } = base;
|
||||
const { round } = util;
|
||||
const { Vector3 } = THREE;
|
||||
|
||||
class Point {
|
||||
constructor(x = 0, y = 0, z = 0, key) {
|
||||
|
|
@ -42,7 +39,7 @@ class Point {
|
|||
}
|
||||
|
||||
toVector3() {
|
||||
return new THREE.Vector3(this.x, this.y, this.z);
|
||||
return new Vector3(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
set(x, y, z) {
|
||||
|
|
@ -126,11 +123,11 @@ class Point {
|
|||
}
|
||||
|
||||
slopeTo(p) {
|
||||
return base.newSlope(this, p);
|
||||
return newSlope(this, p);
|
||||
}
|
||||
|
||||
lineTo(p, k) {
|
||||
return base.newLine(this, p, k);
|
||||
return newLine(this, p, k);
|
||||
}
|
||||
|
||||
isNear(p, dist) {
|
||||
|
|
@ -296,7 +293,7 @@ class Point {
|
|||
np2 = newPoint(p2.x - oy, p2.y + ox, p2.z, key.NONE);
|
||||
np1.op = p1;
|
||||
np2.op = p2;
|
||||
return base.newLine(np1, np2, key.NONE);
|
||||
return newLine(np1, np2, key.NONE);
|
||||
}
|
||||
|
||||
offset(x, y, z) {
|
||||
|
|
@ -652,10 +649,8 @@ function pointFromClipper(cp, z) {
|
|||
return newPoint(cp.X / config.clipper, cp.Y / config.clipper, z);
|
||||
}
|
||||
|
||||
gapp.overlay(base, {
|
||||
export {
|
||||
Point,
|
||||
newPoint,
|
||||
pointFromClipper
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,23 +2,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
gapp.register("geo.points", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { config } = base;
|
||||
|
||||
gapp.overlay(base, {
|
||||
verticesToPoints,
|
||||
pointsToVertices
|
||||
});
|
||||
import { config } from './base.js';
|
||||
import { newPoint } from '../geo/point.js';
|
||||
|
||||
/**
|
||||
* converts a geometry point array into a kiri point array
|
||||
* with optional decimation
|
||||
*/
|
||||
function verticesToPoints(array, options) {
|
||||
export function verticesToPoints(array, options) {
|
||||
let parr = new Array(array.length / 3),
|
||||
i = 0,
|
||||
j = 0,
|
||||
|
|
@ -41,7 +32,7 @@ function verticesToPoints(array, options) {
|
|||
// replace point objects with their equivalents
|
||||
if (maxpass) {
|
||||
while (i < array.length) {
|
||||
let p = base.newPoint(array[i++], array[i++], array[i++]),
|
||||
let p = newPoint(array[i++], array[i++], array[i++]),
|
||||
k = p.key,
|
||||
m = hash[k];
|
||||
if (!m) {
|
||||
|
|
@ -53,7 +44,7 @@ function verticesToPoints(array, options) {
|
|||
}
|
||||
} else {
|
||||
while (i < array.length) {
|
||||
parr[j++] = base.newPoint(array[i++], array[i++], array[i++]);
|
||||
parr[j++] = newPoint(array[i++], array[i++], array[i++]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +115,7 @@ function verticesToPoints(array, options) {
|
|||
return parr;
|
||||
}
|
||||
|
||||
function pointsToVertices(points) {
|
||||
export function pointsToVertices(points) {
|
||||
let vertices = new Float32Array(points.length * 3),
|
||||
i = 0, vi = 0;
|
||||
while (i < points.length) {
|
||||
|
|
@ -134,5 +125,3 @@ function pointsToVertices(points) {
|
|||
}
|
||||
return vertices;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,22 +2,17 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.paths
|
||||
// dep: ext.clip2
|
||||
// dep: ext.earcut
|
||||
// dep: geo.point
|
||||
// dep: geo.bounds
|
||||
// dep: geo.polygons
|
||||
gapp.register("geo.polygon", [], (root, exports) => {
|
||||
import { base, config, earcut, util } from './base.js';
|
||||
import { ClipperLib } from '../ext/clip2.esm.js';
|
||||
import { newBounds } from './bounds.js';
|
||||
import { paths } from './paths.js';
|
||||
import { newPoint, pointFromClipper } from './point.js';
|
||||
import { polygons as POLY } from './polygons.js';
|
||||
|
||||
const { base } = root;
|
||||
const { config, util, polygons, newBounds, newPoint } = base;
|
||||
const { Vector3 } = THREE;
|
||||
|
||||
const POLY = polygons,
|
||||
XAXIS = new THREE.Vector3(1,0,0),
|
||||
let XAXIS = new Vector3(1,0,0),
|
||||
DEG2RAD = Math.PI / 180,
|
||||
ClipperLib = self.ClipperLib,
|
||||
Clipper = ClipperLib.Clipper,
|
||||
ClipType = ClipperLib.ClipType,
|
||||
PolyType = ClipperLib.PolyType,
|
||||
|
|
@ -39,7 +34,7 @@ const POLY = polygons,
|
|||
|
||||
let seqid = Math.round(Math.random() * 0xffffffff);
|
||||
|
||||
class Polygon {
|
||||
export class Polygon {
|
||||
constructor(points) {
|
||||
this.id = seqid++; // polygon unique id
|
||||
this.open = false;
|
||||
|
|
@ -76,11 +71,11 @@ class Polygon {
|
|||
}
|
||||
|
||||
toPath2D(offset) {
|
||||
return base.paths.pointsToPath(this.points, offset, this.open);
|
||||
return paths.pointsToPath(this.points, offset, this.open);
|
||||
}
|
||||
|
||||
toPath3D(offset, height, z) {
|
||||
return base.paths.pathTo3D(this.toPath2D(offset), height, z);
|
||||
return paths.pathTo3D(this.toPath2D(offset), height, z);
|
||||
}
|
||||
|
||||
toString(verbose) {
|
||||
|
|
@ -235,7 +230,7 @@ class Polygon {
|
|||
}
|
||||
|
||||
// perform earcut()
|
||||
let cut = self.earcut(out, holes, 3);
|
||||
let cut = earcut(out, holes, 3);
|
||||
let ret = [];
|
||||
|
||||
// preserve swaps in new polys
|
||||
|
|
@ -467,7 +462,7 @@ class Polygon {
|
|||
return polys
|
||||
.filter(poly => poly.length > 1)
|
||||
.map(poly => {
|
||||
let np = base.newPolygon().setOpen();
|
||||
let np = newPolygon().setOpen();
|
||||
for (let p of poly) {
|
||||
np.push(p);
|
||||
}
|
||||
|
|
@ -1143,7 +1138,7 @@ class Polygon {
|
|||
applyRotations() {
|
||||
for (let point of this.points) {
|
||||
if (point.a) {
|
||||
let p2 = new THREE.Vector3(point.x, point.y, point.z)
|
||||
let p2 = new Vector3(point.x, point.y, point.z)
|
||||
.applyAxisAngle(XAXIS, point.a * DEG2RAD);
|
||||
point.x = p2.x;
|
||||
point.y = p2.y;
|
||||
|
|
@ -1205,15 +1200,76 @@ class Polygon {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* calls function for each point in this polygon
|
||||
* @param {Function} fn to call for each point
|
||||
* @param {boolean} [close=true] whether to close the loop (last point to first)
|
||||
* @param {number} [start=0] starting index
|
||||
* Ease down along the polygonal path.
|
||||
*
|
||||
* 1. Travel from fromPoint to closest point on polygon, to rampZ above that that point,
|
||||
* 2. ease-down starts, following the polygonal path, decreasing Z at a fixed slope until target Z is hit,
|
||||
* 3. then the rest of the path is completed and repeated at target Z until touchdown point is reached.
|
||||
* 4. this function should probably move to CAM prepare since it's only called from there
|
||||
*/
|
||||
forEachPoint(fn, close, start) {
|
||||
forEachPointEaseDown(fn, fromPoint, degrees = 45) {
|
||||
let index = this.findClosestPointTo(fromPoint).index,
|
||||
fromZ = fromPoint.z,
|
||||
offset = 0,
|
||||
points = this.points,
|
||||
length = points.length,
|
||||
touch = -1, // first point to touch target z
|
||||
targetZ = points[0].z,
|
||||
dist2next,
|
||||
last,
|
||||
next,
|
||||
done;
|
||||
|
||||
// Slope for computations.
|
||||
const slope = Math.tan((degrees * Math.PI) / 180);
|
||||
// Z height above polygon Z from which to start the ease-down.
|
||||
// Machine will travel from "fromPoint" to "nearest point x, y, z' => with z' = point z + rampZ",
|
||||
// then start the ease down along path.
|
||||
const rampZ = 2.0;
|
||||
while (true) {
|
||||
next = points[index % length];
|
||||
if (last && next.z < fromZ) {
|
||||
// When "in Ease-Down" (ie. while target Z not yet reached) - follow path while slowly decreasing Z.
|
||||
let deltaZ = fromZ - next.z;
|
||||
dist2next = last.distTo2D(next);
|
||||
let deltaZFullMove = dist2next * slope;
|
||||
|
||||
if (deltaZFullMove > deltaZ) {
|
||||
// Too long: easing along full path would overshoot depth, synth intermediate point at target Z.
|
||||
//
|
||||
// XXX: please check my super basic trig - this should follow from `last` to `next` up until the
|
||||
// intersect at the target Z distance.
|
||||
fn(last.followTo(next, dist2next * deltaZ / deltaZFullMove).setZ(next.z), offset++);
|
||||
} else {
|
||||
// Ok: execute full move at desired slope.
|
||||
next = next.clone().setZ(fromZ - deltaZFullMove);
|
||||
}
|
||||
|
||||
fromZ = next.z;
|
||||
} else if (offset === 0 && next.z < fromZ) {
|
||||
// First point, move to rampZ height above next.
|
||||
let deltaZ = fromZ - next.z;
|
||||
fromZ = next.z + Math.min(deltaZ, rampZ)
|
||||
next = next.clone().setZ(fromZ);
|
||||
}
|
||||
last = next;
|
||||
fn(next, offset++);
|
||||
if ((index % length) === touch) {
|
||||
break;
|
||||
}
|
||||
if (touch < 0 && next.z <= targetZ) {
|
||||
// Save touch-down index so as to be able to "complete" the full cut at target Z,
|
||||
// i.e. keep following the path loop until the touch down point is reached again.
|
||||
touch = ((index + length) % length);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
forEachPoint(fn, close, start) {
|
||||
let index = start || 0,
|
||||
points = this.points,
|
||||
length = points.length,
|
||||
|
|
@ -2003,7 +2059,7 @@ class Polygon {
|
|||
return res.map(array => {
|
||||
let poly = newPolygon();
|
||||
for (let pt of array) {
|
||||
poly.push(base.pointFromClipper(pt, z));
|
||||
poly.push(pointFromClipper(pt, z));
|
||||
}
|
||||
return poly;
|
||||
});
|
||||
|
|
@ -2318,8 +2374,7 @@ class Polygon {
|
|||
|
||||
}
|
||||
|
||||
// use Slope.angleDiff() then re-test path mitering / rendering
|
||||
function slopeDiff(s1, s2) {
|
||||
export function slopeDiff(s1, s2) {
|
||||
const n1 = s1.angle;
|
||||
const n2 = s2.angle;
|
||||
let diff = n2 - n1;
|
||||
|
|
@ -2328,28 +2383,17 @@ function slopeDiff(s1, s2) {
|
|||
return Math.abs(diff);
|
||||
}
|
||||
|
||||
function fromClipperPath(path, z) {
|
||||
export function fromClipperPath(path, z) {
|
||||
let poly = newPolygon(),
|
||||
i = 0,
|
||||
l = path.length;
|
||||
while (i < l) {
|
||||
// poly.push(newPoint(null,null,z,null,path[i++]));
|
||||
poly.push(base.pointFromClipper(path[i++], z));
|
||||
poly.push(pointFromClipper(path[i++], z));
|
||||
}
|
||||
return poly;
|
||||
}
|
||||
|
||||
function newPolygon(points) {
|
||||
export function newPolygon(points) {
|
||||
return new Polygon(points);
|
||||
}
|
||||
|
||||
Polygon.fromArray = function(array) {
|
||||
return newPolygon().fromArray(array);
|
||||
};
|
||||
|
||||
gapp.overlay(base, {
|
||||
Polygon,
|
||||
newPolygon
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,45 +2,45 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
// use: ext.clip2
|
||||
// use: geo.slope
|
||||
// use: geo.polygon
|
||||
gapp.register("geo.polygons", [], (root, exports) => {
|
||||
import { base, util, config } from './base.js';
|
||||
import { newPoint, pointFromClipper } from './point.js';
|
||||
import { newPolygon } from './polygon.js';
|
||||
import { newSlope } from './slope.js';
|
||||
import { paths } from './paths.js';
|
||||
import { ClipperLib } from '../ext/clip2.esm.js';
|
||||
|
||||
const { base } = root;
|
||||
const { util, paths, config, newPoint } = base;
|
||||
const { sqr, numOrDefault } = util;
|
||||
const geo = base;
|
||||
const { numOrDefault } = util;
|
||||
|
||||
const DEG2RAD = Math.PI / 180,
|
||||
SQRT = Math.sqrt,
|
||||
SQR = util.sqr,
|
||||
ABS = Math.abs;
|
||||
|
||||
const ClipperLib = self.ClipperLib,
|
||||
Clipper = ClipperLib.Clipper,
|
||||
ClipType = ClipperLib.ClipType,
|
||||
PolyType = ClipperLib.PolyType,
|
||||
PolyFillType = ClipperLib.PolyFillType,
|
||||
CleanPolygon = Clipper.CleanPolygon,
|
||||
const {
|
||||
Clipper,
|
||||
ClipType,
|
||||
PolyType,
|
||||
PolyFillType,
|
||||
EndType,
|
||||
JoinType,
|
||||
PolyTree,
|
||||
ClipperOffset
|
||||
} = ClipperLib;
|
||||
|
||||
const CleanPolygon = Clipper.CleanPolygon,
|
||||
CleanPolygons = Clipper.CleanPolygons,
|
||||
SimplifyPolygons = Clipper.SimplifyPolygons,
|
||||
FillNonZero = PolyFillType.pftNonZero,
|
||||
FillEvenOdd = PolyFillType.pftEvenOdd,
|
||||
PathSubject = PolyType.ptSubject,
|
||||
PathClip = PolyType.ptClip,
|
||||
EndType = ClipperLib.EndType,
|
||||
JoinType = ClipperLib.JoinType,
|
||||
PolyTree = ClipperLib.PolyTree,
|
||||
ClipXOR = ClipType.ctXor,
|
||||
ClipDiff = ClipType.ctDifference,
|
||||
ClipUnion = ClipType.ctUnion,
|
||||
ClipIntersect = ClipType.ctIntersection,
|
||||
ClipperOffset = ClipperLib.ClipperOffset
|
||||
;
|
||||
ClipIntersect = ClipType.ctIntersection;
|
||||
|
||||
const POLYS = base.polygons = {
|
||||
const POLYS = {
|
||||
clearInner,
|
||||
rayIntersect,
|
||||
alignWindings,
|
||||
|
|
@ -73,14 +73,16 @@ const POLYS = base.polygons = {
|
|||
fingerprint
|
||||
};
|
||||
|
||||
function outer(polys) {
|
||||
export { POLYS };
|
||||
|
||||
export function outer(polys) {
|
||||
for (let p of polys) {
|
||||
p.inner = undefined;
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
|
||||
function inner(polys) {
|
||||
export function inner(polys) {
|
||||
const ret = [];
|
||||
for (let p of polys) {
|
||||
if (p.inner) {
|
||||
|
|
@ -90,7 +92,7 @@ function inner(polys) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
function length(polys) {
|
||||
export function length(polys) {
|
||||
let length = 0;
|
||||
for (let p of polys) {
|
||||
length += p.deepLength;
|
||||
|
|
@ -98,20 +100,20 @@ function length(polys) {
|
|||
return length;
|
||||
}
|
||||
|
||||
function setZ(polys, z) {
|
||||
export function setZ(polys, z) {
|
||||
for (let poly of polys) {
|
||||
poly.setZ(z);
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
|
||||
function clearInner(polys) {
|
||||
export function clearInner(polys) {
|
||||
for (let p of polys) {
|
||||
p.clearInner();
|
||||
}
|
||||
}
|
||||
|
||||
function toClipper(polys = []) {
|
||||
export function toClipper(polys = []) {
|
||||
let out = [];
|
||||
for (let poly of polys) {
|
||||
poly.toClipper(out);
|
||||
|
|
@ -119,16 +121,16 @@ function toClipper(polys = []) {
|
|||
return out;
|
||||
}
|
||||
|
||||
function fromClipperNode(tnode, z) {
|
||||
let poly = base.newPolygon();
|
||||
export function fromClipperNode(tnode, z) {
|
||||
let poly = newPolygon();
|
||||
for (let point of tnode.m_polygon) {
|
||||
poly.push(base.pointFromClipper(point, z));
|
||||
poly.push(pointFromClipper(point, z));
|
||||
}
|
||||
poly.open = tnode.IsOpen;
|
||||
return poly;
|
||||
};
|
||||
}
|
||||
|
||||
function fromClipperTree(tnode, z, tops, parent, minarea) {
|
||||
export function fromClipperTree(tnode, z, tops, parent, minarea) {
|
||||
let poly,
|
||||
polys = tops || [],
|
||||
min = numOrDefault(minarea, 0.1);
|
||||
|
|
@ -150,9 +152,9 @@ function fromClipperTree(tnode, z, tops, parent, minarea) {
|
|||
}
|
||||
|
||||
return polys;
|
||||
};
|
||||
}
|
||||
|
||||
function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
|
||||
export function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
|
||||
let polys = tops || [], poly;
|
||||
|
||||
for (let child of tnode.m_Childs) {
|
||||
|
|
@ -171,9 +173,9 @@ function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
|
|||
}
|
||||
|
||||
return polys;
|
||||
};
|
||||
}
|
||||
|
||||
function cleanClipperTree(tree) {
|
||||
export function cleanClipperTree(tree) {
|
||||
if (tree.m_Childs)
|
||||
for (let child of tree.m_Childs) {
|
||||
child.m_polygon = CleanPolygon(child.m_polygon, config.clipperClean);
|
||||
|
|
@ -181,9 +183,9 @@ function cleanClipperTree(tree) {
|
|||
}
|
||||
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
function filter(array, output, fn) {
|
||||
export function filter(array, output, fn) {
|
||||
for (let poly of array) {
|
||||
poly = fn(poly);
|
||||
if (poly) {
|
||||
|
|
@ -197,14 +199,14 @@ function filter(array, output, fn) {
|
|||
return output;
|
||||
}
|
||||
|
||||
function points(polys) {
|
||||
export function points(polys) {
|
||||
return polys.length ? polys.map(p => p.deepLength).reduce((a,v) => a+v) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* redo nesting of polygons that might already have inners
|
||||
*/
|
||||
function renest(polygons, deep) {
|
||||
export function renest(polygons, deep) {
|
||||
return nest(flatten(polygons, [], true), deep);
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +222,7 @@ function renest(polygons, deep) {
|
|||
* @param {boolean} opentop prevent open polygons from having inners
|
||||
* @returns {Polygon[]} top level parent polygons
|
||||
*/
|
||||
function nest(polygons, deep, opentop) {
|
||||
export function nest(polygons, deep, opentop) {
|
||||
if (!polygons) {
|
||||
return polygons;
|
||||
}
|
||||
|
|
@ -284,7 +286,7 @@ function nest(polygons, deep, opentop) {
|
|||
* @param {boolean} CW
|
||||
* @param {boolean} [recurse]
|
||||
*/
|
||||
function setWinding(array, CW, recurse) {
|
||||
export function setWinding(array, CW, recurse) {
|
||||
if (!array) return;
|
||||
let poly, i = 0;
|
||||
while (i < array.length) {
|
||||
|
|
@ -302,7 +304,7 @@ function setWinding(array, CW, recurse) {
|
|||
* @param {Polygon[]} polys
|
||||
* @return {boolean} true if aligned clockwise
|
||||
*/
|
||||
function alignWindings(polys) {
|
||||
export function alignWindings(polys) {
|
||||
let len = polys.length,
|
||||
fwd = 0,
|
||||
pts = 0,
|
||||
|
|
@ -323,21 +325,14 @@ function alignWindings(polys) {
|
|||
return setCW;
|
||||
}
|
||||
|
||||
function setContains(setA, poly) {
|
||||
export function setContains(setA, poly) {
|
||||
for (let i=0; i<setA.length; i++) {
|
||||
if (setA[i].contains(poly)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten an array of polygons into a single array of polygons.
|
||||
* @param {Polygon[]} polys - input array of polygons
|
||||
* @param {Polygon[]} [to] - output array. if omitted, a new array will be created
|
||||
* @param {boolean} [crush] - if true, remove the inner array after flattening
|
||||
* @returns {Polygon[]} - the flattened array
|
||||
*/
|
||||
function flatten(polys, to, crush) {
|
||||
export function flatten(polys, to, crush) {
|
||||
to = to || [];
|
||||
for (let poly of polys) {
|
||||
poly.flattenTo(to);
|
||||
|
|
@ -358,7 +353,7 @@ function flatten(polys, to, crush) {
|
|||
* @param {number} [minArea]
|
||||
* @returns {Polygon[]} out
|
||||
*/
|
||||
function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
||||
export function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
||||
let min = numOrDefault(minArea, 0.1),
|
||||
out = [];
|
||||
|
||||
|
|
@ -440,63 +435,63 @@ function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
|||
* @param {Polygon[]} polys
|
||||
* @returns {Polygon[]}
|
||||
*/
|
||||
function union(polys, minarea, all, opt = {}) {
|
||||
if (polys.length < 2) return polys;
|
||||
let lpre = length(polys);
|
||||
export function union(polys, minarea, all, opt = {}) {
|
||||
if (polys.length < 2) return polys;
|
||||
let lpre = length(polys);
|
||||
|
||||
if (opt.wasm && geo.wasm) {
|
||||
let min = minarea ?? 0.01;
|
||||
// let deepLength = polys.map(p => p.deepLength).reduce((a,v) => a+v);
|
||||
// if (deepLength < 15000)
|
||||
try {
|
||||
let out = geo.wasm.js.union(polys, polys[0].getZ()).filter(p => p.area() > min);
|
||||
opt.changes = length(out) - lpre;
|
||||
return out;
|
||||
} catch (e) {
|
||||
console.log({union_fail: polys, minarea, all});
|
||||
}
|
||||
}
|
||||
if (opt.wasm && geo.wasm) {
|
||||
let min = minarea ?? 0.01;
|
||||
// let deepLength = polys.map(p => p.deepLength).reduce((a,v) => a+v);
|
||||
// if (deepLength < 15000)
|
||||
try {
|
||||
let out = geo.wasm.js.union(polys, polys[0].getZ()).filter(p => p.area() > min);
|
||||
opt.changes = length(out) - lpre;
|
||||
return out;
|
||||
} catch (e) {
|
||||
console.log({union_fail: polys, minarea, all});
|
||||
}
|
||||
}
|
||||
|
||||
let out = polys.slice(), i, j, union, uset = [], a, b;
|
||||
let out = polys.slice(), i, j, union, uset = [], a, b;
|
||||
|
||||
outer: for (i=0; i<out.length; i++) {
|
||||
if (!out[i]) continue;
|
||||
for (j=i+1; j<out.length; j++) {
|
||||
if (!out[j]) continue;
|
||||
union = out[i].union(out[j], minarea, all);
|
||||
if (union && union.length) {
|
||||
if (opt.onmerge) {
|
||||
a = out[i];
|
||||
b = out[j];
|
||||
}
|
||||
out[i] = null;
|
||||
out[j] = null;
|
||||
if (all) {
|
||||
out.appendAll(union);
|
||||
} else {
|
||||
out.push(union);
|
||||
}
|
||||
if (opt.onmerge) {
|
||||
opt.onmerge(a, b, union);
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
outer: for (i=0; i<out.length; i++) {
|
||||
if (!out[i]) continue;
|
||||
for (j=i+1; j<out.length; j++) {
|
||||
if (!out[j]) continue;
|
||||
union = out[i].union(out[j], minarea, all);
|
||||
if (union && union.length) {
|
||||
if (opt.onmerge) {
|
||||
a = out[i];
|
||||
b = out[j];
|
||||
}
|
||||
out[i] = null;
|
||||
out[j] = null;
|
||||
if (all) {
|
||||
out.appendAll(union);
|
||||
} else {
|
||||
out.push(union);
|
||||
}
|
||||
if (opt.onmerge) {
|
||||
opt.onmerge(a, b, union);
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<out.length; i++) {
|
||||
if (out[i]) uset.push(out[i]);
|
||||
}
|
||||
for (i=0; i<out.length; i++) {
|
||||
if (out[i]) uset.push(out[i]);
|
||||
}
|
||||
|
||||
opt.changes = length(uset) - lpre;
|
||||
return uset;
|
||||
}
|
||||
opt.changes = length(uset) - lpre;
|
||||
return uset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon} poly clipping mask
|
||||
* @returns {?Polygon[]}
|
||||
*/
|
||||
function diff(setA, setB, z) {
|
||||
export function diff(setA, setB, z) {
|
||||
let clip = new Clipper(),
|
||||
tree = new PolyTree(),
|
||||
sp1 = toClipper(setA),
|
||||
|
|
@ -516,7 +511,7 @@ function diff(setA, setB, z) {
|
|||
* @param {Polygon} poly clipping mask
|
||||
* @returns {?Polygon[]}
|
||||
*/
|
||||
function xor(set, z) {
|
||||
export function xor(set, z) {
|
||||
z = z || set[0].getZ();
|
||||
outer: for (;;) {
|
||||
// sort largest to smallest area
|
||||
|
|
@ -559,7 +554,7 @@ function diff(setA, setB, z) {
|
|||
* @param {Polygon[]} setB mask set
|
||||
* @returns {Polygon[]}
|
||||
*/
|
||||
function trimTo(setA, setB) {
|
||||
export function trimTo(setA, setB) {
|
||||
// handle null/empty slices
|
||||
if (setA === setB || setA === null || setB === null) return null;
|
||||
|
||||
|
|
@ -573,7 +568,7 @@ function trimTo(setA, setB) {
|
|||
return out;
|
||||
}
|
||||
|
||||
function sumCirc(polys) {
|
||||
export function sumCirc(polys) {
|
||||
let sum = 0.0;
|
||||
polys.forEach(function(poly) {
|
||||
sum += poly.circularityDeep();
|
||||
|
|
@ -591,7 +586,7 @@ function sumCirc(polys) {
|
|||
* @param {Function} [collector] receives output of each pass
|
||||
* @returns {Polygon[]} last offset
|
||||
*/
|
||||
function expand(polys, distance, z, out, count, distance2, collector, min) {
|
||||
export function expand(polys, distance, z, out, count, distance2, collector, min) {
|
||||
return offset(polys, [distance, distance2 || distance], {
|
||||
z, outs: out, call: collector, minArea: min, count, flat: true
|
||||
});
|
||||
|
|
@ -602,11 +597,11 @@ function expand(polys, distance, z, out, count, distance2, collector, min) {
|
|||
* and return resulting gaps from offsets for thin wall detection in
|
||||
* in FDM mode and uncleared areas in CAM mode.
|
||||
*/
|
||||
function offset(polys, dist, opts = {}) {
|
||||
export function offset(polys, dist, opts = {}) {
|
||||
let open = opts.open ? polys.filter(p => p.open) : [];
|
||||
if (open.length) {
|
||||
open = open.map(p => paths.pointsToPath(p.points, dist, true));
|
||||
open = open.map(p => base.newPolygon().setOpen().addPoints(p.right));
|
||||
open = open.map(p => newPolygon().setOpen().addPoints(p.right));
|
||||
}
|
||||
|
||||
// do not use clipper to offset open lines
|
||||
|
|
@ -706,7 +701,7 @@ function offset(polys, dist, opts = {}) {
|
|||
* as performing subtractive analysis between initial layer shell (ref)
|
||||
* and last offset (cmp) to produce gap candidates (for thinfill)
|
||||
*/
|
||||
function inset(polys, dist, count, z, wasm) {
|
||||
export function inset(polys, dist, count, z, wasm) {
|
||||
let total = count;
|
||||
let layers = [];
|
||||
let ref = polys;
|
||||
|
|
@ -749,7 +744,7 @@ function inset(polys, dist, count, z, wasm) {
|
|||
* @param {number} [maxLen]
|
||||
* @returns {Point[]} supplied output or new array
|
||||
*/
|
||||
function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
||||
export function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
||||
if (polys.length === 0) return;
|
||||
|
||||
let i = 1,
|
||||
|
|
@ -771,7 +766,7 @@ function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
|||
while (angle > 90) angle -= 180;
|
||||
|
||||
// X,Y ray slope derived from angle
|
||||
raySlope = base.newSlope(0,0,
|
||||
raySlope = newSlope(0,0,
|
||||
Math.cos(angle * DEG2RAD) * spacing,
|
||||
Math.sin(angle * DEG2RAD) * spacing
|
||||
);
|
||||
|
|
@ -843,8 +838,8 @@ function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
|||
if (minlen && plen < minlen) continue;
|
||||
if (maxlen && plen > maxlen) continue;
|
||||
}
|
||||
let p1 = base.pointFromClipper(poly.m_polygon[0], zpos);
|
||||
let p2 = base.pointFromClipper(poly.m_polygon[1], zpos);
|
||||
let p1 = pointFromClipper(poly.m_polygon[0], zpos);
|
||||
let p2 = pointFromClipper(poly.m_polygon[1], zpos);
|
||||
let od = rayint.origin.distToLineNew(p1,p2) / spacing;
|
||||
lines.push([p1, p2, od]);
|
||||
}
|
||||
|
|
@ -875,7 +870,7 @@ function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
|||
* @param {boolean} [for_fill]
|
||||
* @returns {Point[]}
|
||||
*/
|
||||
function rayIntersect(start, slope, polygons, for_fill) {
|
||||
export function rayIntersect(start, slope, polygons, for_fill) {
|
||||
let i = 0,
|
||||
flat = [],
|
||||
points = [],
|
||||
|
|
@ -1004,11 +999,11 @@ function rayIntersect(start, slope, polygons, for_fill) {
|
|||
return points;
|
||||
}
|
||||
|
||||
function pd(a,b) {
|
||||
export function pd(a,b) {
|
||||
return a > b ? Math.abs(1-b/a) : Math.abs(1-a/b);
|
||||
}
|
||||
|
||||
function fingerprint(polys) {
|
||||
export function fingerprint(polys) {
|
||||
let recs = flatten(polys).map(p => {
|
||||
return {
|
||||
l: p.length,
|
||||
|
|
@ -1037,7 +1032,7 @@ function fingerprint(polys) {
|
|||
}
|
||||
|
||||
// compare fingerprint arrays
|
||||
function fingerprintCompare(a, b) {
|
||||
export function fingerprintCompare(a, b) {
|
||||
// true if array is the same object
|
||||
if (a === b) {
|
||||
return true;
|
||||
|
|
@ -1079,7 +1074,7 @@ function fingerprintCompare(a, b) {
|
|||
|
||||
// plan a route through an array of polygon center points
|
||||
// starting with the polygon center closest to "start"
|
||||
function route(polys, start) {
|
||||
export function route(polys, start) {
|
||||
let centers = [];
|
||||
let first, minDist = Infinity;
|
||||
for (let poly of polys) {
|
||||
|
|
@ -1116,4 +1111,4 @@ function route(polys, start) {
|
|||
return routed.map(r => r.poly);
|
||||
}
|
||||
|
||||
});
|
||||
export const polygons = POLYS;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* basic slice and line connection. In future, replace kiri's fdm and cam slicers
|
||||
* with wrappers on this one.
|
||||
*/
|
||||
// dep: geo.base
|
||||
// dep: geo.line
|
||||
// dep: geo.point
|
||||
// dep: geo.polygon
|
||||
// dep: geo.polygons
|
||||
gapp.register("geo.slicer", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { config, util, polygons } = base
|
||||
const { newOrderedLine, newPolygon, newPoint } = base;
|
||||
|
||||
const POLY = base.polygons;
|
||||
import { base } from './base.js';
|
||||
import { newOrderedLine } from './line.js';
|
||||
import { newPolygon } from '../geo/polygon.js';
|
||||
import { polygons } from '../geo/polygons.js';
|
||||
import { newPoint } from '../geo/point.js';
|
||||
import { config } from '../geo/base.js';
|
||||
|
||||
function dval(v, dv) {
|
||||
return v !== undefined ? v : dv;
|
||||
|
|
@ -32,7 +25,7 @@ function dval(v, dv) {
|
|||
* @param {Point[]} points vertex array
|
||||
* @param {Object} options slicing parameters
|
||||
*/
|
||||
async function slice(points, options = {}) {
|
||||
export async function slice(points, options = {}) {
|
||||
let zMin = options.zMin || 0,
|
||||
zMax = options.zMax || 0,
|
||||
zInc = options.zInc || 0,
|
||||
|
|
@ -72,7 +65,6 @@ async function slice(points, options = {}) {
|
|||
for (i = 0; i < points.length; i++) {
|
||||
points[i] = points[i].round(3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// gather z-index stats
|
||||
|
|
@ -86,7 +78,7 @@ async function slice(points, options = {}) {
|
|||
if (p1.z === p2.z && p2.z === p3.z && p1.z >= zMin) {
|
||||
// detect faces co-planar with Z and sum the enclosed area
|
||||
let zkey = p1.z,
|
||||
area = Math.abs(util.area2(p1,p2,p3)) / 2;
|
||||
area = Math.abs(base.util.area2(p1,p2,p3)) / 2;
|
||||
if (!zFlat[zkey]) {
|
||||
zFlat[zkey] = area;
|
||||
} else {
|
||||
|
|
@ -322,7 +314,7 @@ function makeZLine(phash, p1, p2, coplanar, edge) {
|
|||
*
|
||||
* @param {number} z
|
||||
*/
|
||||
async function sliceZ(z, points, options = {}) {
|
||||
export async function sliceZ(z, points, options = {}) {
|
||||
if (Array.isArray(z)) {
|
||||
return Promise.all(z.map(z => sliceZ(z, points, options)));
|
||||
}
|
||||
|
|
@ -388,14 +380,14 @@ async function sliceZ(z, points, options = {}) {
|
|||
if (groupFn) {
|
||||
let groups = groupFn(lines, z, options);
|
||||
if (options.xor) {
|
||||
groups = POLY.xor(groups);
|
||||
groups = polygons.xor(groups);
|
||||
}
|
||||
if (options.union) {
|
||||
let points = groups.map(p => p.length);
|
||||
if (points.length > 1) points = points.reduce((a,b) => a + b);
|
||||
// simplistic healing of non-manifold meshes
|
||||
let opt = { x: 1 };
|
||||
let union = POLY.union(POLY.nest(groups), 0.1, true, opt);
|
||||
let union = polygons.union(polygons.nest(groups), 0.1, true, opt);
|
||||
// fall back to xor'ing polygons that might overlap
|
||||
// when one does not cleanly contain the other and we lose lots of points
|
||||
// trigger when 2 polygons and we lose > 40% of points in the union
|
||||
|
|
@ -409,14 +401,14 @@ async function sliceZ(z, points, options = {}) {
|
|||
}
|
||||
// track total poly length changes to determine if healed
|
||||
rval.changes = opt.changes;
|
||||
groups = POLY.flatten(union, null, true);
|
||||
groups = polygons.flatten(union, null, true);
|
||||
}
|
||||
rval.groups = groups;
|
||||
}
|
||||
|
||||
// look for driver-specific slice post-processor
|
||||
if (options.post) {
|
||||
let fn = base.slicePost[options.post];
|
||||
let fn = slicer.slicePost[options.post];
|
||||
if (fn) fn(rval, options);
|
||||
}
|
||||
|
||||
|
|
@ -458,7 +450,7 @@ async function sliceZ(z, points, options = {}) {
|
|||
* @param {number} [index]
|
||||
* @returns {Array}
|
||||
*/
|
||||
function sliceConnect(input, z, opt = {}) {
|
||||
export function sliceConnect(input, z, opt = {}) {
|
||||
let { debug, both } = opt;
|
||||
|
||||
if (both) {
|
||||
|
|
@ -782,7 +774,7 @@ function sliceConnect(input, z, opt = {}) {
|
|||
* @param {Line[]} lines
|
||||
* @returns {Line[]}
|
||||
*/
|
||||
function removeDuplicateLines(lines) {
|
||||
export function removeDuplicateLines(lines) {
|
||||
let output = [],
|
||||
tmplines = [],
|
||||
points = [],
|
||||
|
|
@ -873,12 +865,11 @@ function removeDuplicateLines(lines) {
|
|||
return output;
|
||||
}
|
||||
|
||||
gapp.overlay(base, {
|
||||
export const slicer = {
|
||||
slice,
|
||||
sliceZ,
|
||||
slicePost: {},
|
||||
sliceDedup: removeDuplicateLines,
|
||||
sliceConnect
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,19 +2,13 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// use: geo.line
|
||||
// use: geo.point
|
||||
gapp.register("geo.slope", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { config } = base;
|
||||
import { config } from './base.js';
|
||||
|
||||
const ABS = Math.abs,
|
||||
DEG2RAD = Math.PI / 180,
|
||||
RAD2DEG = 180 / Math.PI;
|
||||
|
||||
class Slope {
|
||||
export class Slope {
|
||||
constructor(p1, p2, dx, dy) {
|
||||
this.dx = p1 && p2 ? p2.x - p1.x : dx;
|
||||
this.dy = p1 && p2 ? p2.y - p1.y : dy;
|
||||
|
|
@ -94,21 +88,13 @@ function angleWithinDelta(a1, a2, delta) {
|
|||
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
|
||||
}
|
||||
|
||||
function newSlope(p1, p2, dx, dy) {
|
||||
export function newSlope(p1, p2, dx, dy) {
|
||||
return new Slope(p1, p2, dx, dy);
|
||||
}
|
||||
|
||||
function newSlopeFromAngle(angle) {
|
||||
export function newSlopeFromAngle(angle) {
|
||||
return newSlope(0,0,
|
||||
Math.cos(angle * DEG2RAD),
|
||||
Math.sin(angle * DEG2RAD)
|
||||
);
|
||||
}
|
||||
|
||||
gapp.overlay(base, {
|
||||
Slope,
|
||||
newSlope,
|
||||
newSlopeFromAngle
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { base } from '../geo/base.js';
|
||||
|
||||
// dep: geo.base
|
||||
// dep: ext.clip2
|
||||
gapp.register("geo.wasm", [], (root, exports) => {
|
||||
|
||||
const { base } = root;
|
||||
const { config } = base;
|
||||
|
||||
const factor = config.clipper;
|
||||
const wasm_ctrl = {
|
||||
|
||||
export const wasm_ctrl = {
|
||||
enable,
|
||||
disable,
|
||||
count: {
|
||||
|
|
@ -20,8 +16,6 @@ const wasm_ctrl = {
|
|||
}
|
||||
};
|
||||
|
||||
gapp.overlay(base, { wasm_ctrl });
|
||||
|
||||
function log() {
|
||||
console.log(...arguments);
|
||||
}
|
||||
|
|
@ -60,7 +54,7 @@ function writePoly(view, poly, inner) {
|
|||
function readPoly(view, z) {
|
||||
let points = view.readU16(true);
|
||||
if (points === 0) return;
|
||||
let poly = self.base.newPolygon();
|
||||
let poly = base.newPolygon();
|
||||
while (points-- > 0) {
|
||||
poly.add(view.readI32(true)/factor, view.readI32(true)/factor, z || 0);
|
||||
}
|
||||
|
|
@ -79,7 +73,7 @@ function readPolys(view, z, out = []) {
|
|||
return out;
|
||||
}
|
||||
|
||||
function polyOffset(polys, offset, z, clean, simple) {
|
||||
export function polyOffset(polys, offset, z, clean, simple) {
|
||||
wasm_ctrl.count.offset++;
|
||||
let wasm = base.wasm,
|
||||
buffer = wasm.shared,
|
||||
|
|
@ -89,7 +83,7 @@ function polyOffset(polys, offset, z, clean, simple) {
|
|||
return polyNest(out);
|
||||
}
|
||||
|
||||
function polyUnion(polys, z) {
|
||||
export function polyUnion(polys, z) {
|
||||
wasm_ctrl.count.union++;
|
||||
let wasm = base.wasm,
|
||||
buffer = wasm.shared,
|
||||
|
|
@ -99,7 +93,7 @@ function polyUnion(polys, z) {
|
|||
return polyNest(out);
|
||||
}
|
||||
|
||||
function polyDiff(polysA, polysB, z, AB, BA) {
|
||||
export function polyDiff(polysA, polysB, z, AB, BA) {
|
||||
wasm_ctrl.count.diff++;
|
||||
let wasm = base.wasm,
|
||||
buffer = wasm.shared,
|
||||
|
|
@ -153,7 +147,7 @@ function readString(pos, len) {
|
|||
return out.join('');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
export function enable() {
|
||||
if (base.wasm || base._wasm) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -200,11 +194,9 @@ function enable() {
|
|||
});
|
||||
}
|
||||
|
||||
function disable() {
|
||||
export function disable() {
|
||||
if (base.wasm) {
|
||||
base.wasm.free(base.wasm.shared);
|
||||
delete base.wasm;
|
||||
// console.log({disabled: geo});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,648 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri-mode.cam.driver
|
||||
// dep: kiri-mode.cam.animate2
|
||||
gapp.register("kiri-mode.cam.animate", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { CAM } = driver;
|
||||
const asLines = false;
|
||||
const asPoints = false;
|
||||
|
||||
// ---( CLIENT FUNCTIONS )---
|
||||
|
||||
// tint points below z=0 with red
|
||||
function add_red_neg_z(material) {
|
||||
material.onBeforeCompile = (shader) => {
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
`#include <worldpos_vertex>`,
|
||||
`
|
||||
#include <worldpos_vertex>
|
||||
vWorldPosition = vec3(transformed);
|
||||
`
|
||||
);
|
||||
|
||||
shader.vertexShader = `
|
||||
varying vec3 vWorldPosition;
|
||||
` + shader.vertexShader;
|
||||
|
||||
shader.fragmentShader = `
|
||||
varying vec3 vWorldPosition;
|
||||
` + shader.fragmentShader;
|
||||
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <dithering_fragment>`,
|
||||
`
|
||||
#include <dithering_fragment>
|
||||
if (vWorldPosition.z < 0.0) {
|
||||
gl_FragColor.rgb += vec3(0.5, 0.0, 0.0); // Add red tint
|
||||
}
|
||||
`
|
||||
);
|
||||
};
|
||||
return material;
|
||||
}
|
||||
|
||||
kiri.load(() => {
|
||||
if (!kiri.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
let meshes = {},
|
||||
button = {},
|
||||
label = {},
|
||||
unitScale = 1,
|
||||
speedValues = [ 1, 2, 4, 8, 32 ],
|
||||
speedPauses = [ 30, 20, 10, 5, 0 ],
|
||||
speedNames = [ "1x", "2x", "4x", "8x", "!!" ],
|
||||
speedMax = speedValues.length - 1,
|
||||
speedIndex = 0,
|
||||
speed,
|
||||
color = 0,
|
||||
material,
|
||||
origin,
|
||||
posOffset = { x:0, y:0, z:0 };
|
||||
|
||||
const { moto } = root;
|
||||
const { space } = moto;
|
||||
const { api } = kiri;
|
||||
|
||||
function animate_clear(api) {
|
||||
let { anim } = api.ui;
|
||||
moto.space.platform.showGridBelow(true);
|
||||
kiri.client.animate_cleanup();
|
||||
Object.keys(meshes).forEach(id => deleteMesh(id));
|
||||
toggleStock(undefined,true,false);
|
||||
api.uc.setVisible(anim.laba, false);
|
||||
api.uc.setVisible(anim.vala, false);
|
||||
}
|
||||
|
||||
function animate(api, delay) {
|
||||
let alert = api.alerts.show("building animation");
|
||||
let settings = api.conf.get();
|
||||
kiri.client.animate_setup(settings, data => {
|
||||
checkMeshCommands(data);
|
||||
if (!(data && data.mesh_add)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { anim } = api.ui;
|
||||
Object.assign(button, {
|
||||
replay: anim.replay,
|
||||
play: anim.play,
|
||||
step: anim.step,
|
||||
pause: anim.pause,
|
||||
speed: anim.speed,
|
||||
trans: anim.trans,
|
||||
model: anim.model,
|
||||
shade: anim.shade
|
||||
});
|
||||
Object.assign(label, {
|
||||
progress: anim.progress,
|
||||
speed: anim.labspd,
|
||||
x: anim.valx,
|
||||
y: anim.valy,
|
||||
z: anim.valz,
|
||||
});
|
||||
|
||||
origin = settings.origin;
|
||||
speedIndex = api.local.getInt('cam.anim.speed') || 0;
|
||||
updateSpeed();
|
||||
setTimeout(step, delay || 0);
|
||||
|
||||
button.replay.onclick = replay;
|
||||
button.play.onclick = play;
|
||||
button.step.onclick = step;
|
||||
button.pause.onclick = pause;
|
||||
button.speed.onclick = fast;
|
||||
button.trans.onclick = toggleTrans;
|
||||
button.model.onclick = toggleModel;
|
||||
button.shade.onclick = toggleStock;
|
||||
button.play.style.display = '';
|
||||
button.pause.style.display = 'none';
|
||||
|
||||
api.event.emit('animate', 'CAM');
|
||||
api.alerts.hide(alert);
|
||||
moto.space.platform.showGridBelow(false);
|
||||
toggleTrans(0,api.local.getBoolean('cam.anim.trans', true));
|
||||
toggleModel(0,api.local.getBoolean('cam.anim.model', false));
|
||||
toggleStock(0,api.local.getBoolean('cam.anim.stock', false));
|
||||
});
|
||||
}
|
||||
|
||||
gapp.overlay(kiri.client, {
|
||||
animate(data, ondone) {
|
||||
kiri.client.send("animate", data, ondone);
|
||||
},
|
||||
|
||||
animate_setup(settings, ondone) {
|
||||
color = settings.controller.dark ? 0x48607B : 0x607FA4;
|
||||
unitScale = settings.controller.units === 'in' ? 1/25.4 : 1;
|
||||
let flatShading = true,
|
||||
transparent = true,
|
||||
opacity = 0.9,
|
||||
side = THREE.DoubleSide;
|
||||
material = new THREE.MeshPhongMaterial({
|
||||
flatShading,
|
||||
transparent,
|
||||
opacity,
|
||||
color,
|
||||
side
|
||||
});
|
||||
add_red_neg_z(material);
|
||||
kiri.client.send("animate_setup", {settings}, ondone);
|
||||
},
|
||||
|
||||
animate_cleanup(data, ondone) {
|
||||
kiri.client.send("animate_cleanup", data, ondone);
|
||||
}
|
||||
});
|
||||
|
||||
gapp.overlay(CAM, {
|
||||
animate,
|
||||
animate_clear
|
||||
});
|
||||
|
||||
function meshAdd(id, ind, pos, sab) {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
if (sab) {
|
||||
// use array buffer shared with worker
|
||||
pos = new Float32Array(sab);
|
||||
}
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
||||
if (ind.length) {
|
||||
geo.setIndex(new THREE.BufferAttribute(new Uint32Array(ind), 1));
|
||||
}
|
||||
let mesh;
|
||||
if (asPoints) {
|
||||
const mat = new THREE.PointsMaterial({
|
||||
transparent: true,
|
||||
opacity: 0.75,
|
||||
color: 0x888888,
|
||||
size: 0.3
|
||||
});
|
||||
mesh = new THREE.Points(geo, mat);
|
||||
} else if (asLines) {
|
||||
const mat = new THREE.LineBasicMaterial({
|
||||
transparent: true,
|
||||
opacity: 0.75,
|
||||
color
|
||||
});
|
||||
mesh = new THREE.LineSegments(geo, mat);
|
||||
} else {
|
||||
geo.computeVertexNormals();
|
||||
mesh = new THREE.Mesh(geo, material);
|
||||
mesh.renderOrder = -10;
|
||||
}
|
||||
space.world.add(mesh);
|
||||
meshes[id] = mesh;
|
||||
}
|
||||
|
||||
function meshUpdates(id) {
|
||||
const mesh = meshes[id];
|
||||
if (!mesh) {
|
||||
return; // animate cancelled
|
||||
}
|
||||
mesh.geometry.attributes.position.needsUpdate = true;
|
||||
space.update();
|
||||
}
|
||||
|
||||
function deleteMesh(id) {
|
||||
space.world.remove(meshes[id]);
|
||||
delete meshes[id];
|
||||
}
|
||||
|
||||
function toggleModel(ev,bool) {
|
||||
api.local.toggle('cam.anim.model', bool);
|
||||
api.widgets.all().forEach(w => w.toggleVisibility(bool));
|
||||
}
|
||||
|
||||
function toggleStock(ev,bool,set) {
|
||||
set !== false && api.local.toggle('cam.anim.stock', bool);
|
||||
return api.event.emit('cam.stock.toggle', bool ?? undefined);
|
||||
}
|
||||
|
||||
function toggleTrans(ev,bool) {
|
||||
bool = api.local.toggle('cam.anim.trans', bool);
|
||||
material.transparent = bool;
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
|
||||
function step() {
|
||||
updateSpeed();
|
||||
kiri.client.animate({speed, steps: 1}, handleGridUpdate);
|
||||
}
|
||||
|
||||
function play(opts) {
|
||||
const { steps } = opts;
|
||||
updateSpeed();
|
||||
if (steps !== 1) {
|
||||
button.play.style.display = 'none';
|
||||
button.pause.style.display = '';
|
||||
}
|
||||
kiri.client.animate({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
}, handleGridUpdate);
|
||||
}
|
||||
|
||||
function fast(opts) {
|
||||
const { steps } = opts;
|
||||
updateSpeed(1);
|
||||
button.play.style.display = 'none';
|
||||
button.pause.style.display = '';
|
||||
kiri.client.animate({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
}, handleGridUpdate);
|
||||
}
|
||||
|
||||
function pause() {
|
||||
button.play.style.display = '';
|
||||
button.pause.style.display = 'none';
|
||||
kiri.client.animate({speed: 0}, handleGridUpdate);
|
||||
}
|
||||
|
||||
function handleGridUpdate(data) {
|
||||
checkMeshCommands(data);
|
||||
if (data && data.progress) {
|
||||
label.progress.value = (data.progress * 100).toFixed(1);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSpeed(inc = 0) {
|
||||
if (inc === Infinity) {
|
||||
speedIndex = speedMax;
|
||||
} else if (inc > 0) {
|
||||
speedIndex = (speedIndex + inc) % speedValues.length;
|
||||
}
|
||||
api.local.set('cam.anim.speed', speedIndex);
|
||||
speed = speedValues[speedIndex];
|
||||
label.speed.value = speedNames[speedIndex];
|
||||
}
|
||||
|
||||
function replay() {
|
||||
animate_clear(api);
|
||||
setTimeout(() => {
|
||||
animate(api, 50);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function checkMeshCommands(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (data.mesh_add) {
|
||||
const { id, ind, pos, offset, sab } = data.mesh_add;
|
||||
meshAdd(id, ind, pos, sab);
|
||||
space.refresh();
|
||||
if (offset) {
|
||||
posOffset = offset;
|
||||
}
|
||||
}
|
||||
if (data.mesh_del) {
|
||||
deleteMesh(data.mesh_del);
|
||||
}
|
||||
if (data.mesh_move) {
|
||||
const { id, pos } = data.mesh_move;
|
||||
const mesh = meshes[id];
|
||||
if (mesh) {
|
||||
mesh.position.x = pos.x;
|
||||
mesh.position.y = pos.y;
|
||||
mesh.position.z = pos.z;
|
||||
space.update();
|
||||
}
|
||||
label.x.value = (pos.x - origin.x).toFixed(2);
|
||||
label.y.value = (pos.y + origin.y).toFixed(2);
|
||||
label.z.value = (pos.z - origin.z).toFixed(2);
|
||||
}
|
||||
if (data.mesh_update) {
|
||||
meshUpdates(data.id);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ---( WORKER FUNCTIONS )---
|
||||
|
||||
kiri.load(() => {
|
||||
if (!kiri.worker) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stock, center, grid, gridX, gridY, rez;
|
||||
let path, pathIndex, tool, tools, last, toolID = 1;
|
||||
|
||||
kiri.worker.animate_setup = function(data, send) {
|
||||
const { settings } = data;
|
||||
const { process } = settings;
|
||||
const print = worker.print;
|
||||
const density = parseInt(settings.controller.animesh) * 1000;
|
||||
|
||||
pathIndex = 0;
|
||||
path = print.output.flat();
|
||||
tools = settings.tools;
|
||||
stock = settings.stock;
|
||||
rez = 1/Math.sqrt(density/(stock.x * stock.y));
|
||||
|
||||
|
||||
//destructure arcs into path points
|
||||
path = path.map(o=>
|
||||
o.arcPoints
|
||||
? [
|
||||
...o.arcPoints.map(point=>
|
||||
({
|
||||
...o,
|
||||
point
|
||||
})
|
||||
),
|
||||
o
|
||||
]
|
||||
: o
|
||||
)
|
||||
.flat();
|
||||
|
||||
|
||||
const step = rez;
|
||||
const stepsX = Math.floor(stock.x / step);
|
||||
const stepsY = Math.floor(stock.y / step);
|
||||
const { pos, ind, sab } = createGrid(stepsX, stepsY, stock, step, true);
|
||||
const offset = {
|
||||
x: process.camOriginCenter ? 0 : stock.x / 2,
|
||||
y: process.camOriginCenter ? 0 : stock.y / 2,
|
||||
z: process.camOriginTop ? -stock.z : 0
|
||||
}
|
||||
|
||||
grid = pos;
|
||||
gridX = stepsX;
|
||||
gridY = stepsY;
|
||||
|
||||
tool = null;
|
||||
last = null;
|
||||
animating = false;
|
||||
animateClear = false;
|
||||
|
||||
center = Object.assign({}, stock.center);
|
||||
center.z -= stock.z / 2;
|
||||
|
||||
send.data({ mesh_add: { id: 0, ind, offset, sab } }, [ ]); // sab not transferrable
|
||||
send.data({ mesh_move: { id: 0, pos: center } });
|
||||
send.done();
|
||||
};
|
||||
|
||||
kiri.worker.animate = function(data, send) {
|
||||
renderPause = data.pause || renderPause;
|
||||
renderSpeed = data.speed || 0;
|
||||
if (animating) {
|
||||
return send.done();
|
||||
}
|
||||
renderSteps = data.steps || 1;
|
||||
renderDone = false;
|
||||
animating = renderSpeed > 0;
|
||||
renderPath(send);
|
||||
};
|
||||
|
||||
kiri.worker.animate_cleanup = function(data, send) {
|
||||
if (animating) {
|
||||
animateClear = true;
|
||||
}
|
||||
};
|
||||
|
||||
function createGrid(stepsX, stepsY, size, step, stock) {
|
||||
const gridPoints = stepsX * stepsY;
|
||||
const sab = new SharedArrayBuffer(gridPoints * 3 * 4)
|
||||
const pos = new Float32Array(sab);
|
||||
const ind = [];
|
||||
const ox = size.x / 2;
|
||||
const oy = size.y / 2;
|
||||
let ex = stepsX - 1;
|
||||
let ey = stepsY - 1;
|
||||
|
||||
// initialize grid points
|
||||
for (let x=0, ai=0; x<stepsX; x++) {
|
||||
for (let y=0; y<stepsY; y++) {
|
||||
let px = pos[ai++] = x * step - ox + step / 2;
|
||||
let py = pos[ai++] = y * step - oy + step / 2;
|
||||
pos[ai++] = stock && (x * y === 0 || x === ex || y === ey) ? 0 : size.z;
|
||||
if (asPoints) {
|
||||
continue;
|
||||
}
|
||||
if (asLines) {
|
||||
if (y > 0) ind.appendAll([
|
||||
(stepsY * x) + (y - 1),
|
||||
(stepsY * x) + (y )
|
||||
]);
|
||||
if (x > 0) ind.appendAll([
|
||||
(stepsY * (x - 1)) + y,
|
||||
(stepsY * (x )) + y
|
||||
]);
|
||||
} else {
|
||||
if (x > 0 && y > 0) {
|
||||
let v0 = stepsY * (x - 1) + y - 1;
|
||||
let v1 = stepsY * (x - 0) + y - 1;
|
||||
let v2 = stepsY * (x - 0) + y;
|
||||
let v3 = stepsY * (x - 1) + y;
|
||||
ind.appendAll([
|
||||
v0, v1, v2, v0, v2, v3
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { pos, ind, sab };
|
||||
}
|
||||
|
||||
let animateClear = false;
|
||||
let animating = false;
|
||||
let renderDist = 0;
|
||||
let renderDone = false;
|
||||
let renderPause = 10;
|
||||
let renderSteps = 0;
|
||||
let renderSpeed = 0;
|
||||
let skipMove = null;
|
||||
let toolUpdate;
|
||||
let depth = 0;
|
||||
|
||||
// send latest tool position and progress bar
|
||||
function renderUpdate(send) {
|
||||
if (toolUpdate) {
|
||||
send.data(toolUpdate);
|
||||
}
|
||||
send.data({ progress: pathIndex / path.length, id: 0, mesh_update: 1 });
|
||||
}
|
||||
|
||||
function renderPath(send) {
|
||||
if (renderDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderSteps-- === 0) {
|
||||
animating = false;
|
||||
renderPath(send);
|
||||
return;
|
||||
}
|
||||
|
||||
if (animating === false || animateClear || renderSpeed === 0) {
|
||||
renderUpdate(send);
|
||||
renderDone = true;
|
||||
animating = false;
|
||||
animateClear = false;
|
||||
send.done();
|
||||
return;
|
||||
}
|
||||
|
||||
let next = path[pathIndex];
|
||||
while (next && next.type === 'laser') {
|
||||
last = next;
|
||||
next = path[++pathIndex];
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
animating = false;
|
||||
renderPath(send);
|
||||
return;
|
||||
}
|
||||
pathIndex++;
|
||||
|
||||
const firstTool = !tool && next.tool;
|
||||
const toolChange = !firstTool && (tool.getID() !== next.tool.getID());
|
||||
if (firstTool || toolChange) {
|
||||
// on real tool change, go to safe Z first
|
||||
if (tool && last.point) {
|
||||
let pos = last.point = {
|
||||
x: last.point.x,
|
||||
y: last.point.y,
|
||||
z: stock.z
|
||||
};
|
||||
send.data({ mesh_move: { toolID, pos }});
|
||||
}
|
||||
updateTool(next.tool, send);
|
||||
}
|
||||
|
||||
const id = toolID;
|
||||
const rezstep = rez;
|
||||
if (last) {
|
||||
const lp = last.point, np = next.point;
|
||||
last = next;
|
||||
// dwell ops have no point
|
||||
if (!np || !lp) {
|
||||
return renderPath(send);
|
||||
}
|
||||
const dx = np.x - lp.x, dy = np.y - lp.y, dz = np.z - lp.z;
|
||||
const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
|
||||
renderDist += dist;
|
||||
|
||||
// skip moves that are less than grid resolution
|
||||
if (renderDist < rezstep) {
|
||||
renderPath(send);
|
||||
return;
|
||||
}
|
||||
|
||||
const md = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz));
|
||||
const st = Math.ceil(md / rezstep);
|
||||
const mx = dx / st, my = dy / st, mz = dz / st;
|
||||
const moves = [];
|
||||
for (let i=0, x=lp.x, y=lp.y, z=lp.z; i<st; i++) {
|
||||
moves.push({x,y,z});
|
||||
x += mx;
|
||||
y += my;
|
||||
z += mz;
|
||||
}
|
||||
moves.push(next.point);
|
||||
renderMoves(id, moves, send);
|
||||
} else {
|
||||
last = next;
|
||||
if (tool) {
|
||||
tool.pos = next.point;
|
||||
toolUpdate = { mesh_move: { id, pos: next.point }};
|
||||
}
|
||||
renderPath(send);
|
||||
}
|
||||
}
|
||||
|
||||
function renderMoves(id, moves, send, seed = 0) {
|
||||
for (let index = seed; index<moves.length; index++) {
|
||||
const pos = moves[index];
|
||||
if (!pos) {
|
||||
throw `no pos @ ${index} of ${moves.length}`;
|
||||
}
|
||||
tool.pos = pos;
|
||||
deformMesh(pos, send);
|
||||
toolUpdate = { mesh_move: { id, pos }};
|
||||
// pause renderer at specified offsets
|
||||
if ((renderSpeed && renderDist >= renderSpeed) || (depth > 600)) {
|
||||
renderDist = depth = 0;
|
||||
renderUpdate(send);
|
||||
setTimeout(() => {
|
||||
renderMoves(id, moves, send, index);
|
||||
}, renderPause);
|
||||
return;
|
||||
}
|
||||
}
|
||||
depth++;
|
||||
renderPath(send);
|
||||
}
|
||||
|
||||
// update stock mesh to reflect tool tip geometry at given XYZ position
|
||||
function deformMesh(pos, send) {
|
||||
const prof = tool.profile;
|
||||
const { size, pix } = tool.profileDim;
|
||||
const mid = Math.floor(pix / 2);
|
||||
const rx = Math.floor((pos.x + stock.x / 2 - size / 2 - center.x) / rez);
|
||||
const ry = Math.floor((pos.y + stock.y / 2 - size / 2 - center.y) / rez);
|
||||
let upos = 0;
|
||||
// deform mesh to lowest point on tool profile
|
||||
for (let i=0, il=prof.length; i < il; ) {
|
||||
const dx = mid + prof[i++];
|
||||
const dy = mid + prof[i++];
|
||||
const dz = prof[i++];
|
||||
const gx = rx + dx;
|
||||
const gy = ry + dy;
|
||||
|
||||
if (gx < 0|| gy < 0 || gx > gridX-1 || gy > gridY-1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const gi = gx * gridY + gy;
|
||||
const iz = gi * 3 + 2;
|
||||
const cz = grid[iz];
|
||||
const tz = tool.pos.z - dz;
|
||||
if (tz < cz) {
|
||||
upos++;
|
||||
grid[iz] = tz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateTool(toolobj, send) {
|
||||
if (tool) {
|
||||
send.data({ mesh_del: toolID });
|
||||
}
|
||||
tool = new CAM.Tool({ tools }, toolobj.getID());
|
||||
tool.generateProfile(rez);
|
||||
const flen = tool.fluteLength() || 15;
|
||||
const slen = tool.shaftLength() || 15;
|
||||
// const frad = tool.fluteDiameter() / 2;
|
||||
const prof = tool.profile;
|
||||
const { size, pix } = tool.profileDim;
|
||||
const { pos, ind, sab } = createGrid(pix, pix, {x:size, y:size, z:flen+slen}, rez);
|
||||
const mid = Math.floor(pix/2);
|
||||
// deform mesh to fit tool profile
|
||||
for (let i=0, il=prof.length; i < il; ) {
|
||||
const dx = mid + prof[i++];
|
||||
const dy = mid + prof[i++];
|
||||
const dz = prof[i++];
|
||||
pos[(dx * pix + dy) * 3 + 2] = -dz;
|
||||
}
|
||||
send.data({ mesh_add: { id:++toolID, ind, sab }});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,670 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.csg
|
||||
// dep: kiri-mode.cam.driver
|
||||
gapp.register("kiri-mode.cam.animate2", [], (root) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { CAM } = driver;
|
||||
|
||||
// ---( CLIENT FUNCTIONS )---
|
||||
|
||||
kiri.load(() => {
|
||||
if (!kiri.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
let meshes = {},
|
||||
button = {},
|
||||
label = {},
|
||||
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;
|
||||
|
||||
const { moto } = root;
|
||||
const { space } = moto;
|
||||
const { api } = kiri;
|
||||
|
||||
function animate_clear2(api) {
|
||||
let { anim } = api.ui;
|
||||
kiri.client.animate_cleanup2();
|
||||
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";
|
||||
}
|
||||
|
||||
function animate2(api, delay) {
|
||||
let alert = api.alerts.show("building animation");
|
||||
let settings = api.conf.get();
|
||||
kiri.client.animate_setup2(settings, data => {
|
||||
handleUpdate(data);
|
||||
if (data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { anim } = api.ui;
|
||||
Object.assign(button, {
|
||||
replay: anim.replay,
|
||||
play: anim.play,
|
||||
step: anim.step,
|
||||
pause: anim.pause,
|
||||
speed: anim.speed,
|
||||
trans: anim.trans,
|
||||
model: anim.model,
|
||||
shade: anim.shade
|
||||
});
|
||||
Object.assign(label, {
|
||||
progress: anim.progress,
|
||||
speed: anim.labspd,
|
||||
x: anim.valx,
|
||||
y: anim.valy,
|
||||
z: anim.valz,
|
||||
a: anim.vala
|
||||
});
|
||||
|
||||
updateSpeed(0);
|
||||
setTimeout(step, delay || 0);
|
||||
toggleTrans(undefined, false);
|
||||
origin = settings.origin;
|
||||
|
||||
button.replay.onclick = replay;
|
||||
button.play.onclick = play;
|
||||
button.step.onclick = step;
|
||||
button.pause.onclick = pause;
|
||||
button.speed.onclick = fast;
|
||||
button.trans.onclick = toggleTrans;
|
||||
button.model.onclick = toggleModel;
|
||||
button.shade.onclick = toggleStock;
|
||||
button.play.style.display = '';
|
||||
button.pause.style.display = 'none';
|
||||
|
||||
api.event.emit('animate', 'CAM');
|
||||
api.alerts.hide(alert);
|
||||
});
|
||||
}
|
||||
|
||||
gapp.overlay(kiri.client, {
|
||||
animate2(data, ondone) {
|
||||
kiri.client.send("animate2", data, ondone);
|
||||
},
|
||||
|
||||
animate_setup2(settings, ondone) {
|
||||
color = settings.controller.dark ? 0x888888 : 0;
|
||||
material = new THREE.MeshMatcapMaterial({
|
||||
flatShading: true,
|
||||
transparent: false,
|
||||
opacity: 0.9,
|
||||
color: 0x888888,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
kiri.client.send("animate_setup2", {settings}, ondone);
|
||||
},
|
||||
|
||||
animate_cleanup2(data, ondone) {
|
||||
kiri.client.send("animate_cleanup2", data, ondone);
|
||||
}
|
||||
});
|
||||
|
||||
gapp.overlay(CAM, {
|
||||
animate2,
|
||||
animate_clear2
|
||||
});
|
||||
|
||||
function meshAdd(id, ind, pos, ilen, plen) {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
const pa = plen ? pos.subarray(0, plen * 3) : pos;
|
||||
const ia = ilen ? ind.subarray(0, ilen) : ind;
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(pa, 3));
|
||||
geo.setIndex(new THREE.BufferAttribute(ia, 1));
|
||||
const mesh = new THREE.Mesh(geo, material);
|
||||
mesh.pos = pos;
|
||||
mesh.ind = ind;
|
||||
space.world.add(mesh);
|
||||
meshes[id] = mesh;
|
||||
}
|
||||
|
||||
function meshUpdate(id, ind, pos, ilen, plen) {
|
||||
const mesh = meshes[id];
|
||||
if (!mesh) {
|
||||
return; // animate cancelled
|
||||
}
|
||||
const geo = mesh.geometry;
|
||||
mesh.pos = pos || mesh.pos;
|
||||
mesh.ind = ind || mesh.ind;
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(mesh.pos.subarray(0, plen * 3), 3));
|
||||
geo.setIndex(new THREE.BufferAttribute(mesh.ind.subarray(0, ilen), 1));
|
||||
geo.attributes.position.needsUpdate = true;
|
||||
geo.index.needsUpdate = true;
|
||||
space.update();
|
||||
}
|
||||
|
||||
function deleteMesh(id) {
|
||||
space.world.remove(meshes[id]);
|
||||
delete meshes[id];
|
||||
}
|
||||
|
||||
function toggleModel(ev,bool) {
|
||||
api.local.toggle('cam.anim.model', bool);
|
||||
api.widgets.all().forEach(w => w.toggleVisibility(bool));
|
||||
}
|
||||
|
||||
function toggleStock(ev,bool,set) {
|
||||
set !== false && api.local.toggle('cam.anim.stock', bool);
|
||||
return api.event.emit('cam.stock.toggle', bool ?? undefined);
|
||||
}
|
||||
|
||||
function toggleTrans(ev,bool) {
|
||||
bool = api.local.toggle('cam.anim.trans', bool);
|
||||
material.transparent = bool;
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
|
||||
function step() {
|
||||
updateSpeed();
|
||||
kiri.client.animate2({speed, steps: 1}, handleUpdate);
|
||||
}
|
||||
|
||||
function play(opts) {
|
||||
const { steps } = opts;
|
||||
updateSpeed();
|
||||
if (steps !== 1) {
|
||||
button.play.style.display = 'none';
|
||||
button.pause.style.display = '';
|
||||
}
|
||||
kiri.client.animate2({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
}, handleUpdate);
|
||||
}
|
||||
|
||||
function fast(opts) {
|
||||
const { steps } = opts;
|
||||
updateSpeed(1);
|
||||
button.play.style.display = 'none';
|
||||
button.pause.style.display = '';
|
||||
kiri.client.animate2({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
}, handleUpdate);
|
||||
}
|
||||
|
||||
function pause() {
|
||||
button.play.style.display = '';
|
||||
button.pause.style.display = 'none';
|
||||
kiri.client.animate2({speed: 0}, handleUpdate);
|
||||
}
|
||||
|
||||
function handleUpdate(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (data.mesh_add) {
|
||||
const { id, ind, pos, ilen, plen } = data.mesh_add;
|
||||
meshAdd(id, ind, pos, ilen, plen);
|
||||
space.refresh();
|
||||
}
|
||||
if (data.mesh_del) {
|
||||
deleteMesh(data.mesh_del);
|
||||
}
|
||||
if (data.mesh_move) {
|
||||
const { id, pos } = data.mesh_move;
|
||||
const mesh = meshes[id];
|
||||
if (mesh) {
|
||||
mesh.position.x = pos.x;
|
||||
mesh.position.y = pos.y;
|
||||
mesh.position.z = pos.z;
|
||||
space.refresh();
|
||||
}
|
||||
label.x.value = (pos.x - origin.x).toFixed(2);
|
||||
label.y.value = (pos.y + origin.y).toFixed(2);
|
||||
label.z.value = (pos.z - origin.z).toFixed(2);
|
||||
}
|
||||
if (data.stock_index !== undefined) {
|
||||
api.widgets.setAxisIndex(data.stock_index);
|
||||
label.a.value = -data.stock_index.toFixed(1);
|
||||
}
|
||||
if (data.mesh_index) {
|
||||
const { id, index } = data.mesh_index;
|
||||
const mesh = meshes[id];
|
||||
if (mesh) {
|
||||
mesh.rotation.x = (Math.PI / 180) * index;
|
||||
space.refresh();
|
||||
}
|
||||
}
|
||||
if (data.mesh_update) {
|
||||
const { id, ind, pos, ilen, plen } = data.mesh_update;
|
||||
meshUpdate(id, ind, pos, ilen, plen);
|
||||
}
|
||||
if (data && data.progress) {
|
||||
label.progress.value = (data.progress * 100).toFixed(1);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSpeed(inc = 0) {
|
||||
if (inc === Infinity) {
|
||||
speedIndex = speedMax;
|
||||
} else if (inc > 0) {
|
||||
speedIndex = (speedIndex + inc) % speedValues.length;
|
||||
}
|
||||
api.local.set('cam.anim.speed', speedIndex);
|
||||
speed = speedValues[speedIndex];
|
||||
label.speed.value = speedNames[speedIndex];
|
||||
}
|
||||
|
||||
function replay() {
|
||||
animate_clear2(api);
|
||||
setTimeout(() => {
|
||||
animate2(api, 50);
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
// ---( WORKER FUNCTIONS )---
|
||||
const { CSG } = root.base;
|
||||
|
||||
let nextMeshID = 1;
|
||||
|
||||
class Stock {
|
||||
constructor(x, y, z) {
|
||||
this.id = nextMeshID++;
|
||||
this.vbuf = undefined;
|
||||
this.ibuf = undefined;
|
||||
this.ilen = 0;
|
||||
this.sends = 0;
|
||||
this.newbuf = true;
|
||||
this.subtracts = 0;
|
||||
this.mesh = CSG.Instance().Manifold.cube([x, y, z], true);
|
||||
}
|
||||
|
||||
send(send) {
|
||||
const newbuf = this.newbuf;
|
||||
const action = this.sends++ === 0 ? 'mesh_add' : 'mesh_update';
|
||||
send.data({ [action]: {
|
||||
id: this.id,
|
||||
ind: newbuf ? this.ibuf : undefined,
|
||||
pos: newbuf ? this.vbuf : undefined,
|
||||
ilen: this.ilen,
|
||||
plen: this.plen
|
||||
} });
|
||||
this.newbuf = false;
|
||||
this.sends++;
|
||||
// console.log({ send: this.id, newbuf, action });
|
||||
}
|
||||
|
||||
translate(x, y, z) {
|
||||
// console.log({ translate: this.id, x, y, z });
|
||||
const oldmesh = this.mesh;
|
||||
this.mesh = this.mesh.translate(x, y, z);
|
||||
this.bounds = CSG.toBox3(this.mesh);
|
||||
oldmesh.delete();
|
||||
return this;
|
||||
}
|
||||
|
||||
updateMesh(updates) {
|
||||
if (this.sends > 0 && this.subtracts === 0) {
|
||||
return;
|
||||
}
|
||||
const subs = this.subtracts;
|
||||
this.subtracts = 0;
|
||||
let start = Date.now();
|
||||
let mesh = this.mesh.getMesh();
|
||||
this.sharedVertexBuffer(mesh.numVert * 3);
|
||||
this.sharedIndexBuffer(mesh.numTri * 3);
|
||||
this.vbuf.set(mesh.vertProperties);
|
||||
this.ibuf.set(mesh.triVerts);
|
||||
let sub = Date.now();
|
||||
updates.push(this);
|
||||
if (false) console.log({
|
||||
update: this.id,
|
||||
time: sub - start,
|
||||
subs,
|
||||
mssub: ((sub - start) / subs).round(3)
|
||||
});
|
||||
}
|
||||
|
||||
subtractTool(toolMesh) {
|
||||
const oldmesh = this.mesh;
|
||||
this.mesh = this.mesh.subtract(toolMesh.mesh);
|
||||
oldmesh.delete();
|
||||
this.subtracts++;
|
||||
}
|
||||
|
||||
sharedVertexBuffer(size) {
|
||||
const old = this.vbuf;
|
||||
this.plen = size;
|
||||
if (old && old.length >= size) {
|
||||
// console.log({svb: this.id, reuse: size, old: old.length});
|
||||
return old;
|
||||
}
|
||||
// let buf = new Float32Array(new SharedArrayBuffer(size * 4));
|
||||
let buf = new Float32Array(new SharedArrayBuffer(size * 4 + 1024 * 1024));
|
||||
// console.log({new_svb: this.id, size, buf});
|
||||
this.newbuf = true;
|
||||
return this.vbuf = buf;
|
||||
}
|
||||
|
||||
sharedIndexBuffer(size) {
|
||||
const old = this.ibuf;
|
||||
this.ilen = size;
|
||||
if (old && old.length >= size) {
|
||||
// console.log({sib: this.id, reuse: size, old: old.length});
|
||||
return old;
|
||||
}
|
||||
// let buf = new Uint32Array(new SharedArrayBuffer(size * 4));
|
||||
let buf = new Uint32Array(new SharedArrayBuffer(size * 4 + 1024 * 1024));
|
||||
// console.log({new_sib: this.id, size, buf});
|
||||
this.newbuf = true;
|
||||
return this.ibuf = buf;
|
||||
}
|
||||
}
|
||||
|
||||
kiri.load(() => {
|
||||
if (!kiri.worker) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
kiri.worker.animate_setup2 = function(data, send) {
|
||||
const { settings } = data;
|
||||
const { process } = settings;
|
||||
const print = worker.print;
|
||||
const density = parseInt(settings.controller.animesh) * 1000;
|
||||
const isIndexed = process.camStockIndexed;
|
||||
|
||||
pathIndex = 0;
|
||||
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;
|
||||
animateClear = false;
|
||||
stockIndex = 0;
|
||||
indexCount = 0;
|
||||
startTime = 0;
|
||||
updates = 0;
|
||||
stockZ = isIndexed ? 0 : stock.z;
|
||||
|
||||
stockSlices = [];
|
||||
const { x, y, z } = stock;
|
||||
const sliceCount = parseInt(settings.controller.animesh || 2000) / 100;
|
||||
const sliceWidth = stock.x / sliceCount;
|
||||
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.done();
|
||||
};
|
||||
|
||||
kiri.worker.animate2 = function(data, send) {
|
||||
renderPause = data.pause || renderPause;
|
||||
renderSpeed = data.speed || 0;
|
||||
if (animating) {
|
||||
return send.done();
|
||||
}
|
||||
renderSteps = data.steps || 1;
|
||||
renderDone = false;
|
||||
animating = renderSpeed > 0;
|
||||
startTime = startTime || Date.now();
|
||||
renderPath(send);
|
||||
};
|
||||
|
||||
kiri.worker.animate_cleanup2 = function(data, send) {
|
||||
if (animating) {
|
||||
animateClear = true;
|
||||
}
|
||||
};
|
||||
|
||||
function renderPath(send) {
|
||||
if (renderDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderSteps-- === 0) {
|
||||
animating = false;
|
||||
renderPath(send);
|
||||
return;
|
||||
}
|
||||
|
||||
if (animating === false || animateClear || renderSpeed === 0) {
|
||||
renderUpdate(send);
|
||||
renderDone = true;
|
||||
animating = false;
|
||||
animateClear = false;
|
||||
send.done();
|
||||
return;
|
||||
}
|
||||
|
||||
let next = path[pathIndex];
|
||||
while (next && next.type === 'laser') {
|
||||
last = next;
|
||||
next = path[++pathIndex];
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
console.log('animation completed in ', ((Date.now() - startTime)/1000).round(2));
|
||||
animating = false;
|
||||
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
|
||||
if (tool && last.point) {
|
||||
let pos = last.point = {
|
||||
x: last.point.x,
|
||||
y: last.point.y,
|
||||
z: stock.z
|
||||
};
|
||||
toolMove(pos);
|
||||
send.data(toolUpdateMsg);
|
||||
}
|
||||
toolUpdate(next.tool.getID(), send);
|
||||
}
|
||||
|
||||
const id = toolID;
|
||||
const rezstep = rez * 2;
|
||||
if (last) {
|
||||
const lp = last.point, np = next.point;
|
||||
last = next;
|
||||
// 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;
|
||||
}
|
||||
|
||||
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 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 });
|
||||
x += mx;
|
||||
y += my;
|
||||
z += mz;
|
||||
}
|
||||
moveDist = 0;
|
||||
moves.push({...next.point, md:sd});
|
||||
renderMoves(id, moves, send);
|
||||
} else {
|
||||
last = next;
|
||||
if (tool) {
|
||||
toolMove(next.point);
|
||||
}
|
||||
renderPath(send);
|
||||
}
|
||||
}
|
||||
|
||||
function renderMoves(id, moves, send, seed = 0) {
|
||||
for (let index = seed; index<moves.length; index++) {
|
||||
const pos = moves[index];
|
||||
if (!pos) {
|
||||
throw `no pos @ ${index} of ${moves.length}`;
|
||||
}
|
||||
const { dx, dy, dz } = pos;
|
||||
toolMove(pos);
|
||||
// console.log('renderMoves', {id, moves, seed});
|
||||
let subs = 0;
|
||||
if (dx || dy || dz < 0)
|
||||
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;
|
||||
renderUpdate(send);
|
||||
setTimeout(() => {
|
||||
renderMoves(id, moves, send, index + 1);
|
||||
}, renderPause);
|
||||
return;
|
||||
}
|
||||
}
|
||||
renderPath(send);
|
||||
}
|
||||
|
||||
// send latest tool position and progress bar
|
||||
function renderUpdate(send) {
|
||||
const updated = []
|
||||
for (let slice of stockSlices) {
|
||||
slice.updateMesh(updated);
|
||||
}
|
||||
for (let slice of updated) {
|
||||
slice.send(send);
|
||||
}
|
||||
if (toolUpdateMsg) {
|
||||
send.data(toolUpdateMsg);
|
||||
}
|
||||
if (stockIndexMsg) {
|
||||
send.data({ stock_index: stockIndex });
|
||||
for (let slice of stockSlices) {
|
||||
send.data({ mesh_index: { id: slice.id, index: -stockIndex } });
|
||||
}
|
||||
stockIndexMsg = false;
|
||||
}
|
||||
send.data({ progress: pathIndex / path.length });
|
||||
updates++;
|
||||
}
|
||||
|
||||
// move tool mesh animation space, update client
|
||||
function toolMove(pos) {
|
||||
toolUpdateMsg = { mesh_move: { id: toolID, pos: { x: pos.x, y: pos.y, z: pos.z } } };
|
||||
if (toolMesh.mesh) {
|
||||
toolMesh.mesh.delete();
|
||||
}
|
||||
toolMesh.mesh = toolMesh.root.translate(pos.x, pos.y, pos.z);
|
||||
if (pos.a !== undefined) {
|
||||
let tmp = toolMesh.mesh.rotate([ pos.a, 0, 0 ]);
|
||||
toolMesh.mesh.delete();
|
||||
toolMesh.mesh = tmp;
|
||||
if (pos.a !== stockIndex) {
|
||||
stockIndexMsg = true;
|
||||
stockIndex = pos.a;
|
||||
}
|
||||
}
|
||||
toolMesh.bounds = CSG.toBox3(toolMesh.mesh);
|
||||
}
|
||||
|
||||
// delete old tool mesh, generate tool mesh, send to client
|
||||
function toolUpdate(toolid, send) {
|
||||
if (tool) {
|
||||
send.data({ mesh_del: toolID });
|
||||
}
|
||||
tool = new CAM.Tool({ tools }, toolid);
|
||||
const Instance = CSG.Instance();
|
||||
const slen = tool.shaftLength() || 15;
|
||||
const srad = tool.shaftDiameter() / 2;
|
||||
const flen = tool.fluteLength() || 15;
|
||||
const frad = toolRadius = tool.fluteDiameter() / 2;
|
||||
const tlen = slen + flen; // total tool length
|
||||
let { cylinder, sphere } = Instance.Manifold;
|
||||
let mesh;
|
||||
if (tool.isBallMill()) {
|
||||
mesh = cylinder(tlen - frad * 2, frad, frad, 20, true)
|
||||
.add(sphere(frad, 20).translate(0, 0, -(tlen - frad * 2)/2))
|
||||
.add(cylinder(slen, srad, srad, 20, true).translate(0, 0, flen/2));
|
||||
} else if (tool.isTaperMill()) {
|
||||
const trad = Math.max(tool.tipDiameter() / 2, 0.001);
|
||||
mesh = cylinder(slen, srad, srad, 20, true).translate(0, 0, slen/2)
|
||||
.add(cylinder(flen, trad, frad, 20, true).translate(0, 0, -flen/2));
|
||||
} else {
|
||||
mesh = cylinder(tlen, frad, frad, 20, true)
|
||||
.add(cylinder(slen, srad, srad, 20, true).translate(0, 0, flen/2));
|
||||
}
|
||||
mesh = mesh.translate(0, 0, (tlen - stockZ) / 2);
|
||||
const raw = mesh.getMesh();
|
||||
const vertex = raw.vertProperties;
|
||||
const index = raw.triVerts;
|
||||
toolMesh = { root: mesh, index, vertex, bounds: CSG.toBox3(mesh) };
|
||||
send.data({ mesh_add: { id:--toolID, ind: index, pos: vertex }});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,236 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: main.kiri
|
||||
// use: kiri.codec
|
||||
// use: mesh.tool
|
||||
gapp.register("kiri-mode.cam.driver", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const CAM = driver.CAM = {};
|
||||
|
||||
CAM.process = {
|
||||
LEVEL: 1,
|
||||
ROUGH: 2,
|
||||
OUTLINE: 3,
|
||||
CONTOUR_X: 4,
|
||||
CONTOUR_Y: 5,
|
||||
TRACE: 6,
|
||||
DRILL: 7
|
||||
};
|
||||
|
||||
// defer loading until kiri.client and kiri.worker exist
|
||||
kiri.load(api => {
|
||||
|
||||
if (kiri.client) {
|
||||
CAM.surface_prep = function(index, ondone) {
|
||||
kiri.client.sync();
|
||||
const settings = api.conf.get();
|
||||
kiri.client.send("cam_surfaces", { settings, index }, output => {
|
||||
ondone(output);
|
||||
});
|
||||
};
|
||||
|
||||
CAM.surface_show = function(widget) {
|
||||
widget.selectFaces(Object.values(widget._surfaces).flat());
|
||||
};
|
||||
|
||||
CAM.cylinder_show = function(widget) {
|
||||
widget.selectFaces(Object.values(widget._cylinders).flat());
|
||||
};
|
||||
|
||||
|
||||
CAM.surface_toggle = function(widget, face, radians, ondone) {
|
||||
let surfaces = widget._surfaces = widget._surfaces || {};
|
||||
for (let [root, faces] of Object.entries(surfaces)) {
|
||||
if (faces.contains(face)) {
|
||||
// delete this face group
|
||||
delete surfaces[root];
|
||||
CAM.surface_show(widget);
|
||||
ondone(Object.keys(surfaces).map(v => parseInt(v)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
kiri.client.send("cam_surface_find", { id: widget.id, face, radians }, faces => {
|
||||
if (faces.length) {
|
||||
surfaces[face] = faces;
|
||||
CAM.surface_show(widget);
|
||||
}
|
||||
ondone(Object.keys(surfaces).map(v => parseInt(v)));
|
||||
});
|
||||
};
|
||||
|
||||
CAM.surface_clear = function(widget) {
|
||||
widget.selectFaces([]);
|
||||
widget._surfaces = {};
|
||||
};
|
||||
|
||||
CAM.traces = function(ondone, single) {
|
||||
kiri.client.sync();
|
||||
const settings = api.conf.get();
|
||||
const widgets = api.widgets.map();
|
||||
kiri.client.send("cam_traces", { settings, single }, output => {
|
||||
const ids = [];
|
||||
kiri.codec.decode(output).forEach(rec => {
|
||||
ids.push(rec.id);
|
||||
widgets[rec.id].traces = rec.traces;
|
||||
});
|
||||
ondone(ids);
|
||||
});
|
||||
};
|
||||
|
||||
CAM.traces_clear = function(ondone) {
|
||||
kiri.client.send("cam_traces_clear", {}, () => {
|
||||
// console.log({clear_traces: true});
|
||||
});
|
||||
};
|
||||
|
||||
CAM.holes = function(indiv,rec,onProgress,onDone) {
|
||||
kiri.client.sync();
|
||||
const settings = api.conf.get();
|
||||
return new Promise((res,rej)=>{
|
||||
kiri.client.send("cam_holes", { settings, rec, indiv }, output => {
|
||||
let out = kiri.codec.decode(output)
|
||||
if(out.progress != undefined){
|
||||
//if a progress message,
|
||||
onProgress(out.progress,out.msg)
|
||||
}else{
|
||||
api.hide.alert(alert);
|
||||
onDone(out);
|
||||
res(out);
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
CAM.cylinderShow = function(onProgress,onDone){
|
||||
|
||||
kiri.client.sync();
|
||||
const settings = api.conf.get();
|
||||
|
||||
|
||||
}
|
||||
|
||||
CAM.cylinderToggle = (widget, face, onDone) => {
|
||||
let cyls = widget._cylinders = widget._cylinders || {};
|
||||
for (let [root, faces] of Object.entries(cyls)) {
|
||||
if (faces.contains(face)) { // if a face group contains the selected face
|
||||
// delete this face group
|
||||
delete cyls[root];
|
||||
CAM.cylinder_show(widget);
|
||||
onDone(Object.keys(cyls).map(v => parseInt(v)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
//send
|
||||
kiri.client.send("cam_cylinder_find", { id: widget.id, face }, ({faces,error}) => {
|
||||
if (faces?.length) {
|
||||
cyls[face] = faces;
|
||||
CAM.cylinder_show(widget);
|
||||
}
|
||||
|
||||
onDone({faces:Object.keys(cyls).map(v => parseInt(v)),error});
|
||||
});
|
||||
}
|
||||
|
||||
CAM.cylinderClear = (widget) => {
|
||||
widget.selectFaces([]);
|
||||
widget._cylinders = {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (kiri.worker) {
|
||||
CAM.surface_prep = function(widget, index) {
|
||||
if (!widget.tool) {
|
||||
let tool = widget.tool = new mesh.tool();
|
||||
let translate = index ? true : false;
|
||||
tool.index(widget.getGeoVertices({ unroll: true, translate }));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
kiri.worker.cam_surfaces = function(data, send) {
|
||||
const { settings, index } = data;
|
||||
const widgets = Object.values(kiri.worker.cache);
|
||||
for (let widget of widgets) {
|
||||
if (index) {
|
||||
widget.setIndexed(true);
|
||||
widget.setAxisIndex(-index);
|
||||
} else {
|
||||
widget.setIndexed(false);
|
||||
widget.setAxisIndex(0);
|
||||
}
|
||||
CAM.surface_prep(widget, index);
|
||||
}
|
||||
send.done({});
|
||||
};
|
||||
|
||||
kiri.worker.cam_surface_find = function(data, send) {
|
||||
const { id, face, radians } = data;
|
||||
const widget = kiri.worker.cache[id];
|
||||
const faces = CAM.surface_find(widget, [face], radians);
|
||||
send.done(faces);
|
||||
}
|
||||
|
||||
kiri.worker.cam_traces = async function(data, send) {
|
||||
const { settings, single } = data;
|
||||
const widgets = Object.values(kiri.worker.cache);
|
||||
const fresh = [];
|
||||
for (let widget of widgets) {
|
||||
if (await CAM.traces(settings, widget, single)) {
|
||||
fresh.push(widget);
|
||||
}
|
||||
}
|
||||
// const fresh = widgets.filter(widget => CAM.traces(settings, widget, single));
|
||||
send.done(kiri.codec.encode(fresh.map(widget => { return {
|
||||
id: widget.id,
|
||||
traces: widget.traces,
|
||||
} } )));
|
||||
};
|
||||
|
||||
kiri.worker.cam_traces_clear = function(data, send) {
|
||||
for (let widget of Object.values(kiri.worker.cache)) {
|
||||
delete widget.traces;
|
||||
delete widget.topo;
|
||||
}
|
||||
send.done({});
|
||||
};
|
||||
|
||||
kiri.worker.cam_holes = async function(data, send) {
|
||||
const { settings, indiv, rec } = data;
|
||||
const widgets = Object.values(kiri.worker.cache);
|
||||
const fresh = [];
|
||||
|
||||
for (let [i,widget] of widgets.entries() ) {
|
||||
if (await CAM.holes(settings, widget, indiv, rec,
|
||||
( prog, msg )=>{ send.data({progress: (i/widgets.length)+(prog/widgets.length),msg})}
|
||||
)){
|
||||
fresh.push(widget);
|
||||
}
|
||||
}
|
||||
// const fresh = widgets.filter(widget => CAM.traces(settings, widget, single));
|
||||
send.done(kiri.codec.encode(fresh.map(widget => { return {
|
||||
id: widget.id,
|
||||
holes: widget.drills,
|
||||
shadowed:widget.shadowedDrills
|
||||
} } )));
|
||||
}
|
||||
|
||||
kiri.worker.cam_cylinder_find = async function(data,send){
|
||||
const { id, face, settings } = data;
|
||||
const widget = kiri.worker.cache[id];
|
||||
try{
|
||||
send.done(CAM.cylinder_poly_find(widget, face));
|
||||
}catch(error){
|
||||
send.done({error});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,353 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.settings
|
||||
gapp.register("kiri-mode.cam.tools", (root, exports) => {
|
||||
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
let { kiri } = root,
|
||||
{ api, consts } = kiri,
|
||||
{ uc, ui } = api,
|
||||
{ MODES } = consts,
|
||||
DOC = document,
|
||||
selectedTool = null,
|
||||
editTools = null,
|
||||
maxTool = 0,
|
||||
toolNames = ['endmill','ballmill','tapermill','drill'];
|
||||
|
||||
api.show.tools = showTools;
|
||||
|
||||
// extend API
|
||||
Object.assign(api.tool, {
|
||||
update: updateTool
|
||||
});
|
||||
|
||||
function settings() {
|
||||
return api.conf.get();
|
||||
}
|
||||
|
||||
function renderTools() {
|
||||
ui.toolSelect.innerHTML = '';
|
||||
maxTool = 0;
|
||||
editTools.forEach(function(tool, index) {
|
||||
maxTool = Math.max(maxTool, tool.number);
|
||||
tool.order = index;
|
||||
let opt = DOC.createElement('option');
|
||||
opt.appendChild(DOC.createTextNode(tool.name));
|
||||
opt.onclick = function() { selectTool(tool) };
|
||||
ui.toolSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function selectTool(tool) {
|
||||
selectedTool = tool;
|
||||
ui.toolName.value = tool.name;
|
||||
ui.toolNum.value = tool.number;
|
||||
ui.toolFluteDiam.value = tool.flute_diam;
|
||||
ui.toolFluteLen.value = tool.flute_len;
|
||||
ui.toolShaftDiam.value = tool.shaft_diam;
|
||||
ui.toolShaftLen.value = tool.shaft_len;
|
||||
ui.toolTaperTip.value = tool.taper_tip || 0;
|
||||
ui.toolMetric.checked = tool.metric;
|
||||
ui.toolType.selectedIndex = toolNames.indexOf(tool.type);
|
||||
if (tool === 'tapermill') {
|
||||
ui.toolTaperAngle.value = kiri.driver.CAM.calcTaperAngle(
|
||||
(tool.flute_diam - tool.taper_tip) / 2, tool.flute_len
|
||||
).round(1);
|
||||
} else if(tool === 'drill'){
|
||||
ui.toolTaperAngle.value = 118;
|
||||
|
||||
} else {
|
||||
ui.toolTaperAngle.value = 0;
|
||||
}
|
||||
renderTool(tool);
|
||||
}
|
||||
|
||||
function otag(o) {
|
||||
if (Array.isArray(o)) {
|
||||
let out = []
|
||||
o.forEach(oe => out.push(otag(oe)));
|
||||
return out.join('');
|
||||
}
|
||||
let tags = [];
|
||||
Object.keys(o).forEach(key => {
|
||||
let val = o[key];
|
||||
let att = [];
|
||||
Object.keys(val).forEach(tk => {
|
||||
let tv = val[tk];
|
||||
att.push(`${tk.replace(/_/g,'-')}="${tv}"`);
|
||||
});
|
||||
tags.push(`<${key} ${att.join(' ')}></${key}>`);
|
||||
});
|
||||
return tags.join('');
|
||||
}
|
||||
|
||||
function renderTool(tool) {
|
||||
let type = selectedTool.type;
|
||||
let taper = type=== 'tapermill'
|
||||
let drill = type === 'drill'
|
||||
const drillAngleRad = 140 * Math.PI / 180
|
||||
ui.toolTaperAngle.disabled = taper ? undefined : 'true';
|
||||
ui.toolTaperTip.disabled = taper ? undefined : 'true';
|
||||
$('tool-view').innerHTML = '<svg id="tool-svg" width="100%" height="100%"></svg>';
|
||||
setTimeout(() => {
|
||||
let svg = $('tool-svg'),
|
||||
pad = 10,
|
||||
dim = { w: svg.clientWidth, h: svg.clientHeight },
|
||||
max = { w: dim.w - pad * 2, h: dim.h - pad * 2},
|
||||
off = { x: pad, y: pad },
|
||||
isBall = type === "ballmill",
|
||||
shaft_fill = "#cccccc",
|
||||
flute_fill = "#dddddd",
|
||||
stroke = "#777777",
|
||||
stroke_width = 3,
|
||||
stroke_thin = stroke_width / 2,
|
||||
shaft = tool.shaft_len || 1,
|
||||
flute = tool.flute_len || 1,
|
||||
drillTip = drill ? 0.5 * tool.flute_diam * Math.sin(drillAngleRad) : 0,
|
||||
total_len = shaft + flute+drillTip,
|
||||
units = dim.h / total_len,
|
||||
shaft_len = (shaft / total_len) * max.h,
|
||||
flute_len = (flute / total_len) * max.h,
|
||||
drill_tip_len = (drillTip / total_len) * max.h,
|
||||
shaft_diam = tool.shaft_diam * units,
|
||||
flute_diam = tool.flute_diam * units,
|
||||
// total_wid = Math.max(flute_diam, shaft_diam),
|
||||
shaft_off = (max.w - shaft_diam) / 2,
|
||||
flute_off = (max.w - flute_diam) / 2,
|
||||
taper_off = (max.w - (tool.taper_tip || 0) * units) / 2,
|
||||
parts = [
|
||||
// shaft rectangle
|
||||
{ rect: {
|
||||
x: off.x + shaft_off,
|
||||
y: off.y,
|
||||
width: max.w - shaft_off * 2,
|
||||
height: shaft_len,
|
||||
fill: shaft_fill,
|
||||
stroke_width,
|
||||
stroke
|
||||
} }
|
||||
];
|
||||
if (taper) {
|
||||
let yoff = off.y + shaft_len;
|
||||
// let mid = dim.w / 2;
|
||||
parts.push({path: {stroke_width, stroke, fill:flute_fill, d:[
|
||||
`M ${off.x + flute_off} ${yoff}`,
|
||||
`L ${off.x + taper_off} ${yoff + flute_len}`,
|
||||
`L ${dim.w - off.x - taper_off} ${yoff + flute_len}`,
|
||||
`L ${dim.w - off.x - flute_off} ${yoff}`,
|
||||
`z`
|
||||
].join('\n')}});
|
||||
} else if(drill){
|
||||
|
||||
const x1 = off.x + flute_off,
|
||||
y1 = off.y + shaft_len,
|
||||
x2 = dim.w - off.x - flute_off,
|
||||
y2 = y1 + flute_len,
|
||||
xMid = dim.w / 2
|
||||
|
||||
parts.push({path: {stroke_width, stroke, fill:flute_fill, d:[
|
||||
`M ${x1} ${y1}`, //move to top left
|
||||
`L ${x1} ${y2}`, //line to bottom left
|
||||
`L ${xMid} ${y2+drill_tip_len}`, //line to bottom mid point
|
||||
`L ${x2} ${y2}`, //line to bottom right
|
||||
`L ${x2} ${y1}`, //line to top right
|
||||
`z`
|
||||
].join('\n')}});
|
||||
//add drill flute lines
|
||||
parts.push({ line: {
|
||||
x1, y1, x2, y2: (y1 + y2) / 2,
|
||||
stroke, stroke_width: stroke_thin
|
||||
} });
|
||||
parts.push({ line: {
|
||||
x1, y1: (y1 + y2) / 2, x2, y2,
|
||||
stroke, stroke_width: stroke_thin
|
||||
} });
|
||||
} else {
|
||||
let fl = isBall ? flute_len - flute_diam/2 : flute_len;
|
||||
let x1 = off.x + flute_off;
|
||||
let y1 = off.y + shaft_len;
|
||||
let x2 = x1 + max.w - flute_off * 2;
|
||||
let y2 = y1 + fl;
|
||||
// flute rectangle
|
||||
parts.push({ rect: {
|
||||
x: off.x + flute_off,
|
||||
y: off.y + shaft_len,
|
||||
width: max.w - flute_off * 2,
|
||||
height: fl,
|
||||
fill: flute_fill,
|
||||
stroke_width,
|
||||
stroke,
|
||||
} });
|
||||
// hatch "fill" flute
|
||||
parts.push({ line: { x1, y1, x2, y2, stroke, stroke_width: stroke_thin } });
|
||||
parts.push({ line: {
|
||||
x1: (x1 + x2) / 2, y1, x2, y2: (y1 + y2) / 2,
|
||||
stroke, stroke_width: stroke_thin
|
||||
} });
|
||||
parts.push({ line: {
|
||||
x1, y1: (y1 + y2) / 2, x2: (x1 + x2) / 2, y2,
|
||||
stroke, stroke_width: stroke_thin
|
||||
} });
|
||||
}
|
||||
if (isBall) {
|
||||
let rad = (max.w - flute_off * 2) / 2;
|
||||
let xend = dim.w - off.x - flute_off;
|
||||
let yoff = off.y + shaft_len + flute_len + stroke_width/2 - flute_diam/2;
|
||||
parts.push({path: {stroke_width, stroke, fill:flute_fill, d:[
|
||||
`M ${off.x + flute_off} ${yoff}`,
|
||||
`A ${rad} ${rad} 0 0 0 ${xend} ${yoff}`,
|
||||
// `L ${off.x + flute_off} ${yoff}`
|
||||
].join('\n')}})
|
||||
}
|
||||
svg.innerHTML = otag(parts);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function updateTool(ev) {
|
||||
selectedTool.name = ui.toolName.value;
|
||||
selectedTool.number = parseInt(ui.toolNum.value);
|
||||
selectedTool.flute_diam = parseFloat(ui.toolFluteDiam.value);
|
||||
selectedTool.flute_len = parseFloat(ui.toolFluteLen.value);
|
||||
selectedTool.shaft_diam = parseFloat(ui.toolShaftDiam.value);
|
||||
selectedTool.shaft_len = parseFloat(ui.toolShaftLen.value);
|
||||
selectedTool.taper_tip = parseFloat(ui.toolTaperTip.value);
|
||||
selectedTool.metric = ui.toolMetric.checked;
|
||||
selectedTool.type = toolNames[ui.toolType.selectedIndex];
|
||||
if (selectedTool.type === 'tapermill') {
|
||||
const CAM = kiri.driver.CAM;
|
||||
const rad = (selectedTool.flute_diam - selectedTool.taper_tip) / 2;
|
||||
if (ev && ev.target === ui.toolTaperAngle) {
|
||||
const angle = parseFloat(ev.target.value);
|
||||
const len = CAM.calcTaperLength(rad, angle * DEG2RAD);
|
||||
selectedTool.flute_len = len;
|
||||
ui.toolTaperAngle.value = angle.round(1);
|
||||
ui.toolFluteLen.value = selectedTool.flute_len.round(4);
|
||||
} else {
|
||||
ui.toolTaperAngle.value = CAM.calcTaperAngle(rad, selectedTool.flute_len).round(1);
|
||||
}
|
||||
} else {
|
||||
ui.toolTaperAngle.value = 0;
|
||||
}
|
||||
renderTools();
|
||||
ui.toolSelect.selectedIndex = selectedTool.order;
|
||||
setToolChanged(true);
|
||||
renderTool(selectedTool);
|
||||
}
|
||||
|
||||
function setToolChanged(changed) {
|
||||
editTools.changed = changed;
|
||||
ui.toolsSave.disabled = !changed;
|
||||
}
|
||||
|
||||
function showTools() {
|
||||
if (api.mode.get_id() !== MODES.CAM) return;
|
||||
api.settings.sync.get().then(_showTools);
|
||||
}
|
||||
|
||||
function _showTools() {
|
||||
let selectedIndex = null;
|
||||
|
||||
editTools = settings().tools.slice().sort((a,b) => {
|
||||
return a.name > b.name ? 1 : -1;
|
||||
});
|
||||
|
||||
setToolChanged(false);
|
||||
|
||||
ui.toolsClose.onclick = function() {
|
||||
if (editTools.changed && !confirm("abandon changes?")) return;
|
||||
api.dialog.hide();
|
||||
};
|
||||
ui.toolAdd.onclick = function() {
|
||||
let metric = settings().controller.units === 'mm';
|
||||
editTools.push(Object.assign({
|
||||
id: Date.now(),
|
||||
number: maxTool + 1,
|
||||
name: "new tool",
|
||||
type: "endmill",
|
||||
taper_tip: 0,
|
||||
metric
|
||||
}, metric ? {
|
||||
shaft_diam: 2,
|
||||
shaft_len: 15,
|
||||
flute_diam: 2,
|
||||
flute_len: 20,
|
||||
} : {
|
||||
shaft_diam: 0.25,
|
||||
shaft_len: 1.5,
|
||||
flute_diam: 0.25,
|
||||
flute_len: 2,
|
||||
}));
|
||||
setToolChanged(true);
|
||||
renderTools();
|
||||
ui.toolSelect.selectedIndex = editTools.length-1;
|
||||
selectTool(editTools[editTools.length-1]);
|
||||
};
|
||||
ui.toolCopy.onclick = function() {
|
||||
let clone = Object.assign({}, selectedTool);
|
||||
let { name } = clone;
|
||||
let split = name.split(' ');
|
||||
let endv = parseInt(split.pop());
|
||||
if (endv) {
|
||||
name = split.join(' ') + ' ' + (endv + 1);
|
||||
} else {
|
||||
name = `${name} 2`;
|
||||
}
|
||||
clone.id = Date.now();
|
||||
clone.number = maxTool + 1;
|
||||
clone.name = name;
|
||||
editTools.push(clone);
|
||||
setToolChanged(true);
|
||||
renderTools();
|
||||
ui.toolSelect.selectedIndex = editTools.length-1;
|
||||
selectTool(editTools[editTools.length-1]);
|
||||
};
|
||||
ui.toolDelete.onclick = function() {
|
||||
editTools.remove(selectedTool);
|
||||
setToolChanged(true);
|
||||
renderTools();
|
||||
};
|
||||
ui.toolsSave.onclick = function() {
|
||||
if (selectedTool) updateTool();
|
||||
settings().tools = editTools.sort((a,b) => {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
});
|
||||
setToolChanged(false);
|
||||
api.conf.save();
|
||||
api.conf.update_fields();
|
||||
api.event.settings();
|
||||
api.settings.sync.put();
|
||||
};
|
||||
ui.toolsImport.onclick = (ev) => api.event.import(ev);
|
||||
ui.toolsExport.onclick = () => {
|
||||
uc.prompt("Export Tools Filename", "tools").then(name => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const record = {
|
||||
version: kiri.version,
|
||||
tools: api.conf.get().tools,
|
||||
time: Date.now()
|
||||
};
|
||||
api.util.download(api.util.b64enc(record), `${name}.km`);
|
||||
});
|
||||
};
|
||||
|
||||
renderTools();
|
||||
if (editTools.length > 0) {
|
||||
selectTool(editTools[0]);
|
||||
ui.toolSelect.selectedIndex = 0;
|
||||
} else {
|
||||
ui.toolAdd.onclick();
|
||||
}
|
||||
|
||||
api.dialog.show('tools');
|
||||
ui.toolSelect.focus();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri-mode.laser.driver
|
||||
gapp.register("kiri-mode.drag.driver", [], (root, exports) => {
|
||||
|
||||
const DRIVERS = root.kiri.driver;
|
||||
const { LASER } = DRIVERS;
|
||||
const { TYPE } = LASER;
|
||||
DRIVERS.DRAG = Object.assign({}, LASER, { name: "DragKnife", type: TYPE.DRAG });
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri-mode.fdm.driver", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { util } = base;
|
||||
const FDM = driver.FDM = { getRangeParameters, extrudePerMM, extrudeMM };
|
||||
|
||||
// shared by client and worker contexts
|
||||
function getRangeParameters(process, index) {
|
||||
if (index === undefined || index === null || index < 0) {
|
||||
return process;
|
||||
}
|
||||
let ranges = process.ranges;
|
||||
if (!(ranges && ranges.length)) {
|
||||
return process;
|
||||
}
|
||||
let params = Object.clone(process);
|
||||
for (let range of ranges) {
|
||||
if (index >= range.lo && index <= range.hi) {
|
||||
for (let [key, value] of Object.entries(range.fields)) {
|
||||
params[key] = value;
|
||||
params._range = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// noz = nozzle diameter
|
||||
// fil = filament diameter
|
||||
// slice = slice height
|
||||
function extrudePerMM(noz, fil, slice) {
|
||||
return ((Math.PI * util.sqr(noz / 2)) / (Math.PI * util.sqr(fil / 2))) * (slice / noz);
|
||||
};
|
||||
|
||||
// dist = distance between extrusion points
|
||||
// perMM = amount extruded per MM (from extrudePerMM)
|
||||
// factor = scaling factor (usually 1.0)
|
||||
function extrudeMM(dist, perMM, factor) {
|
||||
return dist * perMM * factor;
|
||||
}
|
||||
|
||||
// defer loading until client and worker exist
|
||||
kiri.load(api => {
|
||||
|
||||
const { client, worker } = kiri;
|
||||
|
||||
if (client) {
|
||||
FDM.support_generate = function(ondone) {
|
||||
client.clear();
|
||||
client.sync();
|
||||
const settings = api.conf.get();
|
||||
const widgets = api.widgets.map();
|
||||
client.send("fdm_support_generate", { settings }, (gen) => {
|
||||
if (gen && gen.error) {
|
||||
api.show.alert('support generation canceled');
|
||||
return ondone([]);
|
||||
}
|
||||
for (let g of gen) {
|
||||
g.widget = widgets[g.id];
|
||||
}
|
||||
ondone(gen);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
worker.fdm_support_generate = function(data, send) {
|
||||
const { settings } = data;
|
||||
const widgets = Object.values(worker.cache);
|
||||
const fresh = widgets.filter(widget => FDM.supports(settings, widget));
|
||||
send.done(kiri.codec.encode(fresh.map(widget => { return {
|
||||
id: widget.id,
|
||||
supports: widget.supports,
|
||||
} } )));
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: kiri-mode.sla.driver
|
||||
gapp.register("kiri-mode.sla.client", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { util } = base;
|
||||
const { SLA } = driver;
|
||||
|
||||
gapp.overlay(SLA, {
|
||||
init,
|
||||
printDownload
|
||||
});
|
||||
|
||||
function init(kiri, api) {
|
||||
api.event.on("mode.set", (mode) => {
|
||||
if (mode === 'SLA') {
|
||||
api.ui.func.preview.classList.add('hide');
|
||||
} else {
|
||||
api.ui.func.preview.classList.remove('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printDownload(output, api, names) {
|
||||
const { file, width, height, layers, volume } = output;
|
||||
const fileroot = names[0] || "print";
|
||||
const filename = `${fileroot}-${new Date().getTime().toString(36)}`;
|
||||
|
||||
api.modal.show('xsla');
|
||||
// api.ajax("/kiri/output-sla.html", html => {
|
||||
// api.ui.print.innerHTML = html;
|
||||
|
||||
let settings = api.conf.get(),
|
||||
process = settings.process,
|
||||
device = settings.device,
|
||||
print_sec = (process.slaBaseLayers * process.slaBaseOn) +
|
||||
(layers - process.slaBaseLayers) * process.slaLayerOn;
|
||||
|
||||
// add peel lift/drop times to total print time
|
||||
for (let i=0; i<layers; i++) {
|
||||
let dist = process.slaPeelDist,
|
||||
lift = process.slaPeelLiftRate,
|
||||
drop = process.slaPeelDropRate,
|
||||
off = process.slaLayerOff;
|
||||
if (i < process.slaBaseLayers) {
|
||||
dist = process.slaBasePeelDist;
|
||||
lift = process.slaBasePeelLiftRate;
|
||||
off = process.slaBaseOff;
|
||||
}
|
||||
print_sec += (dist * lift) / 60;
|
||||
print_sec += (dist * drop) / 60;
|
||||
print_sec += off;
|
||||
}
|
||||
|
||||
let print_min = Math.floor(print_sec/60),
|
||||
print_hrs = Math.floor(print_min/60),
|
||||
download = $('print-sla');
|
||||
|
||||
// add lift/drop time
|
||||
print_sec -= (print_min * 60);
|
||||
print_min -= (print_hrs * 60);
|
||||
print_sec = Math.round(print_sec).toString().padStart(2,'0');
|
||||
print_min = print_min.toString().padStart(2,'0');
|
||||
print_hrs = print_hrs.toString().padStart(2,'0');
|
||||
|
||||
$('print-filename-sla').value = filename;
|
||||
$('print-volume').value = (volume/1000).round(2);
|
||||
$('print-layers').value = layers;
|
||||
$('print-time').value = `${print_hrs}:${print_min}:${print_sec}`;
|
||||
|
||||
switch (device.deviceName) {
|
||||
case 'Anycubic.Photon':
|
||||
download.innerText += " .photon";
|
||||
download.onclick = () => { saveFile(api, file, ".photon") };
|
||||
break;
|
||||
case 'Anycubic.Photon.S':
|
||||
download.innerText += " .photons";
|
||||
download.onclick = () => { saveFile(api, file, ".photons") };
|
||||
break;
|
||||
case 'Creality.Halot.Sky':
|
||||
default:
|
||||
download.innerText += " .cxdlp";
|
||||
download.onclick = () => { saveFile(api, file, ".cxdlp") };
|
||||
break;
|
||||
}
|
||||
|
||||
// api.modal.show('print');
|
||||
// });
|
||||
}
|
||||
|
||||
function saveFile(api, file, ext) {
|
||||
api.util.download(file, $('print-filename-sla').value + ext);
|
||||
api.modal.hide();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri-mode.laser.driver
|
||||
gapp.register("kiri-mode.wjet.driver", [], (root, exports) => {
|
||||
|
||||
const DRIVERS = root.kiri.driver;
|
||||
const { LASER } = DRIVERS;
|
||||
const { TYPE } = LASER;
|
||||
DRIVERS.WJET = Object.assign({}, LASER, { name: 'WaterJet', type: TYPE.WJET });
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.license
|
||||
// dep: moto.broker
|
||||
// dep: geo.base
|
||||
// dep: geo.polygons
|
||||
// dep: geo.slicer
|
||||
// dep: geo.wasm
|
||||
// dep: kiri.codec
|
||||
// dep: kiri-mode.fdm.post
|
||||
// dep: kiri-mode.cam.topo
|
||||
// dep: kiri-mode.cam.topo4
|
||||
// use: kiri-mode.cam.slicer
|
||||
// dep: ext.clip2
|
||||
gapp.register("kiri-run.minion", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { polygons } = base;
|
||||
const { codec } = kiri;
|
||||
|
||||
const POLY = polygons;
|
||||
const clib = self.ClipperLib;
|
||||
const ctyp = clib.ClipType;
|
||||
const ptyp = clib.PolyType;
|
||||
const cfil = clib.PolyFillType;
|
||||
|
||||
let cache = self.cache = {};
|
||||
let name = "unknown";
|
||||
|
||||
// catch clipper alerts and convert to console messages
|
||||
self.alert = function(o) {
|
||||
console.log(o);
|
||||
};
|
||||
|
||||
self.onmessage = function(msg) {
|
||||
let data = msg.data;
|
||||
let cmd = data.cmd;
|
||||
(funcs[cmd] || funcs.bad)(data, data.seq, cmd);
|
||||
};
|
||||
|
||||
function reply(msg, direct) {
|
||||
self.postMessage(msg, direct);
|
||||
}
|
||||
|
||||
function log() {
|
||||
console.log(`[${name}]`, ...arguments);
|
||||
}
|
||||
|
||||
const funcs = self.minion = {
|
||||
label(data, seq) {
|
||||
name = data.name;
|
||||
},
|
||||
|
||||
config: data => {
|
||||
if (data.base) {
|
||||
Object.assign(base.config, data.base);
|
||||
} else {
|
||||
log({invalid: data});
|
||||
}
|
||||
},
|
||||
|
||||
union: (data, seq) => {
|
||||
if (!(data.polys && data.polys.length)) {
|
||||
reply({ seq, union: codec.encode([]) });
|
||||
return;
|
||||
}
|
||||
let state = { zeros: [] };
|
||||
let polys = codec.decode(data.polys);
|
||||
let union = POLY.union(polys, data.minarea || 0, true);
|
||||
reply({ seq, union: codec.encode(union) }, state.zeros);
|
||||
},
|
||||
|
||||
topShells: (data, seq) => {
|
||||
let top = codec.decode(data.top, {full: true});
|
||||
let {z, count, offset1, offsetN, fillOffset, opt} = data;
|
||||
kiri.driver.FDM.doTopShells(z, top, count, offset1, offsetN, fillOffset, opt);
|
||||
let state = { zeros: [] };
|
||||
reply({ seq, top: codec.encode(top, {full: true}) }, state.zeros);
|
||||
},
|
||||
|
||||
fill: (data, seq) => {
|
||||
let polys = codec.decode(data.polys);
|
||||
let { angle, spacing, minLen, maxLen } = data;
|
||||
let fill = POLY.fillArea(polys, angle, spacing, [], minLen, maxLen);
|
||||
let arr = new Float32Array(fill.length * 4);
|
||||
for (let i=0, p=0; p<fill.length; ) {
|
||||
let pt = fill[p++];
|
||||
arr[i++] = pt.x;
|
||||
arr[i++] = pt.y;
|
||||
arr[i++] = pt.z;
|
||||
arr[i++] = pt.index;
|
||||
}
|
||||
reply({ seq, fill: arr }, [ arr.buffer ]);
|
||||
},
|
||||
|
||||
clip: (data, seq) => {
|
||||
const clip = new clib.Clipper();
|
||||
const ctre = new clib.PolyTree();
|
||||
const clips = [];
|
||||
const M = base.config.clipper;
|
||||
|
||||
let lines = data.lines.map(array => {
|
||||
return codec.decodePointArray2D(array, data.z, (X, Y) => { return {X: X*M, Y: Y*M} })
|
||||
});
|
||||
let polys = data.polys.map(array => {
|
||||
return codec.decodePointArray2D(array, data.z, (X, Y) => { return {X: X*M, Y: Y*M} })
|
||||
});
|
||||
|
||||
clip.AddPaths(lines, ptyp.ptSubject, false);
|
||||
clip.AddPaths(polys, ptyp.ptClip, true);
|
||||
|
||||
const state = { zeros: [] };
|
||||
if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
|
||||
for (let node of ctre.m_AllPolys) {
|
||||
clips.push(codec.encode(POLY.fromClipperNode(node, data.z), state.zeros));
|
||||
}
|
||||
}
|
||||
|
||||
reply({ seq, clips }, state.zeros);
|
||||
},
|
||||
|
||||
sliceZ: (data, seq) => {
|
||||
let { z, points, options } = data;
|
||||
let i = 0, p = 0, realp = new Array(points.length / 3);
|
||||
while (i < points.length) {
|
||||
realp[p++] = base.newPoint(points[i++], points[i++], points[i++]).round(3);
|
||||
}
|
||||
let state = { zero: [] };
|
||||
let output = [];
|
||||
base.sliceZ(z, realp, {
|
||||
...options,
|
||||
each(out) { output.push(out) }
|
||||
}).then(() => {
|
||||
for (let rec of output) {
|
||||
// lines do not pass codec properly (for now)
|
||||
delete rec.lines;
|
||||
}
|
||||
reply({ seq, output: codec.encode(output) }, state.zeros);
|
||||
});
|
||||
},
|
||||
|
||||
putCache: msg => {
|
||||
const { key, data } = msg;
|
||||
// log({ minion_putCache: key, data });
|
||||
if (data) {
|
||||
cache[key] = data;
|
||||
} else {
|
||||
delete cache[key];
|
||||
}
|
||||
},
|
||||
|
||||
clearCache: msg => {
|
||||
for (let key in cache) {
|
||||
delete cache[key];
|
||||
}
|
||||
},
|
||||
|
||||
wasm: data => {
|
||||
if (data.enable) {
|
||||
base.wasm_ctrl.enable();
|
||||
} else {
|
||||
base.wasm_ctrl.disable();
|
||||
}
|
||||
},
|
||||
|
||||
bad: (data, seq, cmd) => {
|
||||
reply({ seq, error: `invalid command (${cmd})` });
|
||||
}
|
||||
};
|
||||
|
||||
moto.broker.publish("minion.started", { funcs, cache, reply, log });
|
||||
|
||||
});
|
||||
149
src/kiri/api.js
149
src/kiri/api.js
|
|
@ -1,149 +0,0 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.broker
|
||||
// dep: data.local
|
||||
// dep: kiri.utils
|
||||
// dep: kiri.consts
|
||||
gapp.register("kiri.api", (root, exports) => {
|
||||
|
||||
let { data, kiri, moto, noop } = root,
|
||||
{ consts, utils } = kiri,
|
||||
{ ajax, o2js, js2o } = utils,
|
||||
lists = consts.LISTS,
|
||||
clone = Object.clone,
|
||||
isHover = false,
|
||||
feature = {
|
||||
seed: true, // seed profiles on first use
|
||||
meta: true, // show selected widget metadata
|
||||
frame: true, // receive frame events
|
||||
alert_event: false, // emit alerts as events instead of display
|
||||
controls: true, // show or not side menus
|
||||
device_filter: undefined, // function to limit devices shown
|
||||
drop_group: undefined, // optional array to group multi drop
|
||||
drop_layout: true, // layout on new drop
|
||||
hoverAdds: false, // when true only searches widget additions
|
||||
on_key: undefined, // function override default key handlers
|
||||
on_key2: [], // allows for multiple key handlers
|
||||
on_load: undefined, // function override file drop loads
|
||||
on_add_stl: undefined, // legacy override stl drop loads
|
||||
on_mouse_up: undefined, // function intercepts mouse up select
|
||||
on_mouse_down: undefined, // function intercepts mouse down
|
||||
work_alerts: true, // allow disabling work progress alerts
|
||||
pmode: consts.PMODES.SPEED, // preview modes
|
||||
// hover: false, // when true fires mouse hover events
|
||||
get hover() {
|
||||
return isHover;
|
||||
},
|
||||
set hover(b) {
|
||||
isHover = b;
|
||||
moto.broker.publish("feature.hover", b);
|
||||
}
|
||||
},
|
||||
onkey = (fn) => {
|
||||
api.feature.on_key2.push(fn);
|
||||
},
|
||||
doit = {
|
||||
undo: noop, // do.js
|
||||
redo: noop // do.js
|
||||
},
|
||||
devel = {
|
||||
xray(layers, raw) {
|
||||
let proc = api.conf.get().process,
|
||||
size = proc.sliceHeight || proc.slaSlice || 1,
|
||||
base = (proc.firstSliceHeight || size);
|
||||
layers = Array.isArray(layers) ? layers : [ layers ];
|
||||
proc.xray = layers.map(l => raw ? l : base + l * size - size / 2);
|
||||
proc.xrayi = layers.slice();
|
||||
api.function.slice();
|
||||
}
|
||||
},
|
||||
local = {
|
||||
get: (key) => localGet(key),
|
||||
getInt: (key) => parseInt(localGet(key)),
|
||||
getFloat: (key) => parseFloat(localGet(key)),
|
||||
getBoolean: (key, def = true) => {
|
||||
let val = localGet(key);
|
||||
return val === true || val === 'true' || val === def;
|
||||
},
|
||||
toggle: (key, val, def) => localSet(key, val ?? !api.local.getBoolean(key, def)),
|
||||
put: (key, val) => localSet(key, val),
|
||||
set: (key, val) => localSet(key, val),
|
||||
},
|
||||
tweak = {
|
||||
line_precision(v) { api.work.config({base:{clipperClean: v}}) },
|
||||
gcode_decimals(v) { api.work.config({base:{gcode_decimals: v}}) }
|
||||
},
|
||||
und = undefined,
|
||||
api = exports({
|
||||
ajax, // via utils
|
||||
alerts: {}, // alerts.js
|
||||
busy: {}, // main.js
|
||||
catalog: und, // main.js
|
||||
clip, // <--
|
||||
clone, // <--
|
||||
color: und, // main.js
|
||||
conf: {}, // settings.js
|
||||
const: {}, // main.js
|
||||
devel, // <--
|
||||
device: {}, // devices.js
|
||||
devices: {}, // devices.js
|
||||
dialog: {}, // main.js
|
||||
doit, // <--
|
||||
event: {}, // main.js
|
||||
feature, // <--
|
||||
function: {}, // function.js
|
||||
group: {}, // main.js
|
||||
help: {}, // main.js
|
||||
hide: {}, // main.js
|
||||
image: {}, // main.js
|
||||
js2o, // via utils
|
||||
language: und, // main.js
|
||||
lists, // <--
|
||||
local, // <--
|
||||
modal: {}, // main.js
|
||||
mode: {}, // main.js
|
||||
o2js, // via utils
|
||||
onkey, // <--
|
||||
platform: {}, // platform.js
|
||||
probe: {}, // main.js
|
||||
process: {}, // main.js
|
||||
sdb: data.local,
|
||||
selection: {}, // selection.js
|
||||
settings: {}, // settings.js
|
||||
show: {}, // main.js
|
||||
space: {}, // main.js
|
||||
tool: {}, // kiri-mode/cam/tools.js
|
||||
tweak, // <--
|
||||
uc: {}, // main.js
|
||||
ui: {}, // main.js
|
||||
util: {}, // main.js
|
||||
var: {
|
||||
layer_lo: 0,
|
||||
layer_hi: 0,
|
||||
layer_max: 0
|
||||
},
|
||||
view: {}, // main.js
|
||||
widgets: {}, // widgets.js
|
||||
work: und, // main.js
|
||||
});
|
||||
|
||||
function clip(text) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.catch(err => console.error('Clipboard Error:', err));
|
||||
}
|
||||
|
||||
function localGet(key) {
|
||||
let sloc = api.conf.get().local;
|
||||
return sloc[key] || api.sdb[key];
|
||||
}
|
||||
|
||||
function localSet(key, val) {
|
||||
let sloc = api.conf.get().local;
|
||||
sloc[key] = api.sdb[key] = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,12 +1,6 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
gapp.register("kiri.alerts", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { api } = kiri;
|
||||
import { api } from './api.js';
|
||||
|
||||
let alerts = [];
|
||||
|
||||
|
|
@ -68,11 +62,14 @@ function update(clear) {
|
|||
}
|
||||
}
|
||||
|
||||
// extend API
|
||||
Object.assign(api.alerts, {
|
||||
export {
|
||||
hide,
|
||||
show,
|
||||
update
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
export default {
|
||||
hide,
|
||||
show,
|
||||
update
|
||||
};
|
||||
189
src/kiri/core/api.js
Normal file
189
src/kiri/core/api.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import alerts from './alerts.js';
|
||||
import settings from './settings.js'
|
||||
import STACKS from './stacks.js';
|
||||
|
||||
import { broker } from '../../moto/broker.js';
|
||||
import { client as work } from './client.js';
|
||||
import { consts, COLOR as color, LISTS as lists, MODES, VIEWS, PATHS } 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 { local as dataLocal } from '../../data/local.js';
|
||||
import { noop, ajax, o2js, js2o } from './utils.js';
|
||||
import { functions } from './function.js';
|
||||
import { platform } from './platform.js';
|
||||
import { selection } from './selection.js';
|
||||
import { stats } from './stats.js';
|
||||
import { newWidget } from './widget.js';
|
||||
import { widgets } from './widgets.js';
|
||||
import { updateTool } from '../mode/cam/tools.js';
|
||||
import { beta, version } from '../../moto/license.js';
|
||||
import { space as SPACE } from '../../moto/space.js';
|
||||
|
||||
import { LANG } from './lang.js';
|
||||
import { LOCAL, SETUP, SECURE } from './main.js';
|
||||
import { UI } from './ui.js';
|
||||
|
||||
import web from '../../moto/webui.js';
|
||||
|
||||
let UC = UI.prefix('kiri').inputAction(settings.conf.update),
|
||||
und = undefined,
|
||||
clone = Object.clone,
|
||||
isHover = false,
|
||||
feature = {
|
||||
seed: true, // seed profiles on first use
|
||||
meta: true, // show selected widget metadata
|
||||
frame: true, // receive frame events
|
||||
alert_event: false, // emit alerts as events instead of display
|
||||
controls: true, // show or not side menus
|
||||
device_filter: und, // function to limit devices shown
|
||||
drop_group: und, // optional array to group multi drop
|
||||
drop_layout: true, // layout on new drop
|
||||
hoverAdds: false, // when true only searches widget additions
|
||||
on_key: und, // function override default key handlers
|
||||
on_key2: [], // allows for multiple key handlers
|
||||
on_load: und, // function override file drop loads
|
||||
on_add_stl: und, // legacy override stl drop loads
|
||||
on_mouse_up: und, // function intercepts mouse up select
|
||||
on_mouse_down: und, // function intercepts mouse down
|
||||
work_alerts: true, // allow disabling work progress alerts
|
||||
pmode: consts.PMODES.SPEED, // preview modes
|
||||
// hover: false, // when true fires mouse hover events
|
||||
get hover() {
|
||||
return isHover;
|
||||
},
|
||||
set hover(b) {
|
||||
isHover = b;
|
||||
broker.publish("feature.hover", b);
|
||||
}
|
||||
},
|
||||
busyVal = 0,
|
||||
busy = {
|
||||
val() { return busyVal },
|
||||
inc() { api.event.emit("busy", ++busyVal) },
|
||||
dec() { api.event.emit("busy", --busyVal) }
|
||||
},
|
||||
onkey = (fn) => {
|
||||
api.feature.on_key2.push(fn);
|
||||
},
|
||||
doit = {
|
||||
undo: noop, // do.js
|
||||
redo: noop // do.js
|
||||
},
|
||||
devel = {
|
||||
xray(layers, raw) {
|
||||
let proc = api.conf.get().process,
|
||||
size = proc.sliceHeight || proc.slaSlice || 1,
|
||||
base = (proc.firstSliceHeight || size);
|
||||
layers = Array.isArray(layers) ? layers : [layers];
|
||||
proc.xray = layers.map(l => raw ? l : base + l * size - size / 2);
|
||||
proc.xrayi = layers.slice();
|
||||
api.function.slice();
|
||||
}
|
||||
},
|
||||
local = {
|
||||
get: (key) => localGet(key),
|
||||
getItem: (key) => localGet(key),
|
||||
getInt: (key) => parseInt(localGet(key)),
|
||||
getFloat: (key) => parseFloat(localGet(key)),
|
||||
getBoolean: (key, def = true) => {
|
||||
let val = localGet(key);
|
||||
return val === true || val === 'true' || val === def;
|
||||
},
|
||||
toggle: (key, val, def) => localSet(key, val ?? !api.local.getBoolean(key, def)),
|
||||
put: (key, val) => localSet(key, val),
|
||||
set: (key, val) => localSet(key, val),
|
||||
setItem: (key, val) => localSet(key, val),
|
||||
},
|
||||
tweak = {
|
||||
line_precision(v) { api.work.config({ base: { clipperClean: v } }) },
|
||||
gcode_decimals(v) { api.work.config({ base: { gcode_decimals: v } }) }
|
||||
};
|
||||
|
||||
function clip(text) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.catch(err => console.error('Clipboard Error:', err));
|
||||
}
|
||||
|
||||
function localGet(key) {
|
||||
let sloc = api.conf.get().local;
|
||||
return sloc[key] || api.sdb[key];
|
||||
}
|
||||
|
||||
function localSet(key, val) {
|
||||
let sloc = api.conf.get().local;
|
||||
sloc[key] = api.sdb[key] = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
ajax,
|
||||
beta,
|
||||
alerts,
|
||||
busy,
|
||||
catalog,
|
||||
client: work,
|
||||
clip,
|
||||
clone,
|
||||
color,
|
||||
conf: settings.conf,
|
||||
const: { LANG, LOCAL, SETUP, SECURE, STACKS, ...consts },
|
||||
devel,
|
||||
device,
|
||||
devices,
|
||||
dialog,
|
||||
doit,
|
||||
event,
|
||||
feature,
|
||||
function: functions,
|
||||
group,
|
||||
help,
|
||||
hide,
|
||||
image,
|
||||
js2o,
|
||||
language: LANG,
|
||||
lists,
|
||||
local,
|
||||
modal,
|
||||
mode,
|
||||
new: {
|
||||
widget: newWidget
|
||||
},
|
||||
noop,
|
||||
o2js,
|
||||
onkey,
|
||||
platform,
|
||||
probe,
|
||||
process,
|
||||
sdb: dataLocal,
|
||||
selection,
|
||||
settings,
|
||||
show,
|
||||
space,
|
||||
SPACE,
|
||||
stacks: STACKS,
|
||||
stats,
|
||||
tool: {
|
||||
update: updateTool
|
||||
},
|
||||
tweak,
|
||||
uc: UC,
|
||||
ui: {},
|
||||
util,
|
||||
var: {
|
||||
layer_lo: 0,
|
||||
layer_hi: 0,
|
||||
layer_max: 0
|
||||
},
|
||||
version,
|
||||
view,
|
||||
web,
|
||||
widgets,
|
||||
work,
|
||||
};
|
||||
|
||||
// allow widget to straddle client / worker FOR NOW
|
||||
self.kiri_api = api;
|
||||
52
src/kiri/core/boxes.js
Normal file
52
src/kiri/core/boxes.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { space } from '../../moto/space.js';
|
||||
|
||||
const { BoxGeometry, Matrix4, MeshPhongMaterial, Quaternion, Vector3, Mesh } = THREE;
|
||||
const boxes = [];
|
||||
|
||||
let lastBox;
|
||||
|
||||
export function getlastbox() {
|
||||
return lastBox;
|
||||
}
|
||||
|
||||
export function delbox(name) {
|
||||
const old = boxes[name];
|
||||
if (old) {
|
||||
old.groupTo.remove(old);
|
||||
}
|
||||
}
|
||||
|
||||
export function addbox(point, color, name, dim = {x:1,y:1,z:1,rz:0}, opt = {}) {
|
||||
delbox(name);
|
||||
|
||||
const box = boxes[name] = new Mesh(
|
||||
new BoxGeometry(dim.x, dim.y, dim.z),
|
||||
new MeshPhongMaterial({
|
||||
transparent: true,
|
||||
opacity: opt.opacity || 0.5,
|
||||
color
|
||||
})
|
||||
);
|
||||
|
||||
box.position.x = point.x;
|
||||
box.position.y = point.y;
|
||||
box.position.z = point.z;
|
||||
|
||||
lastBox = { point, dim };
|
||||
|
||||
const group = opt.group || space.scene;
|
||||
group.add(box);
|
||||
box.groupTo = group;
|
||||
|
||||
if (dim.rz) {
|
||||
opt.rotate = new Quaternion().setFromAxisAngle(new Vector3(0,0,1), dim.rz);
|
||||
}
|
||||
if (opt.rotate) {
|
||||
opt.matrix = new Matrix4().makeRotationFromQuaternion(opt.rotate);
|
||||
}
|
||||
if (opt.matrix) {
|
||||
box.geometry.applyMatrix4(opt.matrix);
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
|
@ -1,27 +1,19 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: kiri.api
|
||||
gapp.register("kiri.client", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
import { api } from './api.js';
|
||||
import { noop } from './utils.js';
|
||||
|
||||
// this code runs in kiri's main loop
|
||||
let loc = self.location,
|
||||
host = loc.hostname,
|
||||
port = loc.port,
|
||||
proto = loc.protocol,
|
||||
debug = self.debug === true,
|
||||
let debug = self.debug === true,
|
||||
time = Date.now,
|
||||
seqid = 1,
|
||||
syncd = {},
|
||||
running = {},
|
||||
worker = null,
|
||||
minions = false,
|
||||
restarting = false
|
||||
// occ = new Worker("/kiri/ext/occ-worker.js", {type:"module"}),
|
||||
;
|
||||
restarting = false,
|
||||
workpath,
|
||||
poolpath;
|
||||
|
||||
/**
|
||||
* @param {Function} fn name of function in kiri.worker
|
||||
|
|
@ -52,13 +44,23 @@ function send(fn, data, onreply, zerocopy) {
|
|||
}
|
||||
|
||||
// code is running in the browser / client context
|
||||
const client = exports({
|
||||
export const client = {
|
||||
send: send,
|
||||
|
||||
setWorkPath(path) {
|
||||
workpath = path;
|
||||
return client;
|
||||
},
|
||||
|
||||
setPoolPath(path) {
|
||||
poolpath = path;
|
||||
return client;
|
||||
},
|
||||
|
||||
pool: {
|
||||
start() {
|
||||
minions = true;
|
||||
send("pool_start", {}, noop);
|
||||
send("pool_start", { url: poolpath }, noop);
|
||||
},
|
||||
|
||||
stop() {
|
||||
|
|
@ -75,8 +77,16 @@ const client = exports({
|
|||
const blob = new Blob([ work ], { type: 'application/javascript' });
|
||||
return new Worker(URL.createObjectURL(blob));
|
||||
} else {
|
||||
let _ = debug ? '_' : '';
|
||||
return new Worker(`/code/kiri_work.js?${_}${gapp.version}`);
|
||||
let worker = new Worker(workpath || api.const.PATHS.work, { type: 'module' });
|
||||
worker.onerror = (error) => {
|
||||
console.log({ WORKER_ERROR: error });
|
||||
error.preventDefault();
|
||||
};
|
||||
worker.onmessageerror = (error) => {
|
||||
console.log({ WORKER_MESSAGE_ERROR: error });
|
||||
error.preventDefault();
|
||||
};
|
||||
return worker;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -89,6 +99,10 @@ const client = exports({
|
|||
return current > 0;
|
||||
},
|
||||
|
||||
start() {
|
||||
client.restart();
|
||||
},
|
||||
|
||||
restart() {
|
||||
// prevent re-entry from cancel callback
|
||||
if (restarting) {
|
||||
|
|
@ -112,7 +126,7 @@ const client = exports({
|
|||
running = {};
|
||||
worker = client.newWorker();
|
||||
|
||||
client.onmessage = worker.onmessage = function(e) {
|
||||
client.onmessage = worker.onmessage = (e) => {
|
||||
let now = time(),
|
||||
reply = e.data,
|
||||
record = running[reply.seq],
|
||||
|
|
@ -161,7 +175,7 @@ const client = exports({
|
|||
// widget sync
|
||||
sync(widgets) {
|
||||
if (!widgets) {
|
||||
widgets = kiri.api.widgets.all();
|
||||
widgets = api.widgets.all();
|
||||
}
|
||||
// sync any widget that has changed
|
||||
for (let widget of widgets.filter(w => w.modified || !syncd[w.id])) {
|
||||
|
|
@ -245,7 +259,7 @@ const client = exports({
|
|||
ondone(null, reply.error);
|
||||
}
|
||||
if (reply.debug) {
|
||||
kiri.api.event.emit("export.debug", reply.debug);
|
||||
api.event.emit("export.debug", reply.debug);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -334,9 +348,8 @@ const client = exports({
|
|||
// console.log({ clearCache_reply: reply });
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// start worker
|
||||
client.restart();
|
||||
self.kiri_client = client;
|
||||
|
||||
});
|
||||
export default client;
|
||||
|
|
@ -1,32 +1,14 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { Layers } from './layers.js';
|
||||
import { newPoint } from '../../geo/point.js';
|
||||
import { Polygon, newPolygon } from '../../geo/polygon.js';
|
||||
import { Slice, newSlice, Top, newTop } from './slice.js';
|
||||
import { Widget, newWidget } from './widget.js';
|
||||
|
||||
// dep: main.kiri
|
||||
// dep: geo.base
|
||||
// dep: geo.polygon
|
||||
// dep: kiri.widget
|
||||
// dep: kiri.slice
|
||||
// dep: kiri.layers
|
||||
gapp.register("kiri.codec", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const decoders = {};
|
||||
const freeMem = true;
|
||||
|
||||
const codec = exports({
|
||||
undef: undefined,
|
||||
encode: encode,
|
||||
decode: decode,
|
||||
registerDecoder: registerDecoder,
|
||||
allocFloat32Array: allocFloat32Array,
|
||||
encodePointArray,
|
||||
decodePointArray,
|
||||
encodePointArray2D,
|
||||
decodePointArray2D,
|
||||
toCodable
|
||||
});
|
||||
|
||||
const TYPE = {
|
||||
WIDGET: 100,
|
||||
SLICE: 200,
|
||||
|
|
@ -210,7 +192,7 @@ function decodePointArray(array) {
|
|||
const points = new Array(length / 3);
|
||||
|
||||
for (let vid=0, pid=0; vid < length; ) {
|
||||
points[pid++] = base.newPoint(array[vid++], array[vid++], array[vid++]);
|
||||
points[pid++] = newPoint(array[vid++], array[vid++], array[vid++]);
|
||||
}
|
||||
|
||||
return points;
|
||||
|
|
@ -225,7 +207,7 @@ function decodePointArray2D(array, z, fn) {
|
|||
for (let vid=0, pid=0; vid < length; ) {
|
||||
points[pid++] = fn ?
|
||||
fn(array[vid++], array[vid++]) :
|
||||
base.newPoint(array[vid++], array[vid++], z);
|
||||
newPoint(array[vid++], array[vid++], z);
|
||||
}
|
||||
|
||||
return points;
|
||||
|
|
@ -233,7 +215,7 @@ function decodePointArray2D(array, z, fn) {
|
|||
|
||||
// ----- Widget Codec -----
|
||||
|
||||
kiri.Widget.prototype.encode = function(state) {
|
||||
Widget.prototype.encode = function(state) {
|
||||
const json = state._json_;
|
||||
const geo = this.getGeoVertices();
|
||||
const coded = {
|
||||
|
|
@ -252,7 +234,7 @@ registerDecoder(TYPE.WIDGET, function(v, state) {
|
|||
const id = v.id,
|
||||
group = v.group || id,
|
||||
track = v.track || undefined,
|
||||
widget = kiri.newWidget(id, kiri.Widget.Groups.forid(group));
|
||||
widget = newWidget(id, Widget.Groups.forid(group));
|
||||
|
||||
widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo);
|
||||
widget.saved = Date.now();
|
||||
|
|
@ -266,7 +248,7 @@ registerDecoder(TYPE.WIDGET, function(v, state) {
|
|||
|
||||
// ----- Slice Codec -----
|
||||
|
||||
kiri.Slice.prototype.encode = function(state) {
|
||||
Slice.prototype.encode = function(state) {
|
||||
const rv = {
|
||||
type: TYPE.SLICE,
|
||||
z: this.z,
|
||||
|
|
@ -279,7 +261,7 @@ kiri.Slice.prototype.encode = function(state) {
|
|||
};
|
||||
|
||||
registerDecoder(TYPE.SLICE, function(v, state) {
|
||||
let slice = kiri.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
|
||||
let slice = newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
|
||||
|
||||
slice.index = v.index;
|
||||
slice.layers = decode(v.layers, state)
|
||||
|
|
@ -289,7 +271,7 @@ registerDecoder(TYPE.SLICE, function(v, state) {
|
|||
|
||||
// ----- Slice.Top Codec -----
|
||||
|
||||
kiri.Top.prototype.encode = function(state) {
|
||||
Top.prototype.encode = function(state) {
|
||||
let obj = {
|
||||
type: TYPE.TOP,
|
||||
poly: encode(this.poly, state)
|
||||
|
|
@ -307,7 +289,7 @@ kiri.Top.prototype.encode = function(state) {
|
|||
};
|
||||
|
||||
registerDecoder(TYPE.TOP, function(v, state) {
|
||||
let top = kiri.newTop(decode(v.poly, state));
|
||||
let top = newTop(decode(v.poly, state));
|
||||
if (state.full) {
|
||||
// top.gaps = decode(v.gaps, state);
|
||||
top.last = decode(v.last, state);
|
||||
|
|
@ -320,7 +302,7 @@ registerDecoder(TYPE.TOP, function(v, state) {
|
|||
|
||||
// ----- Polygon Codec -----
|
||||
|
||||
base.Polygon.prototype.encode = function(state) {
|
||||
Polygon.prototype.encode = function(state) {
|
||||
if (!state.poly) state.poly = {};
|
||||
|
||||
let cached = state.poly[this.id];
|
||||
|
|
@ -352,10 +334,10 @@ registerDecoder(TYPE.POLY, function(v, state) {
|
|||
|
||||
const array = v.array;
|
||||
const length = array.length;
|
||||
const poly = base.newPolygon();
|
||||
const poly = newPolygon();
|
||||
|
||||
for (let vid = 0; vid < length; ) {
|
||||
poly.push(base.newPoint(array[vid++], array[vid++], array[vid++]));
|
||||
poly.push(newPoint(array[vid++], array[vid++], array[vid++]));
|
||||
}
|
||||
|
||||
state.poly[v.id] = poly;
|
||||
|
|
@ -382,7 +364,7 @@ function encodeLayerPolys(polys, state) {
|
|||
});
|
||||
}
|
||||
|
||||
kiri.Layers.prototype.encode = function(state) {
|
||||
Layers.prototype.encode = function(state) {
|
||||
let zeros = state.zeros;
|
||||
let enc = {
|
||||
type: TYPE.LAYERS,
|
||||
|
|
@ -419,7 +401,7 @@ kiri.Layers.prototype.encode = function(state) {
|
|||
};
|
||||
|
||||
registerDecoder(TYPE.LAYERS, function(v, state) {
|
||||
const render = new kiri.Layers();
|
||||
const render = new Layers();
|
||||
const { layers } = v;
|
||||
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
|
|
@ -463,4 +445,28 @@ registerDecoder(TYPE.LAYERS, function(v, state) {
|
|||
return render;
|
||||
});
|
||||
|
||||
});
|
||||
export const codec = {
|
||||
encode,
|
||||
decode,
|
||||
registerDecoder,
|
||||
allocFloat32Array,
|
||||
encodePointArray,
|
||||
decodePointArray,
|
||||
encodePointArray2D,
|
||||
decodePointArray2D,
|
||||
toCodable
|
||||
};
|
||||
|
||||
export {
|
||||
encode,
|
||||
decode,
|
||||
registerDecoder,
|
||||
allocFloat32Array,
|
||||
encodePointArray,
|
||||
decodePointArray,
|
||||
encodePointArray2D,
|
||||
decodePointArray2D,
|
||||
toCodable
|
||||
};
|
||||
|
||||
self.kiri_codec = codec;
|
||||
|
|
@ -1,13 +1,5 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: add.array
|
||||
// dep: data.local
|
||||
gapp.register("kiri.conf", (root, exports) => {
|
||||
|
||||
const { data } = root;
|
||||
const { local } = data;
|
||||
const { clone } = Object;
|
||||
const CVER = 410;
|
||||
|
||||
|
|
@ -219,7 +211,7 @@ const renamed = {
|
|||
outputClockwise: "camConventional"
|
||||
};
|
||||
|
||||
const conf = exports({
|
||||
export const conf = {
|
||||
// --------------- helper functions
|
||||
normalize,
|
||||
device_from_code,
|
||||
|
|
@ -981,7 +973,7 @@ const conf = exports({
|
|||
id: genID(),
|
||||
ver: CVER
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const settings = conf.template;
|
||||
|
||||
|
|
@ -1000,5 +992,3 @@ settings.cdev.LASER = clone(settings.device);
|
|||
settings.cdev.DRAG = clone(settings.device);
|
||||
settings.cdev.WJET = clone(settings.device);
|
||||
settings.cdev.WEDM = clone(settings.device);
|
||||
|
||||
});
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.consts", (root, exports) => {
|
||||
|
||||
const COLOR = {
|
||||
wireframe: 0x444444,
|
||||
wireframe_opacity: 0.25,
|
||||
|
|
@ -150,16 +146,22 @@ const PMODES = {
|
|||
TOOLS: 2
|
||||
};
|
||||
|
||||
const PATHS = {
|
||||
work: "/lib/kiri/run/worker.js",
|
||||
pool: "/lib/kiri/run/minion.js"
|
||||
};
|
||||
|
||||
const SEED = 'kiri-seed';
|
||||
|
||||
exports({
|
||||
PMODES,
|
||||
export let consts = {
|
||||
COLOR,
|
||||
LISTS,
|
||||
MODES,
|
||||
PATHS,
|
||||
PMODES,
|
||||
VIEWS,
|
||||
SEED,
|
||||
beltfact: Math.cos(Math.PI / 4)
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
export { COLOR, LISTS, MODES, VIEWS, PATHS, PMODES, SEED };
|
||||
|
|
@ -13,7 +13,7 @@ const gapp = self.gapp;
|
|||
|
||||
eval( fs.readFileSync("src/add/array.js").toString() );
|
||||
eval( fs.readFileSync("src/data/local.js").toString() );
|
||||
eval( fs.readFileSync("src/kiri/conf.js").toString() );
|
||||
eval( fs.readFileSync("src/kiri/core/conf.js").toString() );
|
||||
|
||||
gapp.main(undefined, undefined, root => {
|
||||
let kiri = root.kiri;
|
||||
382
src/kiri/core/devices.js
Normal file
382
src/kiri/core/devices.js
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { $, h } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { conf } from './conf.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
import { devices as devlist } from '../../pack/kiri-devs.js';
|
||||
import { settings, conf as setconf } from './settings.js';
|
||||
|
||||
export const device = {
|
||||
clone: cloneDevice,
|
||||
code: currentDeviceCode,
|
||||
get: currentDeviceName,
|
||||
set: selectDevice,
|
||||
isBelt
|
||||
};
|
||||
|
||||
export const devices = {
|
||||
show: showDevices,
|
||||
select: selectDevice,
|
||||
refresh: updateDeviceList,
|
||||
update_laser_state: updateLaserState
|
||||
};
|
||||
|
||||
function isBelt() {
|
||||
return setconf.get().device.bedBelt;
|
||||
}
|
||||
|
||||
function currentDeviceName() {
|
||||
return setconf.get().filter[api.mode.get()];
|
||||
}
|
||||
|
||||
function currentDeviceCode() {
|
||||
return setconf.get().devices[currentDeviceName()];
|
||||
}
|
||||
|
||||
function getModeDevices() {
|
||||
return Object.keys(devlist[ api.mode.get_lower() ]).sort();
|
||||
}
|
||||
|
||||
export function showDevices() {
|
||||
settings.sync.get().then(_showDevices);
|
||||
}
|
||||
|
||||
function _showDevices() {
|
||||
updateDeviceList();
|
||||
api.modal.show('setup');
|
||||
}
|
||||
|
||||
function updateDeviceList() {
|
||||
renderDevices(getModeDevices());
|
||||
}
|
||||
|
||||
function updateDeviceName(newname) {
|
||||
let selected = api.device.get(),
|
||||
devs = setconf.get().devices;
|
||||
if (newname !== selected) {
|
||||
devs[newname] = devs[selected];
|
||||
delete devs[selected];
|
||||
selectDevice(newname);
|
||||
updateDeviceList();
|
||||
}
|
||||
}
|
||||
|
||||
function putLocalDevice(devicename, obj) {
|
||||
setconf.get().devices[devicename] = obj;
|
||||
setconf.save();
|
||||
}
|
||||
|
||||
function removeLocalDevice(devicename) {
|
||||
delete setconf.get().devices[devicename];
|
||||
setconf.save();
|
||||
settings.sync.put();
|
||||
}
|
||||
|
||||
function isLocalDevice(devicename) {
|
||||
return setconf.get().devices[devicename] ? true : false;
|
||||
}
|
||||
|
||||
function getSelectedDevice() {
|
||||
return api.device.get();
|
||||
}
|
||||
|
||||
function selectDevice(devicename) {
|
||||
if (isLocalDevice(devicename)) {
|
||||
setDeviceCode(setconf.get().devices[devicename], devicename);
|
||||
} else {
|
||||
let code = devlist[api.mode.get_lower()][devicename];
|
||||
if (code) {
|
||||
setDeviceCode(code, devicename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only for local filters
|
||||
function cloneDevice() {
|
||||
let name = `${getSelectedDevice().replace(/\./g,' ')}`;
|
||||
let code = api.clone(setconf.get().device);
|
||||
code.mode = api.mode.get();
|
||||
if (name.toLowerCase().indexOf('my ') >= 0) {
|
||||
name = `${name} copy`;
|
||||
} else {
|
||||
name = `My ${name}`;
|
||||
}
|
||||
putLocalDevice(name, code);
|
||||
setDeviceCode(code, name);
|
||||
settings.sync.put();
|
||||
}
|
||||
|
||||
function updateLaserState() {
|
||||
const dev = setconf.get().device;
|
||||
$('laser-on').style.display = dev.useLaser ? 'flex' : 'none';
|
||||
$('laser-off').style.display = dev.useLaser ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function setDeviceCode(code, devicename) {
|
||||
api.event.emit('device.select', devicename);
|
||||
try {
|
||||
if (typeof(code) === 'string') code = js2o(code) || {};
|
||||
|
||||
let mode = api.mode.get(),
|
||||
lmode = mode.toLowerCase(),
|
||||
current = setconf.get(),
|
||||
local = isLocalDevice(devicename),
|
||||
dev = current.device = conf.device_from_code(code,mode),
|
||||
dproc = current.devproc[devicename], // last process name for this device
|
||||
newdev = dproc === undefined, // first time device is selected
|
||||
predev = current.filter[mode], // previous device selection
|
||||
chgdev = predev !== devicename; // device is changing
|
||||
|
||||
// fill missing device fields
|
||||
conf.fill_cull_once(dev, conf.defaults[lmode].d);
|
||||
|
||||
// first time device use, add any print profiles and set to default if present
|
||||
if (code.profiles) {
|
||||
for (let profile of code.profiles) {
|
||||
let profname = profile.processName;
|
||||
// if no saved profile by that name for this mode...
|
||||
if (!current.sproc[mode][profname]) {
|
||||
console.log('adding profile', profname, 'to', mode);
|
||||
current.sproc[mode][profname] = profile;
|
||||
}
|
||||
// if it's a new device, seed the new profile name as last profile
|
||||
if (newdev && !current.devproc[devicename]) {
|
||||
console.log('setting default profile for new device', devicename, 'to', profname);
|
||||
current.devproc[devicename] = dproc = profname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dev.new = false;
|
||||
dev.deviceName = devicename;
|
||||
|
||||
let { platform, ui, uc } = api;
|
||||
|
||||
ui.deviceBelt.checked = dev.bedBelt;
|
||||
ui.deviceRound.checked = dev.bedRound;
|
||||
ui.deviceOrigin.checked = dev.ctOriginCenter || dev.originCenter || dev.bedRound;
|
||||
ui.fwRetract.checked = dev.fwRetract;
|
||||
|
||||
// add extruder selection buttons
|
||||
if (dev.extruders) {
|
||||
let ext = api.lists.extruders = [];
|
||||
dev.internal = 0;
|
||||
for (let i=0; i<dev.extruders.length; i++) {
|
||||
ext.push({id:i, name:i});
|
||||
}
|
||||
}
|
||||
|
||||
// disable editing for non-local devices
|
||||
[
|
||||
// ui.deviceName,
|
||||
ui.gcodePre,
|
||||
ui.gcodePost,
|
||||
ui.bedDepth,
|
||||
ui.bedWidth,
|
||||
ui.maxHeight,
|
||||
ui.useLaser,
|
||||
ui.resolutionX,
|
||||
ui.resolutionY,
|
||||
ui.deviceOrigin,
|
||||
ui.deviceRound,
|
||||
ui.deviceBelt,
|
||||
ui.fwRetract,
|
||||
ui.deviceZMax,
|
||||
ui.gcodeTime,
|
||||
ui.gcodeFan,
|
||||
ui.gcodeFeature,
|
||||
ui.gcodeTrack,
|
||||
ui.gcodeLayer,
|
||||
ui.extFilament,
|
||||
ui.extNozzle,
|
||||
ui.spindleMax,
|
||||
ui.gcodeSpindle,
|
||||
ui.gcodeDwell,
|
||||
ui.gcodeChange,
|
||||
ui.gcodeFExt,
|
||||
ui.gcodeSpace,
|
||||
ui.gcodeStrip,
|
||||
ui.gcodeLaserOn,
|
||||
ui.gcodeLaserOff,
|
||||
ui.laserMaxPower,
|
||||
ui.extPrev,
|
||||
ui.extNext,
|
||||
ui.extAdd,
|
||||
ui.extDel,
|
||||
ui.extOffsetX,
|
||||
ui.extOffsetY
|
||||
].forEach(function(e) {
|
||||
e.disabled = !local;
|
||||
});
|
||||
|
||||
ui.deviceSave.disabled = !local;
|
||||
ui.deviceDelete.disabled = !local;
|
||||
ui.deviceRename.disabled = !local;
|
||||
ui.deviceExport.disabled = !local;
|
||||
ui.deviceAdd.style.display = mode === 'SLA' ? 'none' : '';
|
||||
|
||||
if (local) {
|
||||
ui.deviceAdd.innerText = "copy";
|
||||
ui.deviceDelete.style.display = '';
|
||||
ui.deviceRename.style.display = '';
|
||||
ui.deviceExport.style.display = '';
|
||||
} else {
|
||||
ui.deviceAdd.innerText = "customize";
|
||||
ui.deviceDelete.style.display = 'none';
|
||||
ui.deviceRename.style.display = 'none';
|
||||
ui.deviceExport.style.display = 'none';
|
||||
}
|
||||
ui.deviceAdd.disabled = dev.noclone;
|
||||
|
||||
setconf.update_fields();
|
||||
space.platform.setBelt(isBelt());
|
||||
platform.update_size();
|
||||
platform.update_origin();
|
||||
platform.update();
|
||||
updateLaserState();
|
||||
|
||||
// store current device name for this mode
|
||||
current.filter[mode] = devicename;
|
||||
// cache device record for this mode (restored in setMode)
|
||||
current.cdev[mode] = dev;
|
||||
|
||||
if (dproc) {
|
||||
// restore last process associated with this device
|
||||
setconf.load(null, dproc);
|
||||
} else {
|
||||
setconf.update();
|
||||
}
|
||||
|
||||
setconf.save();
|
||||
|
||||
if (isBelt()) {
|
||||
// space.view.setHome(dev.bedBelt ? Math.PI/2 : 0, Math.PI / 2.5);
|
||||
space.view.setHome(0, Math.PI / 2.5);
|
||||
} else {
|
||||
space.view.setHome(0);
|
||||
}
|
||||
// when changing devices, update focus on widgets
|
||||
if (chgdev) {
|
||||
setTimeout(api.space.set_focus, 0);
|
||||
}
|
||||
|
||||
uc.refresh(1);
|
||||
api.event.emit('device.selected', dev);
|
||||
} catch (e) {
|
||||
console.log({error:e, device:code, devicename});
|
||||
api.show.alert(`invalid or deprecated device: "${devicename}"`, 10);
|
||||
api.show.alert(`please select a new device`, 10);
|
||||
throw e;
|
||||
showDevices();
|
||||
}
|
||||
api.function.clear();
|
||||
api.event.settings();
|
||||
}
|
||||
|
||||
function renderDevices(devices) {
|
||||
let selected = api.device.get() || devices[0],
|
||||
features = api.feature,
|
||||
devs = setconf.get().devices,
|
||||
dfilter = typeof(features.device_filter) === 'function' ? features.device_filter : undefined;
|
||||
|
||||
for (let local in devs) {
|
||||
if (!(devs.hasOwnProperty(local) && devs[local])) {
|
||||
continue;
|
||||
}
|
||||
let dev = devs[local],
|
||||
fdmCode = dev.cmd,
|
||||
fdmMode = (api.mode.get() === 'FDM');
|
||||
if (dev.mode ? (dev.mode === api.mode.get()) : (fdmCode ? fdmMode : !fdmMode)) {
|
||||
devices.push(local);
|
||||
}
|
||||
};
|
||||
|
||||
devices = devices.sort();
|
||||
|
||||
let { event, ui } = api;
|
||||
|
||||
event.emit('devices.render', devices);
|
||||
|
||||
ui.deviceSave.onclick = function() {
|
||||
event.emit('device.save');
|
||||
api.function.clear();
|
||||
setconf.save();
|
||||
settings.sync.put();
|
||||
showDevices();
|
||||
api.modal.hide();
|
||||
};
|
||||
ui.deviceAdd.onclick = function() {
|
||||
api.function.clear();
|
||||
cloneDevice();
|
||||
showDevices();
|
||||
};
|
||||
ui.deviceDelete.onclick = function() {
|
||||
api.function.clear();
|
||||
removeLocalDevice(getSelectedDevice());
|
||||
selectDevice(getModeDevices()[0]);
|
||||
showDevices();
|
||||
};
|
||||
ui.deviceRename.onclick = function() {
|
||||
api.uc.prompt(`Rename "${selected}`, selected).then(newname => {
|
||||
if (newname) {
|
||||
updateDeviceName(newname);
|
||||
setconf.save();
|
||||
settings.sync.put();
|
||||
showDevices();
|
||||
} else {
|
||||
showDevices();
|
||||
}
|
||||
});
|
||||
};
|
||||
ui.deviceExport.onclick = function(event) {
|
||||
const record = {
|
||||
version: kiri.version,
|
||||
device: selected,
|
||||
process: api.process.code(),
|
||||
profiles: event.altKey ? settings.prof() : undefined,
|
||||
code: devs[selected],
|
||||
time: Date.now()
|
||||
};
|
||||
let exp = api.util.b64enc(record);
|
||||
api.device.export(exp, selected, { event, record });
|
||||
};
|
||||
|
||||
let dedup = {};
|
||||
let list_cdev = [];
|
||||
let list_mdev = [];
|
||||
devices.forEach(function(device, index) {
|
||||
// prevent device from appearing twice
|
||||
// such as local name = standard device name
|
||||
if (dedup[device]) {
|
||||
return;
|
||||
}
|
||||
dedup[device] = device;
|
||||
let loc = isLocalDevice(device);
|
||||
if (dfilter && dfilter(device) === false) {
|
||||
return;
|
||||
}
|
||||
if (loc) {
|
||||
list_mdev.push(h.option(device));
|
||||
} else {
|
||||
list_cdev.push(h.option(device));
|
||||
}
|
||||
});
|
||||
|
||||
let dev_list = $('dev-list');
|
||||
h.bind(dev_list, [
|
||||
h.option({ _: '-- My Devices --', disabled: true }),
|
||||
...list_mdev,
|
||||
h.option({ _: '-- Stock Devices --', disabled: true }),
|
||||
...list_cdev
|
||||
]);
|
||||
let dev_opts = [...dev_list.options].map(o => o.innerText);
|
||||
dev_list.selectedIndex = dev_opts.indexOf(selected);
|
||||
dev_list.onchange = ev => {
|
||||
const seldev = dev_list.options[dev_list.selectedIndex];
|
||||
selectDevice(seldev.innerText);
|
||||
api.platform.layout();
|
||||
}
|
||||
selectDevice(selected);
|
||||
}
|
||||
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
// dep: kiri.api
|
||||
// use: kiri.selection
|
||||
// use: moto.space
|
||||
gapp.register("kiri.do", [], (root, events) => {
|
||||
import { api } from './api.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
|
||||
const { kiri, moto } = root;
|
||||
const { api } = kiri;
|
||||
const { space } = moto;
|
||||
const { event } = api;
|
||||
|
||||
let stack = [];
|
||||
|
|
@ -44,7 +39,7 @@ let clear = api.doit.clear = function() {
|
|||
};
|
||||
|
||||
function updateButtons() {
|
||||
let isArrange = api.view.get() === kiri.consts.VIEWS.ARRANGE;
|
||||
let isArrange = api.view.get() === api.consts.VIEWS.ARRANGE;
|
||||
$('doit').style.display = isArrange && stack.length ? 'flex' : 'none';
|
||||
$('undo').disabled = stpos === 0;
|
||||
$('redo').disabled = stpos == stack.length;
|
||||
|
|
@ -175,5 +170,3 @@ event.on('mouse.drag.done', () => {
|
|||
});
|
||||
moved = {x:0, y:0};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +1,13 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: ext.md5
|
||||
// dep: geo.base
|
||||
// dep: data.local
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.api
|
||||
// dep: kiri.main
|
||||
gapp.register("kiri.export", [], (root, exports) => {
|
||||
|
||||
const { base, data, kiri } = root;
|
||||
const { api, consts } = kiri;
|
||||
const { local } = data;
|
||||
const { util } = base;
|
||||
const { stats, ui } = api;
|
||||
const { MODES } = consts;
|
||||
|
||||
kiri.export = exportFile;
|
||||
import { $ } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { client } from './client.js';
|
||||
import { local } from '../../data/local.js';
|
||||
import { util } from '../../geo/base.js';
|
||||
import { MODES } from './consts.js';
|
||||
import { LASER as laser_driver } from '../mode/laser/driver.js';
|
||||
import { SLA as sla_client } from '../mode/sla/client.js';
|
||||
|
||||
let printSeq = parseInt(local['kiri-print-seq'] || local['print-seq'] || "0") + 1;
|
||||
|
||||
|
|
@ -29,7 +19,7 @@ function localSet(key, val) {
|
|||
return api.local.set(key, val);
|
||||
}
|
||||
|
||||
function exportFile(options) {
|
||||
export function exportFile(options) {
|
||||
let mode = api.mode.get();
|
||||
let names = api.widgets.all().map(w => w.meta ? w.meta.file : undefined)
|
||||
.filter(v => v)
|
||||
|
|
@ -53,7 +43,7 @@ function callExport(callback, mode, names) {
|
|||
let gcode = [];
|
||||
let section = [];
|
||||
let sections = { };
|
||||
kiri.client.export(api.conf.get(), (line) => {
|
||||
client.export(api.conf.get(), (line) => {
|
||||
if (typeof line !== 'string') {
|
||||
if (line.section) {
|
||||
sections[line.section] = section = [];
|
||||
|
|
@ -75,7 +65,7 @@ function callExport(callback, mode, names) {
|
|||
}
|
||||
|
||||
function callExportLaser(options, names) {
|
||||
kiri.client.export(api.conf.get(), (line) => {
|
||||
client.export(api.conf.get(), (line) => {
|
||||
// engine export uses lines
|
||||
// console.log({unexpected_line: line});
|
||||
}, (output, error) => {
|
||||
|
|
@ -89,14 +79,14 @@ function callExportLaser(options, names) {
|
|||
}
|
||||
|
||||
function callExportSLA(options, names) {
|
||||
kiri.client.export(api.conf.get(), (line) => {
|
||||
client.export(api.conf.get(), (line) => {
|
||||
api.show.progress(line.progress, "exporting");
|
||||
}, (output, error) => {
|
||||
api.show.progress(0);
|
||||
if (error) {
|
||||
api.show.alert(error, 5);
|
||||
} else {
|
||||
kiri.driver.SLA.printDownload(output, api, names);
|
||||
sla_client.printDownload(output, api, names);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -107,7 +97,7 @@ function exportLaserDialog(data, names) {
|
|||
const fileroot = names[0] || "laser";
|
||||
const filename = `${fileroot}-${(printSeq.toString().padStart(3,"0"))}`;
|
||||
const settings = api.conf.get();
|
||||
const driver = kiri.driver.LASER;
|
||||
const driver = laser_driver;
|
||||
|
||||
function download_svg() {
|
||||
api.util.download(
|
||||
|
|
@ -207,7 +197,7 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
ajax.onreadystatechange = function() {
|
||||
if (ajax.readyState === 4) {
|
||||
let status = ajax.status;
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_octo_${status}`);
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_octo_${status}`);
|
||||
if (status >= 200 && status < 300) {
|
||||
api.modal.hide();
|
||||
} else {
|
||||
|
|
@ -278,11 +268,11 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
)
|
||||
.then(t => t.text())
|
||||
.then(t => {
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_local_ok`);
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_local_ok`);
|
||||
console.log({grid_spool_said: t});
|
||||
})
|
||||
.catch(e => {
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_local_err`);
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_local_err`);
|
||||
console.log({grid_local_spool_error: e});
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -394,7 +384,7 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
let status = xhtr.status;
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_grid_${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);
|
||||
|
|
@ -511,7 +501,7 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
})
|
||||
}
|
||||
}
|
||||
kiri.client.zip(files, progress => {
|
||||
client.zip(files, progress => {
|
||||
api.show.progress(progress.percent/100, "generating zip files");
|
||||
}, output => {
|
||||
api.show.progress(0);
|
||||
|
|
@ -681,7 +671,7 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
name: `Metadata/top_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
}];
|
||||
kiri.client.zip(files, progress => {
|
||||
client.zip(files, progress => {
|
||||
api.show.progress(progress.percent/100, "generating 3mf");
|
||||
}, output => {
|
||||
api.show.progress(0);
|
||||
|
|
@ -742,5 +732,3 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
// preview of the generated GCODE (first 64k max)
|
||||
if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,13 +1,5 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: main.kiri
|
||||
// use: data.index
|
||||
gapp.register("kiri.files", [], (root, exports) => {
|
||||
|
||||
const { kiri } = self;
|
||||
|
||||
class Files {
|
||||
constructor(indexdb) {
|
||||
let store = this;
|
||||
|
|
@ -176,8 +168,8 @@ function notifyFileListeners(store) {
|
|||
}
|
||||
}
|
||||
|
||||
kiri.openFiles = function(indexdb) {
|
||||
export const openFiles = function(indexdb) {
|
||||
return new Files(indexdb);
|
||||
};
|
||||
|
||||
});
|
||||
export { saveFileList, notifyFileListeners };
|
||||
|
|
@ -1,19 +1,12 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: load.stl
|
||||
// use: load.svg
|
||||
// use: kiri.api
|
||||
// use: kiri.platform
|
||||
// use: kiri.settings
|
||||
// use: kiri.widget
|
||||
gapp.register("kiri.frame", [], (root, exports) => {
|
||||
import { api } from './api.js';
|
||||
import { load } from '../../load/file.js';
|
||||
import { newWidget } from './widget.js';
|
||||
import { VIEWS } from './consts.js';
|
||||
|
||||
// add frame message api listener
|
||||
window.addEventListener('message', msg => {
|
||||
const { load, kiri, moto } = self;
|
||||
const { api, newWidget } = kiri;
|
||||
const { conf, event, feature, platform, settings, show } = api;
|
||||
|
||||
if (!feature.frame) return;
|
||||
|
|
@ -126,5 +119,3 @@ window.addEventListener('message', msg => {
|
|||
show.progress(data.progress, data.message);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,30 +1,16 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.client
|
||||
// dep: kiri.export
|
||||
// dep: moto.space
|
||||
// use: kiri-mode.cam.client
|
||||
// dep: kiri-mode.fdm.client
|
||||
// dep: kiri-mode.sla.client
|
||||
// dep: kiri-mode.laser.driver
|
||||
// dep: kiri-mode.drag.driver
|
||||
// dep: kiri-mode.wjet.driver
|
||||
// dep: kiri-mode.wedm.driver
|
||||
gapp.register("kiri.function", (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { api, client, consts, utils } = kiri;
|
||||
const { space } = moto;
|
||||
const { COLOR, PMODES } = consts;
|
||||
import { api } from './api.js';
|
||||
import { client } from './client.js';
|
||||
import { codec } from './codec.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
import { COLOR, PMODES } from './consts.js';
|
||||
import { exportFile } from './export.js';
|
||||
|
||||
let complete = {};
|
||||
|
||||
function prepareSlices(callback, scale = 1, offset = 0) {
|
||||
const { conf, event, feature, hide, mode, view, platform, show } = api;
|
||||
const { stacks } = kiri;
|
||||
const { conf, event, feature, hide, mode, view, platform, show, stacks } = api;
|
||||
|
||||
if (view.is_arrange()) {
|
||||
// in arrange mode, create a screenshot at the start slicing
|
||||
|
|
@ -139,10 +125,34 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
|||
}
|
||||
|
||||
function sliceWidget(widget) {
|
||||
// weight each widget progress % by their # vertices
|
||||
let factor = (widget.getVertices().count / defvert);
|
||||
|
||||
widget.slice(settings, (sliced, error) => {
|
||||
function onupdate(update, msg, alert) {
|
||||
if (alert) {
|
||||
api.show.alert(alert);
|
||||
}
|
||||
if (msg && msg !== lastMsg) {
|
||||
let mark = Date.now();
|
||||
if (lastMsg) {
|
||||
let key = widgets.length > 1 ?
|
||||
`${widget.id}_${segNumber++}_${lastMsg}` :
|
||||
`${segNumber++}_${lastMsg}`
|
||||
segtimes[key] = mark - startTime;
|
||||
}
|
||||
lastMsg = msg;
|
||||
startTime = mark;
|
||||
}
|
||||
// on update
|
||||
if (update >= 0) {
|
||||
track[widget.id] = (update || 0) * factor;
|
||||
totalProgress = 0;
|
||||
for (let w of slicing) {
|
||||
totalProgress += (track[w.id] || 0);
|
||||
}
|
||||
show.progress(offset + (totalProgress / slicing.length) * scale, msg);
|
||||
}
|
||||
}
|
||||
|
||||
function ondone(sliced, error) {
|
||||
let mark = Date.now();
|
||||
// update UI info
|
||||
if (sliced) {
|
||||
|
|
@ -169,28 +179,55 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
|||
// start next widget slice
|
||||
sliceNext();
|
||||
}
|
||||
}, (update, msg, alert) => {
|
||||
if (alert) {
|
||||
api.show.alert(alert);
|
||||
}
|
||||
|
||||
// weight each widget progress % by their # vertices
|
||||
let factor = (widget.getVertices().count / defvert);
|
||||
|
||||
widget.settings = settings;
|
||||
widget.clearSlices();
|
||||
|
||||
onupdate(0.0001, "slicing");
|
||||
|
||||
// store slicing visuals
|
||||
widget.stack = api.stacks.create(widget.id, widget.mesh);
|
||||
|
||||
// compensate for zcut (widget moved through floor)
|
||||
widget.stack.obj.view.position.z = widget.track.zcut || 0;
|
||||
|
||||
// in case result of slice is nothing, do not preserve previous
|
||||
widget.slices = []
|
||||
|
||||
// executed from kiri.js
|
||||
client.slice(settings, widget, (reply) => {
|
||||
if (reply.alert) {
|
||||
onupdate(null, null, reply.alert);
|
||||
}
|
||||
if (msg && msg !== lastMsg) {
|
||||
let mark = Date.now();
|
||||
if (lastMsg) {
|
||||
segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime;
|
||||
}
|
||||
lastMsg = msg;
|
||||
startTime = mark;
|
||||
if (reply.update) {
|
||||
onupdate(reply.update, reply.updateStatus);
|
||||
}
|
||||
// on update
|
||||
if (update >= 0) {
|
||||
track[widget.id] = (update || 0) * factor;
|
||||
totalProgress = 0;
|
||||
for (let w of slicing) {
|
||||
totalProgress += (track[w.id] || 0);
|
||||
}
|
||||
show.progress(offset + (totalProgress / slicing.length) * scale, msg);
|
||||
if (reply.send_start) {
|
||||
widget.xfer = {start: reply.send_start};
|
||||
}
|
||||
if (reply.stats) {
|
||||
widget.stats = reply.stats;
|
||||
}
|
||||
if (reply.send_end) {
|
||||
widget.stats.load_time = widget.xfer.start - reply.send_end;
|
||||
}
|
||||
if (reply.slice) {
|
||||
widget.slices.push(codec.decode(reply.slice, {mesh:widget.mesh}));
|
||||
}
|
||||
if (reply.done) {
|
||||
ondone(true);
|
||||
}
|
||||
if (reply.error) {
|
||||
ondone(false, reply.error);
|
||||
}
|
||||
});
|
||||
|
||||
// discard point cache
|
||||
widget.points = undefined;
|
||||
}
|
||||
|
||||
function sliceDone() {
|
||||
|
|
@ -247,9 +284,7 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
|||
}
|
||||
|
||||
function preparePreview(callback, scale = 1, offset = 0) {
|
||||
const { conf, event, feature, hide, mode, view, platform, show } = api;
|
||||
const { stacks } = kiri;
|
||||
|
||||
const { conf, event, feature, hide, mode, view, platform, show, stacks } = api;
|
||||
const widgets = api.widgets.all();
|
||||
const settings = conf.get();
|
||||
const { device, process, controller } = settings;
|
||||
|
|
@ -305,7 +340,7 @@ function preparePreview(callback, scale = 1, offset = 0) {
|
|||
|
||||
client.prepare(settings, (progress, message, layer) => {
|
||||
if (layer) {
|
||||
output.push(kiri.codec.decode(layer));
|
||||
output.push(codec.decode(layer));
|
||||
}
|
||||
if (message && message !== lastMsg) {
|
||||
const mark = Date.now();
|
||||
|
|
@ -407,7 +442,7 @@ function prepareExport() {
|
|||
}
|
||||
api.event.emit("function.export", {mode: settings.mode});
|
||||
complete.export = true;
|
||||
kiri.export(...argsave);
|
||||
exportFile(...argsave);
|
||||
}
|
||||
|
||||
function cancelWorker() {
|
||||
|
|
@ -441,8 +476,12 @@ function parseCode(code, type) {
|
|||
});
|
||||
}
|
||||
|
||||
function clear_progress() {
|
||||
complete = {};
|
||||
}
|
||||
|
||||
// extend API (api.function)
|
||||
const functions = Object.assign(api.function, {
|
||||
export const functions = {
|
||||
slice: prepareSlices,
|
||||
print: preparePreview,
|
||||
prepare: preparePreview,
|
||||
|
|
@ -451,7 +490,16 @@ const functions = Object.assign(api.function, {
|
|||
cancel: cancelWorker,
|
||||
parse: parseCode,
|
||||
clear: client.clear,
|
||||
clear_progress() { complete = {} }
|
||||
});
|
||||
clear_progress,
|
||||
};
|
||||
|
||||
});
|
||||
export {
|
||||
prepareSlices as slice,
|
||||
preparePreview as print,
|
||||
preparePreview as prepare,
|
||||
prepareAnimation as animate,
|
||||
prepareExport as export,
|
||||
cancelWorker as cancel,
|
||||
parseCode as parse,
|
||||
clear_progress,
|
||||
};
|
||||
2171
src/kiri/core/init.js
Normal file
2171
src/kiri/core/init.js
Normal file
File diff suppressed because it is too large
Load diff
1
src/kiri/core/lang-en.js
Symbolic link
1
src/kiri/core/lang-en.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../web/kiri/lang/en.js
|
||||
|
|
@ -1,11 +1,6 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.lang", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const LANG = kiri.lang = { current: {} };
|
||||
const LANG = self.lang = { current: {} };
|
||||
const KDFL = 'en-us';
|
||||
|
||||
let lset = navigator.language.toLocaleLowerCase();
|
||||
|
|
@ -78,4 +73,6 @@ LANG.set = function() {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
});
|
||||
export default { LANG };
|
||||
|
||||
export { LANG };
|
||||
|
|
@ -1,25 +1,9 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { newPolygon, Polygon } from '../../geo/polygon.js';
|
||||
import { polygons as POLY } from '../../geo/polygons.js';
|
||||
|
||||
/*
|
||||
* provides the `output` abstraction for turning poly lines (open and closed)
|
||||
* into primordial geometries (faces, lines, color ranges). used inside of
|
||||
* workers as part of slicing and preview output.
|
||||
*/
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.paths
|
||||
// dep: geo.polygon
|
||||
// dep: geo.polygons
|
||||
gapp.register("kiri.layers", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { polygons, newPolygon } = base;
|
||||
|
||||
const POLY = base.polygons;
|
||||
|
||||
class Layers {
|
||||
export class Layers {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
|
@ -94,7 +78,7 @@ class Layers {
|
|||
options.open = true;
|
||||
const polys = [];
|
||||
for (let i=0; i<lines.length-1; i += 2) {
|
||||
polys.push(new base.Polygon()
|
||||
polys.push(new Polygon()
|
||||
.append(lines[i])
|
||||
.append(lines[i+1])
|
||||
.setOpen());
|
||||
|
|
@ -110,11 +94,14 @@ class Layers {
|
|||
|
||||
// an open or closed polygon
|
||||
addPoly(poly, options) {
|
||||
return this.addPolys([poly], options);
|
||||
return this.addPolys([ poly ], options);
|
||||
}
|
||||
|
||||
// a polygon rendered as a webgl line
|
||||
addPolys(polys, options) {
|
||||
if (!Array.isArray(polys)) {
|
||||
throw "polys must be an array";
|
||||
}
|
||||
if (polys.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -186,8 +173,7 @@ class Layers {
|
|||
p1.setZ(poly.z);
|
||||
p2.setZ(poly.z);
|
||||
}
|
||||
this.addPolys(p1);
|
||||
this.addPolys(p2);
|
||||
this.addPolys([ p1, p2 ]);
|
||||
}
|
||||
const color = opts.color ?
|
||||
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
|
||||
|
|
@ -277,7 +263,3 @@ function flat(polys) {
|
|||
return POLY.flatten([polys.clone(true)], [], true);
|
||||
}
|
||||
}
|
||||
|
||||
kiri.Layers = Layers;
|
||||
|
||||
});
|
||||
959
src/kiri/core/main.js
Normal file
959
src/kiri/core/main.js
Normal file
|
|
@ -0,0 +1,959 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import './frame.js';
|
||||
|
||||
import lang from './lang.js';
|
||||
import STACKS from './stacks.js';
|
||||
|
||||
import { $ } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { broker } from '../../moto/broker.js';
|
||||
import { consts } from './consts.js';
|
||||
import { Index } from '../../data/index.js';
|
||||
import { local as dataLocal } from '../../data/local.js';
|
||||
import { openFiles } from './files.js';
|
||||
import { platform } from './platform.js';
|
||||
import { selection } from './selection.js';
|
||||
import { settings } from './settings.js';
|
||||
import { showDevices } from './devices.js';
|
||||
import { space as SPACE } from '../../moto/space.js';
|
||||
import { stats } from './stats.js';
|
||||
import { noop, utils } from './utils.js';
|
||||
import { version } from '../../moto/license.js';
|
||||
import { Widget, newWidget } from './widget.js';
|
||||
import { showTools } from '../mode/cam/tools.js';
|
||||
|
||||
let { parseOpt, o2js, js2o, ls2o } = utils,
|
||||
{ COLOR, MODES, VIEWS } = consts,
|
||||
LANG = lang.current,
|
||||
WIN = self.window,
|
||||
DOC = self.document,
|
||||
LOC = self.location,
|
||||
SETUP = parseOpt(LOC.search.substring(1)),
|
||||
SECURE = isSecure(LOC.protocol),
|
||||
LOCAL = self.debug && !SETUP.remote,
|
||||
EVENT = broker,
|
||||
SDB = dataLocal,
|
||||
FILES = openFiles(new Index(SETUP.d ? SETUP.d[0] : 'kiri')),
|
||||
clone = Object.clone,
|
||||
MODE = MODES.FDM,
|
||||
viewMode = VIEWS.ARRANGE,
|
||||
autoSaveTimer = null,
|
||||
inits = parseInt(SDB.getItem('kiri-init') || stats.get('init') || 0) + 1;
|
||||
|
||||
// update version and init count
|
||||
SDB.setItem('kiri-init', inits);
|
||||
stats.set('init', inits);
|
||||
stats.set('kiri', version);
|
||||
|
||||
// allow widget to straddle client / worker FOR NOW
|
||||
self.kiri_catalog = FILES;
|
||||
|
||||
export const dialog = {
|
||||
show: showModal,
|
||||
hide: hideModal,
|
||||
update_process_list: updateProcessList
|
||||
};
|
||||
|
||||
export const help = {
|
||||
show: showHelp,
|
||||
file: showHelpFile
|
||||
};
|
||||
|
||||
export const event = {
|
||||
on(t,l) { return EVENT.on(t,l) },
|
||||
emit(t,m,o) { return EVENT.publish(t,m,o) },
|
||||
bind(t,m,o) { return EVENT.bind(t,m,o) },
|
||||
alerts(clr) { api.alerts.update(clr) },
|
||||
import: loadFile,
|
||||
settings: triggerSettingsEvent
|
||||
};
|
||||
|
||||
export const group = {
|
||||
merge: groupMerge,
|
||||
split: groupSplit,
|
||||
};
|
||||
|
||||
export const hide = {
|
||||
alert(rec, recs) { api.alerts.hide(...arguments) },
|
||||
import: noop,
|
||||
slider: hideSlider
|
||||
};
|
||||
|
||||
export const image = {
|
||||
dialog: loadImageDialog,
|
||||
convert: loadImageConvert
|
||||
};
|
||||
|
||||
export const modal = {
|
||||
show: showModal,
|
||||
hide: hideModal,
|
||||
visible: modalShowing
|
||||
};
|
||||
|
||||
export const mode = {
|
||||
get_id() { return MODE },
|
||||
get_lower: getModeLower,
|
||||
get: getMode,
|
||||
set: setMode,
|
||||
switch: switchMode,
|
||||
set_expert: noop,
|
||||
is_fdm() { return MODE === MODES.FDM },
|
||||
is_cam() { return MODE === MODES.CAM },
|
||||
is_sla() { return MODE === MODES.SLA },
|
||||
is_drag() { return MODE === MODES.DRAG },
|
||||
is_wedm() { return MODE === MODES.WEDM },
|
||||
is_wjet() { return MODE === MODES.WJET },
|
||||
is_laser() { return MODE === MODES.LASER },
|
||||
is_2d() { return false ||
|
||||
api.mode.is_drag() ||
|
||||
api.mode.is_wedm() ||
|
||||
api.mode.is_wjet() ||
|
||||
api.mode.is_laser()
|
||||
}
|
||||
};
|
||||
|
||||
export const probe = {
|
||||
live: "https://live.grid.space",
|
||||
grid: noop,
|
||||
local: noop
|
||||
};
|
||||
|
||||
export const process = {
|
||||
code: currentProcessCode,
|
||||
get: currentProcessName
|
||||
};
|
||||
|
||||
export const show = {
|
||||
alert() { return api.alerts.show(...arguments) },
|
||||
controls: setControlsVisible,
|
||||
devices: showDevices,
|
||||
import() { api.ui.import.style.display = '' },
|
||||
layer: setVisibleLayer,
|
||||
local: showLocal,
|
||||
progress: setProgress,
|
||||
slices: showSlices,
|
||||
tools: showTools
|
||||
};
|
||||
|
||||
export const space = {
|
||||
reload,
|
||||
auto_save,
|
||||
restore: restoreWorkspace,
|
||||
clear: clearWorkspace,
|
||||
save: saveWorkspace,
|
||||
set_focus: setFocus,
|
||||
update: SPACE.update,
|
||||
is_dark() { return settings.ctrl().dark }
|
||||
};
|
||||
|
||||
export const util = {
|
||||
isSecure,
|
||||
download: downloadBlob,
|
||||
ui2rec() { api.conf.update_from(...arguments) },
|
||||
rec2ui() { api.conf.update_fields(...arguments) },
|
||||
b64enc(obj) { return base64js.fromByteArray(new TextEncoder().encode(JSON.stringify(obj))) },
|
||||
b64dec(obj) { return JSON.parse(new TextDecoder().decode(base64js.toByteArray(obj))) }
|
||||
};
|
||||
|
||||
export const view = {
|
||||
get() { return viewMode },
|
||||
set() { setViewMode(...arguments) },
|
||||
set_arrange() { api.view.set(VIEWS.ARRANGE) },
|
||||
set_slice() { api.view.set(VIEWS.SLICE) },
|
||||
set_preview() { api.view.set(VIEWS.PREVIEW) },
|
||||
set_animate() { api.view.set(VIEWS.ANIMATE) },
|
||||
is_arrange() { return viewMode === VIEWS.ARRANGE },
|
||||
is_slice() { return viewMode === VIEWS.SLICE },
|
||||
is_preview() { return viewMode === VIEWS.PREVIEW },
|
||||
is_animate() { return viewMode === VIEWS.ANIMATE },
|
||||
update_stack_labels: updateStackLabelState,
|
||||
update_slider_max: updateSliderMax,
|
||||
update_slider: updateSlider,
|
||||
update_speeds: updateSpeeds,
|
||||
hide_slices: hideSlices,
|
||||
snapshot: null,
|
||||
edges: setEdges,
|
||||
unit_scale: unitScale,
|
||||
wireframe: setWireframe
|
||||
};
|
||||
|
||||
// add show() to catalog for API
|
||||
FILES.show = showCatalog;
|
||||
|
||||
// patch broker for api backward compatibility
|
||||
EVENT.on = (topic, listener) => {
|
||||
EVENT.subscribe(topic, listener);
|
||||
return EVENT;
|
||||
};
|
||||
|
||||
function updateStackLabelState() {
|
||||
const settings = api.conf.get();
|
||||
// match label checkboxes to preference
|
||||
for (let label of api.stacks.getLabels()) {
|
||||
let check = `${settings.mode}-${api.view.get()}-${label}`;
|
||||
api.stacks.setVisible(label, settings.labels[check] !== false);
|
||||
}
|
||||
}
|
||||
|
||||
function setFocus(sel, point) {
|
||||
if (point) {
|
||||
SPACE.platform.setCenter(point.x, point.z, point.y);
|
||||
SPACE.view.setFocus(new THREE.Vector3(point.x, point.y, point.z));
|
||||
return;
|
||||
}
|
||||
if (sel === undefined) {
|
||||
sel = api.widgets.all();
|
||||
} else if (!Array.isArray) {
|
||||
sel = [ sel ];
|
||||
} else if (sel.length === 0) {
|
||||
sel = api.widgets.all();
|
||||
}
|
||||
let pos = { x:0, y:0, z:0 };
|
||||
for (let widget of sel) {
|
||||
pos.x += widget.track.pos.x;
|
||||
pos.y += widget.track.pos.y;
|
||||
pos.z += widget.track.pos.z;
|
||||
}
|
||||
if (sel.length) {
|
||||
pos.x /= sel.length;
|
||||
pos.y /= sel.length;
|
||||
pos.z /= sel.length;
|
||||
}
|
||||
let cam_index = api.conf.get().process.camStockIndexed || false;
|
||||
let focus_z = cam_index ? 0 : platform.top_z() / 2;
|
||||
SPACE.platform.setCenter(pos.x, -pos.y, focus_z);
|
||||
SPACE.view.setFocus(new THREE.Vector3(pos.x, focus_z, -pos.y));
|
||||
}
|
||||
|
||||
function reload() {
|
||||
api.event.emit('reload');
|
||||
do_reload(100);
|
||||
}
|
||||
|
||||
function do_reload(time) {
|
||||
// allow time for async saves to complete and busy to to to zero
|
||||
setTimeout(() => {
|
||||
if (api.busy.val() === 0) {
|
||||
LOC.reload();
|
||||
} else {
|
||||
console.log(`reload deferred on busy=${api.busy.val()}`);
|
||||
do_reload(250);
|
||||
}
|
||||
}, time || 100);
|
||||
}
|
||||
|
||||
function auto_save() {
|
||||
if (!settings.ctrl().autoSave) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(autoSaveTimer);
|
||||
autoSaveTimer = setTimeout(() => {
|
||||
api.space.save(true);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Utility Functions
|
||||
******************************************************************* */
|
||||
|
||||
function unitScale() {
|
||||
return api.mode.is_cam() && settings.ctrl().units === 'in' ? 25.4 : 1;
|
||||
}
|
||||
|
||||
function triggerSettingsEvent() {
|
||||
api.event.emit('settings', settings.get());
|
||||
}
|
||||
|
||||
function isSecure(proto) {
|
||||
return proto.toLowerCase().indexOf("https") === 0;
|
||||
}
|
||||
|
||||
function setProgress(value = 0, msg) {
|
||||
value = (value * 100).round(4);
|
||||
api.ui.progress.width = value+'%';
|
||||
if (self.debug) {
|
||||
// console.log(msg, value.round(2));
|
||||
api.ui.prostatus.style.display = 'flex';
|
||||
if (msg) {
|
||||
api.ui.prostatus.innerHTML = msg;
|
||||
} else {
|
||||
api.ui.prostatus.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bound(v,min,max) {
|
||||
return Math.max(min,Math.min(max,v));
|
||||
}
|
||||
|
||||
function updateSpeeds(maxSpeed, minSpeed) {
|
||||
const { ui } = api;
|
||||
ui.speeds.style.display =
|
||||
maxSpeed &&
|
||||
settings.mode !== 'SLA' &&
|
||||
settings.mode !== 'LASER' &&
|
||||
viewMode === VIEWS.PREVIEW &&
|
||||
ui.showSpeeds.checked ? 'block' : '';
|
||||
if (maxSpeed) {
|
||||
const colors = [];
|
||||
for (let i = 0; i <= maxSpeed; i += maxSpeed / 20) {
|
||||
colors.push(Math.round(Math.max(i, 1)));
|
||||
}
|
||||
api.client.colors(colors, maxSpeed, speedColors => {
|
||||
const list = [];
|
||||
Object.keys(speedColors).map(v => parseInt(v)).sort((a, b) => b - a).forEach(speed => {
|
||||
const color = speedColors[speed];
|
||||
const hex = color.toString(16).padStart(6, 0);
|
||||
const r = (color >> 16) & 0xff;
|
||||
const g = (color >> 8) & 0xff;
|
||||
const b = (color >> 0) & 0xff;
|
||||
const style = `background-color:#${hex}`;
|
||||
list.push(`<label style="${style}">${speed}</label>`);
|
||||
});
|
||||
ui.speedbar.innerHTML = list.join('');
|
||||
});
|
||||
api.event.emit('preview.speeds', {
|
||||
min: minSpeed,
|
||||
max: maxSpeed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateSlider() {
|
||||
api.event.emit("slider.set", {
|
||||
start: (api.var.layer_lo / api.var.layer_max),
|
||||
end: (api.var.layer_hi / api.var.layer_max)
|
||||
});
|
||||
api.conf.update_fields_from_range();
|
||||
}
|
||||
|
||||
function setVisibleLayer(h, l) {
|
||||
h = h >= 0 ? h : api.var.layer_hi;
|
||||
l = l >= 0 ? l : api.var.layer_lo;
|
||||
api.var.layer_hi = bound(h, 0, api.var.layer_max);
|
||||
api.var.layer_lo = bound(l, 0, h);
|
||||
api.event.emit("slider.label");
|
||||
updateSlider();
|
||||
showSlices();
|
||||
}
|
||||
|
||||
function setWireframe(bool, color, opacity) {
|
||||
api.widgets.each(function(w) { w.setWireframe(bool, color, opacity) });
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function setEdges(bool) {
|
||||
if (bool && bool.toggle) {
|
||||
api.local.toggle('model.edges');
|
||||
} else {
|
||||
api.local.set('model.edges', bool);
|
||||
}
|
||||
bool = api.local.getBoolean('model.edges');
|
||||
api.widgets.each(w => w.setEdges(bool));
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function updateSliderMax(set) {
|
||||
let max = STACKS.getRange().tallest - 1;
|
||||
api.var.layer_max = api.ui.sliderMax.innerText = max;
|
||||
if (set || max < api.var.layer_hi) {
|
||||
api.var.layer_hi = api.var.layer_max;
|
||||
api.event.emit("slider.label");
|
||||
updateSlider();
|
||||
}
|
||||
}
|
||||
|
||||
function hideSlices() {
|
||||
STACKS.clear();
|
||||
api.widgets.opacity(COLOR.model_opacity);
|
||||
api.widgets.each(function(widget) {
|
||||
widget.setWireframe(false);
|
||||
});
|
||||
}
|
||||
|
||||
function setWidgetVisibility(bool) {
|
||||
api.widgets.each(w => {
|
||||
if (bool) {
|
||||
w.show();
|
||||
} else {
|
||||
w.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* hide or show slice-layers and their sub-elements
|
||||
*
|
||||
* @param {number} [layer]
|
||||
*/
|
||||
function showSlices(layer) {
|
||||
if (viewMode === VIEWS.ARRANGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
showSlider();
|
||||
|
||||
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
|
||||
layer = parseInt(layer);
|
||||
} else {
|
||||
layer = api.var.layer_hi;
|
||||
}
|
||||
|
||||
layer = bound(layer, 0, api.var.layer_max);
|
||||
if (layer < api.var.layer_lo) api.var.layer_lo = layer;
|
||||
api.var.layer_hi = layer;
|
||||
api.event.emit("slider.label");
|
||||
|
||||
updateSlider();
|
||||
STACKS.setRange(api.var.layer_lo, api.var.layer_hi);
|
||||
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function showSlider() {
|
||||
api.ui.layers.style.display = 'flex';
|
||||
api.ui.slider.style.display = 'flex';
|
||||
}
|
||||
|
||||
function hideSlider() {
|
||||
api.ui.layers.style.display = 'none';
|
||||
api.ui.slider.style.display = 'none';
|
||||
api.ui.speeds.style.display = 'none';
|
||||
}
|
||||
|
||||
function loadImageDialog(image, name, force) {
|
||||
if (!force && image.byteLength > 2500000) {
|
||||
return api.uc.confirm("Large images may fail to import<br>Consider resizing under 1000 x 1000<br>Proceed with import?").then(ok => {
|
||||
if (ok) {
|
||||
loadImageDialog(image, name, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
const opt = {pre: [
|
||||
"<div class='f-col a-center'>",
|
||||
" <h3>Image Conversion</h3>",
|
||||
" <p class='t-just' style='width:300px;line-height:1.5em'>",
|
||||
" This will create a 3D model from a 2D PNG image. Photos must",
|
||||
" be blurred to be usable. Values from 0=off to 50=high are suggested.",
|
||||
" Higher values incur more processing time.",
|
||||
" </p>",
|
||||
" <div class='f-row t-right'><table>",
|
||||
" <tr><th>blur value</th><td><input id='png-blur' value='0' size='3'></td>",
|
||||
" <th> invert image</th><td><input id='png-inv' type='checkbox'></td></tr>",
|
||||
" <tr><th>base size</th><td><input id='png-base' value='0' size='3'></td>",
|
||||
" <th> invert alpha</th><td><input id='alpha-inv' type='checkbox'></td></tr>",
|
||||
" <tr><th>border size</th><td><input id='png-border' value='0' size='3'></td>",
|
||||
" <th></th><td></td></tr>",
|
||||
" </table></div>",
|
||||
"</div>"
|
||||
]};
|
||||
api.uc.confirm(undefined, {convert:true, cancel:false}, undefined, opt).then((ok) => {
|
||||
if (ok) {
|
||||
loadImage(image, {
|
||||
file: name,
|
||||
blur: parseInt($('png-blur').value) || 0,
|
||||
base: parseInt($('png-base').value) || 0,
|
||||
border: parseInt($('png-border').value) || 0,
|
||||
inv_image: $('png-inv').checked,
|
||||
inv_alpha: $('alpha-inv').checked
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadImage(image, opt = {}) {
|
||||
const info = Object.assign({settings: settings.get(), png:image}, opt);
|
||||
api.client.image2mesh(info, progress => {
|
||||
api.show.progress(progress, "converting");
|
||||
}, vertices => {
|
||||
api.show.progress(0);
|
||||
const widget = newWidget().loadVertices(vertices);
|
||||
widget.meta.file = opt.file;
|
||||
platform.add(widget);
|
||||
});
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Selection Functions
|
||||
******************************************************************* */
|
||||
|
||||
function groupMerge() {
|
||||
Widget.Groups.merge(selection.widgets(true));
|
||||
}
|
||||
|
||||
function groupSplit() {
|
||||
Widget.Groups.split(selection.widgets(false));
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Settings Functions
|
||||
******************************************************************* */
|
||||
|
||||
// convert any image type to png
|
||||
function loadImageConvert(res, name) {
|
||||
let url = URL.createObjectURL(new Blob([res]));
|
||||
|
||||
$('mod-any').innerHTML = `<img id="xsrc" src="${url}"><canvas id="xdst"></canvas>`;
|
||||
|
||||
let img = $('xsrc');
|
||||
let can = $('xdst');
|
||||
|
||||
img.onload = () => {
|
||||
can.width = img.width;
|
||||
can.height = img.height;
|
||||
let ctx = can.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
fetch(can.toDataURL()).then(r => r.arrayBuffer()).then(data => {
|
||||
loadImageDialog(data, name);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function loadFile(ev) {
|
||||
// use modern Filesystem api when available
|
||||
if (false && window.showOpenFilePicker) {
|
||||
window.showOpenFilePicker().then(files => {
|
||||
return Promise.all(files.map(fh => fh.getFile()))
|
||||
}).then(files => {
|
||||
if (files.length) {
|
||||
api.platform.load_files(files);
|
||||
}
|
||||
}).catch(e => { /* ignore cancel */ });
|
||||
return;
|
||||
}
|
||||
api.ui.load.click();
|
||||
}
|
||||
|
||||
function saveWorkspace(quiet) {
|
||||
api.conf.save();
|
||||
const newWidgets = [];
|
||||
const oldWidgets = js2o(SDB.getItem('ws-widgets'), []);
|
||||
api.widgets.each(function(widget) {
|
||||
if (widget.synth) return;
|
||||
newWidgets.push(widget.id);
|
||||
oldWidgets.remove(widget.id);
|
||||
widget.saveState();
|
||||
let ann = api.widgets.annotate(widget.id);
|
||||
ann.file = widget.meta.file;
|
||||
ann.url = widget.meta.url;
|
||||
});
|
||||
SDB.setItem('ws-widgets', o2js(newWidgets));
|
||||
oldWidgets.forEach(wid => {
|
||||
Widget.deleteFromState(wid);
|
||||
});
|
||||
// eliminate dangling saved widgets
|
||||
FILES.deleteFilter(key => newWidgets.indexOf(key.substring(8)) < 0, "ws-save-", "ws-savf");
|
||||
if (!quiet) {
|
||||
api.show.alert("workspace saved", 1);
|
||||
}
|
||||
}
|
||||
|
||||
function restoreWorkspace(ondone, skip_widget_load) {
|
||||
let newset = api.conf.restore(false),
|
||||
camera = newset.controller.view,
|
||||
toload = ls2o('ws-widgets',[]),
|
||||
loaded = 0,
|
||||
position = true;
|
||||
|
||||
api.conf.update_fields();
|
||||
platform.update_size();
|
||||
|
||||
SPACE.view.reset();
|
||||
if (camera) {
|
||||
SPACE.view.load(camera);
|
||||
} else {
|
||||
SPACE.view.home();
|
||||
}
|
||||
|
||||
if (skip_widget_load) {
|
||||
if (ondone) {
|
||||
ondone();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// remove any widgets from platform
|
||||
api.widgets.each(function(widget) {
|
||||
platform.delete(widget);
|
||||
});
|
||||
|
||||
// load any widget by name that was saved to the workspace
|
||||
toload.forEach(function(widgetid) {
|
||||
Widget.loadFromState(widgetid, function(widget) {
|
||||
if (widget) {
|
||||
platform.add(widget, 0, position, true);
|
||||
let ann = api.widgets.annotate(widgetid);
|
||||
widget.meta.file = ann.file;
|
||||
widget.meta.url = ann.url;
|
||||
}
|
||||
if (++loaded === toload.length) {
|
||||
platform.deselect();
|
||||
if (ondone) {
|
||||
ondone();
|
||||
setTimeout(() => {
|
||||
platform.update_bounds();
|
||||
SPACE.update();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
}, position);
|
||||
});
|
||||
|
||||
return toload.length > 0;
|
||||
}
|
||||
|
||||
function clearWorkspace() {
|
||||
// free up worker cache/mem
|
||||
api.client.clear();
|
||||
platform.select_all();
|
||||
platform.delete(selection.meshes());
|
||||
}
|
||||
|
||||
function modalShowing() {
|
||||
return api.ui.modal.style.display === 'flex';
|
||||
}
|
||||
|
||||
function showModal(which) {
|
||||
let mod = api.ui.modal,
|
||||
style = mod.style,
|
||||
visible = modalShowing(),
|
||||
info = { pct: 0 };
|
||||
|
||||
// hide all modals befroe showing another
|
||||
Object.keys(api.ui.modals).forEach(name => {
|
||||
api.ui.modals[name].style.display = name === which ? 'flex' : '';
|
||||
});
|
||||
|
||||
function ondone() {
|
||||
api.event.emit('modal.show', which);
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
return ondone();
|
||||
}
|
||||
|
||||
style.height = '0';
|
||||
style.display = 'flex';
|
||||
|
||||
new TWEEN.Tween(info).
|
||||
easing(TWEEN.Easing.Quadratic.InOut).
|
||||
to({ pct: 100 }, 100).
|
||||
onUpdate(() => { style.height = `${info.pct}%` }).
|
||||
onComplete(ondone).
|
||||
start();
|
||||
}
|
||||
|
||||
function hideModal() {
|
||||
if (!modalShowing()) {
|
||||
return;
|
||||
}
|
||||
let mod = api.ui.modal, style = mod.style, info={pct:100};
|
||||
new TWEEN.Tween(info).
|
||||
easing(TWEEN.Easing.Quadratic.InOut).
|
||||
to({pct:0}, 100).
|
||||
onUpdate(() => { style.height = `${info.pct}%` }).
|
||||
onComplete(() => {
|
||||
style.display = '';
|
||||
api.event.emit('modal.hide');
|
||||
}).
|
||||
start();
|
||||
}
|
||||
|
||||
function showCatalog() {
|
||||
showModal("files");
|
||||
}
|
||||
|
||||
function editSettings(e) {
|
||||
let current = settings.get(),
|
||||
mode = getMode(),
|
||||
name = e.target.getAttribute("name"),
|
||||
load = current.sproc[mode][name],
|
||||
loadstr = JSON.stringify(load,null,4).split('\n');
|
||||
api.uc.prompt(`settings for "${name}"`, loadstr).then(edit => {
|
||||
if (edit) {
|
||||
try {
|
||||
current.sproc[mode][name] = JSON.parse(edit);
|
||||
if (name === settings.proc().processName) {
|
||||
api.conf.load(null, name);
|
||||
}
|
||||
api.conf.save();
|
||||
api.settings.sync.put();
|
||||
} catch (e) {
|
||||
console.log({ malformed_settings: e });
|
||||
api.uc.alert('malformed settings object');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportSettings(e) {
|
||||
let current = settings.get(),
|
||||
mode = getMode(),
|
||||
name = e.target.getAttribute("name"),
|
||||
data = api.util.b64enc({
|
||||
process: current.sproc[mode][name],
|
||||
version,
|
||||
moto: moto.id,
|
||||
time: Date.now(),
|
||||
mode,
|
||||
name
|
||||
});
|
||||
api.uc.prompt("Export Process Filename", name).then(name => {
|
||||
if (name) {
|
||||
api.util.download(data, `${name}.km`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteSettings(e) {
|
||||
let current = settings.get();
|
||||
let name = e.target.getAttribute("del");
|
||||
delete current.sproc[getMode()][name];
|
||||
api.settings.sync.put();
|
||||
updateProcessList();
|
||||
api.conf.save();
|
||||
triggerSettingsEvent();
|
||||
}
|
||||
|
||||
function updateProcessList() {
|
||||
let current = settings.get();
|
||||
let list = [], s = current, sp = s.sproc[getMode()] || {}, table = api.ui.settingsList;
|
||||
table.innerHTML = '';
|
||||
for (let k in sp) {
|
||||
if (sp.hasOwnProperty(k)) list.push(k);
|
||||
}
|
||||
list.filter(n => n !=='default').sort().forEach(function(sk) {
|
||||
let row = DOC.createElement('div'),
|
||||
load = DOC.createElement('button'),
|
||||
edit = DOC.createElement('button'),
|
||||
xprt = DOC.createElement('button'),
|
||||
del = DOC.createElement('button'),
|
||||
name = sk;
|
||||
|
||||
load.setAttribute('load', sk);
|
||||
load.onclick = (ev) => {
|
||||
api.conf.load(undefined, sk);
|
||||
updateProcessList();
|
||||
hideModal();
|
||||
}
|
||||
load.appendChild(DOC.createTextNode(sk));
|
||||
if (sk == settings.proc().processName) {
|
||||
load.setAttribute('class', 'selected')
|
||||
}
|
||||
api.ui.settingsName.value = settings.proc().processName;
|
||||
|
||||
del.setAttribute('del', sk);
|
||||
del.setAttribute('title', "remove '"+sk+"'");
|
||||
del.innerHTML = '<i class="far fa-trash-alt"></i>';
|
||||
del.onclick = deleteSettings;
|
||||
|
||||
edit.setAttribute('name', sk);
|
||||
edit.setAttribute('title', 'edit');
|
||||
edit.innerHTML = '<i class="far fa-edit"></i>';
|
||||
edit.onclick = editSettings;
|
||||
|
||||
xprt.setAttribute('name', sk);
|
||||
xprt.setAttribute('title', 'export');
|
||||
xprt.innerHTML = '<i class="fas fa-download"></i>';
|
||||
xprt.onclick = exportSettings;
|
||||
|
||||
row.setAttribute("class", "flow-row");
|
||||
row.appendChild(edit);
|
||||
row.appendChild(load);
|
||||
row.appendChild(xprt);
|
||||
row.appendChild(del);
|
||||
table.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
showHelpFile(`local`,() => {});
|
||||
}
|
||||
|
||||
function showHelpFile(local,then) {
|
||||
if (!local) {
|
||||
WIN.open("//docs.grid.space/", "_help");
|
||||
return;
|
||||
}
|
||||
const LANG = api.language.current;
|
||||
$('kiri-version').innerHTML = `${LANG.version} ${version}`;
|
||||
showModal('help');
|
||||
api.event.emit('help.show', local);
|
||||
}
|
||||
|
||||
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}/`);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
const isCAM = settings.mode() === 'CAM';
|
||||
viewMode = mode;
|
||||
platform.deselect();
|
||||
selection.update_info();
|
||||
// clear any bounds forced by, for example, WireEDM previews
|
||||
api.platform.set_bounds();
|
||||
// disable clear in non-arrange modes
|
||||
['view-arrange','act-slice','act-preview','act-animate'].forEach(el => {
|
||||
$(el).classList.remove('selected')
|
||||
});
|
||||
$('render-tools').classList.add('hide');
|
||||
switch (mode) {
|
||||
case VIEWS.ARRANGE:
|
||||
$('view-arrange').classList.add('selected');
|
||||
$('render-tools').classList.remove('hide');
|
||||
api.function.clear_progress();
|
||||
api.client.clear();
|
||||
STACKS.clear();
|
||||
hideSlider();
|
||||
updateSpeeds();
|
||||
setVisibleLayer();
|
||||
setWidgetVisibility(true);
|
||||
api.widgets.opacity(1);
|
||||
api.view.edges(api.local.getBoolean('model.edges'));
|
||||
break;
|
||||
case VIEWS.SLICE:
|
||||
$('act-slice').classList.add('selected');
|
||||
updateSpeeds();
|
||||
updateSliderMax();
|
||||
setWidgetVisibility(true);
|
||||
!isCAM && api.view.edges(false);
|
||||
break;
|
||||
case VIEWS.PREVIEW:
|
||||
$('act-preview').classList.add('selected');
|
||||
setWidgetVisibility(true);
|
||||
!isCAM && api.view.edges(false);
|
||||
break;
|
||||
case VIEWS.ANIMATE:
|
||||
$('act-animate').classList.add('selected');
|
||||
!isCAM && api.view.edges(false);
|
||||
break;
|
||||
default:
|
||||
console.log("invalid view mode: "+mode);
|
||||
return;
|
||||
}
|
||||
api.event.emit('view.set', mode);
|
||||
DOC.activeElement.blur();
|
||||
}
|
||||
|
||||
function getMode() {
|
||||
return settings.mode();
|
||||
}
|
||||
|
||||
function getModeLower() {
|
||||
return getMode().toLowerCase();
|
||||
}
|
||||
|
||||
function switchMode(mode) {
|
||||
setMode(mode, null, platform.update_size);
|
||||
}
|
||||
|
||||
function setMode(mode, lock, then) {
|
||||
if (!MODES[mode]) {
|
||||
console.log("invalid mode: "+mode);
|
||||
mode = 'FDM';
|
||||
}
|
||||
const current = settings.get();
|
||||
// change mode constants
|
||||
current.mode = mode;
|
||||
MODE = MODES[mode];
|
||||
// gcode edit area for any non-SLA mode
|
||||
api.uc.setVisible($('gcode-edit'), mode !== 'SLA');
|
||||
// highlight selected mode menu item
|
||||
["FDM","CAM","SLA","LASER","DRAG","WJET","WEDM"].forEach(sm => {
|
||||
const cl = $(`mode-${sm.toLowerCase()}`).classList;
|
||||
if (sm === mode) {
|
||||
cl.add('selected');
|
||||
} else {
|
||||
cl.remove('selected');
|
||||
}
|
||||
});
|
||||
// restore cached device profile for this mode
|
||||
if (current.cdev[mode]) {
|
||||
current.device = clone(current.cdev[mode]);
|
||||
api.event.emit('device.select', api.device.get());
|
||||
}
|
||||
// hide/show
|
||||
api.uc.setVisible($('set-tools'), mode === 'CAM');
|
||||
// updates right-hand menu by enabling/disabling fields
|
||||
setViewMode(VIEWS.ARRANGE);
|
||||
api.uc.setMode(MODE);
|
||||
// sanitize and persist settings
|
||||
api.conf.load();
|
||||
api.conf.save();
|
||||
// other housekeeping
|
||||
triggerSettingsEvent();
|
||||
platform.update_selected();
|
||||
selection.update_bounds(api.widgets.all());
|
||||
api.conf.update_fields();
|
||||
// because device dialog, if showing, needs to be updated
|
||||
if (modalShowing()) {
|
||||
api.show.devices();
|
||||
}
|
||||
api.space.restore(null, true);
|
||||
api.event.emit("mode.set", mode);
|
||||
if (then) {
|
||||
then();
|
||||
}
|
||||
}
|
||||
|
||||
function currentProcessName() {
|
||||
return settings.get().cproc[getMode()];
|
||||
}
|
||||
|
||||
function currentProcessCode() {
|
||||
return settings.get().sproc[getMode()][currentProcessName()];
|
||||
}
|
||||
|
||||
function setControlsVisible(show) {
|
||||
// TODO fix
|
||||
// $('mid-left').style.display = show ? 'flex' : 'none';
|
||||
// $('mid-right').style.display = show ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function downloadBlob(data, filename) {
|
||||
let url = WIN.URL.createObjectURL(new Blob([data], {type: "octet/stream"}));
|
||||
$('mod-any').innerHTML = `<a id="_dexport_" href="${url}" download="${filename}">x</a>`;
|
||||
$('_dexport_').click();
|
||||
}
|
||||
|
||||
// prevent safari from exiting full screen mode
|
||||
DOC.onkeydown = function (evt) { if (evt.keyCode == 27) evt.preventDefault() }
|
||||
|
||||
export { FILES as catalog, LOCAL, SETUP, SECURE };
|
||||
|
|
@ -1,17 +1,6 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* adapted from
|
||||
* http://codeincomplete.com/posts/2011/5/7/bin_packing/
|
||||
*/
|
||||
|
||||
gapp.register("kiri.pack", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
class Packer {
|
||||
export class Packer {
|
||||
constructor(w, h, spacing, opt = {}) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
|
|
@ -150,7 +139,3 @@ class Packer {
|
|||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
kiri.Pack = Packer;
|
||||
|
||||
});
|
||||
|
|
@ -1,26 +1,16 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { $, h } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { ajax, js2o } from './utils.js';
|
||||
import { base } from '../../geo/base.js';
|
||||
import { load } from '../../load/file.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
import { Widget, newWidget } from './widget.js';
|
||||
import { Packer } from './pack.js';
|
||||
|
||||
// dep: moto.space
|
||||
// dep: moto.webui
|
||||
// dep: kiri.api
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.utils
|
||||
// dep: kiri.widget
|
||||
// dep: load.stl
|
||||
// dep: load.obj
|
||||
// dep: load.3mf
|
||||
// dep: load.gbr
|
||||
// use: kiri-mode.cam.tool
|
||||
gapp.register("kiri.platform", [], (root, exports) => {
|
||||
|
||||
const { base, kiri, moto, load } = root;
|
||||
const { api, consts, driver, utils, newWidget, Widget } = kiri;
|
||||
const { ajax, js2o } = utils;
|
||||
const { space } = moto;
|
||||
const { util } = base;
|
||||
const { COLOR, MODES } = consts;
|
||||
import { COLOR, MODES } from './consts.js';
|
||||
import { THREE } from '../../ext/three.js';
|
||||
|
||||
const V0 = new THREE.Vector3(0,0,0);
|
||||
|
||||
|
|
@ -525,7 +515,7 @@ function platformDelete(widget, defer) {
|
|||
api.event.emit('widget.delete', widget);
|
||||
return;
|
||||
}
|
||||
kiri.client.clear(widget);
|
||||
api.client.clear(widget);
|
||||
api.widgets.remove(widget);
|
||||
api.selection.remove(widget);
|
||||
Widget.Groups.remove(widget);
|
||||
|
|
@ -719,12 +709,12 @@ function platformLayout() {
|
|||
mp = [sz.x, sz.y],
|
||||
ms = [mp[0] / 2, mp[1] / 2],
|
||||
c = Widget.Groups.blocks().sort(),
|
||||
p = new kiri.Pack(ms[0], ms[1], gap).fit(c);
|
||||
p = new Packer(ms[0], ms[1], gap).fit(c);
|
||||
|
||||
while (!p.packed) {
|
||||
ms[0] *= 1.1;
|
||||
ms[1] *= 1.1;
|
||||
p = new kiri.Pack(ms[0], ms[1], gap).fit(c);
|
||||
p = new Packer(ms[0], ms[1], gap).fit(c);
|
||||
}
|
||||
|
||||
for (i = 0; i < c.length; i++) {
|
||||
|
|
@ -809,7 +799,7 @@ function platformLoadFiles(files, group) {
|
|||
if (api.conf.get().controller.devel) {
|
||||
api.event.emit('cam.parse.gerber', { data: text });
|
||||
} else {
|
||||
kiri.client.gerber2mesh(text, progress => {
|
||||
api.client.gerber2mesh(text, progress => {
|
||||
api.show.progress(progress, "converting");
|
||||
}, vertices => {
|
||||
api.show.progress(0);
|
||||
|
|
@ -847,7 +837,7 @@ function platformLoadFiles(files, group) {
|
|||
api.function.parse(data.textDecode('utf-8'), 'gcode');
|
||||
load_dec();
|
||||
} else if (issvg) {
|
||||
loadSVGDialog(opt => {
|
||||
loadSVGDialog(opt => {
|
||||
group = group || [];
|
||||
let svg = load.SVG.parse(data.textDecode('utf-8'), opt);
|
||||
let ind = 0;
|
||||
|
|
@ -921,7 +911,7 @@ function fitDeviceToWidgets() {
|
|||
}
|
||||
|
||||
// extend API (api.platform)
|
||||
const platform = Object.assign(api.platform, {
|
||||
export const platform = {
|
||||
fit: fitDeviceToWidgets,
|
||||
add: platformAdd,
|
||||
changed: platformChanged,
|
||||
|
|
@ -950,6 +940,4 @@ const platform = Object.assign(api.platform, {
|
|||
show_volume: space.platform.showVolume,
|
||||
top_z() { return topZ },
|
||||
clear() { api.space.clear(); api.space.save(true) }
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
@ -1,19 +1,13 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { arcToPath } from '../../geo/paths.js';
|
||||
import { consts } from './consts.js';
|
||||
import { newPoint } from '../../geo/point.js';
|
||||
import { util } from '../../geo/base.js';
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
// dep: geo.polygon
|
||||
// dep: geo.paths
|
||||
// dep: kiri.consts
|
||||
gapp.register("kiri.print", [], (root, evets) => {
|
||||
|
||||
const { base, kiri } = self;
|
||||
const { paths, util, newPoint } = base;
|
||||
const { arcToPath } = paths;
|
||||
const { numOrDefault } = util;
|
||||
const { beltfact } = kiri.consts;
|
||||
const { beltfact } = consts;
|
||||
|
||||
const XAXIS = new THREE.Vector3(1,0,0);
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
||||
|
|
@ -463,10 +457,10 @@ class Print {
|
|||
|
||||
let arcPoints = arcToPath( prevPoint, point, 64,{ clockwise:g2,center}) ?? []
|
||||
let emit = g2 ? 2 : 3;
|
||||
|
||||
|
||||
// console.log("clone point",structuredClone({point,prevPoint,center,arcPoints,emit}));
|
||||
// console.log("pointer point",{point,prevPoint,center,arcPoints,emit});
|
||||
|
||||
|
||||
outputPoint(point,prevPoint,emit,{center,arcPoints});
|
||||
// scope.addOutput(seq, point, emit, pos.F, tool,{center,arcPoints});
|
||||
}
|
||||
|
|
@ -646,8 +640,8 @@ class Output {
|
|||
|
||||
const { type, center, arcPoints } = (options ?? {});
|
||||
//speed, tool, type, center, arcPoints
|
||||
this.point = point; // point to emit
|
||||
this.emit = emit; // emit (feed for printers, power for lasers, cut for cam)
|
||||
this.point = point;
|
||||
this.emit = Number(emit); //convert bools into 0/1
|
||||
this.speed = speed;
|
||||
this.tool = tool;
|
||||
this.type = type;
|
||||
|
|
@ -680,9 +674,7 @@ function newPrint(settings, widgets, id) {
|
|||
return new Print(settings, widgets, id);
|
||||
};
|
||||
|
||||
gapp.overlay(kiri, {
|
||||
export {
|
||||
Print,
|
||||
newPrint
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
@ -1,35 +1,24 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { Layers } from './layers.js';
|
||||
import { newPoint } from '../../geo/point.js';
|
||||
import { newPolygon } from '../../geo/polygon.js';
|
||||
import { newSlopeFromAngle } from '../../geo/slope.js';
|
||||
|
||||
/*
|
||||
* uses `layers.js` to convert paths (usually preview) into primordial geometries.
|
||||
* the input is based on an array (layers) of arrays containing `Output` objects
|
||||
*/
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
// dep: geo.polygon
|
||||
gapp.register("kiri.render", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { config, util, newPolygon } = base;
|
||||
const hsV = 0.9;
|
||||
const XAXIS = new THREE.Vector3(1,0,0);
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
||||
exports({
|
||||
path,
|
||||
is_dark,
|
||||
rate_to_color
|
||||
});
|
||||
function worker() {
|
||||
return self.kiri_worker.current;
|
||||
}
|
||||
|
||||
function is_cam() {
|
||||
return root.worker.print.settings.mode === 'CAM';
|
||||
return worker().print.settings.mode === 'CAM';
|
||||
}
|
||||
|
||||
function is_dark() {
|
||||
return root.worker.print.settings.controller.dark ? true : false;
|
||||
return worker().print.settings.controller.dark ? true : false;
|
||||
};
|
||||
|
||||
function rate_to_color(rate, max) {
|
||||
|
|
@ -44,17 +33,17 @@ function rate_to_color(rate, max) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate visual representation of a gcode program.
|
||||
* @param {Output[][]} levels - Array of arrays containing `Output` objects.
|
||||
* @param {function} update - Called with a completion percentage and the rendered layer.
|
||||
* @param {{tools: Object, flat: Boolean, thin: Boolean, speed: Boolean, lineWidth: Number, toolMode: Boolean, z: Number, action: String, other: String}} [opts] - Optional parameters.
|
||||
* @returns {Promise.kiri.Layers[]} - Array of layers.
|
||||
*/
|
||||
/**
|
||||
* Generate visual representation of a gcode program.
|
||||
* @param {Output[][]} levels - Array of arrays containing `Output` objects.
|
||||
* @param {function} update - Called with a completion percentage and the rendered layer.
|
||||
* @param {{tools: Object, flat: Boolean, thin: Boolean, speed: Boolean, lineWidth: Number, toolMode: Boolean, z: Number, action: String, other: String}} [opts] - Optional parameters.
|
||||
* @returns {Promise.kiri.Layers[]} - Array of layers.
|
||||
*/
|
||||
async function path(levels, update, opts = {}) {
|
||||
levels = levels.filter(level => level.length);
|
||||
if (levels.length === 0) {
|
||||
self.worker.print.maxSpeed = 0;
|
||||
worker().print.maxSpeed = 0;
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -107,10 +96,11 @@ async function path(levels, update, opts = {}) {
|
|||
// }).reduce((a, v) => Math.max(a, v)) + 1;
|
||||
|
||||
// for reporting
|
||||
self.worker.print.minSpeed = minspd;
|
||||
self.worker.print.maxSpeed = maxspd;
|
||||
self.worker.print.thinColor = thin;
|
||||
self.worker.print.flatColor = flat;
|
||||
let print = worker().print;
|
||||
print.minSpeed = minspd;
|
||||
print.maxSpeed = maxspd;
|
||||
print.thinColor = thin;
|
||||
print.flatColor = flat;
|
||||
|
||||
let lastTool = null;
|
||||
let lastEnd = null;
|
||||
|
|
@ -138,7 +128,8 @@ async function path(levels, update, opts = {}) {
|
|||
const engages = [];
|
||||
const lasers = [];
|
||||
const sparks = [];
|
||||
const output = new kiri.Layers();
|
||||
const output = new Layers();
|
||||
|
||||
layers.push(output);
|
||||
|
||||
const pushPrint = (toolid, poly) => {
|
||||
|
|
@ -172,8 +163,8 @@ async function path(levels, update, opts = {}) {
|
|||
let p2 = new THREE.Vector3(point.x, point.y, point.z)
|
||||
.applyAxisAngle(XAXIS, point.a * DEG2RAD);
|
||||
// reconstruct point point for display without A axis
|
||||
outPoint = base.newPoint(p2.x, p2.y, p2.z);
|
||||
// let sp = base.newPoint(outPoint.x * 1.1, outPoint.y * 1.1, outPoint.z * 1.1);
|
||||
outPoint = newPoint(p2.x, p2.y, p2.z);
|
||||
// let sp = newPoint(outPoint.x * 1.1, outPoint.y * 1.1, outPoint.z * 1.1);
|
||||
// sparks.push(outPoint, sp);
|
||||
}
|
||||
if (out.tool !== lastTool) {
|
||||
|
|
@ -321,8 +312,8 @@ async function path(levels, update, opts = {}) {
|
|||
.addAreas(heads.map(points => {
|
||||
const {p1, p2} = points;
|
||||
const slope = p2.slopeTo(p1);
|
||||
const s1 = base.newSlopeFromAngle(slope.angle + 20);
|
||||
const s2 = base.newSlopeFromAngle(slope.angle - 20);
|
||||
const s1 = newSlopeFromAngle(slope.angle + 20);
|
||||
const s2 = newSlopeFromAngle(slope.angle - 20);
|
||||
const p3 = points.p2.projectOnSlope(s1, arrowSize);
|
||||
const p4 = points.p2.projectOnSlope(s2, arrowSize);
|
||||
return newPolygon().addPoints([p2,p3,p4]).setZ(p2.z + 0.01);
|
||||
|
|
@ -543,4 +534,14 @@ function color4(rgb, inc, seg) {
|
|||
}
|
||||
}
|
||||
|
||||
});
|
||||
export const render = {
|
||||
path,
|
||||
is_dark,
|
||||
rate_to_color
|
||||
};
|
||||
|
||||
export {
|
||||
path,
|
||||
is_dark,
|
||||
rate_to_color
|
||||
};
|
||||
|
|
@ -1,16 +1,8 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.space
|
||||
// dep: kiri.api
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.utils
|
||||
gapp.register("kiri.selection", (root, exports) => {
|
||||
|
||||
const { kiri, moto, noop } = self;
|
||||
const { api, consts, utils } = kiri;
|
||||
const { space } = moto;
|
||||
import { api } from './api.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
import { THREE } from '../../ext/three.js';
|
||||
|
||||
const selectedMeshes = [];
|
||||
|
||||
|
|
@ -296,7 +288,7 @@ function setDisabled(bool) {
|
|||
}
|
||||
|
||||
// extend API (api.selection)
|
||||
const selection = Object.assign(api.selection, {
|
||||
export const selection = {
|
||||
move,
|
||||
merge,
|
||||
scale,
|
||||
|
|
@ -320,6 +312,4 @@ const selection = Object.assign(api.selection, {
|
|||
disable() { setDisabled(true) },
|
||||
opacity() { api.widgets.opacity(...arguments) },
|
||||
meshes() { return selectedMeshes.slice() },
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
@ -1,23 +1,19 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
import { $, h } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { conf } from './conf.js';
|
||||
import { codec } from './codec.js';
|
||||
import { consts } from './consts.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
import { local } from '../../data/local.js';
|
||||
import { utils } from './utils.js';
|
||||
import { version } from '../../moto/license.js';
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.conf
|
||||
// dep: kiri.utils
|
||||
// dep: moto.space
|
||||
// dep: data.local
|
||||
// use: kiri.widgets
|
||||
// use: ext.base64
|
||||
gapp.register("kiri.settings", (root, exports) => {
|
||||
|
||||
const { data, kiri, moto, noop } = self;
|
||||
const { api, conf, consts, utils } = kiri;
|
||||
const { space } = moto;
|
||||
const { local } = data;
|
||||
const { clone } = Object;
|
||||
const { areEqual, ls2o, js2o } = utils;
|
||||
const { COLOR } = consts;
|
||||
const { clone } = Object;
|
||||
|
||||
const localFilterKey ='kiri-gcode-filters';
|
||||
const localFilters = js2o(local.getItem(localFilterKey)) || [];
|
||||
|
||||
|
|
@ -506,7 +502,7 @@ function settingsExport(opts = {}) {
|
|||
const widgets = api.widgets.all();
|
||||
const note = opts.node || undefined;
|
||||
const shot = opts.work || opts.screen ? space.screenshot() : undefined;
|
||||
const work = opts.work ? kiri.codec.encode(widgets,{_json_:true}) : undefined;
|
||||
const work = opts.work ? codec.encode(widgets,{_json_:true}) : undefined;
|
||||
const view = opts.work ? space.view.save() : undefined;
|
||||
const setn = Object.clone(settings);
|
||||
// stuff in legacy annotations for re-import
|
||||
|
|
@ -515,7 +511,7 @@ function settingsExport(opts = {}) {
|
|||
}
|
||||
const xprt = {
|
||||
settings: setn,
|
||||
version: kiri.version,
|
||||
version: version,
|
||||
screen: shot,
|
||||
space: space.info,
|
||||
note: note,
|
||||
|
|
@ -600,7 +596,7 @@ function settingsImport(data, ask) {
|
|||
work.type = 100;
|
||||
}
|
||||
}
|
||||
kiri.codec.decode(data.work).forEach(widget => {
|
||||
codec.decode(data.work).forEach(widget => {
|
||||
api.platform.add(widget, 0, true, true);
|
||||
});
|
||||
if (data.view) {
|
||||
|
|
@ -760,9 +756,14 @@ function setEnableWASM(bool) {
|
|||
api.event.emit("set.assembly", bool);
|
||||
}
|
||||
|
||||
// extend API (api.conf)
|
||||
Object.assign(api.conf, {
|
||||
dbo: () => { return ls2o('ws-settings') },
|
||||
// merged api for backward compatibility
|
||||
const apiSet = {
|
||||
dbo() { return ls2o('ws-settings') },
|
||||
dev() { return settings.device },
|
||||
proc() { return settings.process },
|
||||
ctrl() { return settings.controller },
|
||||
mode() { return settings.mode },
|
||||
prof() { return settings.sproc[settings.mode] },
|
||||
get: getSettings,
|
||||
put: putSettings,
|
||||
load: loadSettings,
|
||||
|
|
@ -777,25 +778,26 @@ Object.assign(api.conf, {
|
|||
restore: restoreSettings,
|
||||
export: settingsExport,
|
||||
import: settingsImport,
|
||||
});
|
||||
|
||||
// extend API (api.settings)
|
||||
Object.assign(api.settings, {
|
||||
get: getSettings,
|
||||
import: settingsImport,
|
||||
import_zip: settingsImportZip,
|
||||
import_url: settingsImportUrl,
|
||||
import_prusa: settingsPrusaConvert,
|
||||
dev() { return settings.device },
|
||||
proc() { return settings.process },
|
||||
ctrl() { return settings.controller },
|
||||
mode() { return settings.mode },
|
||||
prof() { return settings.sproc[settings.mode] },
|
||||
sync: {
|
||||
async get() {},
|
||||
async put() {},
|
||||
status: false
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
// for backward compatibility
|
||||
apiSet.conf = apiSet;
|
||||
apiSet.set = apiSet;
|
||||
apiSet.settings = apiSet;
|
||||
|
||||
export default apiSet;
|
||||
|
||||
// for backward compatibility
|
||||
export {
|
||||
apiSet as conf,
|
||||
apiSet as set,
|
||||
apiSet as settings
|
||||
};
|
||||
|
|
@ -1,14 +1,8 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.polygons
|
||||
gapp.register("kiri.slice", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
|
||||
const POLY = base.polygons;
|
||||
import { Layers } from './layers.js';
|
||||
import { newPoint } from '../../geo/point.js';
|
||||
import { polygons as POLY } from '../../geo/polygons.js';
|
||||
|
||||
let tracker;
|
||||
|
||||
|
|
@ -41,7 +35,7 @@ class Slice {
|
|||
*/
|
||||
output() {
|
||||
if (this.layers) return this.layers;
|
||||
let layers = this.layers = new kiri.Layers();
|
||||
let layers = this.layers = new Layers();
|
||||
if (tracker) {
|
||||
layers.setRotation(-tracker.rotation || 0);
|
||||
}
|
||||
|
|
@ -150,7 +144,7 @@ class Slice {
|
|||
} else {
|
||||
// create top object from object bundle passed back by slicePost()
|
||||
let top = new Top(data.poly);
|
||||
top.thin_fill = data.thin_fill ? data.thin_fill.map(p => base.newPoint(p.x,p.y,p.z)) : undefined;
|
||||
top.thin_fill = data.thin_fill ? data.thin_fill.map(p => newPoint(p.x,p.y,p.z)) : undefined;
|
||||
top.fill_lines = data.fill_lines;
|
||||
top.fill_sparse = data.fill_sparse;
|
||||
top.fill_off = data.fill_off;
|
||||
|
|
@ -251,12 +245,10 @@ function newSlice(z, view) {
|
|||
return new Slice(z, view);
|
||||
}
|
||||
|
||||
gapp.overlay(kiri, {
|
||||
export {
|
||||
Top,
|
||||
Slice,
|
||||
newTop,
|
||||
newSlice,
|
||||
setSliceTracker
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
@ -1,15 +1,9 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.stack", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
/*
|
||||
* converts `layers.js` output data structures into three.js meshes for display
|
||||
*/
|
||||
class Stack {
|
||||
export class Stack {
|
||||
constructor(view, freeMem, shiny) {
|
||||
this._view = view;
|
||||
this.view = view.newGroup();
|
||||
|
|
@ -245,8 +239,6 @@ class Stack {
|
|||
}
|
||||
}
|
||||
|
||||
kiri.Stack = Stack;
|
||||
|
||||
let shininess = 15,
|
||||
specular = 0x444444,
|
||||
emissive = 0x101010,
|
||||
|
|
@ -301,5 +293,3 @@ function createLambertMaterial(color, flat) {
|
|||
side: flat ? THREE.DoubleSide : THREE.FrontSide
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.stack
|
||||
// use: kiri.api
|
||||
// use: kiri.ui
|
||||
gapp.register("kiri.stacks", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
import { $ } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { Stack } from './stack.js';
|
||||
|
||||
let freeMem = true,
|
||||
stacks = {},
|
||||
|
|
@ -18,7 +13,7 @@ let freeMem = true,
|
|||
|
||||
function init() {
|
||||
labels = $("layers");
|
||||
API = kiri.api,
|
||||
API = api,
|
||||
UC = API.uc,
|
||||
UI = API.ui;
|
||||
}
|
||||
|
|
@ -80,7 +75,7 @@ function create(name, view) {
|
|||
}
|
||||
const stack = stacks[name] = {
|
||||
layers: [ ],
|
||||
obj: new kiri.Stack(view, freeMem, kiri.api.conf.get().controller.shiny),
|
||||
obj: new Stack(view, freeMem, api.conf.get().controller.shiny),
|
||||
add: function(layers) {
|
||||
let lmap = layers.layers;
|
||||
let map = stack.obj.addLayers(layers);
|
||||
|
|
@ -93,9 +88,9 @@ function create(name, view) {
|
|||
mat.visible = ctrl.toggle.checked;
|
||||
});
|
||||
if (ctrl.toggle.checked) {
|
||||
kiri.api.event.emit("stack.show", label);
|
||||
api.event.emit("stack.show", label);
|
||||
} else {
|
||||
kiri.api.event.emit("stack.hide", label)
|
||||
api.event.emit("stack.hide", label)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
@ -155,7 +150,7 @@ function setFraction(frac) {
|
|||
});
|
||||
}
|
||||
|
||||
kiri.stacks = {
|
||||
export default {
|
||||
clear,
|
||||
create,
|
||||
rotate,
|
||||
|
|
@ -170,4 +165,17 @@ kiri.stacks = {
|
|||
setFreeMem
|
||||
};
|
||||
|
||||
});
|
||||
export {
|
||||
clear,
|
||||
create,
|
||||
rotate,
|
||||
remove,
|
||||
getStack,
|
||||
getStacks,
|
||||
getRange,
|
||||
setRange,
|
||||
getLabels,
|
||||
setVisible,
|
||||
setFraction,
|
||||
setFreeMem
|
||||
};
|
||||
|
|
@ -1,20 +1,12 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.broker
|
||||
// dep: kiri.api
|
||||
// dep: kiri.utils
|
||||
gapp.register("kiri.stats", [], (root, exports) => {
|
||||
|
||||
const { data, kiri, gapp } = root;
|
||||
const { broker } = gapp;
|
||||
const { api, utils } = kiri;
|
||||
const { js2o, o2js } = utils;
|
||||
import { local } from '../../data/local.js';
|
||||
import { broker } from '../../moto/broker.js';
|
||||
import { js2o, o2js } from './utils.js';
|
||||
|
||||
class Stats {
|
||||
constructor(db) {
|
||||
this.db = db || data.local;
|
||||
this.db = db || local;
|
||||
this.obj = js2o(this.db['stats'] || '{}');
|
||||
let o = this.obj, k;
|
||||
for (k in o) {
|
||||
|
|
@ -56,6 +48,6 @@ class Stats {
|
|||
}
|
||||
}
|
||||
|
||||
kiri.stats = new Stats();
|
||||
export const stats = new Stats();
|
||||
|
||||
});
|
||||
export { stats as Stats };
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
// dep: add.three
|
||||
// dep: kiri.api
|
||||
gapp.register("kiri.tools", (root, exports) => {
|
||||
import { api } from './api.js';
|
||||
import { THREE } from '../../ext/three.js';
|
||||
|
||||
const { Vector3, Quaternion } = THREE;
|
||||
const { kiri, moto } = root;
|
||||
const { api } = kiri;
|
||||
|
||||
const XAXIS = new THREE.Vector3(1,0,0);
|
||||
const ZAXIS = new THREE.Vector3(0,0,1);
|
||||
|
|
@ -164,4 +162,5 @@ api.event.on('mouse.hover.up', (ev) => {
|
|||
endIt();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
export { onLayFlatSelect, onFaceUpSelect, startIt, endIt, cleanup, scale, onDone };
|
||||
859
src/kiri/core/ui.js
Normal file
859
src/kiri/core/ui.js
Normal file
|
|
@ -0,0 +1,859 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { $ } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
|
||||
let DOC = self.document,
|
||||
inputAction = null,
|
||||
lastAddTo = null,
|
||||
lastGroup = null,
|
||||
lastDiv = null,
|
||||
addTo = null,
|
||||
bindTo = null,
|
||||
groups = {},
|
||||
groupSticky = false,
|
||||
groupName = undefined,
|
||||
heads = {}, // hideable group heads (clickable label)
|
||||
hidden = {}, // hidden groups (by name)
|
||||
hasModes = [],
|
||||
setters = [],
|
||||
lastMode = null,
|
||||
lastExpert = true,
|
||||
prefix = "tab",
|
||||
units = 1,
|
||||
lastChange = null,
|
||||
lastBtn = null,
|
||||
lastTxt = null,
|
||||
lastPop = null;
|
||||
|
||||
export const UI = {
|
||||
prefix: function(pre) { prefix = pre; return UI },
|
||||
inputAction: function(fn) { inputAction = fn; return UI },
|
||||
lastChange: function() { return lastChange },
|
||||
checkpoint,
|
||||
restore,
|
||||
refresh,
|
||||
setHidden,
|
||||
setMode,
|
||||
bound,
|
||||
toInt,
|
||||
toFloat,
|
||||
toDegsFloat,
|
||||
isSticky,
|
||||
setSticky,
|
||||
newBoolean,
|
||||
newButton,
|
||||
newBlank,
|
||||
newDiv,
|
||||
newElement,
|
||||
newExpand,
|
||||
endExpand,
|
||||
newGCode,
|
||||
newGroup,
|
||||
newLabel,
|
||||
newInput,
|
||||
newValue,
|
||||
newRange,
|
||||
newRow,
|
||||
newSelect,
|
||||
newText,
|
||||
setGroup,
|
||||
addUnits,
|
||||
setUnits,
|
||||
confirm,
|
||||
prompt,
|
||||
alert,
|
||||
onBlur,
|
||||
setEnabled(el, bool) {
|
||||
if (bool) {
|
||||
el.removeAttribute('disabled');
|
||||
} else {
|
||||
el.setAttribute('disabled','');
|
||||
}
|
||||
},
|
||||
setVisible(el, bool) {
|
||||
UI.setClass(el, 'hide', !bool);
|
||||
},
|
||||
setClass(el, clazz, bool) {
|
||||
if (bool) {
|
||||
el.classList.add(clazz);
|
||||
} else {
|
||||
el.classList.remove(clazz);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function setHidden(map) {
|
||||
hidden = map;
|
||||
refresh();
|
||||
for (let ctrl of Object.values(heads)) {
|
||||
ctrl.update();
|
||||
}
|
||||
}
|
||||
|
||||
function onBlur(obj, fn) {
|
||||
if (Array.isArray(obj)) {
|
||||
for (let o of obj) onBlur(o, fn);
|
||||
return;
|
||||
}
|
||||
obj.addEventListener('blur', fn);
|
||||
}
|
||||
|
||||
function alert(message) {
|
||||
return confirm(message, {ok:true});
|
||||
}
|
||||
|
||||
function prompt(message, value) {
|
||||
return confirm(message, {ok:true, cancel:undefined}, value);
|
||||
}
|
||||
|
||||
function confirm(message, buttons, input, opt = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let { feature } = api;
|
||||
let onkey_save = feature.on_key;
|
||||
feature.on_key = key => {
|
||||
// console.log({ eat_key: key });
|
||||
return true;
|
||||
};
|
||||
let dialog = $('dialog');
|
||||
let btns = buttons || {
|
||||
"yes": true,
|
||||
"no": false
|
||||
};
|
||||
let rnd = Date.now().toString(36);
|
||||
let html = [
|
||||
`<div class="confirm f-col a-stretch" style="padding:5px !important">`
|
||||
];
|
||||
if (message) {
|
||||
html.push(`<label style="user-select:text">${message}</label>`);
|
||||
}
|
||||
if (opt.pre) {
|
||||
html = opt.pre.appendAll(html);
|
||||
}
|
||||
let iid;
|
||||
if (Array.isArray(input)) {
|
||||
iid = `confirm-input-${rnd}`;
|
||||
html.append(`<div><textarea rows="15" cols="40" class="grow" type="text" spellcheck="false" id="${iid}"></textarea></div>`);
|
||||
} else if (input !== undefined) {
|
||||
iid = `confirm-input-${rnd}`;
|
||||
html.append(`<div><input class="grow" type="text" spellcheck="false" id="${iid}"/></div>`);
|
||||
}
|
||||
html.append(`<div class="f-row j-end">`);
|
||||
Object.entries(btns).forEach((row,i) => {
|
||||
html.append(`<button id="confirm-${i}-${rnd}">${row[0]}</button>`);
|
||||
});
|
||||
html.append(`</div></div>`);
|
||||
if (opt.post) {
|
||||
html.appendAll(opt.post);
|
||||
}
|
||||
dialog.innerHTML = html.join('');
|
||||
function done(value) {
|
||||
dialog.close();
|
||||
feature.on_key = onkey_save;
|
||||
if (value !== undefined) {
|
||||
setTimeout(() => { resolve(value) }, 150);
|
||||
}
|
||||
}
|
||||
if (iid) {
|
||||
let array = Array.isArray(input);
|
||||
iid = $(iid);
|
||||
iid.value = array ? input.join('\n') : input;
|
||||
if (!array)
|
||||
iid.onkeypress = (ev) => {
|
||||
if (ev.key === 'Enter' || ev.charCode === 13) {
|
||||
done(ev.target.value);
|
||||
}
|
||||
};
|
||||
}
|
||||
Object.entries(btns).forEach((row,i) => {
|
||||
$(`confirm-${i}-${rnd}`).onclick = (ev) => {
|
||||
let value = iid && row[1] ? iid.value : row[1];
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
done(value);
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
dialog.showModal();
|
||||
if (iid) {
|
||||
iid.focus();
|
||||
iid.selectionStart = 0;
|
||||
iid.selectionEnd = iid.value.length;
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
setMode(lastMode);
|
||||
setters.forEach(input => {
|
||||
if (input.setv) {
|
||||
input.setv(input.real);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
lastMode = mode;
|
||||
hasModes.forEach(div => div.setMode(mode));
|
||||
}
|
||||
|
||||
function checkpoint(at) {
|
||||
return { addTo: at || addTo, lastDiv: at || lastDiv, lastGroup, groupName };
|
||||
}
|
||||
|
||||
function restore(opt = {}) {
|
||||
addTo = opt.addTo || addTo;
|
||||
bindTo = opt.bindTo || null;
|
||||
lastDiv = opt.lastDiv || lastDiv;
|
||||
lastGroup = opt.lastGroup || lastGroup;
|
||||
groupName = opt.groupName || groupName;
|
||||
}
|
||||
|
||||
// at present only used by the layers popup menu
|
||||
function setGroup(div) {
|
||||
addTo = lastDiv = div;
|
||||
groupName = undefined;
|
||||
lastGroup = [];
|
||||
return div;
|
||||
}
|
||||
|
||||
function newElement(type, opt = {}) {
|
||||
let el = DOC.createElement(type);
|
||||
if (opt.id) {
|
||||
el.setAttribute("id", opt.id);
|
||||
}
|
||||
if (opt.class) {
|
||||
for (let cl of opt.class.split(' ')) {
|
||||
el.classList.add(cl);
|
||||
}
|
||||
}
|
||||
if (opt.attr) {
|
||||
for (let [key, val] of Object.entries(opt.attr)) {
|
||||
el.setAttribute(key, val);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function newGroup(label, div, opt = {}) {
|
||||
lastDiv = div = (div || lastDiv);
|
||||
|
||||
let group = opt.group || label,
|
||||
row = DOC.createElement('div'),
|
||||
dbkey = `beta-${prefix}-show-${group}`,
|
||||
link;
|
||||
|
||||
if (opt.class) {
|
||||
opt.class.split(' ').forEach(ce => {
|
||||
row.classList.add(ce);
|
||||
});
|
||||
} else {
|
||||
row.setAttribute("class", "set-header");
|
||||
}
|
||||
|
||||
if (div && opt.driven) {
|
||||
addModeControls(div, opt);
|
||||
}
|
||||
|
||||
addTo = lastDiv;
|
||||
|
||||
div.appendChild(row);
|
||||
if (label) {
|
||||
link = DOC.createElement('a');
|
||||
link.appendChild(DOC.createTextNode(label));
|
||||
row.appendChild(link);
|
||||
}
|
||||
|
||||
addModeControls(row, opt);
|
||||
lastGroup = groups[group] = [];
|
||||
lastGroup.key = dbkey;
|
||||
groupName = group;
|
||||
|
||||
if (opt.hideable) {
|
||||
let pad = DOC.createElement('i');
|
||||
let arr = DOC.createElement('span');
|
||||
pad.setAttribute('class','grow');
|
||||
row.appendChild(pad);
|
||||
row.appendChild(arr);
|
||||
const ctrl = heads[group] = {
|
||||
row,
|
||||
arr,
|
||||
update() {
|
||||
if (!hidden[group]) {
|
||||
arr.innerHTML = '<i class="fa-solid fa-caret-down"></i>';
|
||||
row.classList.add('hidden');
|
||||
} else {
|
||||
arr.innerHTML = '<i class="fa-solid fa-caret-up"></i>';
|
||||
row.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
row.onclick = () => {
|
||||
hidden[group] = !hidden[group];
|
||||
refresh();
|
||||
ctrl.update();
|
||||
};
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function addCollapsableElement(parent, options = {}) {
|
||||
let row = newDiv(options);
|
||||
if (parent) parent.appendChild(row);
|
||||
if (lastGroup) lastGroup.push(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
function bound(low,high) {
|
||||
return function(v) {
|
||||
if (isNaN(v)) return low;
|
||||
return v < low ? low : v > high ? high : v;
|
||||
};
|
||||
}
|
||||
|
||||
function toInt() {
|
||||
let nv = this.value !== '' ? parseInt(this.value) : null;
|
||||
if (isNaN(nv)) nv = 0;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
this.value = nv;
|
||||
if (this.setv) {
|
||||
return this.real = Math.round(nv * units);
|
||||
};
|
||||
return nv;
|
||||
}
|
||||
|
||||
function toFloat() {
|
||||
let nv = this.value !== '' ? parseFloat(this.value) : null;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
if (this.setv) {
|
||||
return this.setv(nv * units);
|
||||
} else {
|
||||
this.value = nv;
|
||||
}
|
||||
return nv;
|
||||
}
|
||||
|
||||
function toDegsFloat(){
|
||||
let nv = this.value !== '' ? parseFloat(this.value) : null;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
nv = (nv+360)% 360; // bound the val to 0-359.99
|
||||
if (this.setv) {
|
||||
return this.setv(nv);
|
||||
} else {
|
||||
this.value = nv;
|
||||
}
|
||||
return nv;
|
||||
}
|
||||
|
||||
function raw() {
|
||||
return this.value !== '' ? this.value : null;
|
||||
}
|
||||
|
||||
function setUnits(v) {
|
||||
if (v !== units) {
|
||||
units = v;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function newLabel(text, opt = {}) {
|
||||
let label = DOC.createElement('label');
|
||||
label.appendChild(DOC.createTextNode(text));
|
||||
label.setAttribute("class", "noselect");
|
||||
if (opt.class) opt.class.split(' ').forEach(cl => {
|
||||
label.classList.add(cl);
|
||||
})
|
||||
return label;
|
||||
}
|
||||
|
||||
function newValue(size = 6, opt = {}) {
|
||||
let value = DOC.createElement('input');
|
||||
value.setAttribute("size", size);
|
||||
value.setAttribute("readonly", '');
|
||||
value.setAttribute("class", "nooutline noselect");
|
||||
if (opt.class) opt.class.split(' ').forEach(cl => {
|
||||
value.classList.add(cl);
|
||||
})
|
||||
return value;
|
||||
}
|
||||
|
||||
function addId(el, opt = {}) {
|
||||
if (opt.id) {
|
||||
el.setAttribute("id", opt.id);
|
||||
}
|
||||
}
|
||||
|
||||
function safecall(fn) {
|
||||
try {
|
||||
return fn();
|
||||
} catch (e) {
|
||||
// console.log({ safecall_error: e });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function addModeControls(el, opt = {}) {
|
||||
el.__opt = opt;
|
||||
el.showMe = function() {
|
||||
if (opt.trace) console.log({ showMe: el });
|
||||
el.classList.remove('hide');
|
||||
};
|
||||
el.hideMe = function() {
|
||||
if (opt.trace) console.log({ hideMe: el });
|
||||
el.classList.add('hide');
|
||||
};
|
||||
el.setVisible = function(show) {
|
||||
if (opt.trace) console.log({ setVisible: show });
|
||||
if (show) el.showMe();
|
||||
else el.hideMe();
|
||||
};
|
||||
el.setMode = function(mode) {
|
||||
let hidn = hidden[el._group] === true;
|
||||
let xprt = opt.expert === undefined || (opt.expert === lastExpert);
|
||||
let show = opt.show ? safecall(opt.show) : true;
|
||||
let disp = opt.visible ? opt.visible() : true;
|
||||
let hmod = el.hasMode(mode);
|
||||
if (opt.trace) console.log({ setMode: mode, xprt, show, disp, hmod, modes:el.modes });
|
||||
if (opt.manual) return;
|
||||
el.setVisible(!hidn && hmod && show && xprt && disp);
|
||||
}
|
||||
el.hasMode = function(mode) {
|
||||
return (el.modes.length === 0) ||
|
||||
(el.modes.contains && el.modes.contains(mode)) ||
|
||||
(el.modes === mode);
|
||||
}
|
||||
el.modes = opt.modes || [];
|
||||
hasModes.push(el);
|
||||
}
|
||||
|
||||
function newDiv(opt = {}) {
|
||||
let div = DOC.createElement(opt.tag || 'div');
|
||||
addModeControls(div, opt);
|
||||
(opt.addto || addTo).appendChild(div);
|
||||
if (opt.addto) lastDiv = addTo = div;
|
||||
if (opt.class) div.setAttribute('class', opt.class);
|
||||
lastGroup?.push(div);
|
||||
div._group = groupName;
|
||||
return div;
|
||||
}
|
||||
|
||||
function newExpand(label, opt = {}, opteach = {}) {
|
||||
let div = DOC.createElement('details');
|
||||
div.setAttribute('class', opt.class || 'f-col');
|
||||
addModeControls(div, opt);
|
||||
|
||||
let summary = DOC.createElement('summary');
|
||||
summary.setAttribute('class', opt.class || 'var-row');
|
||||
summary.innerHTML = `<label>${label}</label>`;
|
||||
|
||||
div.appendChild( summary );
|
||||
div.collapse = () => {
|
||||
div.removeAttribute('open');
|
||||
};
|
||||
|
||||
lastAddTo = addTo;
|
||||
addTo.appendChild(div);
|
||||
addTo = div;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function endExpand() {
|
||||
addTo = lastAddTo;
|
||||
return addTo;
|
||||
}
|
||||
|
||||
function isSticky() {
|
||||
return groupSticky;
|
||||
}
|
||||
|
||||
function setSticky(bool) {
|
||||
groupSticky = bool;
|
||||
}
|
||||
|
||||
function newGCode(label, options) {
|
||||
let opt = options || {},
|
||||
btn = DOC.createElement("button"),
|
||||
txt = DOC.createElement("textarea"),
|
||||
area = opt.area;
|
||||
|
||||
txt.setAttribute("wrap", "off");
|
||||
txt.setAttribute("spellcheck", "false");
|
||||
txt.setAttribute("style", "resize: none");
|
||||
txt.onblur = bindTo || inputAction;
|
||||
txt.button = btn;
|
||||
|
||||
btn.setAttribute("class", "basis-50");
|
||||
btn.appendChild(DOC.createTextNode(label));
|
||||
btn.setAttribute("title", opt.title || undefined);
|
||||
btn.onclick = function(ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.target === txt) {
|
||||
// drop clicks on TextArea
|
||||
ev.target.focus();
|
||||
} else {
|
||||
let fc = area.firstChild;
|
||||
if (fc) area.removeChild(fc);
|
||||
area.appendChild(txt);
|
||||
txt.scrollTop = 0;
|
||||
txt.scrollLeft = 0;
|
||||
txt.selectionEnd = 0;
|
||||
let rows = txt.value.split('\n');
|
||||
let cols = 0;
|
||||
rows.forEach(row => {
|
||||
cols = Math.max(cols, row.length);
|
||||
});
|
||||
let showing = btn === lastBtn;
|
||||
if (lastTxt) {
|
||||
lastTxt.classList.remove('txt-sel');
|
||||
}
|
||||
if (lastBtn) {
|
||||
lastBtn.classList.remove('btn-sel');
|
||||
}
|
||||
if (!showing) {
|
||||
btn.classList.add('btn-sel');
|
||||
lastTxt = btn;
|
||||
lastBtn = btn;
|
||||
txt.focus();
|
||||
} else {
|
||||
inputAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addModeControls(btn, opt);
|
||||
|
||||
return txt;
|
||||
}
|
||||
|
||||
function newText(label, options) {
|
||||
let opt = options || {},
|
||||
inline = opt.row,
|
||||
row = inline ? lastDiv : newDiv(options),
|
||||
btn = DOC.createElement("button"),
|
||||
pop = DOC.createElement("div"),
|
||||
txt = DOC.createElement("textarea"),
|
||||
area = opt.area;
|
||||
|
||||
txt.setAttribute("wrap", "off");
|
||||
txt.setAttribute("spellcheck", "false");
|
||||
txt.setAttribute("style", "resize: none");
|
||||
txt.onblur = inputAction;
|
||||
|
||||
btn.appendChild(DOC.createTextNode(inline ? label : "edit"));
|
||||
|
||||
btn.onclick = function(ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.target === txt) {
|
||||
// drop clicks on TextArea
|
||||
ev.target.focus();
|
||||
} else {
|
||||
let fc = area.firstChild;
|
||||
if (fc) area.removeChild(fc);
|
||||
area.appendChild(txt);
|
||||
// first time, button click / show
|
||||
btn.parentNode.onclick = btn.onclick;
|
||||
txt.scrollTop = 0;
|
||||
txt.scrollLeft = 0;
|
||||
txt.selectionEnd = 0;
|
||||
let rows = txt.value.split('\n');
|
||||
let cols = 0;
|
||||
rows.forEach(row => {
|
||||
cols = Math.max(cols, row.length);
|
||||
});
|
||||
|
||||
let showing = pop === lastPop;
|
||||
if (lastBtn) {
|
||||
lastBtn.classList.remove('btn-sel');
|
||||
}
|
||||
if (lastTxt) {
|
||||
lastTxt.classList.remove('txt-sel');
|
||||
}
|
||||
if (!showing) {
|
||||
row.classList.add('txt-sel');
|
||||
pop.style.display = "flex";
|
||||
lastPop = pop;
|
||||
lastTxt = row;
|
||||
txt.focus();
|
||||
} else {
|
||||
inputAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
addModeControls(btn, opt);
|
||||
addId(btn, opt);
|
||||
|
||||
if (!inline) {
|
||||
row.appendChild(newLabel(label));
|
||||
row.setAttribute("class", "var-row");
|
||||
}
|
||||
row.appendChild(btn);
|
||||
if (opt.title) row.setAttribute("title", options.title);
|
||||
if (row.setVisible) {
|
||||
btn.setVisible = row.setVisible;
|
||||
}
|
||||
|
||||
return txt;
|
||||
}
|
||||
|
||||
function newInput(label, opt = {}) {
|
||||
let row = newDiv(opt),
|
||||
hide = opt.hide,
|
||||
size = opt.size || 5,
|
||||
height = opt.height || 0,
|
||||
ip = height > 1 ? DOC.createElement('textarea') : DOC.createElement('input'),
|
||||
action = opt.action || bindTo || inputAction;
|
||||
|
||||
row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", opt.class || "var-row");
|
||||
if (height > 1) {
|
||||
ip.setAttribute("cols", size);
|
||||
ip.setAttribute("rows", height);
|
||||
ip.setAttribute("wrap", "off");
|
||||
} else {
|
||||
if (Number.isInteger(size)) {
|
||||
ip.setAttribute("size", size);
|
||||
} else {
|
||||
ip.setAttribute("style", `width:${size}`);
|
||||
}
|
||||
}
|
||||
ip.setAttribute("type", "text");
|
||||
ip.setAttribute("spellcheck", "false");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (opt.disabled) ip.setAttribute("disabled", "true");
|
||||
if (opt.title) row.setAttribute("title", opt.title);
|
||||
if (opt.convert) ip.convert = opt.convert.bind(ip);
|
||||
if (opt.bound) ip.bound = opt.bound;
|
||||
if (opt.action) action = opt.action;
|
||||
ip.addEventListener('focus', function(event) {
|
||||
setSticky(true);
|
||||
});
|
||||
if (action) {
|
||||
ip.addEventListener('keydown', function(event) {
|
||||
let key = event.key;
|
||||
if (
|
||||
opt.text ||
|
||||
(key >= '0' && key <= '9') ||
|
||||
key === '.' ||
|
||||
key === '-' ||
|
||||
key === 'Backspace' ||
|
||||
key === 'Delete' ||
|
||||
key === 'ArrowLeft' ||
|
||||
key === 'ArrowRight' ||
|
||||
key === 'Tab' ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
(key === ',' && options.comma)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
ip.addEventListener('keyup', function(event) {
|
||||
if (event.keyCode === 13) {
|
||||
ip.blur();
|
||||
}
|
||||
});
|
||||
ip.addEventListener('blur', function(event) {
|
||||
setSticky(false);
|
||||
lastChange = ip;
|
||||
action(event);
|
||||
if (opt.trigger) {
|
||||
refresh(opt.trigger === 1 || opt.trigger === true);
|
||||
}
|
||||
});
|
||||
if (opt.units) {
|
||||
addUnits(ip, opt.round || 3);
|
||||
}
|
||||
}
|
||||
if (!ip.convert) ip.convert = raw.bind(ip);
|
||||
ip.setVisible = row.setVisible;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function addUnits(input, round) {
|
||||
setters.push(input);
|
||||
input.setv = function(value) {
|
||||
if (typeof(value) === 'number') {
|
||||
input.real = value;
|
||||
input.value = (value / units).round(round);
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
return input.real;
|
||||
};
|
||||
return input;
|
||||
}
|
||||
|
||||
function newRange(label, options) {
|
||||
let row = newDiv(options),
|
||||
ip = DOC.createElement('input'),
|
||||
hide = options && options.hide,
|
||||
action = bindTo || inputAction;
|
||||
|
||||
if (label) row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", "var-row");
|
||||
ip.setAttribute("type", "range");
|
||||
ip.setAttribute("min", (options && options.min ? options.min : 0));
|
||||
ip.setAttribute("max", (options && options.max ? options.max : 100));
|
||||
ip.setAttribute("value", 0);
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (options) {
|
||||
if (options.title) {
|
||||
ip.setAttribute("title", options.title);
|
||||
row.setAttribute("title", options.title);
|
||||
}
|
||||
if (options.action) action = options.action;
|
||||
}
|
||||
ip.setVisible = row.setVisible;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newSelect(label, options = {}, source) {
|
||||
let row = newDiv(options),
|
||||
ip = DOC.createElement('select'),
|
||||
hide = options && options.hide,
|
||||
action = bindTo || inputAction;
|
||||
|
||||
row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
if (Array.isArray(source)) {
|
||||
ip._source = source;
|
||||
} else {
|
||||
row.setAttribute("source", source || "tools");
|
||||
}
|
||||
row.setAttribute("class", "var-row");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (options.convert) ip.convert = options.convert.bind(ip);
|
||||
if (options.disabled) ip.setAttribute("disabled", "true");
|
||||
if (options.title) row.setAttribute("title", options.title);
|
||||
if (options.action) action = options.action;
|
||||
ip.setVisible = row.setVisible;
|
||||
ip.onchange = function(ev) {
|
||||
lastChange = ip;
|
||||
action();
|
||||
if (options.trigger) {
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
ip.onclick = (ev) => {
|
||||
groupSticky = true;
|
||||
};
|
||||
// because firefox
|
||||
ip.onmouseenter = (ev) => {
|
||||
groupSticky = true;
|
||||
};
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newBoolean(label, action = bindTo, opt = {}) {
|
||||
let row = newDiv(opt),
|
||||
ip = DOC.createElement('input'),
|
||||
hide = opt.hide;
|
||||
|
||||
if (label) {
|
||||
row.appendChild(newLabel(label));
|
||||
}
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", "var-row");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
ip.setAttribute("type", "checkbox");
|
||||
ip.checked = false;
|
||||
if (opt.disabled) {
|
||||
ip.setAttribute("disabled", "true");
|
||||
}
|
||||
if (opt.title) {
|
||||
ip.setAttribute("title", opt.title);
|
||||
row.setAttribute("title", opt.title);
|
||||
}
|
||||
if (action) {
|
||||
ip.onclick = function(ev) {
|
||||
action(ip);
|
||||
if (opt.trigger) {
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
}
|
||||
ip.setVisible = row.setVisible;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newBlank(options) {
|
||||
let opt = options || {},
|
||||
row = newDiv(opt),
|
||||
hide = opt.hide;
|
||||
|
||||
row.isBlank = true;
|
||||
row.style.display = hide ? 'none' : '';
|
||||
|
||||
if (!opt.driven) {
|
||||
row.setAttribute("class", "var-row");
|
||||
}
|
||||
|
||||
if (opt.class) {
|
||||
opt.class.split(' ').forEach(ce => {
|
||||
row.classList.add(ce);
|
||||
});
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
// unlike other elements, does not auto-add to a row
|
||||
function newButton(label, action, opt = {}) {
|
||||
let b = DOC.createElement('button');
|
||||
|
||||
b.onclick = function() {
|
||||
switch (typeof action) {
|
||||
case "string":
|
||||
api.event.emit(action);
|
||||
break;
|
||||
case "function":
|
||||
action(...arguments);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (opt.class) {
|
||||
opt.class.split(' ').forEach(ce => {
|
||||
b.classList.add(ce);
|
||||
});
|
||||
}
|
||||
if (opt.icon) {
|
||||
let d = DOC.createElement('div');
|
||||
d.innerHTML = opt.icon;
|
||||
b.appendChild(d);
|
||||
}
|
||||
if (opt.title) {
|
||||
b.setAttribute('title', opt.title);
|
||||
}
|
||||
if (label) {
|
||||
b.appendChild(DOC.createTextNode(label));
|
||||
}
|
||||
|
||||
addModeControls(b, opt);
|
||||
addId(b, opt);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
function newRow(children, options) {
|
||||
let row = addCollapsableElement((options && options.noadd) ? null : addTo);
|
||||
if (children) children.forEach(function (c) { row.appendChild(c) });
|
||||
addModeControls(row, options);
|
||||
if (options && options.class) {
|
||||
options.class.split(' ').forEach(ce => {
|
||||
row.classList.add(ce);
|
||||
});
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.utils", [], (root, exports) => {
|
||||
import { ajax as moto_ajax } from '../../moto/ajax.js';
|
||||
import { local as dataLocal } from '../../data/local.js';
|
||||
|
||||
function parseOpt(ov) {
|
||||
let opt = {}, kv, kva;
|
||||
|
|
@ -28,7 +27,7 @@ function encodeOpt(opt) {
|
|||
}
|
||||
|
||||
function ajax(url, fn, rt, po, hd) {
|
||||
return moto.ajax.new(fn, rt).request(url, po, hd);
|
||||
return moto_ajax.new(fn, rt).request(url, po, hd);
|
||||
}
|
||||
|
||||
function o2js(o,def) {
|
||||
|
|
@ -46,7 +45,7 @@ function js2o(s,def) {
|
|||
|
||||
function ls2o(key,def) {
|
||||
// defer ref b/c it may run in a worker
|
||||
return js2o(root.data.local.getItem(key),def);
|
||||
return js2o(dataLocal.getItem(key),def);
|
||||
}
|
||||
|
||||
// split 24 bit color into [ r, g, b ]
|
||||
|
|
@ -99,11 +98,14 @@ function trackFn(fn, name) {
|
|||
}
|
||||
}
|
||||
|
||||
exports({
|
||||
function noop() {}
|
||||
|
||||
export {
|
||||
trackFn,
|
||||
areEqual,
|
||||
parseOpt,
|
||||
encodeOpt,
|
||||
noop,
|
||||
ajax,
|
||||
o2js,
|
||||
js2o,
|
||||
|
|
@ -111,6 +113,19 @@ exports({
|
|||
avgc,
|
||||
rgb,
|
||||
a2c
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
export let utils = {
|
||||
trackFn,
|
||||
areEqual,
|
||||
parseOpt,
|
||||
encodeOpt,
|
||||
noop,
|
||||
ajax,
|
||||
o2js,
|
||||
js2o,
|
||||
ls2o,
|
||||
avgc,
|
||||
rgb,
|
||||
a2c
|
||||
};
|
||||
|
|
@ -1,23 +1,11 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
// dep: geo.points
|
||||
// dep: geo.polygons
|
||||
// dep: kiri.utils
|
||||
// use: kiri.codec
|
||||
// use: mesh.util
|
||||
gapp.register("kiri.widget", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { api, driver, utils } = kiri;
|
||||
const { util, polygons } = base;
|
||||
const { Mesh, newPoint, verticesToPoints } = base;
|
||||
const { inRange, time } = util;
|
||||
const { avgc, trackFn } = utils;
|
||||
import { base } from '../../geo/base.js';
|
||||
import { avgc } from './utils.js';
|
||||
import { verticesToPoints } from '../../geo/points.js';
|
||||
import { util as mesh_util } from '../../mesh/util.js';
|
||||
|
||||
const { inRange, time } = base.util;
|
||||
const solid_opacity = 1.0;
|
||||
const groups = [];
|
||||
|
||||
|
|
@ -28,12 +16,14 @@ let nextId = 0;
|
|||
|
||||
function newWidget(id,group) { return new Widget(id,group) }
|
||||
|
||||
function catalog() { return kiri.catalog }
|
||||
function catalog() { return self.kiri_catalog };
|
||||
|
||||
function index() { return catalog().index }
|
||||
function index() { return self.kiri_catalog.index }
|
||||
|
||||
class Widget {
|
||||
constructor(id, group) {
|
||||
this.api = self.kiri_api;
|
||||
|
||||
this.id = id || Date.now().toString(36)+(nextId++);
|
||||
this.grouped = group ? true : false;
|
||||
this.group = group || [];
|
||||
|
|
@ -347,7 +337,7 @@ class Widget {
|
|||
}
|
||||
|
||||
selectFaces(faces) {
|
||||
let groups = mesh.util.facesToGroups(faces || []);
|
||||
let groups = mesh_util.facesToGroups(faces || []);
|
||||
let geo = this.mesh.geometry;
|
||||
geo.clearGroups();
|
||||
for (let group of groups) {
|
||||
|
|
@ -503,8 +493,8 @@ class Widget {
|
|||
w._move(x, y, z, abs);
|
||||
});
|
||||
// allow for use in engine / cli
|
||||
if ((x || y || z) && api && api.event) {
|
||||
api.event.emit('widget.move', {widget: this, pos: {x, y, z}, abs});
|
||||
if ((x || y || z) && this.api && this.api.event) {
|
||||
this.api.event.emit('widget.move', {widget: this, pos: {x, y, z}, abs});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -555,8 +545,8 @@ class Widget {
|
|||
w._scale(x, y, z);
|
||||
});
|
||||
this.center(false);
|
||||
if (api && api.event) {
|
||||
api.event.emit('widget.scale', {widget: this, x, y, z});
|
||||
if (this.api && this.api.event) {
|
||||
this.api.event.emit('widget.scale', {widget: this, x, y, z});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -586,8 +576,8 @@ class Widget {
|
|||
if (this.outline) {
|
||||
this.setEdges(true);
|
||||
}
|
||||
if ((x || y || z) && api && api.event) {
|
||||
api.event.emit('widget.rotate', {widget: this, x, y, z});
|
||||
if ((x || y || z) && this.api && this.api.event) {
|
||||
this.api.event.emit('widget.rotate', {widget: this, x, y, z});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -630,8 +620,8 @@ class Widget {
|
|||
w._mirror();
|
||||
});
|
||||
this.center();
|
||||
if (api && api.event) {
|
||||
api.event.emit('widget.mirror', {widget: this});
|
||||
if (this.api && this.api.event) {
|
||||
this.api.event.emit('widget.mirror', {widget: this});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -758,113 +748,21 @@ class Widget {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* processes points into facets, then into slices
|
||||
*
|
||||
* once upon a time there were multiple slicers. this was the fastest in most cases.
|
||||
* lines are added to all the buckets they cross. then buckets are processed in order.
|
||||
* buckets are contiguous ranges of z slicers. the advantage of this method is that
|
||||
* as long as a large percentage of lines do not cross large z distances, this reduces
|
||||
* the number of lines each slice has to consider thus improving speed.
|
||||
*
|
||||
* @params {Object} settings
|
||||
* @params {Function} [ondone]
|
||||
* @params {Function} [onupdate]
|
||||
*/
|
||||
slice(settings, ondone, onupdate) {
|
||||
let widget = this;
|
||||
let startTime = time();
|
||||
|
||||
widget.settings = settings;
|
||||
widget.clearSlices();
|
||||
onupdate(0.0001, "slicing");
|
||||
|
||||
if (kiri.client && !widget.inWorker) {
|
||||
// store slicing visuals
|
||||
widget.stack = kiri.stacks.create(widget.id, widget.mesh);
|
||||
// compensate for zcut (widget moved through floor)
|
||||
widget.stack.obj.view.position.z = widget.track.zcut || 0;
|
||||
// in case result of slice is nothing, do not preserve previous
|
||||
widget.slices = []
|
||||
|
||||
// executed from kiri.js
|
||||
kiri.client.slice(settings, this, function(reply) {
|
||||
if (reply.alert) {
|
||||
onupdate(null, null, reply.alert);
|
||||
}
|
||||
if (reply.update) {
|
||||
onupdate(reply.update, reply.updateStatus);
|
||||
}
|
||||
if (reply.send_start) {
|
||||
widget.xfer = {start: reply.send_start};
|
||||
}
|
||||
if (reply.stats) {
|
||||
widget.stats = reply.stats;
|
||||
}
|
||||
if (reply.send_end) {
|
||||
widget.stats.load_time = widget.xfer.start - reply.send_end;
|
||||
}
|
||||
if (reply.slice) {
|
||||
widget.slices.push(kiri.codec.decode(reply.slice, {mesh:widget.mesh}));
|
||||
}
|
||||
if (reply.done) {
|
||||
ondone(true);
|
||||
}
|
||||
if (reply.error) {
|
||||
ondone(false, reply.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (kiri.server) {
|
||||
// executed from kiri-worker.js
|
||||
let catchdone = function(error) {
|
||||
if (error) {
|
||||
return ondone(error);
|
||||
}
|
||||
|
||||
onupdate(1.0, "transfer");
|
||||
|
||||
widget.stats.slice_time = time() - startTime;
|
||||
|
||||
ondone();
|
||||
};
|
||||
|
||||
let catchupdate = function(progress, message, alert) {
|
||||
onupdate(progress, message, alert);
|
||||
};
|
||||
|
||||
let drv = driver[settings.mode.toUpperCase()];
|
||||
|
||||
if (drv) {
|
||||
let promise = drv.slice(settings, widget, catchupdate, catchdone);
|
||||
if (promise) promise.catch(error => ondone(error));
|
||||
} else {
|
||||
console.log('invalid mode: '+settings.mode);
|
||||
ondone('invalid mode: '+settings.mode);
|
||||
}
|
||||
}
|
||||
|
||||
// discard point cache
|
||||
widget.points = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* render to provided stack
|
||||
*/
|
||||
render(stack) {
|
||||
const mark = Date.now();
|
||||
if (this.slices)
|
||||
this.slices.forEach(slice => {
|
||||
for (let slice of this.slices || []) {
|
||||
if (slice.layers) {
|
||||
stack.add(slice.layers);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Date.now() - mark;
|
||||
}
|
||||
|
||||
setEdges(set) {
|
||||
if (!(api && api.conf)) {
|
||||
if (!(this.api && this.api.conf)) {
|
||||
// missing api features in engine mode
|
||||
return;
|
||||
}
|
||||
|
|
@ -878,8 +776,8 @@ class Widget {
|
|||
|
||||
}
|
||||
if (set) {
|
||||
let dark = api.space.is_dark();
|
||||
let angle = api.conf.get().controller.edgeangle || 20;
|
||||
let dark = this.api.space.is_dark();
|
||||
let angle = this.api.conf.get().controller.edgeangle || 20;
|
||||
let edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||
let material = new THREE.LineBasicMaterial({ color: 0 });
|
||||
this.outline = new THREE.LineSegments(edges, material);
|
||||
|
|
@ -889,7 +787,7 @@ class Widget {
|
|||
}
|
||||
|
||||
setWireframe(set, color, opacity) {
|
||||
if (!(api && api.conf)) {
|
||||
if (!(this.api && this.api.conf)) {
|
||||
// missing api features in engine mode
|
||||
return;
|
||||
}
|
||||
|
|
@ -901,7 +799,7 @@ class Widget {
|
|||
this.wire = null;
|
||||
}
|
||||
if (set) {
|
||||
let dark = api.space.is_dark();
|
||||
let dark = this.api.space.is_dark();
|
||||
let mat = new THREE.MeshBasicMaterial({
|
||||
wireframe: true,
|
||||
color: dark ? 0xaaaaaa : 0,
|
||||
|
|
@ -911,7 +809,7 @@ class Widget {
|
|||
let wire = widget.wire = new THREE.Mesh(mesh.geometry.shallowClone(), mat);
|
||||
mesh.add(wire);
|
||||
}
|
||||
if (api.view.is_arrange()) {
|
||||
if (this.api.view.is_arrange()) {
|
||||
this.setColor(this.color);
|
||||
} else {
|
||||
this.setColor(0x888888,undefined,false);
|
||||
|
|
@ -1066,7 +964,4 @@ Widget.deleteFromState = function(id,ondone) {
|
|||
index().remove('ws-save-'+id, ondone);
|
||||
};
|
||||
|
||||
kiri.Widget = Widget;
|
||||
kiri.newWidget = newWidget;
|
||||
|
||||
});
|
||||
export { Widget, newWidget };
|
||||
|
|
@ -1,14 +1,9 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: load.file
|
||||
// use: kiri.selection
|
||||
// use: kiri.platform
|
||||
gapp.register("kiri.widgets", (root, exports) => {
|
||||
|
||||
const { data, kiri, moto, noop } = root;
|
||||
const { api, consts, utils, newWidget, Widget } = kiri;
|
||||
import { $ } from '../../moto/webui.js';
|
||||
import { api } from './api.js';
|
||||
import { load } from '../../load/file.js';
|
||||
import { Widget, newWidget } from './widget.js';
|
||||
|
||||
let WIDGETS = [];
|
||||
|
||||
|
|
@ -36,7 +31,7 @@ function rename(sel) {
|
|||
return;
|
||||
}
|
||||
let widget = widgets[0];
|
||||
kiri.ui.prompt("new widget name", widget.meta.file || "no name").then(newname => {
|
||||
api.uc.prompt("new widget name", widget.meta.file || "no name").then(newname => {
|
||||
if (newname) {
|
||||
widget.meta.file = newname;
|
||||
api.platform.changed();
|
||||
|
|
@ -89,7 +84,7 @@ function meshes() {
|
|||
|
||||
function opacity(value) {
|
||||
api.widgets.each(w => w.setOpacity(value));
|
||||
moto.space.update();
|
||||
api.space.update();
|
||||
}
|
||||
|
||||
function setIndexed(value) {
|
||||
|
|
@ -101,7 +96,7 @@ function setAxisIndex(value) {
|
|||
}
|
||||
|
||||
// extend API (api.widgets)
|
||||
const widgets = Object.assign(api.widgets, {
|
||||
export const widgets = {
|
||||
load: Widget.loadFromCatalog,
|
||||
new: newWidget,
|
||||
map,
|
||||
|
|
@ -120,6 +115,4 @@ const widgets = Object.assign(api.widgets, {
|
|||
each(fn) { WIDGETS.slice().forEach(widget => fn(widget)) },
|
||||
for(fn) { widgets.each(fn) },
|
||||
forid(id) { return WIDGETS.filter(w => w.id === id)[0] }
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue