move src2 -> src, web2 -> web, remove old module system, fix deps

This commit is contained in:
Stewart Allen 2025-07-06 23:44:33 -04:00
commit ce4f5d6e78
488 changed files with 26179 additions and 26609 deletions

486
app.js
View file

@ -12,7 +12,7 @@ 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');
@ -58,25 +58,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;
@ -103,173 +84,7 @@ 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]});
}
generateDevices();
mod.on.test((req) => {
let cookie = cookieValue(req.headers.cookie, "version") || undefined;
@ -295,10 +110,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,29 +120,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");
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");
mod.static("/v2/", "web2");
function load_modules(root, force) {
// load modules
lastmod(`${dir}/${root}`) && fs.readdirSync(`${dir}/${root}`).forEach(mdir => {
@ -353,10 +149,10 @@ function init(mod) {
}
// load development and 3rd party modules
load_modules('mod');
// load_modules('mod');
// load optional local modules
load_modules('mods');
// load_modules('mods');
// run loads injected by modules
while (load.length) {
@ -366,9 +162,6 @@ function init(mod) {
logger.log({on_load_fail: e});
}
}
// runs after module loads / injects
prepareScripts();
};
// either add module assets to path or require(init.js)
@ -391,7 +184,6 @@ function initModule(mod, file, dir) {
// express functions added here show up at "/api/" url root
api: api,
adm: {
reload: prepareScripts,
setver: (ver) => { oversion = ver },
crossOrigin: (bool) => { crossOrigin = bool }
},
@ -446,19 +238,6 @@ function initModule(mod, file, dir) {
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 }
@ -482,51 +261,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 = {};
@ -564,28 +298,30 @@ function handleSetup(req, res, next) {
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;
if (path === '/v2/lib/mesh/work.js') {
req.url = req.app.path = '/v2/lib/pack/mesh-work.js';
} else if (path === '/v2/lib/main/mesh.js') {
req.url = req.app.path = '/v2/lib/pack/mesh-main.js';
} else if (path === '/v2/lib/kiri-run/minion.js') {
req.url = req.app.path = '/v2/lib/pack/kiri-pool.js';
} else if (path === '/v2/lib/kiri-run/worker.js') {
req.url = req.app.path = '/v2/lib/pack/kiri-work.js';
} else if (path === '/v2/lib/main/kiri.js') {
req.url = req.app.path = '/v2/lib/pack/kiri-main.js';
if (path === '/lib/mesh/work.js') {
req.url = req.app.path = '/lib/pack/mesh-work.js';
} else if (path === '/lib/main/mesh.js') {
req.url = req.app.path = '/lib/pack/mesh-main.js';
} else if (path === '/lib/kiri-run/minion.js') {
req.url = req.app.path = '/lib/pack/kiri-pool.js';
} else if (path === '/lib/kiri-run/worker.js') {
req.url = req.app.path = '/lib/pack/kiri-work.js';
} else if (path === '/lib/main/kiri.js') {
req.url = req.app.path = '/lib/pack/kiri-main.js';
}
// add cors headers on rewrite
if (path !== req.url) {
console.log('rewrite', path, req.url);
addCorsHeaders(req, res);
// console.log('rewrite', path, req.url);
}
next();
} else {
@ -631,58 +367,6 @@ 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");
@ -698,128 +382,7 @@ function generateDevices() {
});
let dstr = JSON.stringify(devs);
synth.devices = `self.devices = ${dstr};`;
fs.writeFileSync(PATH.join(dir,"src2","pack","devices.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;
fs.writeFileSync(PATH.join(dir,"src","pack","devices.js"), `export const devices = ${dstr};`);
}
function minify(path) {
@ -942,10 +505,11 @@ function cookieValue(cookie,key) {
function rewriteHtmlVersion(req, res, next) {
if ([
"/v2/kiri/",
"/v2/lib/kiri-run/minion.js",
"/v2/lib/kiri-run/worker.js",
"/v2/lib/kiri-run/minion.js"
"/kiri/",
"/mesh/",
"/lib/mesh/work.js",
"/lib/kiri-run/worker.js",
"/lib/kiri-run/minion.js"
].indexOf(req.app.path) >= 0) {
addCorsHeaders(req, res);
} else if ([

View file

@ -78,8 +78,7 @@
"webpack-cli": "^5.1.4"
},
"scripts": {
"setup": "npm i && cd mods && npm i",
"setup2": "npm run setup && npm run dev -- --dryrun && npm run preinstall2",
"setup": "npm i && npm run dev -- --dryrun && npx webpack --config src/webpack/webpack-three-esm.js && node src/webpack/esbuild.config.mjs",
"dev": "gs-app-server --debug",
"prod": "gs-app-server",
"prod-dryrun": "gs-app-server --dryrun",
@ -98,14 +97,12 @@
"build-mac-intel": "npm run build -- --mac --x64",
"mesh-build": "webpack --config webpack.config.js",
"mesh-build-dev": "webpack --config webpack.dev.config.js",
"serve-mesh": "npx serve dist/v2 -p 8181",
"mklinks": "find src web -type l | xargs -I{} sh -c 'echo \"{},$(readlink {})\"' > 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",
"postbuild": "node bin/electron-post.js",
"preinstall": "node bin/install-pre.js && npx webpack --config bin/webpack-three.js",
"preinstall2": "npx webpack --config src2/webpack/webpack-three-esm.js && node src2/webpack/esbuild.config.mjs",
"preinstall": "",
"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",

View file

@ -1,7 +1,11 @@
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
"use strict";
// extend Array, String, Number, Math
const AP = {};
gapp.register("add.array", [], (root, exports) => {
let AP = {};
if (!AP.flat) {
AP.flat = function() {
@ -212,11 +216,12 @@ Array.handle = function(fn) {
}
};
// Apply all methods to Array.prototype
Object.assign(Array.prototype, AP);
// Export for ES modules
export { AP as arrayPrototype };
for (let i in AP) {
Object.defineProperty(Array.prototype, i, {
value: AP[i],
enumerable: false
});
}
Float32Array.prototype.toShared = function() {
const newvert = new Float32Array(new SharedArrayBuffer(this.buffer.byteLength));
@ -260,3 +265,5 @@ Number.prototype.round = function(digits) {
const pow = Math.pow(10, digits || 3);
return Math.round(this.valueOf() * pow) / pow;
};
});

View file

@ -1,10 +1,11 @@
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
// use: add.array
import { arrayPrototype } from './array.js';
"use strict";
// Make classes available globally
class ArrayWriter {
// use: add.array
gapp.register("add.class", [], (root, exports) => {
self.ArrayWriter = class ArrayWriter {
constructor() {
this.pos = 0;
this.array = [];
@ -47,7 +48,7 @@ class ArrayWriter {
}
};
class DataWriter {
self.DataWriter = class DataWriter {
constructor(view, pos) {
this.view = view;
this.pos = pos || 0;
@ -105,7 +106,7 @@ class DataWriter {
}
}
class DataReader {
self.DataReader = class DataReader {
constructor(view, pos) {
this.view = view;
this.pos = pos || 0;
@ -170,9 +171,4 @@ class DataReader {
}
}
self.ArrayWriter = ArrayWriter;
self.DataWriter = DataWriter;
self.DataReader = DataReader;
// Export for ES modules
export { ArrayWriter, DataWriter, DataReader };
});

View file

@ -1,13 +1,16 @@
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
"use strict";
// dep: ext.three
import {
gapp.register("add.three", [], (root, exports) => {
const {
THREE, SVGLoader, BufferGeometryUtils,
LineMaterial, Line2, LineGeometry, LineSegments2,
LineSegmentsGeometry, MeshBVHLib } from '../ext/three.js';
// Make THREE available globally
self.THREE = THREE;
LineMaterial, Line2, LineGeometry, LineSegments2, LineSegmentsGeometry
} = ThreeBundle;
root.THREE = THREE;
THREE.BufferGeometryUtils = BufferGeometryUtils;
THREE.SVGLoader = SVGLoader.SVGLoader;
THREE.LineMaterial = LineMaterial;
@ -144,5 +147,4 @@ THREE.Object3D.prototype.removeAll = function() {
this.children = [];
};
// Export for ES modules
export { THREE };
});

View file

@ -1,14 +1,19 @@
/** Copyright Stewart Allen -- All Rights Reserved */
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
"use strict";
export const data = {
Index: IDBStore,
open() {
return new IDBStore(...arguments);
}
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
let IDB, IRR, local = null;
try {
@ -314,5 +319,4 @@ SP.clear = function(key, store = this.current) {
.objectStore(store).clear();
};
export const open = data.open;
export const Index = IDBStore;
});

47
src.old/data/local.js Normal file
View file

@ -0,0 +1,47 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
gapp.register("data.local", [], (root, exports) => {
const { data } = root;
function Local() {
this.__data__ = {};
this.__mem__ = true;
}
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__ = {};
};
try {
// deprecate 'Local' at some point
let local = data.local =self.localStorage;
let testkey = '__test';
local.setItem(testkey, 1);
local.getItem(testkey);
local.removeItem(testkey);
} catch (e) {
data.local = new Local();
let msg = "localStorage disabled: application may not function properly";
console.log(msg);
// alert(msg);
}
});

View file

@ -1,6 +1,9 @@
// SOURCE: https://stackoverflow.com/questions/1655769/fastest-md5-implementation-in-javascript
gapp.register("ext.md5", [], (root, exports) => {
export function hash(e) {
exports({ hash });
function hash(e) {
function h(a, b) {
var c, d, e, f, g;
e = a & 2147483648;
@ -64,4 +67,6 @@ export 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()
}
};
});

View file

@ -1,8 +1,12 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { newPoint } from './point.js';
import earcut from '../ext/earcut.js';
"use strict";
// use: add.array
// use: add.class
gapp.register("geo.base", [], (root, exports) => {
const base = root.base = {};
const round_decimal_precision = 5;
function time() {
@ -215,7 +219,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 = newPoint(
let ip = base.newPoint(
p1x + (a * d1x), // x
p1y + (a * d1y), // y
p3.z || p4.z, // z
@ -260,7 +264,7 @@ function intersectRayLine(ro, s1, p1, p2, infinite) {
b = n2 / d;
if (infinite || (inCloseRange(b, 0, 1) && a >= 0)) {
let ip = newPoint(
let ip = base.newPoint(
p1x + (a * s1x),
p1y + (a * s1y),
p2.z || ro.z,
@ -524,7 +528,7 @@ function comma(v) {
* Connect to base
******************************************************************* */
export const key = {
base.key = {
NONE: "",
PROJECT: "project",
SEGINT: "segint",
@ -532,7 +536,7 @@ export const key = {
PARALLEL: "parallel"
};
export const config = {
base.config = {
// size of gcode debug arrow head
debug_arrow: 0.25,
// default # of decimal places in generated gcode
@ -598,7 +602,7 @@ export const config = {
clipperClean: 250
};
export const util = {
base.util = {
sqr,
lerp,
time,
@ -635,10 +639,4 @@ export const util = {
zInPlane
};
export const base = {
config,
key,
util
};
export { earcut };
});

View file

@ -1,9 +1,14 @@
/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */
import { config, util } from './base.js';
import { newPoint } from './point.js';
"use strict";
export class Bounds {
// dep: geo.base
gapp.register("geo.bounds", [], (root, exports) => {
const { base } = root;
const { config, util } = base;
class Bounds {
constructor() {
this.minx = 10e7;
this.miny = 10e7;
@ -116,7 +121,7 @@ export class Bounds {
}
center(z = 0) {
return newPoint(this.centerx(), this.centery(), z);
return base.newPoint(this.centerx(), this.centery(), z);
}
centerx() {
@ -128,6 +133,9 @@ export class Bounds {
}
}
export function newBounds() {
return new Bounds();
}
gapp.overlay(base, {
Bounds,
newBounds() { return new Bounds() }
});
});

View file

@ -2,17 +2,24 @@
"use strict";
import { THREE } from '../ext/three.js';
import manifold from '../ext/manifold.js';
// dep: geo.base
// mod: ext.manifold
gapp.register("geo.csg", [], (root, exports) => {
const { base, ext } = root;
const debug = true;
const precision = 0.001;
const factor = 1/precision;
function log() {
console.log(...arguments);
if (root?.mesh?.log) {
root.mesh.log(...arguments)
} else if (root.debug === true) {
console.log(...arguments);
}
}
export const CSG = {
const CSG = {
// accepts 2 or more arguments with threejs vertex position arrays
union() {
return CSG.moduleOp('manifold.union', 'union', ...arguments);
@ -116,10 +123,23 @@ function indexVertices(pos) {
}
let Instance;
manifold({
locateFile() { return "/wasm/manifold.wasm" }
}).then(inst => {
ext.manifold.then(mod => {
mod.default({
locateFile(a,b,c) {
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
});
});

View file

@ -1,5 +1,11 @@
/** 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 = {};
@ -11,7 +17,7 @@ let lastSlice = 0;
* @param off {number} z offset value from 0-1
* @param res {number} resolution (pixels/slices per side)
*/
export function slice(off, res, val) {
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();
@ -164,7 +170,7 @@ export function slice(off, res, val) {
}
// merge co-linear and distance threshold
export function filter(poly, inc) {
function filter(poly, inc) {
if (poly.length <= 2) {
return poly;
}
@ -194,16 +200,21 @@ export function filter(poly, inc) {
}
e1 = el;
}
nupoly.push(poly[poly.length-1]);
return nupoly;
}
export function distTo(a, b, dir) {
function distTo(a, b, dir) {
let dx = a.x - b.x;
let dy = a.y - b.y;
if (dir === 'lr') {
return Math.abs(dx);
} else if (dir === 'td') {
return Math.abs(dy);
}
// bias distance by prevailing direction of discovery to join stragglers
if (dir === 'lr') dx = dx / 2;
if (dir === 'td') dy = dy / 2;
return Math.sqrt(dx * dx + dy * dy);
}
gapp.overlay(base, {
gyroid: { slice }
})
});

View file

@ -1,6 +1,13 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
export class Line {
"use strict";
// dep: geo.base
gapp.register("geo.line", [], (root, exports) => {
const { base } = root;
class Line {
constructor(p1, p2, key) {
if (!key) key = [p1.key, p2.key].join(';');
this.p1 = p1;
@ -47,10 +54,18 @@ export class Line {
}
}
export function newLine(p1, p2, key) {
function newLine(p1, p2, key) {
return new Line(p1, p2, key);
}
export function newOrderedLine(p1, p2, key) {
function newOrderedLine(p1, p2, key) {
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
}
gapp.overlay(base, {
Line,
newLine,
newOrderedLine
});
});

View file

@ -1,7 +1,19 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { base } from './base.js';
import { newPoint } from './point.js';
"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;
/**
* emit each element in an array based on
@ -9,21 +21,21 @@ import { newPoint } from './point.js';
* elements with { first, last } points and
* may be open polys, unlike poly2polyEmit
*/
export function tip2tipEmit(array, startPoint, emitter) {
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;
}
});
@ -44,7 +56,7 @@ export function tip2tipEmit(array, startPoint, emitter) {
* to be more like outputOrderClosest() and have the option to account for
* depth in determining distance
*/
export function poly2polyEmit(array, startPoint, emitter, opt = {}) {
function poly2polyEmit(array, startPoint, emitter, opt = {}) {
let marker = opt.mark || 'delete';
let mindist, dist, found, count = 0;
for (;;) {
@ -62,21 +74,21 @@ export 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;
}
});
@ -90,23 +102,23 @@ export 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;
}
export function calc_normal(p1, p2) {
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 });
}
export function end_vertex(n1, n2, off, start) {
function end_vertex(n1, n2, off, start) {
let dx, dy;
if (start) {
dx = n2.dx * off;
@ -118,7 +130,7 @@ export function end_vertex(n1, n2, off, start) {
return { dx, dy, vp: n1.p2 };
}
export function calc_vertex(n1, n2, off, vp) {
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;
@ -136,7 +148,7 @@ export function calc_vertex(n1, n2, off, vp) {
return { dx, dy, vp: vp || n1.p2, io, vl };
}
export function v2pl(rec) {
function v2pl(rec) {
let p = rec.vp.clone();
p.x += rec.dx;
p.y += rec.dy;
@ -144,7 +156,7 @@ export function v2pl(rec) {
return p;
}
export function v2pr(rec) {
function v2pr(rec) {
let p = rec.vp.clone();
p.x -= rec.dx;
p.y -= rec.dy;
@ -152,17 +164,17 @@ export function v2pr(rec) {
return p;
}
export function pointsToPath(points, offset, open, miter = 1.5) {
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);
@ -179,7 +191,7 @@ export 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;
@ -192,10 +204,10 @@ export 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;
@ -217,7 +229,7 @@ export 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);
@ -324,7 +336,7 @@ export function pointsToPath(points, offset, open, miter = 1.5) {
return { left, right, faces, normals, open };
}
export function pathTo3D(path, height, z) {
function pathTo3D(path, height, z) {
const { faces, normals, left, right, open } = path;
const out = [];
const nrm = [];
@ -344,14 +356,14 @@ export 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);
@ -360,15 +372,15 @@ export 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);
@ -376,12 +388,12 @@ export 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
@ -394,12 +406,12 @@ export 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();
@ -410,11 +422,11 @@ export 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 };
@ -422,7 +434,7 @@ export function pathTo3D(path, height, z) {
// produces indexed geometry which isn't ideal for rendering because
// the default threejs generated vertex normals aren't accurate
export function shapeToPath(shape, points, closed) {
function shapeToPath(shape, points, closed) {
closed = closed !== undefined ? closed : true;
const profileGeometry = new THREE.ShapeGeometry(shape);
@ -439,9 +451,9 @@ export 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);
@ -476,7 +488,7 @@ export 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++) {
@ -512,7 +524,7 @@ export function shapeToPath(shape, points, closed) {
index.push(p8, p7, p5);
}
return { index, faces };
return {index, faces};
}
/**
@ -527,27 +539,31 @@ export function shapeToPath(shape, points, closed) {
*
* @return {Array<Point>} an array of points representing the arc.
*/
export function arcToPath(start, end, arcdivs = 24, opts) {
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 (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);
@ -556,7 +572,7 @@ export 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
@ -569,10 +585,10 @@ export function arcToPath(start, end, arcdivs = 24, opts) {
let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI;
let ad = base.util.thetaDiff(a1, a2, clockwise); // angle difference in radians
let samePoint = Math.abs(ad) < 0.001
let ofFull = Math.abs(ad) / (2 * Math.PI);
let steps = samePoint ? arcdivs : Math.max(Math.floor(arcdivs * ofFull), 4);
let step = (samePoint ? (Math.PI * 2) : ad) / steps;
let numPoints = steps / step;
let ofFull = Math.abs(ad)/(2*Math.PI);
let steps = samePoint? arcdivs : Math.max(Math.floor( arcdivs * ofFull),4);
let step = (samePoint? (Math.PI*2) : ad) / steps;
let numPoints = steps/ step;
let zStart = start.z;
let zStep = dz / numPoints;
let rot = a1 + step;
@ -591,9 +607,9 @@ export function arcToPath(start, end, arcdivs = 24, opts) {
// }
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,
@ -607,7 +623,7 @@ export function arcToPath(start, end, arcdivs = 24, opts) {
return arr
}
export class FloatPacker {
class FloatPacker {
constructor(size, factor) {
this.size = size;
this.factor = Math.min(factor || 1.2, 1.1);
@ -626,7 +642,7 @@ export 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];
}
}
@ -640,11 +656,15 @@ export class FloatPacker {
}
}
export const paths = {
tip2tipEmit,
base.paths = {
poly2polyEmit,
tip2tipEmit,
shapeToPath,
pointsToPath,
pathTo3D,
FloatPacker
}
arcToPath,
vertexNormal: calc_vertex,
segmentNormal: calc_normal
};
});

View file

@ -1,10 +1,13 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { util, config, key } from './base.js';
import { newLine } from './line.js';
import { newSlope } from './slope.js';
"use strict";
const { Vector3 } = THREE;
// dep: geo.base
gapp.register("geo.point", [], (root, exports) => {
const { base } = root;
const { util, config, key } = base;
const { round } = util;
class Point {
constructor(x = 0, y = 0, z = 0, key) {
@ -39,7 +42,7 @@ class Point {
}
toVector3() {
return new Vector3(this.x, this.y, this.z);
return new THREE.Vector3(this.x, this.y, this.z);
}
set(x, y, z) {
@ -123,11 +126,11 @@ class Point {
}
slopeTo(p) {
return newSlope(this, p);
return base.newSlope(this, p);
}
lineTo(p, k) {
return newLine(this, p, k);
return base.newLine(this, p, k);
}
isNear(p, dist) {
@ -293,7 +296,7 @@ class Point {
np2 = newPoint(p2.x - oy, p2.y + ox, p2.z, key.NONE);
np1.op = p1;
np2.op = p2;
return newLine(np1, np2, key.NONE);
return base.newLine(np1, np2, key.NONE);
}
offset(x, y, z) {
@ -649,8 +652,10 @@ function pointFromClipper(cp, z) {
return newPoint(cp.X / config.clipper, cp.Y / config.clipper, z);
}
export {
gapp.overlay(base, {
Point,
newPoint,
pointFromClipper
};
});
});

View file

@ -2,14 +2,23 @@
"use strict";
import { config } from './base.js';
import { newPoint } from '../geo/point.js';
// dep: geo.base
// dep: geo.point
gapp.register("geo.points", [], (root, exports) => {
const { base } = root;
const { config } = base;
gapp.overlay(base, {
verticesToPoints,
pointsToVertices
});
/**
* converts a geometry point array into a kiri point array
* with optional decimation
*/
export function verticesToPoints(array, options) {
function verticesToPoints(array, options) {
let parr = new Array(array.length / 3),
i = 0,
j = 0,
@ -32,7 +41,7 @@ export function verticesToPoints(array, options) {
// replace point objects with their equivalents
if (maxpass) {
while (i < array.length) {
let p = newPoint(array[i++], array[i++], array[i++]),
let p = base.newPoint(array[i++], array[i++], array[i++]),
k = p.key,
m = hash[k];
if (!m) {
@ -44,7 +53,7 @@ export function verticesToPoints(array, options) {
}
} else {
while (i < array.length) {
parr[j++] = newPoint(array[i++], array[i++], array[i++]);
parr[j++] = base.newPoint(array[i++], array[i++], array[i++]);
}
}
@ -115,7 +124,7 @@ export function verticesToPoints(array, options) {
return parr;
}
export function pointsToVertices(points) {
function pointsToVertices(points) {
let vertices = new Float32Array(points.length * 3),
i = 0, vi = 0;
while (i < points.length) {
@ -125,3 +134,5 @@ export function pointsToVertices(points) {
}
return vertices;
}
});

View file

@ -2,17 +2,22 @@
"use strict";
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';
// 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) => {
const { Vector3 } = THREE;
const { base } = root;
const { config, util, polygons, newBounds, newPoint } = base;
let XAXIS = new Vector3(1,0,0),
const POLY = polygons,
XAXIS = new THREE.Vector3(1,0,0),
DEG2RAD = Math.PI / 180,
ClipperLib = self.ClipperLib,
Clipper = ClipperLib.Clipper,
ClipType = ClipperLib.ClipType,
PolyType = ClipperLib.PolyType,
@ -34,7 +39,7 @@ let XAXIS = new Vector3(1,0,0),
let seqid = Math.round(Math.random() * 0xffffffff);
export class Polygon {
class Polygon {
constructor(points) {
this.id = seqid++; // polygon unique id
this.open = false;
@ -71,11 +76,11 @@ export class Polygon {
}
toPath2D(offset) {
return paths.pointsToPath(this.points, offset, this.open);
return base.paths.pointsToPath(this.points, offset, this.open);
}
toPath3D(offset, height, z) {
return paths.pathTo3D(this.toPath2D(offset), height, z);
return base.paths.pathTo3D(this.toPath2D(offset), height, z);
}
toString(verbose) {
@ -230,7 +235,7 @@ export class Polygon {
}
// perform earcut()
let cut = earcut(out, holes, 3);
let cut = self.earcut(out, holes, 3);
let ret = [];
// preserve swaps in new polys
@ -462,7 +467,7 @@ export class Polygon {
return polys
.filter(poly => poly.length > 1)
.map(poly => {
let np = newPolygon().setOpen();
let np = base.newPolygon().setOpen();
for (let p of poly) {
np.push(p);
}
@ -1138,7 +1143,7 @@ export class Polygon {
applyRotations() {
for (let point of this.points) {
if (point.a) {
let p2 = new Vector3(point.x, point.y, point.z)
let p2 = new THREE.Vector3(point.x, point.y, point.z)
.applyAxisAngle(XAXIS, point.a * DEG2RAD);
point.x = p2.x;
point.y = p2.y;
@ -1200,76 +1205,15 @@ export class Polygon {
return false;
}
/**
* 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
* 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
*/
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) {
forEachPoint(fn, close, start) {
let index = start || 0,
points = this.points,
length = points.length,
@ -2059,7 +2003,7 @@ export class Polygon {
return res.map(array => {
let poly = newPolygon();
for (let pt of array) {
poly.push(pointFromClipper(pt, z));
poly.push(base.pointFromClipper(pt, z));
}
return poly;
});
@ -2374,7 +2318,8 @@ export class Polygon {
}
export function slopeDiff(s1, s2) {
// use Slope.angleDiff() then re-test path mitering / rendering
function slopeDiff(s1, s2) {
const n1 = s1.angle;
const n2 = s2.angle;
let diff = n2 - n1;
@ -2383,17 +2328,28 @@ export function slopeDiff(s1, s2) {
return Math.abs(diff);
}
export function fromClipperPath(path, z) {
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(pointFromClipper(path[i++], z));
poly.push(base.pointFromClipper(path[i++], z));
}
return poly;
}
export function newPolygon(points) {
function newPolygon(points) {
return new Polygon(points);
}
Polygon.fromArray = function(array) {
return newPolygon().fromArray(array);
};
gapp.overlay(base, {
Polygon,
newPolygon
});
});

View file

@ -2,45 +2,45 @@
"use strict";
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';
// dep: geo.base
// dep: geo.point
// use: ext.clip2
// use: geo.slope
// use: geo.polygon
gapp.register("geo.polygons", [], (root, exports) => {
const geo = base;
const { numOrDefault } = util;
const { base } = root;
const { util, paths, config, newPoint } = base;
const { sqr, numOrDefault } = util;
const DEG2RAD = Math.PI / 180,
SQRT = Math.sqrt,
SQR = util.sqr,
ABS = Math.abs;
const {
Clipper,
ClipType,
PolyType,
PolyFillType,
EndType,
JoinType,
PolyTree,
ClipperOffset
} = ClipperLib;
const CleanPolygon = Clipper.CleanPolygon,
const ClipperLib = self.ClipperLib,
Clipper = ClipperLib.Clipper,
ClipType = ClipperLib.ClipType,
PolyType = ClipperLib.PolyType,
PolyFillType = ClipperLib.PolyFillType,
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;
ClipIntersect = ClipType.ctIntersection,
ClipperOffset = ClipperLib.ClipperOffset
;
const POLYS = {
const POLYS = base.polygons = {
clearInner,
rayIntersect,
alignWindings,
@ -73,16 +73,14 @@ const POLYS = {
fingerprint
};
export { POLYS };
export function outer(polys) {
function outer(polys) {
for (let p of polys) {
p.inner = undefined;
}
return polys;
}
export function inner(polys) {
function inner(polys) {
const ret = [];
for (let p of polys) {
if (p.inner) {
@ -92,7 +90,7 @@ export function inner(polys) {
return ret;
}
export function length(polys) {
function length(polys) {
let length = 0;
for (let p of polys) {
length += p.deepLength;
@ -100,20 +98,20 @@ export function length(polys) {
return length;
}
export function setZ(polys, z) {
function setZ(polys, z) {
for (let poly of polys) {
poly.setZ(z);
}
return polys;
}
export function clearInner(polys) {
function clearInner(polys) {
for (let p of polys) {
p.clearInner();
}
}
export function toClipper(polys = []) {
function toClipper(polys = []) {
let out = [];
for (let poly of polys) {
poly.toClipper(out);
@ -121,16 +119,16 @@ export function toClipper(polys = []) {
return out;
}
export function fromClipperNode(tnode, z) {
let poly = newPolygon();
function fromClipperNode(tnode, z) {
let poly = base.newPolygon();
for (let point of tnode.m_polygon) {
poly.push(pointFromClipper(point, z));
poly.push(base.pointFromClipper(point, z));
}
poly.open = tnode.IsOpen;
return poly;
}
};
export function fromClipperTree(tnode, z, tops, parent, minarea) {
function fromClipperTree(tnode, z, tops, parent, minarea) {
let poly,
polys = tops || [],
min = numOrDefault(minarea, 0.1);
@ -152,9 +150,9 @@ export function fromClipperTree(tnode, z, tops, parent, minarea) {
}
return polys;
}
};
export function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
let polys = tops || [], poly;
for (let child of tnode.m_Childs) {
@ -173,9 +171,9 @@ export function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
}
return polys;
}
};
export function cleanClipperTree(tree) {
function cleanClipperTree(tree) {
if (tree.m_Childs)
for (let child of tree.m_Childs) {
child.m_polygon = CleanPolygon(child.m_polygon, config.clipperClean);
@ -183,9 +181,9 @@ export function cleanClipperTree(tree) {
}
return tree;
}
};
export function filter(array, output, fn) {
function filter(array, output, fn) {
for (let poly of array) {
poly = fn(poly);
if (poly) {
@ -199,14 +197,14 @@ export function filter(array, output, fn) {
return output;
}
export function points(polys) {
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
*/
export function renest(polygons, deep) {
function renest(polygons, deep) {
return nest(flatten(polygons, [], true), deep);
}
@ -222,7 +220,7 @@ export function renest(polygons, deep) {
* @param {boolean} opentop prevent open polygons from having inners
* @returns {Polygon[]} top level parent polygons
*/
export function nest(polygons, deep, opentop) {
function nest(polygons, deep, opentop) {
if (!polygons) {
return polygons;
}
@ -286,7 +284,7 @@ export function nest(polygons, deep, opentop) {
* @param {boolean} CW
* @param {boolean} [recurse]
*/
export function setWinding(array, CW, recurse) {
function setWinding(array, CW, recurse) {
if (!array) return;
let poly, i = 0;
while (i < array.length) {
@ -304,7 +302,7 @@ export function setWinding(array, CW, recurse) {
* @param {Polygon[]} polys
* @return {boolean} true if aligned clockwise
*/
export function alignWindings(polys) {
function alignWindings(polys) {
let len = polys.length,
fwd = 0,
pts = 0,
@ -325,14 +323,21 @@ export function alignWindings(polys) {
return setCW;
}
export function setContains(setA, poly) {
function setContains(setA, poly) {
for (let i=0; i<setA.length; i++) {
if (setA[i].contains(poly)) return true;
}
return false;
}
export function flatten(polys, to, crush) {
/**
* 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) {
to = to || [];
for (let poly of polys) {
poly.flattenTo(to);
@ -353,7 +358,7 @@ export function flatten(polys, to, crush) {
* @param {number} [minArea]
* @returns {Polygon[]} out
*/
export function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
let min = numOrDefault(minArea, 0.1),
out = [];
@ -435,63 +440,63 @@ export function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
* @param {Polygon[]} polys
* @returns {Polygon[]}
*/
export function union(polys, minarea, all, opt = {}) {
if (polys.length < 2) return polys;
let lpre = length(polys);
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[]}
*/
export function diff(setA, setB, z) {
function diff(setA, setB, z) {
let clip = new Clipper(),
tree = new PolyTree(),
sp1 = toClipper(setA),
@ -511,7 +516,7 @@ export function diff(setA, setB, z) {
* @param {Polygon} poly clipping mask
* @returns {?Polygon[]}
*/
export function xor(set, z) {
function xor(set, z) {
z = z || set[0].getZ();
outer: for (;;) {
// sort largest to smallest area
@ -554,7 +559,7 @@ export function xor(set, z) {
* @param {Polygon[]} setB mask set
* @returns {Polygon[]}
*/
export function trimTo(setA, setB) {
function trimTo(setA, setB) {
// handle null/empty slices
if (setA === setB || setA === null || setB === null) return null;
@ -568,7 +573,7 @@ export function trimTo(setA, setB) {
return out;
}
export function sumCirc(polys) {
function sumCirc(polys) {
let sum = 0.0;
polys.forEach(function(poly) {
sum += poly.circularityDeep();
@ -586,7 +591,7 @@ export function sumCirc(polys) {
* @param {Function} [collector] receives output of each pass
* @returns {Polygon[]} last offset
*/
export function expand(polys, distance, z, out, count, distance2, collector, min) {
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
});
@ -597,11 +602,11 @@ export 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.
*/
export function offset(polys, dist, opts = {}) {
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 => newPolygon().setOpen().addPoints(p.right));
open = open.map(p => base.newPolygon().setOpen().addPoints(p.right));
}
// do not use clipper to offset open lines
@ -701,7 +706,7 @@ export function offset(polys, dist, opts = {}) {
* as performing subtractive analysis between initial layer shell (ref)
* and last offset (cmp) to produce gap candidates (for thinfill)
*/
export function inset(polys, dist, count, z, wasm) {
function inset(polys, dist, count, z, wasm) {
let total = count;
let layers = [];
let ref = polys;
@ -744,7 +749,7 @@ export function inset(polys, dist, count, z, wasm) {
* @param {number} [maxLen]
* @returns {Point[]} supplied output or new array
*/
export function fillArea(polys, angle, spacing, output, minLen, maxLen) {
function fillArea(polys, angle, spacing, output, minLen, maxLen) {
if (polys.length === 0) return;
let i = 1,
@ -766,7 +771,7 @@ export function fillArea(polys, angle, spacing, output, minLen, maxLen) {
while (angle > 90) angle -= 180;
// X,Y ray slope derived from angle
raySlope = newSlope(0,0,
raySlope = base.newSlope(0,0,
Math.cos(angle * DEG2RAD) * spacing,
Math.sin(angle * DEG2RAD) * spacing
);
@ -838,8 +843,8 @@ export function fillArea(polys, angle, spacing, output, minLen, maxLen) {
if (minlen && plen < minlen) continue;
if (maxlen && plen > maxlen) continue;
}
let p1 = pointFromClipper(poly.m_polygon[0], zpos);
let p2 = pointFromClipper(poly.m_polygon[1], zpos);
let p1 = base.pointFromClipper(poly.m_polygon[0], zpos);
let p2 = base.pointFromClipper(poly.m_polygon[1], zpos);
let od = rayint.origin.distToLineNew(p1,p2) / spacing;
lines.push([p1, p2, od]);
}
@ -870,7 +875,7 @@ export function fillArea(polys, angle, spacing, output, minLen, maxLen) {
* @param {boolean} [for_fill]
* @returns {Point[]}
*/
export function rayIntersect(start, slope, polygons, for_fill) {
function rayIntersect(start, slope, polygons, for_fill) {
let i = 0,
flat = [],
points = [],
@ -999,11 +1004,11 @@ export function rayIntersect(start, slope, polygons, for_fill) {
return points;
}
export function pd(a,b) {
function pd(a,b) {
return a > b ? Math.abs(1-b/a) : Math.abs(1-a/b);
}
export function fingerprint(polys) {
function fingerprint(polys) {
let recs = flatten(polys).map(p => {
return {
l: p.length,
@ -1032,7 +1037,7 @@ export function fingerprint(polys) {
}
// compare fingerprint arrays
export function fingerprintCompare(a, b) {
function fingerprintCompare(a, b) {
// true if array is the same object
if (a === b) {
return true;
@ -1074,7 +1079,7 @@ export function fingerprintCompare(a, b) {
// plan a route through an array of polygon center points
// starting with the polygon center closest to "start"
export function route(polys, start) {
function route(polys, start) {
let centers = [];
let first, minDist = Infinity;
for (let poly of polys) {
@ -1111,4 +1116,4 @@ export function route(polys, start) {
return routed.map(r => r.poly);
}
export const polygons = POLYS;
});

View file

@ -1,16 +1,23 @@
/** 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) => {
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';
const { base } = root;
const { config, util, polygons } = base
const { newOrderedLine, newPolygon, newPoint } = base;
const POLY = base.polygons;
function dval(v, dv) {
return v !== undefined ? v : dv;
@ -25,7 +32,7 @@ function dval(v, dv) {
* @param {Point[]} points vertex array
* @param {Object} options slicing parameters
*/
export async function slice(points, options = {}) {
async function slice(points, options = {}) {
let zMin = options.zMin || 0,
zMax = options.zMax || 0,
zInc = options.zInc || 0,
@ -65,6 +72,7 @@ export async function slice(points, options = {}) {
for (i = 0; i < points.length; i++) {
points[i] = points[i].round(3);
}
}
// gather z-index stats
@ -78,7 +86,7 @@ export 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(base.util.area2(p1,p2,p3)) / 2;
area = Math.abs(util.area2(p1,p2,p3)) / 2;
if (!zFlat[zkey]) {
zFlat[zkey] = area;
} else {
@ -314,7 +322,7 @@ function makeZLine(phash, p1, p2, coplanar, edge) {
*
* @param {number} z
*/
export async function sliceZ(z, points, options = {}) {
async function sliceZ(z, points, options = {}) {
if (Array.isArray(z)) {
return Promise.all(z.map(z => sliceZ(z, points, options)));
}
@ -380,14 +388,14 @@ export async function sliceZ(z, points, options = {}) {
if (groupFn) {
let groups = groupFn(lines, z, options);
if (options.xor) {
groups = polygons.xor(groups);
groups = POLY.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 = polygons.union(polygons.nest(groups), 0.1, true, opt);
let union = POLY.union(POLY.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
@ -401,14 +409,14 @@ export async function sliceZ(z, points, options = {}) {
}
// track total poly length changes to determine if healed
rval.changes = opt.changes;
groups = polygons.flatten(union, null, true);
groups = POLY.flatten(union, null, true);
}
rval.groups = groups;
}
// look for driver-specific slice post-processor
if (options.post) {
let fn = slicer.slicePost[options.post];
let fn = base.slicePost[options.post];
if (fn) fn(rval, options);
}
@ -450,7 +458,7 @@ export async function sliceZ(z, points, options = {}) {
* @param {number} [index]
* @returns {Array}
*/
export function sliceConnect(input, z, opt = {}) {
function sliceConnect(input, z, opt = {}) {
let { debug, both } = opt;
if (both) {
@ -774,7 +782,7 @@ export function sliceConnect(input, z, opt = {}) {
* @param {Line[]} lines
* @returns {Line[]}
*/
export function removeDuplicateLines(lines) {
function removeDuplicateLines(lines) {
let output = [],
tmplines = [],
points = [],
@ -865,11 +873,12 @@ export function removeDuplicateLines(lines) {
return output;
}
export const slicer = {
gapp.overlay(base, {
slice,
sliceZ,
slicePost: {},
sliceDedup: removeDuplicateLines,
sliceConnect
}
});
});

View file

@ -2,13 +2,19 @@
"use strict";
import { config } from './base.js';
// dep: geo.base
// use: geo.line
// use: geo.point
gapp.register("geo.slope", [], (root, exports) => {
const { base } = root;
const { config } = base;
const ABS = Math.abs,
DEG2RAD = Math.PI / 180,
RAD2DEG = 180 / Math.PI;
export class Slope {
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;
@ -88,13 +94,21 @@ function angleWithinDelta(a1, a2, delta) {
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
}
export function newSlope(p1, p2, dx, dy) {
function newSlope(p1, p2, dx, dy) {
return new Slope(p1, p2, dx, dy);
}
export function newSlopeFromAngle(angle) {
function newSlopeFromAngle(angle) {
return newSlope(0,0,
Math.cos(angle * DEG2RAD),
Math.sin(angle * DEG2RAD)
);
}
gapp.overlay(base, {
Slope,
newSlope,
newSlopeFromAngle
});
});

View file

@ -1,12 +1,16 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { base } from '../geo/base.js';
"use strict";
// dep: geo.base
// dep: ext.clip2
gapp.register("geo.wasm", [], (root, exports) => {
const { base } = root;
const { config } = base;
const factor = config.clipper;
export const wasm_ctrl = {
const wasm_ctrl = {
enable,
disable,
count: {
@ -16,6 +20,8 @@ export const wasm_ctrl = {
}
};
gapp.overlay(base, { wasm_ctrl });
function log() {
console.log(...arguments);
}
@ -54,7 +60,7 @@ function writePoly(view, poly, inner) {
function readPoly(view, z) {
let points = view.readU16(true);
if (points === 0) return;
let poly = base.newPolygon();
let poly = self.base.newPolygon();
while (points-- > 0) {
poly.add(view.readI32(true)/factor, view.readI32(true)/factor, z || 0);
}
@ -73,7 +79,7 @@ function readPolys(view, z, out = []) {
return out;
}
export function polyOffset(polys, offset, z, clean, simple) {
function polyOffset(polys, offset, z, clean, simple) {
wasm_ctrl.count.offset++;
let wasm = base.wasm,
buffer = wasm.shared,
@ -83,7 +89,7 @@ export function polyOffset(polys, offset, z, clean, simple) {
return polyNest(out);
}
export function polyUnion(polys, z) {
function polyUnion(polys, z) {
wasm_ctrl.count.union++;
let wasm = base.wasm,
buffer = wasm.shared,
@ -93,7 +99,7 @@ export function polyUnion(polys, z) {
return polyNest(out);
}
export function polyDiff(polysA, polysB, z, AB, BA) {
function polyDiff(polysA, polysB, z, AB, BA) {
wasm_ctrl.count.diff++;
let wasm = base.wasm,
buffer = wasm.shared,
@ -147,7 +153,7 @@ function readString(pos, len) {
return out.join('');
}
export function enable() {
function enable() {
if (base.wasm || base._wasm) {
return;
}
@ -194,9 +200,11 @@ export function enable() {
});
}
export function disable() {
function disable() {
if (base.wasm) {
base.wasm.free(base.wasm.shared);
delete base.wasm;
// console.log({disabled: geo});
}
}
});

Some files were not shown because too many files have changed in this diff Show more